src/viewer/scene/lights/PointLight.js
import {Light} from './Light.js';
import {RenderState} from '../webgl/RenderState.js';
import {RenderBuffer} from '../webgl/RenderBuffer.js';
import {math} from '../math/math.js';
/**
* A positional light source that originates from a single point and spreads outward in all directions, with optional attenuation over distance.
*
* * Has a position in {@link PointLight#pos}, but no direction.
* * Defined in either *World* or *View* coordinate space. When in World-space, {@link PointLight#pos} is relative to
* the World coordinate system, and will appear to move as the {@link Camera} moves. When in View-space,
* {@link PointLight#pos} is relative to the View coordinate system, and will behave as if fixed to the viewer's head.
* * Has {@link PointLight#constantAttenuation}, {@link PointLight#linearAttenuation} and {@link PointLight#quadraticAttenuation}
* factors, which indicate how intensity attenuates over distance.
* * {@link AmbientLight}s, {@link PointLight}s and {@link PointLight}s are registered by their {@link Component#id} on {@link Scene#lights}.
*
* ## Usage
*
* In the example below we'll replace the {@link Scene}'s default light sources with three World-space PointLights.
*
* [[Run this example](/examples/index.html#lights_PointLight_world)]
*
* ````javascript
* import {Viewer, Mesh, buildSphereGeometry, buildPlaneGeometry,
* ReadableGeometry, PhongMaterial, Texture, PointLight} from "xeokit-sdk.es.js";
*
* // Create a Viewer and arrange the camera
*
* const viewer = new Viewer({
* canvasId: "myCanvas"
* });
*
* viewer.scene.camera.eye = [0, 0, 5];
* viewer.scene.camera.look = [0, 0, 0];
* viewer.scene.camera.up = [0, 1, 0];
*
* // Replace the Scene's default lights with three custom world-space PointLights
*
* viewer.scene.clearLights();
*
* new PointLight(viewer.scene,{
* id: "keyLight",
* pos: [-80, 60, 80],
* color: [1.0, 0.3, 0.3],
* intensity: 1.0,
* space: "world"
* });
*
* new PointLight(viewer.scene,{
* id: "fillLight",
* pos: [80, 40, 40],
* color: [0.3, 1.0, 0.3],
* intensity: 1.0,
* space: "world"
* });
*
* new PointLight(viewer.scene,{
* id: "rimLight",
* pos: [-20, 80, -80],
* color: [0.6, 0.6, 0.6],
* intensity: 1.0,
* space: "world"
* });
*
* // Create a sphere and ground plane
*
* new Mesh(viewer.scene, {
* geometry: new ReadableGeometry(viewer.scene, buildSphereGeometry({
* radius: 1.3
* }),
* material: new PhongMaterial(viewer.scene, {
* diffuse: [0.7, 0.7, 0.7],
* specular: [1.0, 1.0, 1.0],
* emissive: [0, 0, 0],
* alpha: 1.0,
* ambient: [1, 1, 0],
* diffuseMap: new Texture(viewer.scene, {
* src: "textures/diffuse/uvGrid2.jpg"
* })
* })
* });
*
* new Mesh(viewer.scene, {
* geometry: buildPlaneGeometry(ReadableGeometry, viewer.scene, {
* xSize: 30,
* zSize: 30
* }),
* material: new PhongMaterial(viewer.scene, {
* diffuseMap: new Texture(viewer.scene, {
* src: "textures/diffuse/uvGrid2.jpg"
* }),
* backfaces: true
* }),
* position: [0, -2.1, 0]
* });
* ````
*/
class PointLight extends Light {
/**
@private
*/
get type() {
return "PointLight";
}
/**
* @param {Component} owner Owner component. When destroyed, the owner will destroy this PointLight as well.
* @param {*} [cfg] The PointLight configuration
* @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.
* @param {Number[]} [cfg.pos=[ 1.0, 1.0, 1.0 ]] Position, in either World or View space, depending on the value of the **space** parameter.
* @param {Number[]} [cfg.color=[0.7, 0.7, 0.8 ]] Color of this PointLight.
* @param {Number} [cfg.intensity=1.0] Intensity of this PointLight, as a factor in range ````[0..1]````.
* @param {Number} [cfg.constantAttenuation=0] Constant attenuation factor.
* @param {Number} [cfg.linearAttenuation=0] Linear attenuation factor.
* @param {Number} [cfg.quadraticAttenuation=0]Quadratic attenuation factor.
* @param {String} [cfg.space="view"]The coordinate system this PointLight is defined in - "view" or "world".
* @param {Boolean} [cfg.castsShadow=false] Flag which indicates if this PointLight casts a castsShadow.
*/
constructor(owner, cfg = {}) {
super(owner, cfg);
const self = this;
this._shadowRenderBuf = null;
this._shadowViewMatrix = null;
this._shadowProjMatrix = null;
this._shadowViewMatrixDirty = true;
this._shadowProjMatrixDirty = true;
const camera = this.scene.camera;
const canvas = this.scene.canvas;
this._onCameraViewMatrix = camera.on("viewMatrix", () => {
this._shadowViewMatrixDirty = true;
});
this._onCameraProjMatrix = camera.on("projMatrix", () => {
this._shadowProjMatrixDirty = true;
});
this._onCanvasBoundary = canvas.on("boundary", () => {
this._shadowProjMatrixDirty = true;
});
this._state = new RenderState({
type: "point",
pos: math.vec3([1.0, 1.0, 1.0]),
color: math.vec3([0.7, 0.7, 0.8]),
intensity: 1.0, attenuation: [0.0, 0.0, 0.0],
space: cfg.space || "view",
castsShadow: false,
getShadowViewMatrix: () => {
if (self._shadowViewMatrixDirty) {
if (!self._shadowViewMatrix) {
self._shadowViewMatrix = math.identityMat4();
}
const eye = self._state.pos;
const look = camera.look;
const up = camera.up;
math.lookAtMat4v(eye, look, up, self._shadowViewMatrix);
self._shadowViewMatrixDirty = false;
}
return self._shadowViewMatrix;
},
getShadowProjMatrix: () => {
if (self._shadowProjMatrixDirty) { // TODO: Set when canvas resizes
if (!self._shadowProjMatrix) {
self._shadowProjMatrix = math.identityMat4();
}
const canvas = self.scene.canvas.canvas;
math.perspectiveMat4(70 * (Math.PI / 180.0), canvas.clientWidth / canvas.clientHeight, 0.1, 500.0, self._shadowProjMatrix);
self._shadowProjMatrixDirty = false;
}
return self._shadowProjMatrix;
},
getShadowRenderBuf: () => {
if (!self._shadowRenderBuf) {
self._shadowRenderBuf = new RenderBuffer(self.scene.canvas.canvas, self.scene.canvas.gl, {size: [1024, 1024]}); // Super old mobile devices have a limit of 1024x1024 textures
}
return self._shadowRenderBuf;
}
});
this.pos = cfg.pos;
this.color = cfg.color;
this.intensity = cfg.intensity;
this.constantAttenuation = cfg.constantAttenuation;
this.linearAttenuation = cfg.linearAttenuation;
this.quadraticAttenuation = cfg.quadraticAttenuation;
this.castsShadow = cfg.castsShadow;
this.scene._lightCreated(this);
}
/**
* Sets the position of this PointLight.
*
* This will be either World- or View-space, depending on the value of {@link PointLight#space}.
*
* Default value is ````[1.0, 1.0, 1.0]````.
*
* @param {Number[]} pos The position.
*/
set pos(pos) {
this._state.pos.set(pos || [1.0, 1.0, 1.0]);
this._shadowViewMatrixDirty = true;
this.glRedraw();
}
/**
* Gets the position of this PointLight.
*
* This will be either World- or View-space, depending on the value of {@link PointLight#space}.
*
* Default value is ````[1.0, 1.0, 1.0]````.
*
* @returns {Number[]} The position.
*/
get pos() {
return this._state.pos;
}
/**
* Sets the RGB color of this PointLight.
*
* Default value is ````[0.7, 0.7, 0.8]````.
*
* @param {Number[]} color The PointLight's RGB color.
*/
set color(color) {
this._state.color.set(color || [0.7, 0.7, 0.8]);
this.glRedraw();
}
/**
* Gets the RGB color of this PointLight.
*
* Default value is ````[0.7, 0.7, 0.8]````.
*
* @returns {Number[]} The PointLight's RGB color.
*/
get color() {
return this._state.color;
}
/**
* Sets the intensity of this PointLight.
*
* Default intensity is ````1.0```` for maximum intensity.
*
* @param {Number} intensity The PointLight's intensity
*/
set intensity(intensity) {
intensity = intensity !== undefined ? intensity : 1.0;
this._state.intensity = intensity;
this.glRedraw();
}
/**
* Gets the intensity of this PointLight.
*
* Default value is ````1.0```` for maximum intensity.
*
* @returns {Number} The PointLight's intensity.
*/
get intensity() {
return this._state.intensity;
}
/**
* Sets the constant attenuation factor for this PointLight.
*
* Default value is ````0````.
*
* @param {Number} value The constant attenuation factor.
*/
set constantAttenuation(value) {
this._state.attenuation[0] = value || 0.0;
this.glRedraw();
}
/**
* Gets the constant attenuation factor for this PointLight.
*
* Default value is ````0````.
*
* @returns {Number} The constant attenuation factor.
*/
get constantAttenuation() {
return this._state.attenuation[0];
}
/**
* Sets the linear attenuation factor for this PointLight.
*
* Default value is ````0````.
*
* @param {Number} value The linear attenuation factor.
*/
set linearAttenuation(value) {
this._state.attenuation[1] = value || 0.0;
this.glRedraw();
}
/**
* Gets the linear attenuation factor for this PointLight.
*
* Default value is ````0````.
*
* @returns {Number} The linear attenuation factor.
*/
get linearAttenuation() {
return this._state.attenuation[1];
}
/**
* Sets the quadratic attenuation factor for this PointLight.
*
* Default value is ````0````.
*
* @param {Number} value The quadratic attenuation factor.
*/
set quadraticAttenuation(value) {
this._state.attenuation[2] = value || 0.0;
this.glRedraw();
}
/**
* Gets the quadratic attenuation factor for this PointLight.
*
* Default value is ````0````.
*
* @returns {Number} The quadratic attenuation factor.
*/
get quadraticAttenuation() {
return this._state.attenuation[2];
}
/**
* Sets if this PointLight casts a shadow.
*
* Default value is ````false````.
*
* @param {Boolean} castsShadow Set ````true```` to cast shadows.
*/
set castsShadow(castsShadow) {
castsShadow = !!castsShadow;
if (this._state.castsShadow === castsShadow) {
return;
}
this._state.castsShadow = castsShadow;
this._shadowViewMatrixDirty = true;
this.glRedraw();
}
/**
* Gets if this PointLight casts a shadow.
*
* Default value is ````false````.
*
* @returns {Boolean} ````true```` if this PointLight casts shadows.
*/
get castsShadow() {
return this._state.castsShadow;
}
/**
* Destroys this PointLight.
*/
destroy() {
const camera = this.scene.camera;
const canvas = this.scene.canvas;
camera.off(this._onCameraViewMatrix);
camera.off(this._onCameraProjMatrix);
canvas.off(this._onCanvasBoundary);
super.destroy();
this._state.destroy();
if (this._shadowRenderBuf) {
this._shadowRenderBuf.destroy();
}
this.scene._lightDestroyed(this);
this.glRedraw();
}
}
export {PointLight};