GRDMAP, GRDWTR AND OCEAN MODULES
In General
Click here for a full-screen display that also shows fps.

You can use either the GrdWtr or GrdMap module together with the Ocean module to create a scrolling animated ocean surface that extends to the horizon. The GrdWtr module is a simplified version of the GrdMap module and creates a standard version of the ocean. The GrdMap module is a generalized module that gives the user the ability to define the geometries and textures for each grid. We will be expanding this module to work with land textures.

  Waves from the movie "Dunkirk" compared to waves generated by this module.
The Grid

Both the GrdWtr and GrdMap modules use the same grid map. This grid map has 3 nested grids of squares. Grid0 has 16x16 squares, each of size GrdSiz (e.g. 1 mile, range = 8 miles). Grid1 has 16x16 squares, each of size GrdSiz*4 (e.g. 4 miles, range = 32 miles). Grid2 has 16x16 squares, each of size GrdSiz*16 (e.g. 16 miles, range = 128 miles).

Using the Ocean and GrdWtr Modules in Your Program

The GrdWtr module is a simplified version of the GrdMap module and creates a standard version of the ocean. The ocean is untextured. The color of the ocean is a blend of the ocean color (WtrCol) variable and the SkyBox texture. Thus, merely changing the WtrCol variable may not have the desired effect since you also have to overcome the influence of the SkyBox texture. You can change the size of the smallest grid squares to increase resolution. However, this increases the risk of tiling as the altitude increases.

Load the Ocean and GrdWtr Modules

You can insert these kinds of instructions at the beginning of your program to load the modules:

import {Ocean} from "three/addons/air/Oceans.js";
import {GrdMap} from "three/addons/air/GrdWtr.js";
Define the Variables

You can add these variables to the beginning of your program:

/*= GRID DATA ================================================================*/
let GrdRes = 512;
let GrdSiz = 3200;		// Size of Smallest Grid Square (meters)
let GrdSeg = 256;		// Segments per Plane (256 = OK, 512 = too much)

/*= OCEAN MODULE =============================================================*/
let ocean = 0;
let WndSpd = 20.0;
let WndHdg = 0.0;
let Choppy = 1;
let WavMax = 5;			// Maximum wave height (set height of outer waves)
let wav_ = {
      // Inputs
      Res: GrdRes,		// Resolution - segments per square (default = 512)
      Siz: GrdSiz,		// Size of Smallest Square = default = 3200m = 2 miles
      WSp: WndSpd,		// Wind Speed
      WHd: WndHdg,		// Wind Heading
      Chp: Choppy,		// default = 1
      DTm: 0.0,			// Delta time
      // Results
      Dsp: 0,			// The Displacement Map
      Nrm: 0,			// The Normal Map
      // Other Program Variables
      Spd: 1.0,			// Animation speed - use in main program (lower is faster)
   };

/*= GRID MODULE ==============================================================*/
let grids = 0;
let grx_ = {
      MSP: new THREE.Vector3 (), // MapSpdX, MapPosY, MapSpdZ (meters)
      // Inputs
      RCs: 16,			// Squares in each of first 2 grids
      Siz: GrdSiz,		// Size of smallest square
      Stp: 4,			// Size multiplier for squares in last 2 grids
      Seg: GrdSeg,		// Segments for smallest square
      WMx: WavMax,		// Max wave height, used to lower outer squares
      Col: WtrCol,		// Color
      Dsp: 0,			// Displacement Map (from Ocean)
      Nrm: 0,			// Normal Map (from Ocean)
      Env: 0,			// EnvMap
      // Results
      Grd: [],			// Index of Grids (0-2)
      Geo: [],			// Master Index of Basic Geometries
      Mat: [],			// Master Index of Basic Materials
   };
Initialization

You can add the following instructions to the Initialization section of your program:

   // Initialize Ocean
   ocean = new Ocean(renderer, camera, scene, wav_);
   // Set Aircraft Map Speed and Altitude
   grx_.MSP.z = beg_ZSpeed;	// Z-Speed (N/S) in meters per second
   grx_.MSP.x = beg_XSpeed;	// X-Speed (E/W) in meters per second
   grx_.MSP.y = beg_Altitude;	// Altitude in meters
   // Load GrdMap Variables
   grx_.Dsp = wav_.Dsp;
   grx_.Nrm = wav_.Nrm;
   grx_.Env = envMap;
   // Initialize GridMap
   grids = new GrdMap(grx_, scene);

where renderer, camera and scene are the names of the variables for your renderer, camera and scene.

Rendering

You can add the following instructions to the rendering section of your program:

   // Update Grid Data
   grx_.MSP.z = new_ZSpeed;
   grx_.MSP.x = new_XSpeed;
   grx_.MSP.y = new_Altitude;
   // Render Ocean
   wav_.DTm = difTim * wav_.Spd || 0.0;
   ocean.render(wav_);
   // Render Grids
   grids.update(grx_);

In order to compute wav._DTm, your main program needs to include a timer. A simple example is shown below.

Using the Ocean and GrdMap Modules in Your Program

