define(["kick/core/ProjectAsset", "kick/core/Constants", "kick/core/Util", "kick/core/EngineSingleton"],
function (ProjectAsset, Constants, Util, EngineSingleton) {
"use strict";
/**
* @module kick.texture
*/
/**
* A movie texture associated with a video element (or canvas tag) will update the content every frame (when it is bound).
* @class MovieTexture
* @namespace kick.texture
* @constructor
* @param {Object} [config]
* @extends kick.core.ProjectAsset
*/
return function (config) {
// extend ProjectAsset
ProjectAsset(this, config, "kick.texture.MovieTexture");
if (Constants._ASSERT){
if (config === EngineSingleton.engine){
Util.fail("MovieTexture constructor changed - engine parameter is removed");
}
}
var engine = EngineSingleton.engine,
gl = engine.gl,
glState = engine.glState,
texture0 = Constants.GL_TEXTURE0,
_name = "MovieTexture",
_videoElement = null,
_textureId = gl.createTexture(),
_wrapS = Constants.GL_CLAMP_TO_EDGE,
_wrapT = Constants.GL_CLAMP_TO_EDGE,
_minFilter = Constants.GL_NEAREST,
_magFilter = Constants.GL_NEAREST,
_intFormat = Constants.GL_RGBA,
_skipFrames = 0,
_generateMipmaps = false,
timer = engine.time,
thisObj = this,
lastGrappedFrame = -1,
setTextureProperties = function(){
gl.texParameteri(Constants.GL_TEXTURE_2D, Constants.GL_TEXTURE_MAG_FILTER, _magFilter);
gl.texParameteri(Constants.GL_TEXTURE_2D, Constants.GL_TEXTURE_MIN_FILTER, _minFilter);
gl.texParameteri(Constants.GL_TEXTURE_2D, Constants.GL_TEXTURE_WRAP_S, _wrapS);
gl.texParameteri(Constants.GL_TEXTURE_2D, Constants.GL_TEXTURE_WRAP_T, _wrapT);
},
contextLost = function () {
gl = null;
},
contextRestored = function (newGl) {
gl = newGl;
_textureId = gl.createTexture();
setTextureProperties();
};
/**
* Bind the current texture
* And update the texture from the video element (unless it has already been updated in this frame)
* @method bind
*/
this.bind = function (textureSlot) {
gl.activeTexture(texture0 + textureSlot);
gl.bindTexture(Constants.GL_TEXTURE_2D, _textureId);
if (lastGrappedFrame < timer.frame && _videoElement) {
lastGrappedFrame = timer.frame + _skipFrames;
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,
gl.UNSIGNED_BYTE, _videoElement);
if (_generateMipmaps) {
gl.generateMipmap(Constants.GL_TEXTURE_2D);
}
}
};
/**
* Deallocates the texture from memory
* @method destroy
*/
this.destroy = function () {
if (_textureId !== null) {
glState.currentMaterial = null; // for material to rebind
gl.deleteTexture(_textureId);
_textureId = null;
engine.project.removeResourceDescriptor(thisObj.uid);
engine.removeEventListener("contextLost", contextLost);
engine.removeEventListener("contextRestored", contextRestored);
}
};
/**
* Creates a 2x2 temporary image (checkerboard)
* @method setTemporaryTexture
*/
this.setTemporaryTexture = function () {
var blackWhiteCheckerboard = new Uint8Array([255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255]);
thisObj.bind(0); // bind to texture slot 0
gl.pixelStorei(Constants.GL_UNPACK_ALIGNMENT, 1);
gl.texImage2D(Constants.GL_TEXTURE_2D, 0, Constants.GL_RGB, 2, 2, 0, Constants.GL_RGB, Constants.GL_UNSIGNED_BYTE, blackWhiteCheckerboard);
setTextureProperties();
glState.currentMaterial = null; // for material to rebind
};
Object.defineProperties(this, {
/**
* @property name
* @type String
*/
name: {
get: function () {
return _name;
},
set: function (newValue) {
_name = newValue;
}
},
/**
* 0 means update movie texture every frame. 1 skip one frame update, 2 skips two frames etc.
* @property skipFrames
* @type {Number}
* @default 0
*/
skipFrames: {
get: function () {
return _skipFrames;
},
set: function (newValue) {
_skipFrames = newValue;
}
},
/**
* @property videoElement
* @type {VideoElement}
*/
videoElement: {
get: function () {
return _videoElement;
},
set: function (newValue) {
_videoElement = newValue;
}
},
/**
* Autogenerate mipmap levels<br>
* Note that enabling auto mipmap on movie textures uses a lot of resources.
* @property generateMipmaps
* @type Boolean
* @default false
*/
generateMipmaps: {
get: function () {
return _generateMipmaps;
},
set: function (value) {
if (Constants._ASSERT) {
if (typeof value !== 'boolean') {
Util.fail("MovieTexture.generateMipmaps was not a boolean");
}
}
_generateMipmaps = value;
}
},
/**
* @property textureId
* @type {Number}
* @protected
*/
textureId: {
value: _textureId
},
/**
* Texture.wrapS should be either GL\_CLAMP\_TO\_EDGE or GL\_REPEAT<br>
* @property wrapS
* @type Object
* @default GL_REPEAT
*/
wrapS: {
get: function () {
return _wrapS;
},
set: function (value) {
if (Constants._ASSERT) {
if (value !== Constants.GL_CLAMP_TO_EDGE && value !== Constants.GL_REPEAT) {
Util.fail("Texture.wrapS should be either GL_CLAMP_TO_EDGE or GL_REPEAT");
}
}
_wrapS = value;
gl.bindTexture(Constants.GL_TEXTURE_2D, _textureId);
gl.texParameteri(Constants.GL_TEXTURE_2D, Constants.GL_TEXTURE_WRAP_S, _wrapS);
glState.currentMaterial = null; // for material to rebind
}
},
/**
* Texture.wrapT should be either GL\_CLAMP\_TO\_EDGE or GL\_REPEAT<br>
* @property wrapT
* @type Object
* @default GL_REPEAT
*/
wrapT: {
get: function () {
return _wrapT;
},
set: function (value) {
if (Constants._ASSERT) {
if (value !== Constants.GL_CLAMP_TO_EDGE && value !== Constants.GL_REPEAT) {
Util.fail("Texture.wrapT should be either GL_CLAMP_TO_EDGE or GL_REPEAT");
}
}
_wrapT = value;
gl.bindTexture(Constants.GL_TEXTURE_2D, _textureId);
gl.texParameteri(Constants.GL_TEXTURE_2D, Constants.GL_TEXTURE_WRAP_T, _wrapT);
glState.currentMaterial = null; // for material to rebind
}
},
/**
* Texture.minFilter should be either GL\_NEAREST, GL\_LINEAR, GL\_NEAREST\_MIPMAP\_NEAREST, <br>
* GL\_LINEAR\_MIPMAP\_NEAREST, GL\_NEAREST\_MIPMAP\_LINEAR, GL\_LINEAR\_MIPMAP\_LINEAR<br>
* @property minFilter
* @type Object
* @default GL_LINEAR
*/
minFilter: {
get: function () {
return _minFilter;
},
set: function (value) {
if (Constants._ASSERT) {
if (value !== Constants.GL_NEAREST &&
value !== Constants.GL_LINEAR &&
value !== Constants.GL_NEAREST_MIPMAP_NEAREST &&
value !== Constants.GL_LINEAR_MIPMAP_NEAREST &&
value !== Constants.GL_NEAREST_MIPMAP_LINEAR &&
value !== Constants.GL_LINEAR_MIPMAP_LINEAR) {
Util.fail("Texture.minFilter should be either GL_NEAREST, GL_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_LINEAR");
}
}
_minFilter = value;
gl.bindTexture(Constants.GL_TEXTURE_2D, _textureId);
gl.texParameteri(Constants.GL_TEXTURE_2D, Constants.GL_TEXTURE_MIN_FILTER, _minFilter);
glState.currentMaterial = null; // for material to rebind
}
},
/**
* Texture.magFilter should be either GL\_NEAREST or GL\_LINEAR. <br>
* @property magFilter
* @type Object
* @default GL_LINEAR
*/
magFilter: {
get: function () {
return _magFilter;
},
set: function (value) {
if (Constants._ASSERT) {
if (value !== Constants.GL_NEAREST && value !== Constants.GL_LINEAR) {
Util.fail("Texture.magFilter should be either GL_NEAREST or GL_LINEAR");
}
}
_magFilter = value;
gl.bindTexture(Constants.GL_TEXTURE_2D, _textureId);
gl.texParameteri(Constants.GL_TEXTURE_2D, Constants.GL_TEXTURE_MAG_FILTER, _magFilter);
glState.currentMaterial = null; // for material to rebind
}
},
/**
* Specifies the internal format of the image (format on GPU)<br>
* Must be one of the following:
* GL\_ALPHA,
* GL\_RGB,
* GL\_RGBA,
* GL\_LUMINANCE,
* GL\_LUMINANCE_ALPHA
* @property internalFormat
* @type Number
* @default GL_RGBA
* @deprecated
*/
internalFormat: {
get: function () {
return _intFormat;
},
set: function (value) {
if (value !== Constants.GL_ALPHA &&
value !== Constants.GL_RGB &&
value !== Constants.GL_RGBA &&
value !== Constants.GL_LUMINANCE &&
value !== Constants.GL_LUMINANCE_ALPHA) {
Util.fail("Texture.internalFormat should be either GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, or LUMINANCE_ALPHA");
}
_intFormat = value;
}
}
});
/**
* Serializes the data into a JSON object (that can be used as a config parameter in the constructor)<br>
* Note that the texture data is not serialized in the json format. <br>
* This means that either setImage() or setImageData() must be called before the texture can be bound<br>
* @method toJSON
* @return {Object} config element
*/
this.toJSON = function () {
return {
uid: thisObj.uid,
wrapS: _wrapS,
wrapT: _wrapT,
minFilter: _minFilter,
name: _name,
magFilter: _magFilter
};
};
// Invoke init in project asset
this.init(config);
engine.addEventListener("contextLost", contextLost);
engine.addEventListener("contextRestored", contextRestored);
};
});