///@INFO: UNCOMMON
/**
* Allows to render 2d canvas primitives, but they are rendered into a plane that can be positioned in 3D space.
* It also supports to store the texture so it can be used in another material.
*
* The CANVAS2D mode renders busing a native Canvas2D, which has all the features but it could be slower because it has to upload the full canvas every frame.
* The WEBGL mode renders the canvas using WebGL calls, it is faster but the quality is worse and some features are not available (but you can render other textures as images)
* To fill the canvas you must have a Script in the same node, that contains a method called OnRenderCanvas
* @class Canvas3D
* @namespace LS.Components
* @constructor
* @param {String} object to configure from
*/
function Canvas3D(o)
{
this.enabled = true;
this.mode = 1;
this.width = 512;
this.height = 512;
this.texture_name = ":canvas3D";
this.visible = true;
this.input_active = true; //used for LS.GUI methods
this.use_node_material = false;
this.generate_mipmaps = false;
this._clear_buffer = true; //not public, just here in case somebody wants it
this._skip_backside = true;
this._texture = null;
this._fbo = null;
this._RI = null;
this._standard_material = null;
this._mouse = vec3.create();
this._is_mouse_inside = false;
this._local_mouse = {
mousex: 0,
mousey: 0
};
this._local_mouse_click = {
mousex: 0,
mousey: 0
}
if(o)
this.configure(o);
}
Canvas3D.icon = "mini-icon-brush.png";
Canvas3D.MODE_CANVAS2D = 1;
Canvas3D.MODE_WEBGL = 2;
Canvas3D.MODE_IMMEDIATE = 3; //not supported yet
Canvas3D["@mode"] = { type:"enum", values: { "Canvas2D":Canvas3D.MODE_CANVAS2D, "WebGL":Canvas3D.MODE_WEBGL } };
Canvas3D["@width"] = { type:"number", step:1, precision:0 };
Canvas3D["@height"] = { type:"number", step:1, precision:0 };
Canvas3D["@texture_name"] = { type:"string" };
Object.defineProperty( Canvas3D.prototype, "texture", {
set: function(){
throw("Canvas3D texture cannot be set manually");
},
get: function(){
return this._texture;
},
enumerable: false
});
Canvas3D.prototype.onAddedToScene = function(scene)
{
LEvent.bind(scene,"readyToRender",this.onRender,this);
}
Canvas3D.prototype.onRemovedFromScene = function(scene)
{
LEvent.unbind(scene,"readyToRender",this.onRender,this);
}
Canvas3D.prototype.onAddedToNode = function( node )
{
if(!this.texture_name)
this.texture_name = ":canvas3D";
LEvent.bind( node, "collectRenderInstances", this.onCollectInstances, this );
}
Canvas3D.prototype.onRemovedFromNode = function( node )
{
LEvent.unbind( node, "collectRenderInstances", this.onCollectInstances, this );
}
//called before rendering scene
Canvas3D.prototype.onRender = function()
{
if(!this.enabled)
return;
var w = this.width|0;
var h = this.height|0;
//create resources
if( this.mode == Canvas3D.MODE_CANVAS2D )
{
if(!this._canvas)
this._canvas = document.createElement("canvas");
if(this._canvas.width != w)
this._canvas.width = w;
if(this._canvas.height != h)
this._canvas.height = h;
}
if(this.mode != Canvas3D.MODE_IMMEDIATE)
{
if(!this._texture || this._texture.width != w || this._texture.height != h)
this._texture = new GL.Texture(w,h,{ format: GL.RGBA, filter: GL.LINEAR, wrap: GL.CLAMP_TO_EDGE });
}
//project mouse into the canvas plane
if(this.visible)
this.projectMouse();
//render the canvas
if( this.mode == Canvas3D.MODE_CANVAS2D )
{
var ctx = this._canvas.getContext("2d");
if(this._clear_buffer)
ctx.clearRect(0,0,this._canvas.width,this._canvas.height); //clear
LS.GUI._ctx = ctx;
this._root.processActionInComponents("onRenderCanvas",[ctx,this._canvas,this._mouse,this]);
LS.GUI._ctx = gl;
this._texture.uploadImage( this._canvas );
}
else if ( this.mode == Canvas3D.MODE_WEBGL )
{
var ctx = gl;
if(!this._fbo)
this._fbo = new GL.FBO();
this._fbo.setTextures([this._texture]);
this._fbo.bind();
gl.start2D();
if(this._clear_buffer)
{
gl.clearColor(0,0,0,0);
gl.clear(GL.COLOR_BUFFER_BIT);
}
LS.GUI._ctx = gl;
this._root.processActionInComponents("onRenderCanvas",[ctx,this._texture,this._mouse,this]);
gl.finish2D();
this._fbo.unbind();
}
else //not implemented yet
{
//requires to support extra_projection in canvas2DtoWebGL which is not yet implemented
return;
}
//process and share the texture
if(this._texture)
{
if(this.generate_mipmaps && isPowerOfTwo(w) && isPowerOfTwo(h) )
{
this._texture.setParameter( GL.TEXTURE_MIN_FILTER, GL.LINEAR_MIPMAP_LINEAR );
gl.generateMipmap( gl.TEXTURE_2D );
}
else
this._texture.setParameter( GL.TEXTURE_MIN_FILTER, GL.LINEAR );
LS.RM.registerResource( this.texture_name || ":canvas3D", this._texture );
}
//restore stuff
if( this._prev_mouse )
{
LS.Input.Mouse = this._prev_mouse;
this._prev_mouse = null;
}
if( LS.Input.current_click && this._prev_click_mouse )
{
LS.Input.current_click = this._prev_click_mouse;
this._prev_click_mouse = null;
}
}
Canvas3D.prototype.onCollectInstances = function(e,instances)
{
if(!this.enabled || !this.visible || !this._texture)
return;
if(!this._RI)
this._RI = new LS.RenderInstance();
var RI = this._RI;
var material = null;
if(this.use_node_material)
material = this._root.getMaterial();
if(!material)
material = this._standard_material;
if(!material)
material = this._standard_material = new LS.MaterialClasses.StandardMaterial({ flags: { ignore_lights: true, cast_shadows: false }, blend_mode: LS.Blend.ALPHA });
material.setTexture("color", this.texture_name || ":canvas3D" );
var sampler = material.textures["color"];
RI.fromNode( this._root );
RI.setMaterial( material );
if(!this._mesh)
this._mesh = GL.Mesh.plane();
RI.setMesh(this._mesh);
instances.push(RI);
return instances;
}
Canvas3D.prototype.clear = function( redraw )
{
if( this.mode == Canvas3D.MODE_CANVAS2D )
{
var ctx = this._canvas.getContext("2d");
ctx.clearRect(0,0,this._canvas.width,this._canvas.height); //clear
}
else if( this.mode == Canvas3D.MODE_WEBGL )
{
if(this._texture)
this._texture.fill([0,0,0,0]);
}
if(redraw)
this.onRender();
}
Canvas3D.prototype.projectMouse = function()
{
var camera = LS.Renderer._main_camera;
if(!camera)
return;
//Canvas Plane
if(!this.root.transform)
{
this._mouse[0] = LS.Input.Mouse.x;
this._mouse[1] = LS.Input.Mouse.y;
this._is_mouse_inside = true;
return;
}
this._is_mouse_inside = false;
var mousex = LS.Input.Mouse.x;
var mousey = LS.Input.Mouse.y;
var w = this.width|0;
var h = this.height|0;
if( !this.input_active )
{
mousex = -1;
mousey = -1;
this._mouse[0] = mousex;
this._mouse[1] = mousey;
}
else
{
this._mouse[0] = -1;
this._mouse[1] = -1;
var ray = camera.getRayInPixel( mousex, mousey );
var camera_front = camera.getFront();
var temp = vec3.create();
var plane_normal = this.root.transform.localVectorToGlobal( LS.FRONT, temp );
if( !this._skip_backside || vec3.dot( ray.direction, plane_normal ) > 0.0 )
{
var local_origin = this.root.transform.globalToLocal( ray.origin, temp );
var local_direction = this.root.transform.globalVectorToLocal( ray.direction );
if( geo.testRayPlane( local_origin, local_direction, LS.ZEROS, LS.FRONT, this._mouse ) )
{
this._mouse[0] = (this._mouse[0] + 0.5) * w;
this._mouse[1] = h - (this._mouse[1] + 0.5) * h;
}
}
}
//mark the mouse as inside
if( this._mouse[0] >= 0 && this._mouse[0] < w &&
this._mouse[1] >= 0 && this._mouse[1] < h )
this._is_mouse_inside = true;
//hacks to work with the LS.GUI...
this._local_mouse.mousex = this._mouse[0];
this._local_mouse.mousey = this._mouse[1];
this._prev_mouse = LS.Input.Mouse;
LS.Input.Mouse = this._local_mouse;
if( LS.Input.current_click )
{
this._local_mouse_click.mousex = this._mouse[0];
this._local_mouse_click.mousey = this._mouse[1];
this._prev_click_mouse = LS.Input.current_click;
LS.Input.current_click = this._local_mouse_click;
}
}
/*
Canvas3D.prototype.getResources = function(res)
{
if( this.material && this.material.constructor === String )
res[this.material] = LS.Material;
return res;
}
Canvas3D.prototype.onResourceRenamed = function (old_name, new_name, resource)
{
if(this.material == old_name)
this.material = new_name;
}
*/
LS.registerComponent( Canvas3D );