The GrdMap module is a more generalized module which allows you to define the Geometries and Materials for each set of Grids. For example, you can add a texture to the ocean and a static normal map to the farthest grids.

Load the Modules

You can add the instructions to the beginning of your program to load the modules:

import {Ocean} from "three/addons/air/Oceans.js";
import {GrdMap} from "three/addons/air/GrdWtr.js";
import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
Variables

You can add these variables to your program:

/*= GRID DATA ================================================================*/
let GrdRes = 512;
let GrdSiz = 3200;		// Size of Smallest Grid Square (meters)
let GrdSeg = 256;		// Segments per Plane (256 = OK, 512 = too much)
let GeoAll = [];		// Array used to assemble Grids

/*= OCEAN MODULE =============================================================*/
let ocean = 0;
let WndSpd = 20.0;
let WndHdg = 0.0;
let Choppy = 1;
let WavMax = 5;			// Maximum wave height (set height of outer waves)
let wav_ = {
      // Inputs
      Res: GrdRes,		// Resolution - segments per square (default = 512)
      Siz: GrdSiz,		// Size of Smallest Square = default = 3200m = 2 miles
      WSp: WndSpd,		// Wind Speed
      WHd: WndHdg,		// Wind Heading
      Chp: Choppy,		// default = 1
      DTm: 0.0,			// Delta time
      // Results
      Dsp: 0,			// The Displacement Map
      Nrm: 0,			// The Normal Map
      // Other Program Variables
      Spd: 1.0,			// Animation speed - use in main program (lower is faster)
   };

/*= GRID TEXTURES ============================================================*/
//  Textures (these need to be loaded in Main Program)
//  Diffuse Map (Optional - All Grids)
let WtrSrc = "https://threejs.org/examples/textures/waternormals.jpg";	// 0 = Don't load
let WtrTxt = 0;
//  Normal Map (Grid2 Only)
let NrmSrc = "https://threejs.org/examples/textures/waternormals.jpg";
let WtrNrm = 0;

/*= GRID MODULE ==============================================================*/
let grids = 0;
let grx_ = {
      MSP: new THREE.Vector3 (), // MSX, MPY, MSZ (meters)
      RCs: 16,			// Squares in each of first 2 grids
      Siz: GrdSiz,		// Size of smallest square
      Stp: 4,			// Size multiplier for squares in last 2 grids
      Seg: GrdSeg,		// Segments for smallest square
      Grd: [],			// Index of Grids (0-2)
      Geo: [],			// Master Index of Basic Geometries
      Mat: [],			// Master Index of Basic Materials
      WMx: WavMax,		// Max wave height, used to lower outer squares
   };
The GeoMap Functions

The GrdMap module allows you to create custom textures and geometries which are then loaded into the GridMap. You can add the GeoMap Functions below to your main program, which will allow you to add custom textures to the ocean and to customize your materials:

/*= GEOMAT ===================================================================*/

function loadGeoMat() {
   // Load Diffuse Map (Optional - All Grids)
   if (WtrSrc) {		// Load the texture only if there is an address
      txtrLoader.load(WtrSrc, function(texture) {
         texture.format = THREE.RGBAFormat;
         texture.magFilter = THREE.LinearFilter;
         texture.minFilter = THREE.LinearMipMapLinearFilter;
         texture.generateMipmaps = true;
         texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
         texture.offset.set(0,0);
         texture.needsUpdate = true;
         WtrTxt = texture;
      });
   }
   // Normal Map (Grid2 Only)
   txtrLoader.load(NrmSrc, function(texture) {
      texture.format = THREE.RGBAFormat;
      texture.magFilter = THREE.LinearFilter;
      texture.minFilter = THREE.LinearMipMapLinearFilter;
      texture.generateMipmaps = true;
      texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
      texture.offset.set(0,0);
      texture.repeat.set(16,16);
      texture.needsUpdate = true
      WtrNrm = texture;
   });
}

