Skip to content

⬅️ Back to Table of Contents

📄 Water2.js

📊 Analysis Summary

Metric Count
🔧 Functions 2
🧱 Classes 1
📦 Imports 13
📊 Variables & Constants 21

📚 Table of Contents

🛠️ File Location:

📂 examples/jsm/objects/Water2.js

📦 Imports

Name Source
Clock three
Color three
Matrix4 three
Mesh three
RepeatWrapping three
ShaderMaterial three
TextureLoader three
UniformsLib three
UniformsUtils three
Vector2 three
Vector4 three
Reflector ../objects/Reflector.js
Refractor ../objects/Refractor.js

Variables & Constants

Name Type Kind Value Exported
scope this let/var this
color any let/var ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0xF...
textureWidth any let/var options.textureWidth !== undefined ? options.textureWidth : 512
textureHeight any let/var options.textureHeight !== undefined ? options.textureHeight : 512
clipBias any let/var options.clipBias !== undefined ? options.clipBias : 0
flowDirection any let/var options.flowDirection !== undefined ? options.flowDirection : new Vector2( 1,...
flowSpeed any let/var options.flowSpeed !== undefined ? options.flowSpeed : 0.03
reflectivity any let/var options.reflectivity !== undefined ? options.reflectivity : 0.02
scale any let/var options.scale !== undefined ? options.scale : 1
shader any let/var options.shader !== undefined ? options.shader : Water.WaterShader
textureLoader any let/var new TextureLoader()
flowMap any let/var options.flowMap \|\| undefined
normalMap0 any let/var options.normalMap0 \|\| textureLoader.load( 'textures/water/Water_1_M_Normal....
normalMap1 any let/var options.normalMap1 \|\| textureLoader.load( 'textures/water/Water_2_M_Normal....
cycle 0.15 let/var 0.15
halfCycle number let/var cycle * 0.5
textureMatrix any let/var new Matrix4()
clock any let/var new Clock()
reflector Reflector let/var new Reflector( geometry, { textureWidth: textureWidth, textureHeight: texture...
refractor Refractor let/var new Refractor( geometry, { textureWidth: textureWidth, textureHeight: texture...
config any let/var scope.material.uniforms[ 'config' ]

Functions

updateTextureMatrix(camera: any): void

Parameters:

  • camera any

Returns: void

Calls:

  • textureMatrix.set
  • textureMatrix.multiply
Code
function updateTextureMatrix( camera ) {

            textureMatrix.set(
                0.5, 0.0, 0.0, 0.5,
                0.0, 0.5, 0.0, 0.5,
                0.0, 0.0, 0.5, 0.5,
                0.0, 0.0, 0.0, 1.0
            );

            textureMatrix.multiply( camera.projectionMatrix );
            textureMatrix.multiply( camera.matrixWorldInverse );
            textureMatrix.multiply( scope.matrixWorld );

        }

updateFlow(): void

Returns: void

Calls:

  • clock.getDelta

Internal Comments:

// Important: The distance between offsets should be always the value of "halfCycle".
// Moreover, both offsets should be in the range of [ 0, cycle ].
// This approach ensures a smooth water flow and avoids "reset" effects.

Code
function updateFlow() {

            const delta = clock.getDelta();
            const config = scope.material.uniforms[ 'config' ];

            config.value.x += flowSpeed * delta; // flowMapOffset0
            config.value.y = config.value.x + halfCycle; // flowMapOffset1

            // Important: The distance between offsets should be always the value of "halfCycle".
            // Moreover, both offsets should be in the range of [ 0, cycle ].
            // This approach ensures a smooth water flow and avoids "reset" effects.

            if ( config.value.x >= cycle ) {

                config.value.x = 0;
                config.value.y = halfCycle;

            } else if ( config.value.y >= cycle ) {

                config.value.y = config.value.y - cycle;

            }

        }

Classes

Water

Class Code
class Water extends Mesh {

    /**
     * Constructs a new water instance.
     *
     * @param {BufferGeometry} geometry - The water's geometry.
     * @param {module:Water2~Options} [options] - The configuration options.
     */
    constructor( geometry, options = {} ) {

        super( geometry );

        /**
         * This flag can be used for type testing.
         *
         * @type {boolean}
         * @readonly
         * @default true
         */
        this.isWater = true;

        this.type = 'Water';

        const scope = this;

        const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0xFFFFFF );
        const textureWidth = options.textureWidth !== undefined ? options.textureWidth : 512;
        const textureHeight = options.textureHeight !== undefined ? options.textureHeight : 512;
        const clipBias = options.clipBias !== undefined ? options.clipBias : 0;
        const flowDirection = options.flowDirection !== undefined ? options.flowDirection : new Vector2( 1, 0 );
        const flowSpeed = options.flowSpeed !== undefined ? options.flowSpeed : 0.03;
        const reflectivity = options.reflectivity !== undefined ? options.reflectivity : 0.02;
        const scale = options.scale !== undefined ? options.scale : 1;
        const shader = options.shader !== undefined ? options.shader : Water.WaterShader;

        const textureLoader = new TextureLoader();

        const flowMap = options.flowMap || undefined;
        const normalMap0 = options.normalMap0 || textureLoader.load( 'textures/water/Water_1_M_Normal.jpg' );
        const normalMap1 = options.normalMap1 || textureLoader.load( 'textures/water/Water_2_M_Normal.jpg' );

        const cycle = 0.15; // a cycle of a flow map phase
        const halfCycle = cycle * 0.5;
        const textureMatrix = new Matrix4();
        const clock = new Clock();

        // internal components

        if ( Reflector === undefined ) {

            console.error( 'THREE.Water: Required component Reflector not found.' );
            return;

        }

        if ( Refractor === undefined ) {

            console.error( 'THREE.Water: Required component Refractor not found.' );
            return;

        }

        const reflector = new Reflector( geometry, {
            textureWidth: textureWidth,
            textureHeight: textureHeight,
            clipBias: clipBias
        } );

        const refractor = new Refractor( geometry, {
            textureWidth: textureWidth,
            textureHeight: textureHeight,
            clipBias: clipBias
        } );

        reflector.matrixAutoUpdate = false;
        refractor.matrixAutoUpdate = false;

        // material

        this.material = new ShaderMaterial( {
            name: shader.name,
            uniforms: UniformsUtils.merge( [
                UniformsLib[ 'fog' ],
                shader.uniforms
            ] ),
            vertexShader: shader.vertexShader,
            fragmentShader: shader.fragmentShader,
            transparent: true,
            fog: true
        } );

        if ( flowMap !== undefined ) {

            this.material.defines.USE_FLOWMAP = '';
            this.material.uniforms[ 'tFlowMap' ] = {
                type: 't',
                value: flowMap
            };

        } else {

            this.material.uniforms[ 'flowDirection' ] = {
                type: 'v2',
                value: flowDirection
            };

        }

        // maps

        normalMap0.wrapS = normalMap0.wrapT = RepeatWrapping;
        normalMap1.wrapS = normalMap1.wrapT = RepeatWrapping;

        this.material.uniforms[ 'tReflectionMap' ].value = reflector.getRenderTarget().texture;
        this.material.uniforms[ 'tRefractionMap' ].value = refractor.getRenderTarget().texture;
        this.material.uniforms[ 'tNormalMap0' ].value = normalMap0;
        this.material.uniforms[ 'tNormalMap1' ].value = normalMap1;

        // water

        this.material.uniforms[ 'color' ].value = color;
        this.material.uniforms[ 'reflectivity' ].value = reflectivity;
        this.material.uniforms[ 'textureMatrix' ].value = textureMatrix;

        // initial values

        this.material.uniforms[ 'config' ].value.x = 0; // flowMapOffset0
        this.material.uniforms[ 'config' ].value.y = halfCycle; // flowMapOffset1
        this.material.uniforms[ 'config' ].value.z = halfCycle; // halfCycle
        this.material.uniforms[ 'config' ].value.w = scale; // scale

        // functions

        function updateTextureMatrix( camera ) {

            textureMatrix.set(
                0.5, 0.0, 0.0, 0.5,
                0.0, 0.5, 0.0, 0.5,
                0.0, 0.0, 0.5, 0.5,
                0.0, 0.0, 0.0, 1.0
            );

            textureMatrix.multiply( camera.projectionMatrix );
            textureMatrix.multiply( camera.matrixWorldInverse );
            textureMatrix.multiply( scope.matrixWorld );

        }

        function updateFlow() {

            const delta = clock.getDelta();
            const config = scope.material.uniforms[ 'config' ];

            config.value.x += flowSpeed * delta; // flowMapOffset0
            config.value.y = config.value.x + halfCycle; // flowMapOffset1

            // Important: The distance between offsets should be always the value of "halfCycle".
            // Moreover, both offsets should be in the range of [ 0, cycle ].
            // This approach ensures a smooth water flow and avoids "reset" effects.

            if ( config.value.x >= cycle ) {

                config.value.x = 0;
                config.value.y = halfCycle;

            } else if ( config.value.y >= cycle ) {

                config.value.y = config.value.y - cycle;

            }

        }

        //

        this.onBeforeRender = function ( renderer, scene, camera ) {

            updateTextureMatrix( camera );
            updateFlow();

            scope.visible = false;

            reflector.matrixWorld.copy( scope.matrixWorld );
            refractor.matrixWorld.copy( scope.matrixWorld );

            reflector.onBeforeRender( renderer, scene, camera );
            refractor.onBeforeRender( renderer, scene, camera );

            scope.visible = true;

        };

    }

}