Object.assign(pc, function () {
/**
* @component
* @constructor
* @name pc.LightComponent
* @classdesc The Light Component enables the Entity to light the scene. There are three types
* of light: directional, point and spot. Directional lights are global in that they are
* considered to be infinitely far away and light the entire scene. Point and spot lights
* are local in that they have a position and a range. A spot light is a specialization of
* a point light where light is emitted in a cone rather than in all directions. Lights
* also have the ability to cast shadows to add realism to your scenes.
* @description Creates a new Light Component.
* @param {pc.LightComponentSystem} system The ComponentSystem that created this Component.
* @param {pc.Entity} entity The Entity that this Component is attached to.
* @example
* // Add a pc.LightComponent to an entity
* var entity = new pc.Entity();
* entity.addComponent('light', {
* type: "point",
* color: new pc.Color(1, 0, 0),
* range: 10
* });
* @example
* // Get the pc.LightComponent on an entity
* var lightComponent = entity.light;
* @example
* // Update a property on a light component
* entity.light.range = 20;
* @property {String} type The type of light. Can be:
* <ul>
* <li>"directional": A light that is infinitely far away and lights the entire scene from one direction.</li>
* <li>"point": A light that illuminates in all directions from a point.</li>
* <li>"spot": A light that illuminates in all directions from a point and is bounded by a cone.</li>
* </ul>
* Defaults to "directional".
* @property {pc.Color} color The Color of the light. The alpha component of the color is
* ignored. Defaults to white (1, 1, 1).
* @property {Number} intensity The brightness of the light. Defaults to 1.
* @property {Boolean} castShadows If enabled the light will cast shadows. Defaults to false.
* @property {Number} shadowDistance The distance from the viewpoint beyond which shadows
* are no longer rendered. Affects directional lights only. Defaults to 40.
* @property {Number} shadowResolution The size of the texture used for the shadow map.
* Valid sizes are 64, 128, 256, 512, 1024, 2048. Defaults to 1024.
* @property {Number} shadowBias The depth bias for tuning the appearance of the shadow
* mapping generated by this light. Defaults to 0.05.
* @property {Number} normalOffsetBias Normal offset depth bias. Defaults to 0.
* @property {Number} range The range of the light. Affects point and spot lights only.
* Defaults to 10.
* @property {Number} innerConeAngle The angle at which the spotlight cone starts
* to fade off. The angle is specified in degrees. Affects spot lights only. Defaults
* to 40.
* @property {Number} outerConeAngle The angle at which the spotlight cone has faded
* to nothing. The angle is specified in degrees. Affects spot lights only. Defaults
* to 45.
* @property {Number} falloffMode Controls the rate at which a light attentuates from
* its position. Can be:
* <ul>
* <li>{@link pc.LIGHTFALLOFF_LINEAR}: Linear.</li>
* <li>{@link pc.LIGHTFALLOFF_INVERSESQUARED}: Inverse squared.</li>
* </ul>
* Affects point and spot lights only. Defaults to pc.LIGHTFALLOFF_LINEAR.
* @property {Number} mask Defines a mask to determine which {@link pc.MeshInstance}s are
* lit by this light. Defaults to 1.
* @property {Boolean} affectDynamic If enabled the light will affect non-lightmapped objects
* @property {Boolean} affectLightmapped If enabled the light will affect lightmapped objects
* @property {Boolean} bake If enabled the light will be rendered into lightmaps
* @property {Boolean} bakeDir If enabled and bake=true, the light's direction will contribute to directional lightmaps.
* Be aware, that directional lightmap is an approximation and can only hold single direction per pixel.
* Intersecting multiple lights with bakeDir=true may lead to incorrect look of specular/bump-mapping in the area of intersection.
* The error is not always visible though, and highly scene-dependent.
* @property {Number} shadowUpdateMode Tells the renderer how often shadows must be updated for this light. Options:
* <ul>
* <li>{@link pc.SHADOWUPDATE_NONE}: Don't render shadows.</li>
* <li>{@link pc.SHADOWUPDATE_THISFRAME}: Render shadows only once (then automatically switches to pc.SHADOWUPDATE_NONE).</li>
* <li>{@link pc.SHADOWUPDATE_REALTIME}: Render shadows every frame (default).</li>
* </ul>
* @property {Number} shadowType Type of shadows being rendered by this light. Options:
* <ul>
* <li>{@link pc.SHADOW_PCF3}: Render depth (color-packed on WebGL 1.0), can be used for PCF 3x3 sampling.</li>
* <li>{@link pc.SHADOW_VSM8}: Render packed variance shadow map. All shadow receivers must also cast shadows for this mode to work correctly.</li>
* <li>{@link pc.SHADOW_VSM16}: Render 16-bit exponential variance shadow map. Requires OES_texture_half_float extension. Falls back to pc.SHADOW_VSM8, if not supported.</li>
* <li>{@link pc.SHADOW_VSM32}: Render 32-bit exponential variance shadow map. Requires OES_texture_float extension. Falls back to pc.SHADOW_VSM16, if not supported.</li>
* <li>{@link pc.SHADOW_PCF5}: Render depth buffer only, can be used for hardware-accelerated PCF 5x5 sampling. Requires WebGL2. Falls back to pc.SHADOW_PCF3 on WebGL 1.0.</li>
* </ul>
* @property {Number} vsmBlurMode Blurring mode for variance shadow maps:
* <ul>
* <li>{@link pc.BLUR_BOX}: Box filter.</li>
* <li>{@link pc.BLUR_GAUSSIAN}: Gaussian filter. May look smoother than box, but requires more samples.</li>
* </ul>
* @property {Number} vsmBlurSize Number of samples used for blurring a variance shadow map. Only uneven numbers work, even are incremented. Minimum value is 1, maximum is 25.
* @property {Number} cookieAsset Asset that has texture that will be assigned to cookie internally once asset resource is available.
* @property {pc.Texture} cookie Projection texture. Must be 2D for spot and cubemap for point (ignored if incorrect type is used).
* @property {Number} cookieIntensity Projection texture intensity (default is 1).
* @property {Boolean} cookieFalloff Toggle normal spotlight falloff when projection texture is used. When set to false, spotlight will work like a pure texture projector (only fading with distance). Default is false.
* @property {String} cookieChannel Color channels of the projection texture to use. Can be "r", "g", "b", "a", "rgb" or any swizzled combination.
* @property {Number} cookieAngle Angle for spotlight cookie rotation.
* @property {pc.Vec2} cookieScale Spotlight cookie scale.
* @property {pc.Vec2} cookieOffset Spotlight cookie position offset.
* @property {Boolean} isStatic Mark light as non-movable (optimization)
* @property {Array} layers An array of layer IDs ({@link pc.Layer#id}) to which this light should belong.
* Don't push/pop/splice or modify this array, if you want to change it - set a new one instead.
* @extends pc.Component
*/
var LightComponent = function LightComponent(system, entity) {
pc.Component.call(this, system, entity);
this._cookieAsset = null;
this._cookieAssetId = null;
this._cookieAssetAdd = false;
this._cookieMatrix = null;
};
LightComponent.prototype = Object.create(pc.Component.prototype);
LightComponent.prototype.constructor = LightComponent;
var _props = [];
var _propsDefault = [];
var _defineProperty = function (name, defaultValue, setFunc, skipEqualsCheck) {
var c = LightComponent.prototype;
_props.push(name);
_propsDefault.push(defaultValue);
Object.defineProperty(c, name, {
get: function () {
return this.data[name];
},
set: function (value) {
var data = this.data;
var oldValue = data[name];
if (!skipEqualsCheck && oldValue === value) return;
data[name] = value;
if (setFunc) setFunc.call(this, value, oldValue);
},
configurable: true
});
};
var _defineProps = function () {
_defineProperty("enabled", true, function (newValue, oldValue) {
this.onSetEnabled(null, oldValue, newValue);
});
_defineProperty("light", null);
_defineProperty("type", 'directional', function (newValue, oldValue) {
this.system.changeType(this, oldValue, newValue);
// refresh light properties because changing the type does not reset the
// light properties
this.refreshProperties();
});
_defineProperty("color", new pc.Color(1, 1, 1), function (newValue, oldValue) {
this.light.setColor(newValue);
}, true);
_defineProperty("intensity", 1, function (newValue, oldValue) {
this.light.intensity = newValue;
});
_defineProperty("castShadows", false, function (newValue, oldValue) {
this.light.castShadows = newValue;
});
_defineProperty("shadowDistance", 40, function (newValue, oldValue) {
this.light.shadowDistance = newValue;
});
_defineProperty("shadowResolution", 1024, function (newValue, oldValue) {
this.light.shadowResolution = newValue;
});
_defineProperty("shadowBias", 0.05, function (newValue, oldValue) {
this.light.shadowBias = -0.01 * newValue;
});
_defineProperty("normalOffsetBias", 0, function (newValue, oldValue) {
this.light.normalOffsetBias = newValue;
});
_defineProperty("range", 10, function (newValue, oldValue) {
this.light.attenuationEnd = newValue;
});
_defineProperty("innerConeAngle", 40, function (newValue, oldValue) {
this.light.innerConeAngle = newValue;
});
_defineProperty("outerConeAngle", 45, function (newValue, oldValue) {
this.light.outerConeAngle = newValue;
});
_defineProperty("falloffMode", pc.LIGHTFALLOFF_LINEAR, function (newValue, oldValue) {
this.light.falloffMode = newValue;
});
_defineProperty("shadowType", pc.SHADOW_PCF3, function (newValue, oldValue) {
this.light.shadowType = newValue;
});
_defineProperty("vsmBlurSize", 11, function (newValue, oldValue) {
this.light.vsmBlurSize = newValue;
});
_defineProperty("vsmBlurMode", pc.BLUR_GAUSSIAN, function (newValue, oldValue) {
this.light.vsmBlurMode = newValue;
});
_defineProperty("vsmBias", 0.01 * 0.25, function (newValue, oldValue) {
this.light.vsmBias = newValue;
});
_defineProperty("cookieAsset", null, function (newValue, oldValue) {
if (this._cookieAssetId && ((newValue instanceof pc.Asset && newValue.id === this._cookieAssetId) || newValue === this._cookieAssetId))
return;
this.onCookieAssetRemove();
this._cookieAssetId = null;
if (newValue instanceof pc.Asset) {
this.data.cookieAsset = newValue.id;
this._cookieAssetId = newValue.id;
this.onCookieAssetAdd(newValue);
} else if (typeof newValue === 'number') {
this._cookieAssetId = newValue;
var asset = this.system.app.assets.get(newValue);
if (asset) {
this.onCookieAssetAdd(asset);
} else {
this._cookieAssetAdd = true;
this.system.app.assets.on('add:' + this._cookieAssetId, this.onCookieAssetAdd, this);
}
}
});
_defineProperty("cookie", null, function (newValue, oldValue) {
this.light.cookie = newValue;
});
_defineProperty("cookieIntensity", 1, function (newValue, oldValue) {
this.light.cookieIntensity = newValue;
});
_defineProperty("cookieFalloff", true, function (newValue, oldValue) {
this.light.cookieFalloff = newValue;
});
_defineProperty("cookieChannel", "rgb", function (newValue, oldValue) {
this.light.cookieChannel = newValue;
});
_defineProperty("cookieAngle", 0, function (newValue, oldValue) {
if (newValue !== 0 || this.cookieScale !== null) {
if (!this._cookieMatrix) this._cookieMatrix = new pc.Vec4();
var scx = 1;
var scy = 1;
if (this.cookieScale) {
scx = this.cookieScale.x;
scy = this.cookieScale.y;
}
var c = Math.cos(newValue * pc.math.DEG_TO_RAD);
var s = Math.sin(newValue * pc.math.DEG_TO_RAD);
this._cookieMatrix.set(c / scx, -s / scx, s / scy, c / scy);
this.light.cookieTransform = this._cookieMatrix;
} else {
this.light.cookieTransform = null;
}
});
_defineProperty("cookieScale", null, function (newValue, oldValue) {
if (newValue !== null || this.cookieAngle !== 0) {
if (!this._cookieMatrix) this._cookieMatrix = new pc.Vec4();
var scx = newValue.x;
var scy = newValue.y;
var c = Math.cos(this.cookieAngle * pc.math.DEG_TO_RAD);
var s = Math.sin(this.cookieAngle * pc.math.DEG_TO_RAD);
this._cookieMatrix.set(c / scx, -s / scx, s / scy, c / scy);
this.light.cookieTransform = this._cookieMatrix;
} else {
this.light.cookieTransform = null;
}
}, true);
_defineProperty("cookieOffset", null, function (newValue, oldValue) {
this.light.cookieOffset = newValue;
}, true);
_defineProperty("shadowUpdateMode", pc.SHADOWUPDATE_REALTIME, function (newValue, oldValue) {
this.light.shadowUpdateMode = newValue;
});
_defineProperty("mask", 1, function (newValue, oldValue) {
this.light.mask = newValue;
});
_defineProperty("affectDynamic", true, function (newValue, oldValue) {
if (newValue) {
this.light.mask |= pc.MASK_DYNAMIC;
} else {
this.light.mask &= ~pc.MASK_DYNAMIC;
}
this.light.mask = this.light._mask;
});
_defineProperty("affectLightmapped", false, function (newValue, oldValue) {
if (newValue) {
this.light.mask |= pc.MASK_BAKED;
if (this.bake) this.light.mask &= ~pc.MASK_LIGHTMAP;
} else {
this.light.mask &= ~pc.MASK_BAKED;
if (this.bake) this.light.mask |= pc.MASK_LIGHTMAP;
}
this.light.mask = this.light._mask;
});
_defineProperty("bake", false, function (newValue, oldValue) {
if (newValue) {
this.light.mask |= pc.MASK_LIGHTMAP;
if (this.affectLightmapped) this.light.mask &= ~pc.MASK_BAKED;
} else {
this.light.mask &= ~pc.MASK_LIGHTMAP;
if (this.affectLightmapped) this.light.mask |= pc.MASK_BAKED;
}
this.light.mask = this.light._mask;
});
_defineProperty("bakeDir", true, function (newValue, oldValue) {
this.light.bakeDir = newValue;
});
_defineProperty("isStatic", false, function (newValue, oldValue) {
this.light.isStatic = newValue;
});
_defineProperty("layers", [pc.LAYERID_WORLD], function (newValue, oldValue) {
var i, layer;
for (i = 0; i < oldValue.length; i++) {
layer = this.system.app.scene.layers.getLayerById(oldValue[i]);
if (!layer) continue;
layer.removeLight(this);
}
for (i = 0; i < newValue.length; i++) {
layer = this.system.app.scene.layers.getLayerById(newValue[i]);
if (!layer) continue;
if (this.enabled && this.entity.enabled) {
layer.addLight(this);
}
}
});
};
_defineProps();
Object.defineProperty(LightComponent.prototype, "enable", {
get: function () {
console.warn("WARNING: enable: Property is deprecated. Query enabled property instead.");
return this.enabled;
},
set: function (value) {
console.warn("WARNING: enable: Property is deprecated. Set enabled property instead.");
this.enabled = value;
}
});
Object.assign(LightComponent.prototype, {
addLightToLayers: function () {
var layer;
for (var i = 0; i < this.layers.length; i++) {
layer = this.system.app.scene.layers.getLayerById(this.layers[i]);
if (!layer) continue;
layer.addLight(this);
}
},
removeLightFromLayers: function () {
var layer;
for (var i = 0; i < this.layers.length; i++) {
layer = this.system.app.scene.layers.getLayerById(this.layers[i]);
if (!layer) continue;
layer.removeLight(this);
}
},
onLayersChanged: function (oldComp, newComp) {
if (this.enabled && this.entity.enabled) {
this.addLightToLayers();
}
oldComp.off("add", this.onLayerAdded, this);
oldComp.off("remove", this.onLayerRemoved, this);
newComp.on("add", this.onLayerAdded, this);
newComp.on("remove", this.onLayerRemoved, this);
},
onLayerAdded: function (layer) {
var index = this.layers.indexOf(layer.id);
if (index < 0) return;
if (this.enabled && this.entity.enabled) {
layer.addLight(this);
}
},
onLayerRemoved: function (layer) {
var index = this.layers.indexOf(layer.id);
if (index < 0) return;
layer.removeLight(this);
},
refreshProperties: function () {
var name;
for (var i = 0; i < _props.length; i++) {
name = _props[i];
this[name] = this[name];
}
if (this.enabled && this.entity.enabled)
this.onEnable();
},
updateShadow: function () {
this.light.updateShadow();
},
onCookieAssetSet: function () {
var forceLoad = false;
if (this._cookieAsset.type === 'cubemap' && !this._cookieAsset.loadFaces) {
this._cookieAsset.loadFaces = true;
forceLoad = true;
}
if (!this._cookieAsset.resource || forceLoad)
this.system.app.assets.load(this._cookieAsset);
if (this._cookieAsset.resource)
this.onCookieAssetLoad();
},
onCookieAssetAdd: function (asset) {
if (this._cookieAssetId !== asset.id)
return;
this._cookieAsset = asset;
if (this.light._enabled)
this.onCookieAssetSet();
this._cookieAsset.on('load', this.onCookieAssetLoad, this);
this._cookieAsset.on('remove', this.onCookieAssetRemove, this);
},
onCookieAssetLoad: function () {
if (!this._cookieAsset || !this._cookieAsset.resource)
return;
this.cookie = this._cookieAsset.resource;
},
onCookieAssetRemove: function () {
if (!this._cookieAssetId)
return;
if (this._cookieAssetAdd) {
this.system.app.assets.off('add:' + this._cookieAssetId, this.onCookieAssetAdd, this);
this._cookieAssetAdd = false;
}
if (this._cookieAsset) {
this._cookieAsset.off('load', this.onCookieAssetLoad, this);
this._cookieAsset.off('remove', this.onCookieAssetRemove, this);
this._cookieAsset = null;
}
this.cookie = null;
},
onEnable: function () {
this.light.enabled = true;
this.system.app.scene.on("set:layers", this.onLayersChanged, this);
if (this.system.app.scene.layers) {
this.system.app.scene.layers.on("add", this.onLayerAdded, this);
this.system.app.scene.layers.on("remove", this.onLayerRemoved, this);
}
if (this.enabled && this.entity.enabled) {
this.addLightToLayers();
}
if (this._cookieAsset && !this.cookie)
this.onCookieAssetSet();
},
onDisable: function () {
this.light.enabled = false;
this.system.app.scene.off("set:layers", this.onLayersChanged, this);
if (this.system.app.scene.layers) {
this.system.app.scene.layers.off("add", this.onLayerAdded, this);
this.system.app.scene.layers.off("remove", this.onLayerRemoved, this);
}
this.removeLightFromLayers();
}
});
return {
LightComponent: LightComponent,
_lightProps: _props,
_lightPropsDefault: _propsDefault
};
}());