function initGeoMat() {
// Define the Materials and Geometries Referenced in grx_.Geo and grx_.Mat
   let siz = grx_.Siz;
   let stp = grx_.Stp;
   /*- Grid0 ----------------------------------------------------------------*/
   let n = 0;
   let seg = grx_.Seg;
   grx_.Mat[n] = new THREE.MeshPhysicalMaterial({
         color: WtrCol,
         map: WtrTxt,		// 0 if no map loaded
         displacementMap: wav_.Dsp, // Animated displacementMap
         normalMap: wav_.Nrm,	// Animated normalMap
         envMap: envMap,
         normalScale: new THREE.Vector2(2.5,2.5),
         metalness: 1.0,	// 1 for max reflection
         roughness: 0.7,	// 0 for max reflection
         reflectivity: 0.5,	// 1 for max reflection
         envMapIntensity: 5,	// max reflection suggested = 5
         premultipliedAlpha: true,
      });
   grx_.Geo[n] = new THREE.PlaneGeometry(siz,siz,seg,seg);
         grx_.Geo[0].rotateX(-Math.PI * 0.5);
   /*- Grid1 ----------------------------------------------------------------*/
   // This far out, you cannot see the Animated displacementMap, 
   // but you can still see the Animated normalMap
   n = 1;
   grx_.Mat[n] = new THREE.MeshPhysicalMaterial({
         color: WtrCol,
         map: WtrTxt,		// 0 if no map loaded
         normalMap: wav_.Nrm,	// Animated normalMap
         envMap: envMap,
         normalScale: new THREE.Vector2(2.5,2.5),
         metalness: 1.0,	// 1 for max reflection
         roughness: 0.7,	// 0 for max reflection
         reflectivity: 0.5,	// 1 for max reflection
         envMapIntensity: 5,	// max reflection suggested = 5
         premultipliedAlpha: true,
      });
   //  This allows you to repeat the Animated normalMap in this 4x4 Square
   let idx = 0;
   let ctr = (0.5*stp-0.5)*siz;	// 2 = 0.5; 3 = 1.0; 4 = 1.5
   for (let z = 0; z < stp; z++) {
      for (let x = 0; x < stp; x++) {
         GeoAll[idx] = new THREE.PlaneGeometry(siz,siz);
         GeoAll[idx].rotateX(-Math.PI * 0.5);
         GeoAll[idx].translate(x*siz-ctr, 0, z*siz-ctr);
         idx++;
      }
   }
   let max = stp*stp;
   for (let i = 1; i < max; i++) {
      GeoAll[0] = BufferGeometryUtils.mergeGeometries([GeoAll[0], GeoAll[i]], false);
   }
   grx_.Geo[n] = GeoAll[0];
   /*- Grid2 ----------------------------------------------------------------*/
   //  This far out, you cannot see the animation, so you can use a static normalMap
   n = 2;
   siz = grx_.Siz*grx_.Stp*grx_.Stp;
   grx_.Mat[n] = new THREE.MeshPhysicalMaterial({
         color: WtrCol,
         map: WtrTxt,		// 0 if no map loaded
         normalMap: WtrNrm,	// Static normalMap
         envMap: envMap,
         normalScale: new THREE.Vector2(2.5,2.5),
         metalness: 1.0,	// 1 for max reflection
         roughness: 0.7,	// 0 for max reflection
         reflectivity: 0.5,	// 1 for max reflection
         envMapIntensity: 5,	// max reflection suggested = 5
         premultipliedAlpha: true,
      });
   grx_.Geo[n] = new THREE.PlaneGeometry(siz,siz);
   grx_.Geo[2].rotateX(-Math.PI * 0.5);
}
Loading

You can add the following instruction to the loading section of your program:

   loadGeoMat();
Initialization

You can add the following instructions to the Initialization section of your program:

   // Initialize Ocean
   ocean = new Ocean(renderer, camera, scene, wav_);
   // Define GridMap Materials and Geometries
   initGeoMat();		// Initialize Grid Materials
   // Set Aircraft Map Speed and Altitude
   grx_.MSP.z = beg_ZSpeed;	// Z-Speed (N/S) in meters per second
   grx_.MSP.x = beg_XSpeed;	// X-Speed (E/W) in meters per second
   grx_.MSP.y = beg_Altitude;	// Altitude in meters
   // Initialize GridMap
   grids = new GrdMap(grx_, scene);

where renderer, camera and scene are the names of the variables for your renderer, camera and scene.

Rendering

You can add the following instructions to the rendering section of your program:

   // Update Grid Data
   grx_.MSP.z = new_ZSpeed;
   grx_.MSP.x = new_XSpeed;
   grx_.MSP.y = new_Altitude;
   // Render Ocean
   wav_.DTm = difTim * wav_.Spd || 0.0;
   ocean.render(wav_);
   // Render Grids
   grids.update(grx_);

In order to compute wav._DTm, your main program needs to include a timer. A simple example is shown below.

A Simple Timer

Here is a simple timer you can use to update wav_.DTm.

Initialize the clock in the variables section with this command and general variables:

   let clock = new THREE.Clock();
   let oldTim, nowTim, difTim = 0;

In your render loop, insert the following:

   nowTim = clock.getElapsedTime();
   difTim = nowTim-oldTim;
   oldTim = nowTim;
   wav_.DTm = difTim * wav_.Spd || 0.0;
Limitations

As altitude increases, you might notice a "tiling" problem. You can address this problem by increasing the size of the smallest grid. However, this will result in a decrease in resolution at lower altitudes.

The Zip File

Here is a zip file that includes the following files:
* Oceans.js - the Ocean module.
* GrdWtr.js - a simple version of the GrdMap module designed to work with the Ocean module.
* GrdMap.js - the GrdMap module that allows you to customize materials and geometries
* SMD_X3X_mod.html - a sample program that uses the Ocean and GrdWtr modules.
* SMD_X3X_local.html - a sample program that includes the above modules that you can run locally.
* SMD_X3W_mod.html - a sample program that uses the Ocean and GrdMap modules.
* SMD_X3W_local.html - a sample program that includes the above modules that you can run locally.