/**
A **SpotLight** defines a positional light source that originates from a single point and eminates in a given direction,
to illuminate {{#crossLink "Mesh"}}Meshes{{/crossLink}}.
## Overview
* SpotLights have a position and direction.
* SpotLights may be defined in either **World** or **View** coordinate space. When in World-space, their positions
are relative to the World coordinate system, and will appear to move as the {{#crossLink "Camera"}}{{/crossLink}} moves.
When in View-space, their positions are relative to the View coordinate system, and will behave as if fixed to the viewer's
head as the {{#crossLink "Camera"}}{{/crossLink}} moves.
* SpotLights have {{#crossLink "SpotLight/constantAttenuation:property"}}{{/crossLink}}, {{#crossLink "SpotLight/linearAttenuation:property"}}{{/crossLink}} and
{{#crossLink "SpotLight/quadraticAttenuation:property"}}{{/crossLink}} factors, which indicate how their intensity attenuates over distance.
* A SpotLight can also have a {{#crossLink "Shadow"}}{{/crossLink}} component, to configure it to cast a shadow.
* {{#crossLink "AmbientLight"}}{{/crossLink}}, {{#crossLink "DirLight"}}{{/crossLink}},
{{#crossLink "SpotLight"}}{{/crossLink}} and {{#crossLink "PointLight"}}{{/crossLink}} instances are registered by ID
on {{#crossLink "Scene/lights:property"}}Scene#lights{{/crossLink}} for convenient access.
## Examples
TODO
## Usage
In the example below we'll customize the default Scene's light sources, defining an AmbientLight and a couple of
SpotLights, then create a Phong-shaded box mesh.
````javascript
new xeogl.AmbientLight({
color: [0.8, 0.8, 0.8],
intensity: 0.5
});
new xeogl.SpotLight({
pos: [0, 100, 100],
dir: [0, -1, 0],
color: [0.5, 0.7, 0.5],
intensity: 1
constantAttenuation: 0,
linearAttenuation: 0,
quadraticAttenuation: 0,
space: "view"
});
new xeogl.PointLight({
pos: [0, 100, 100],
dir: [0, -1, 0],
color: [0.5, 0.7, 0.5],
intensity: 1
constantAttenuation: 0,
linearAttenuation: 0,
quadraticAttenuation: 0,
space: "view"
});
// Create box mesh
new xeogl.Mesh({
material: new xeogl.PhongMaterial({
ambient: [0.5, 0.5, 0.5],
diffuse: [1,0.3,0.3]
}),
geometry: new xeogl.BoxGeometry()
});
````
@class SpotLight
@module xeogl
@submodule lighting
@constructor
@extends Component
@param [owner] {Component} Owner component. When destroyed, the owner will destroy this component as well. Creates this component within the default {{#crossLink "Scene"}}{{/crossLink}} when omitted.
@param [cfg] {*} The SpotLight configuration
@param [cfg.id] {String} Optional ID, unique among all components in the parent {{#crossLink "Scene"}}Scene{{/crossLink}}, generated automatically when omitted.
@param [cfg.meta] {String:Object} Optional map of user-defined metadata to attach to this SpotLight.
@param [cfg.pos=[ 1.0, 1.0, 1.0 ]] {Float32Array} Position, in either World or View space, depending on the value of the **space** parameter.
@param [cfg.dir=[ 0.0, -1.0, 0.0 ]] {Float32Array} Direction in which this Spotlight is shining, in either World or View space, depending on the value of the **space** parameter.
@param [cfg.color=[0.7, 0.7, 0.8 ]] {Float32Array} Color of this SpotLight.
@param [cfg.intensity=1.0] {Number} Intensity of this SpotLight.
@param [cfg.constantAttenuation=0] {Number} Constant attenuation factor.
@param [cfg.linearAttenuation=0] {Number} Linear attenuation factor.
@param [cfg.quadraticAttenuation=0] {Number} Quadratic attenuation factor.
@param [cfg.space="view"] {String} The coordinate system this SpotLight is defined in - "view" or "world".
@param [cfg.shadow=false] {Boolean} Flag which indicates if this SpotLight casts a shadow.
*/
import {Component} from '../component.js';
import {State} from '../renderer/state.js';
import {RenderBuffer} from '../renderer/renderBuffer.js';
import {math} from '../math/math.js';
import {componentClasses} from "./../componentClasses.js";
const type = "xeogl.SpotLight";
class SpotLight extends Component {
/**
JavaScript class name for this Component.
For example: "xeogl.AmbientLight", "xeogl.MetallicMaterial" etc.
@property type
@type String
@final
*/
get type() {
return type;
}
init(cfg) {
super.init(cfg);
const self = this;
this._shadowRenderBuf = null;
this._shadowViewMatrix = null;
this._shadowProjMatrix = null;
this._shadowViewMatrixDirty = true;
this._shadowProjMatrixDirty = true;
this._state = new State({
type: "spot",
pos: math.vec3([1.0, 1.0, 1.0]),
dir: math.vec3([0.0, -1.0, 0.0]),
color: math.vec3([0.7, 0.7, 0.8]),
intensity: 1.0,
attenuation: [0.0, 0.0, 0.0],
space: cfg.space || "view",
shadow: false,
shadowDirty: true,
getShadowViewMatrix: (function () {
const look = math.vec3();
const up = math.vec3([0, 1, 0]);
return function () {
if (self._shadowViewMatrixDirty) {
if (!self._shadowViewMatrix) {
self._shadowViewMatrix = math.identityMat4();
}
math.addVec3(self._state.pos, self._state.dir, look);
math.lookAtMat4v(self._state.pos, look, up, self._shadowViewMatrix);
self._shadowViewMatrixDirty = false;
}
return self._shadowViewMatrix;
};
})(),
getShadowProjMatrix: function () {
if (self._shadowProjMatrixDirty) { // TODO: Set when canvas resizes
if (!self._shadowProjMatrix) {
self._shadowProjMatrix = math.identityMat4();
}
const canvas = self.scene.canvas.canvas;
math.perspectiveMat4(60 * (Math.PI / 180.0), canvas.clientWidth / canvas.clientHeight, 0.1, 400.0, self._shadowProjMatrix);
self._shadowProjMatrixDirty = false;
}
return self._shadowProjMatrix;
},
getShadowRenderBuf: function () {
if (!self._shadowRenderBuf) {
self._shadowRenderBuf = new RenderBuffer(self.scene.canvas.canvas, self.scene.canvas.gl);
}
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.shadow = cfg.shadow;
this.scene._lightCreated(this);
}
/**
The position of this SpotLight.
This will be either World- or View-space, depending on the value of {{#crossLink "SpotLight/space:property"}}{{/crossLink}}.
@property pos
@default [1.0, 1.0, 1.0]
@type Array(Number)
*/
set pos(value) {
this._state.pos.set(value || [1.0, 1.0, 1.0]);
this._shadowViewMatrixDirty = true;
this._renderer.imageDirty();
}
get pos() {
return this._state.pos;
}
/**
The direction in which the light is shining.
@property dir
@default [1.0, 1.0, 1.0]
@type Float32Array
*/
set dir(value) {
this._state.dir.set(value || [1.0, 1.0, 1.0]);
this._shadowViewMatrixDirty = true;
this._renderer.imageDirty();
}
get dir() {
return this._state.dir;
}
/**
The color of this SpotLight.
@property color
@default [0.7, 0.7, 0.8]
@type Float32Array
*/
set color(value) {
this._state.color.set(value || [0.7, 0.7, 0.8]);
this._renderer.imageDirty();
}
get color() {
return this._state.color;
}
/**
The intensity of this SpotLight.
Fires a {{#crossLink "SpotLight/intensity:event"}}{{/crossLink}} event on change.
@property intensity
@default 1.0
@type Number
*/
set intensity(value) {
value = value !== undefined ? value : 1.0;
this._state.intensity = value;
this._renderer.imageDirty();
}
get intensity() {
return this._state.intensity;
}
/**
The constant attenuation factor for this SpotLight.
@property constantAttenuation
@default 0
@type Number
*/
set constantAttenuation(value) {
this._state.attenuation[0] = value || 0.0;
this._renderer.imageDirty();
}
get constantAttenuation() {
return this._state.attenuation[0];
}
/**
The linear attenuation factor for this SpotLight.
@property linearAttenuation
@default 0
@type Number
*/
set linearAttenuation(value) {
this._state.attenuation[1] = value || 0.0;
this._renderer.imageDirty();
}
get linearAttenuation() {
return this._state.attenuation[1];
}
/**
The quadratic attenuation factor for this SpotLight.
@property quadraticAttenuation
@default 0
@type Number
*/
set quadraticAttenuation(value) {
this._state.attenuation[2] = value || 0.0;
this._renderer.imageDirty();
}
get quadraticAttenuation() {
return this._state.attenuation[2];
}
/**
Flag which indicates if this SpotLight casts a shadow.
@property shadow
@default false
@type Boolean
*/
set shadow(value) {
value = !!value;
if (this._state.shadow === value) {
return;
}
this._state.shadow = value;
this._shadowViewMatrixDirty = true;
this._renderer.imageDirty();
this.fire("dirty", true);
}
get shadow() {
return this._state.shadow;
}
destroy() {
super.destroy();
this._state.destroy();
if (this._shadowRenderBuf) {
this._shadowRenderBuf.destroy();
}
this.scene._lightDestroyed(this);
}
}
componentClasses[type] = SpotLight;
export{SpotLight};