src/passes/ShockWavePass.js
import { Vector3 } from "three";
import { CopyMaterial, ShockWaveMaterial } from "../materials";
import { Pass } from "./Pass.js";
/**
* Half PI.
*
* @type {Number}
* @private
*/
const HALF_PI = Math.PI * 0.5;
/**
* A vector.
*
* @type {Vector3}
* @private
*/
const v = new Vector3();
/**
* A vector.
*
* @type {Vector3}
* @private
*/
const ab = new Vector3();
/**
* A shock wave pass.
*/
export class ShockWavePass extends Pass {
/**
* Constructs a new shock wave pass.
*
* @param {Camera} camera - The main camera.
* @param {Vector3} [epicenter] - The world position of the shock wave epicenter.
* @param {Object} [options] - The options.
* @param {Number} [options.speed=1.0] - The animation speed.
* @param {Number} [options.maxRadius=1.0] - The extent of the shock wave.
* @param {Number} [options.waveSize=0.2] - The wave size.
* @param {Number} [options.amplitude=0.05] - The distortion amplitude.
*/
constructor(camera, epicenter = new Vector3(), options = {}) {
super("ShockWavePass");
/**
* The main camera.
*
* @type {Object3D}
*/
this.mainCamera = camera;
/**
* The epicenter.
*
* @type {Vector3}
* @example shockWavePass.epicenter = myMesh.position;
*/
this.epicenter = epicenter;
/**
* The object position in screen space.
*
* @type {Vector3}
* @private
*/
this.screenPosition = new Vector3();
/**
* The speed of the shock wave animation.
*
* @type {Number}
*/
this.speed = (options.speed !== undefined) ? options.speed : 2.0;
/**
* A time accumulator.
*
* @type {Number}
* @private
*/
this.time = 0.0;
/**
* Indicates whether the shock wave animation is active.
*
* @type {Boolean}
* @private
*/
this.active = false;
/**
* A shock wave shader material.
*
* @type {ShockWaveMaterial}
* @private
*/
this.shockWaveMaterial = new ShockWaveMaterial(options);
this.shockWaveMaterial.uniforms.center.value = this.screenPosition;
/**
* A copy shader material.
*
* @type {CopyMaterial}
* @private
*/
this.copyMaterial = new CopyMaterial();
}
/**
* Emits the shock wave.
*/
explode() {
this.time = 0.0;
this.active = true;
}
/**
* Renders the effect.
*
* @param {WebGLRenderer} renderer - The renderer.
* @param {WebGLRenderTarget} inputBuffer - A frame buffer that contains the result of the previous pass.
* @param {WebGLRenderTarget} outputBuffer - A frame buffer that serves as the output render target unless this pass renders to screen.
* @param {Number} [delta] - The time between the last frame and the current one in seconds.
* @param {Boolean} [stencilTest] - Indicates whether a stencil mask is active.
*/
render(renderer, inputBuffer, outputBuffer, delta, stencilTest) {
const epicenter = this.epicenter;
const mainCamera = this.mainCamera;
const screenPosition = this.screenPosition;
const shockWaveMaterial = this.shockWaveMaterial;
const uniforms = shockWaveMaterial.uniforms;
const center = uniforms.center;
const radius = uniforms.radius;
const maxRadius = uniforms.maxRadius;
const waveSize = uniforms.waveSize;
this.copyMaterial.uniforms.tDiffuse.value = inputBuffer.texture;
this.setFullscreenMaterial(this.copyMaterial);
if(this.active) {
// Calculate direction vectors.
mainCamera.getWorldDirection(v);
ab.copy(mainCamera.position).sub(epicenter);
// Don't render the effect if the object is behind the camera.
if(v.angleTo(ab) > HALF_PI) {
// Scale the effect based on distance to the object.
uniforms.cameraDistance.value = mainCamera.position.distanceTo(epicenter);
// Calculate the screen position of the epicenter.
screenPosition.copy(epicenter).project(mainCamera);
center.value.x = (screenPosition.x + 1.0) * 0.5;
center.value.y = (screenPosition.y + 1.0) * 0.5;
uniforms.tDiffuse.value = inputBuffer.texture;
this.setFullscreenMaterial(shockWaveMaterial);
}
// Update the shock wave radius based on time.
this.time += delta * this.speed;
radius.value = this.time - waveSize.value;
if(radius.value >= (maxRadius.value + waveSize.value) * 2) {
this.active = false;
}
}
renderer.render(this.scene, this.camera, this.renderToScreen ? null : outputBuffer);
}
/**
* Updates the size of this pass.
*
* @param {Number} width - The width.
* @param {Number} height - The height.
*/
setSize(width, height) {
this.shockWaveMaterial.uniforms.aspect.value = width / height;
}
}