define(["kick/core/Constants", "kick/core/Util", "kick/math/Quat", "kick/math/Mat4", "kick/math/Vec4", "kick/math/Vec3", "kick/math/Aabb", "kick/math/Frustum", "./EngineUniforms", "./CameraPicking", "kick/material/Material", "kick/texture/RenderTexture", "kick/core/EngineSingleton", "kick/core/Observable"],
function (Constants, Util, Quat, Mat4, Vec4, Vec3, Aabb, Frustum, EngineUniforms, CameraPicking, Material, RenderTexture, EngineSingleton, Observable) {
"use strict";
/**
* @module kick.scene
*/
var DEBUG = Constants._DEBUG,
ASSERT = Constants._ASSERT,
Camera;
/**
* Creates a game camera
* @example
* // create a game object in [0,0,0] facing down the -z axis
* var cameraObject = engine.activeScene.createGameObject();
* cameraObject.transform.position = [0,0,5];
* // create a orthographic camera
* var camera = new kick.scene.Camera({
* perspective: false,
* left:-5,
* right:5,
* top:5,
* bottom:-5
* });
* cameraObject.addComponent(camera);
*
* @class Camera
* @namespace kick.scene
* @extends kick.scene.Component
* @constructor
* @param {Config} configuration with same properties as the Camera
*/
Camera = function (config) {
var gl,
glState,
thisObj = this,
transform,
engine = EngineSingleton.engine,
_enabled = true,
c = Constants,
_renderShadow = false,
_renderTarget = null,
_fieldOfView = 60 * Constants._DEGREE_TO_RADIAN,
_near = 0.1,
_far = 1000,
_left = -1,
_right = 1,
_bottom = -1,
_top = 1,
_clearColor = Vec4.clone([0, 0, 0, 1]),
_shadowmapClearColor = Vec4.clone([1, 1, 1, 1]),
_perspective = true,
_clearFlagColor = true,
_clearFlagDepth = true,
_replacementMaterial = null,
_currentClearFlags,
_cameraIndex = 1,
_layerMask = 0xffffffff,
_shadowmapMaterial,
_scene,
pickingObject = null,
projectionMatrix = Mat4.create(),
viewMatrix = Mat4.create(),
viewProjectionMatrix = Mat4.create(),
lightMatrix = Mat4.create(),
engineUniforms = new EngineUniforms({
viewMatrix: viewMatrix,
projectionMatrix: projectionMatrix,
viewProjectionMatrix: viewProjectionMatrix,
lightMatrix: lightMatrix,
currentCamera: thisObj,
currentCameraTransform: null
}),
isContextListenerRegistered = false,
contextLost = function () {
gl = null;
},
contextRestored = function (newGL) {
gl = newGL;
},
renderableComponentsBackGroundAndGeometry = [],
renderableComponentsTransparent = [],
renderableComponentsOverlay = [],
renderableComponentsArray = [renderableComponentsBackGroundAndGeometry, renderableComponentsTransparent, renderableComponentsOverlay],
_normalizedViewportRect = Vec4.clone([0, 0, 1, 1]),
offsetMatrix = Mat4.clone([
0.5, 0, 0, 0,
0, 0.5, 0, 0,
0, 0, 0.5, 0,
0.5, 0.5, 0.5, 1
]),
shadowLightProjection,
shadowLightOffsetFromCamera,
isNumber = function (o) {
return typeof o === "number";
},
isBoolean = function (o) {
return typeof o === "boolean";
},
computeClearFlag = function () {
_currentClearFlags = (_clearFlagColor ? c.GL_COLOR_BUFFER_BIT : 0) | (_clearFlagDepth ? c.GL_DEPTH_BUFFER_BIT : 0);
},
setupClearColor = function (color) {
if (glState.currentClearColor !== color) {
glState.currentClearColor = color;
gl.clearColor(color[0], color[1], color[2], color[3]);
}
},
assertNumber = function (newValue, name) {
if (!isNumber(newValue)) {
Util.fail("Camera." + name + " must be number");
}
},
setupViewport = function (offsetX, offsetY, width, height) {
gl.viewport(offsetX, offsetY, width, height);
gl.scissor(offsetX, offsetY, width, height);
},
/**
* Compare two objects based on renderOrder value, then on material.shader.uid (if exist)
* and finally on mesh.
* @method compareRenderOrder
* @param {Component} a
* @param {Component} b
* @return Number
* @private
*/
compareRenderOrder = function (a, b) {
var aRenderOrder = a.renderOrder || 1000,
bRenderOrder = b.renderOrder || 1000,
getMeshUid,
getMeshShaderUid = function (o, defaultValue) {
var names = ["material", "shader", "uid"],
i;
for (i = 0; i < names.length; i++) {
o = o[names[i]];
if (!o) {
if (DEBUG) {
Util.warn("Cannot find uid of " + o);
}
return defaultValue;
}
}
return o;
};
getMeshUid = function (o, defaultValue) {
return o.mesh.uid || defaultValue;
};
if (aRenderOrder === bRenderOrder && a.material && b.material) {
aRenderOrder = getMeshShaderUid(a, aRenderOrder);
bRenderOrder = getMeshShaderUid(b, aRenderOrder);
}
if (aRenderOrder === bRenderOrder && a.mesh && b.mesh) {
aRenderOrder = getMeshUid(a, aRenderOrder);
bRenderOrder = getMeshUid(b, aRenderOrder);
}
return aRenderOrder - bRenderOrder;
},
sortTransparentBackToFront = function () {
// calculate distances
var temp = Vec3.create(),
cameraPosition = transform.position,
i,
object,
objectPosition;
for (i = renderableComponentsTransparent.length - 1; i >= 0; i--) {
object = renderableComponentsTransparent[i];
objectPosition = object.gameObject.transform.position;
object.distanceToCamera = Vec3.squaredLength(Vec3.subtract(temp, objectPosition, cameraPosition));
}
function compareDistanceToCamera(a, b) {
return b.distanceToCamera - a.distanceToCamera;
}
renderableComponentsTransparent.sort(compareDistanceToCamera);
},
/**
* @method renderSceneObjects
* @param sceneLightObj
* @param shader
* @private
*/
renderSceneObjects = (function () {
var aabbWorldSpace = Aabb.create(),
frustumPlanes = new Float32Array(24);
return function (sceneLightObj, replacementMaterial) {
var cullByViewFrustum = function (component) {
var componentAabb = component.aabb,
gameObject = component.gameObject;
if (componentAabb && gameObject) {
Aabb.transform(aabbWorldSpace, componentAabb, gameObject.transform.getGlobalMatrix());
return Frustum.intersectAabb(frustumPlanes, aabbWorldSpace) === Frustum.OUTSIDE;
}
return false;
},
render = function (renderableComponents) {
var length = renderableComponents.length,
j,
renderableComponent;
for (j = 0; j < length; j++) {
renderableComponent = renderableComponents[j];
if (!cullByViewFrustum(renderableComponent)) {
renderableComponent.render(engineUniforms, replacementMaterial);
}
}
};
// update frustum planes
Frustum.extractPlanes(frustumPlanes, engineUniforms.viewProjectionMatrix, false);
engineUniforms.sceneLights = sceneLightObj;
render(renderableComponentsBackGroundAndGeometry);
render(renderableComponentsTransparent);
render(renderableComponentsOverlay);
};
}()),
renderShadowMap = function (sceneLightObj) {
var directionalLight = sceneLightObj.directionalLight,
directionalLightTransform = directionalLight.gameObject.transform,
shadowRenderTexture = directionalLight.shadowRenderTexture,
renderTextureDimension = shadowRenderTexture.dimension,
renderTextureWidth = renderTextureDimension[0],
renderTextureHeight = renderTextureDimension[1],
transformedOffsetFromCamera = Vec3.create(),
cameraPosition = Vec3.create();
setupViewport(0, 0, renderTextureWidth, renderTextureHeight);
shadowRenderTexture.bind();
setupClearColor(_shadowmapClearColor);
gl.clear(c.GL_COLOR_BUFFER_BIT | c.GL_DEPTH_BUFFER_BIT);
// fitting:
// Using a sphere with the center in front of the camera (based on 0.5 * engine.config.shadowDistance)
// The actual light volume is a bit larger than the sphere (to include the corners).
// The near plane of the light volume is extended by the engine.config.shadowNearMultiplier
// Note that this is a very basic fitting algorithm with rooms for improvement
Mat4.copy(projectionMatrix, shadowLightProjection);
// find the position of the light 'center' in world space
transformedOffsetFromCamera = Quat.multiplyVec3(transformedOffsetFromCamera, transform.rotation, [0, 0, -shadowLightOffsetFromCamera]);
cameraPosition = Vec3.add(cameraPosition, transformedOffsetFromCamera, transform.position);
// adjust to reduce flicker when rotating camera
cameraPosition[0] = Math.round(cameraPosition[0]);
cameraPosition[1] = Math.round(cameraPosition[1]);
cameraPosition[2] = Math.round(cameraPosition[2]);
Mat4.setTRSInverse(viewMatrix, cameraPosition, directionalLightTransform.localRotation, [1, 1, 1]);
Mat4.multiply(viewProjectionMatrix, projectionMatrix, viewMatrix);
// update light matrix (will be used when scene is rendering with shadow map shader)
Mat4.multiply(lightMatrix, Mat4.multiply(lightMatrix, offsetMatrix, projectionMatrix), viewMatrix);
renderSceneObjects(sceneLightObj, _shadowmapMaterial);
shadowRenderTexture.unbind();
},
/**
* Add components that implements the render function and match the camera layerMask to cameras renderable components
* @method componentAdded
* @param {kick.scene.Component} component
* @private
*/
componentAdded = function (component) {
var renderOrder,
array;
if (ASSERT){
if (Array.isArray(component)){
Util.fail("Should be array");
}
}
if (typeof component.render === "function" && (component.gameObject.layer & _layerMask)) {
renderOrder = component.renderOrder || 1000;
if (renderOrder < 2000) {
array = renderableComponentsBackGroundAndGeometry;
} else if (renderOrder >= 3000) {
array = renderableComponentsOverlay;
} else {
array = renderableComponentsTransparent;
}
if (!Util.contains(array, component)) {
Util.insertSorted(component, array, compareRenderOrder);
if (component.addEventListener) {
component.addEventListener('componentUpdated', componentUpdated);
}
}
}
},
/**
* @method componentRemoved
* @param {kick.scene.Component} component
* @return {Boolean}
* @private
*/
componentRemoved = function (component) {
var removed = false,
j;
if (ASSERT){
if (Array.isArray(component)){
Util.fail("Should be array");
}
}
if (typeof (component.render) === "function") {
for (j = renderableComponentsArray.length - 1; j >= 0; j--) {
removed |= Util.removeElementFromArray(renderableComponentsArray[j], component);
}
}
if (removed) {
if (component.removeEventListener){
component.removeEventListener('componentUpdated', componentUpdated);
}
}
return removed;
},
componentUpdated = function (component) {
componentRemoved(component);
componentAdded(component);
},
initShadowMap = function(){
var shadowRadius,
nearPlanePosition,
_shadowmapShader,
materialConfig;
_shadowmapShader = engine.project.load(engine.project.ENGINE_SHADER___SHADOWMAP);
materialConfig = {
name: "Shadow map material",
shader: _shadowmapShader
};
_shadowmapMaterial = new Material(materialConfig);
// calculate the shadow projection based on engine.config parameters
shadowLightOffsetFromCamera = engine.config.shadowDistance * 0.5; // first find radius
shadowRadius = shadowLightOffsetFromCamera * 1.55377397403004; // sqrt(2+sqrt(2))
nearPlanePosition = -shadowRadius * engine.config.shadowNearMultiplier;
shadowLightProjection = Mat4.create();
Mat4.ortho(shadowLightProjection, -shadowRadius, shadowRadius, -shadowRadius, shadowRadius,
nearPlanePosition, shadowRadius);
};
/**
* Schedules a camera picking session. During next repaint a picking session is done. If the pick hits some
* game objects, then a callback is added to the event queue (and will run in next frame). The pickObject can
* be used for getting UV coordinate for the point (if available)
* @method pickPoint
* @param {function} gameObjectPickedFn callback function with the signature function(pickObject)
* @param {Number} x coordinate in screen coordinates (between 0 and canvas width - 1)
* @param {Number} y coordinate in screen coordinates (between 0 and canvas height - 1)
*/
this.pickPoint = function (gameObjectPickedFn, x, y) {
if (!pickingObject) {
pickingObject = new CameraPicking(setupClearColor, renderSceneObjects, _scene, thisObj);
}
pickingObject.add({
gameObjectPickedFn: gameObjectPickedFn,
x: x,
y: glState.viewportSize[1] - y,
width: 1,
height: 1,
point: true
});
};
/**
* Schedules a camera picking session. During next repaint a picking session is done. If the pick hits some
* game objects, then a callback is added to the event queue (and will run in next frame).
* Note since the WebGL window coordinate has the origin in the lower left corner and browsers coordinate
* system has the origin in the upper left corner, you may need to compute y as canvas.height - mouseCoordinate.y
* @method pick
* @param {function} gameObjectPickedFn callback function with the signature function(gameObject, hitCount)
* @param {Number} x coordinate in screen coordinates (between 0 and canvas width - 1)
* @param {Number} y coordinate in screen coordinates (between 0 and canvas height - 1)
* @param {Number} [width=1]
* @param {Number} [height=1]
*/
this.pick = function (gameObjectPickedFn, x, y, width, height) {
width = width || 1;
height = height || 1;
if (!pickingObject) {
pickingObject = new CameraPicking(setupClearColor, renderSceneObjects, _scene);
}
pickingObject.add({
gameObjectPickedFn: gameObjectPickedFn,
x: x,
y: y,
width: width,
height: height
});
};
Observable.call(this, [
/**
* Fired every frame when camera render is done
* @event postRender
* @param {kick.scene.Camera} camera
*/
"postRender"
]
);
/**
* Clear the screen and set the projectionMatrix and modelViewMatrix on the glState object.
* Called during renderScene
* @method setupCamera
*/
this.setupCamera = function () {
var viewportDimension = _renderTarget ? _renderTarget.dimension : glState.viewportSize,
viewPortWidth = viewportDimension[0],
viewPortHeight = viewportDimension[1],
offsetX = viewPortWidth * _normalizedViewportRect[0],
offsetY = viewPortHeight * _normalizedViewportRect[1],
width = viewPortWidth * _normalizedViewportRect[2],
height = viewPortHeight * _normalizedViewportRect[3],
globalMatrixInv;
setupViewport(offsetX, offsetY, width, height);
glState.currentMaterial = null; // clear current material
setupClearColor(_clearColor);
gl.clear(_currentClearFlags);
if (_perspective) {
Mat4.perspective(projectionMatrix, _fieldOfView, width / height,
_near, _far);
} else {
Mat4.ortho(projectionMatrix, _left, _right, _bottom, _top,
_near, _far);
}
globalMatrixInv = transform.getGlobalTRSInverse();
Mat4.copy(viewMatrix, globalMatrixInv);
Mat4.multiply(viewProjectionMatrix, projectionMatrix, viewMatrix);
};
/**
* Handles the camera setup (get fast reference to transform and glcontext).
* Also register component listener on scene
* @method activated
*/
this.activated = function () {
var gameObject = this.gameObject,
componentsWithRender,
i;
engineUniforms.currentCameraTransform = gameObject.transform;
if (!isContextListenerRegistered) {
isContextListenerRegistered = true;
engine.addEventListener('contextLost', contextLost);
engine.addEventListener('contextRestored', contextRestored);
}
transform = gameObject.transform;
gl = engine.gl;
glState = engine.glState;
_scene = gameObject.scene;
renderableComponentsBackGroundAndGeometry.length = 0;
renderableComponentsTransparent.length = 0;
renderableComponentsOverlay.length = 0;
_scene.addEventListener('componentAdded', componentAdded);
_scene.addEventListener('componentRemoved', componentRemoved);
// add current components in scene
componentsWithRender = _scene.findComponentsWithMethod("render");
for (i = 0; i < componentsWithRender.length; i++) {
componentAdded(componentsWithRender[i]);
}
// init shadowmap
if (engine.config.shadows) {
initShadowMap();
} else if (_renderShadow) {
_renderShadow = false; // disable render shadow
if (ASSERT) {
Util.fail("engine.config.shadows must be enabled for shadows");
}
}
};
/**
* Deregister component listener on scene
* @method deactivated
*/
this.deactivated = function () {
_scene.removeComponentListener(thisObj);
};
/**
* @method renderScene
* @param {kick.scene.SceneLights} sceneLightObj
*/
this.renderScene = function (sceneLightObj) {
var i,
textureId;
if (!_enabled) {
return;
}
if (_renderShadow && sceneLightObj.directionalLight && sceneLightObj.directionalLight.shadow) {
glState.currentMaterial = null; // clear current material
renderShadowMap(sceneLightObj);
}
// setup render target
if (_renderTarget) {
_renderTarget.bind();
}
thisObj.setupCamera();
sceneLightObj.recomputeLight(viewMatrix);
if (renderableComponentsTransparent.length > 0) {
sortTransparentBackToFront();
}
renderSceneObjects(sceneLightObj, _replacementMaterial);
if (_renderTarget) {
_renderTarget.unbind();
}
if (_renderTarget && _renderTarget.colorTexture && _renderTarget.colorTexture.generateMipmaps) {
textureId = _renderTarget.colorTexture.textureId;
gl.bindTexture(gl.TEXTURE_2D, textureId);
gl.generateMipmap(gl.TEXTURE_2D);
}
if (pickingObject) {
pickingObject.handlePickRequests(sceneLightObj, engineUniforms);
}
thisObj.fireEvent("postRender", thisObj);
};
Object.defineProperties(this, {
/**
* Allows usage of replacement material on camera rendering
* Default value is null.
* @property replacementMaterial
* @type kick.material.Shader
*/
replacementMaterial: {
get: function () { return _replacementMaterial; },
set: function (newValue) { _replacementMaterial = newValue; }
},
/**
* Default is true
* @property enabled
* @type Boolean
*/
enabled: {
get: function () { return _enabled; },
set: function (newValue) { _enabled = newValue; }
},
/**
* Default false
* @property renderShadow
* @type Boolean
*/
renderShadow: {
get: function () { return _renderShadow; },
set: function (newValue) {
if (engine) { // if object is initialized
if (engine.config.shadows) {
_renderShadow = newValue;
} else if (newValue) {
if (ASSERT) {
Util.fail("engine.config.shadows must be enabled for shadows");
}
}
} else {
_renderShadow = newValue;
}
}
},
/**
* Camera renders only objects where the components layer exist in the layer mask. <br>
* @property layerMask
* @type Number
*/
layerMask: {
get: function () { return _layerMask; },
set: function (newValue) {
if (c._ASSERT) {
if (!isNumber(newValue)) {
Util.fail("Camera.layerMask should be a number");
}
}
_layerMask = newValue;
}
},
/**
* Set the render target of the camera. Null means screen framebuffer.<br>
* @property renderTarget
* @type kick.texture.RenderTexture
*/
renderTarget: {
get: function () { return _renderTarget; },
set: function (newValue) {
if (c._ASSERT) {
if (newValue !== null && !(newValue instanceof RenderTexture)) {
Util.fail("Camera.renderTarget should be null or a kick.texture.RenderTexture");
}
}
_renderTarget = newValue;
}
},
/**
* Set the field of view Y in degrees<br>
* Only used when perspective camera type. Default 60.0.
* Must be between 1 and 179
* @property fieldOfView
* @type Number
*/
fieldOfView: {
get: function () { return _fieldOfView * Constants._RADIAN_TO_DEGREE; },
set: function (newValue) {
if (c._ASSERT) {
assertNumber(newValue, "fieldOfView");
}
_fieldOfView = Math.min(179, Math.max(newValue, 1)) * Constants._DEGREE_TO_RADIAN;
}
},
/**
* Set the near clipping plane of the view volume<br>
* Used in both perspective and orthogonale camera.<br>
* Default 0.1
* @property near
* @type Number
*/
near: {
get: function () {
return _near;
},
set: function (newValue) {
if (c._ASSERT) {
assertNumber(newValue, "near");
}
_near = newValue;
}
},
/**
* Set the far clipping plane of the view volume<br>
* Used in both perspective and orthogonale camera.<br>
* Default 1000.0
* @property far
* @type Number
*/
far: {
get: function () {
return _far;
},
set: function (newValue) {
if (c._ASSERT) {
assertNumber(newValue, "far");
}
_far = newValue;
}
},
/**
* True means camera is perspective projection, false means orthogonale projection<br>
* Default true
* @property perspective
* @type Boolean
*/
perspective: {
get: function () {
return _perspective;
},
set: function (newValue) {
if (c._ASSERT) {
if (!isBoolean(newValue)) {
Util.fail("Camera.perspective must be a boolean");
}
}
_perspective = newValue;
}
},
/**
* Only used for orthogonal camera type (!cameraTypePerspective). Default -1
* @property left
* @type Number
*/
left: {
get: function () {
return _left;
},
set: function (newValue) {
if (c._ASSERT) {
assertNumber(newValue, "left");
}
_left = newValue;
}
},
/**
* Only used for orthogonal camera type (!cameraTypePerspective). Default 1
* @property right
* @type Number
*/
right: {
get: function () {
return _right;
},
set: function (newValue) {
if (c._ASSERT) {
assertNumber(newValue, "right");
}
_right = newValue;
}
},
/**
* Only used when orthogonal camera type (!cameraTypePerspective). Default -1
* @property bottom
* @type Number
*/
bottom: {
get: function () {
return _bottom;
},
set: function (newValue) {
if (c._ASSERT) {
assertNumber(newValue, "bottom");
}
_bottom = newValue;
}
},
/**
* Only used when orthogonal camera type (!cameraTypePerspective). Default 1
* @property top
* @type Number
*/
top: {
get: function () {
return _top;
},
set: function (newValue) {
if (c._ASSERT) {
assertNumber(newValue, "top");
}
_top = newValue;
}
},
/**
* The sorting order when multiple cameras exists in the scene.<br>
* Cameras with lowest number is rendered first.
* @property cameraIndex
* @type Number
* @default 1
*/
cameraIndex: {
get: function () {
return _cameraIndex;
},
set: function (newValue) {
if (c._ASSERT) {
assertNumber(newValue, "cameraIndex");
}
_cameraIndex = newValue;
}
},
/**
* Only used when orthogonal camera type (!cameraTypePerspective). Default [0,0,0,1]
* @property clearColor
* @type kick.math.Vec4
*/
clearColor: {
get: function () {
return Vec4.clone(_clearColor);
},
set: function (newValue) {
_clearColor = Vec4.clone(newValue);
}
},
/**
* Indicates if the camera should clear color buffer.<br>
* Default value is true
* @property clearFlagColor
* @type Boolean
*/
clearFlagColor: {
get: function () {
return _clearFlagColor;
},
set: function (newValue) {
computeClearFlag();
_clearFlagColor = newValue;
}
},
/**
* Indicates if the camera should clear the depth buffer.<br>
* Default is true.
* @property clearFlagDepth
* @type Boolean
*/
clearFlagDepth: {
get: function () {
return _clearFlagDepth;
},
set: function (newValue) {
computeClearFlag();
_clearFlagDepth = newValue;
}
},
/**
* Normalized viewport rect [xOffset,yOffset,xWidth,yHeight]<br>
* Default is [0,0,1,1]
* @property normalizedViewportRect
* @type Array_Number
*/
normalizedViewportRect: {
get: function () {
return _normalizedViewportRect;
},
set: function (newValue) {
if (c._ASSERT) {
if (newValue.length !== 4) {
Util.fail("Camera.normalizedViewportRect must be Float32Array of length 4");
}
}
Vec4.copy(_normalizedViewportRect, newValue);
}
},
/**
* Viewport rect [xOffset,yOffset,xWidth,yHeight]
* @property viewportRect
* @type Array_Number
*/
viewportRect: {
get: function () {
var res = Vec4.clone(_normalizedViewportRect),
canvasDimension = engine.canvasDimension;
res[0] *= canvasDimension[0];
res[2] *= canvasDimension[0];
res[1] *= canvasDimension[1];
res[3] *= canvasDimension[1];
return res;
},
set: function (value) {
if (c._ASSERT) {
if (value.length !== 4) {
Util.fail("Camera.viewportRect must be Float32Array of length 4");
}
}
var res = Vec4.clone(value),
canvasDimension = engine.canvasDimension;
res[0] /= canvasDimension[0];
res[2] /= canvasDimension[0];
res[1] /= canvasDimension[1];
res[3] /= canvasDimension[1];
Vec4.copy(_normalizedViewportRect, res);
}
},
/**
* Name of the component componentType = "camera".
* @example
* var camera = gameObject.camera;
* @property componentType
* @type String
* @final
*/
componentType: {value:"camera"}
});
/**
* Destroy camera component
* @method destroy
*/
this.destroy = function () {
if (isContextListenerRegistered) {
isContextListenerRegistered = false;
engine.removeEventListener('contextLost', contextLost);
engine.removeEventListener('contextRestored', contextRestored);
}
if (pickingObject) {
pickingObject.destroy();
pickingObject = null;
}
};
/**
* Serialize object
* @method toJSON
* @return {Object} data object
*/
this.toJSON = function () {
return {
type: "kick/scene/Camera",
uid: thisObj.uid || (engine ? engine.getUID(thisObj) : 0),
config: {
enabled: _enabled,
renderShadow: _renderShadow,
layerMask: _layerMask,
renderTarget: Util.getJSONReference(_renderTarget),
fieldOfView: _fieldOfView,
near: _near,
far: _far,
perspective: _perspective,
left: _left,
right: _right,
bottom: _bottom,
top: _top,
cameraIndex: _cameraIndex,
clearColor: Util.typedArrayToArray(_clearColor),
clearFlagColor: _clearFlagColor,
clearFlagDepth: _clearFlagDepth,
normalizedViewportRect: Util.typedArrayToArray(_normalizedViewportRect)
}
};
};
Util.applyConfig(this, config);
computeClearFlag();
};
/**
* Reset the camera clear flags
* @method setupClearFlags
* @param {Boolean} clearColor
* @param {Boolean} clearDepth
*/
Camera.prototype.setupClearFlags = function (clearColor, clearDepth) {
this.clearColor = clearColor;
this.clearDepth = clearDepth;
delete this._currentClearFlags;
};
return Camera;
});