API Docs for:
Show:

File: ../src/render/debug.js

///@INFO: UNCOMMON
/**	DebugRender
* Used to render debug information like skeletons, a grid, etc
* I moved it from WebGLStudio to LS so it could help when working with scenes coded without the editor
*
* @class DebugRender
* @namespace LS
* @constructor
*/
function DebugRender()
{
	this.debug_points = []; //used for debugging, allows to draw points easily

	//current frame data to render (we store it so we can render with less drawcalls)
	this._points = []; //linear array with x,y,z, x,y,z, ...
	this._points_color = [];
	this._points_nodepth = []; //linear array with x,y,z, x,y,z, ...
	this._points_color_nodepth = [];
	this._lines = []; //vec3,vec3 array
	this._lines_color = []; //
	this._names = []; //array of [vec3, string]

	this.grid_texture_url = "imgs/grid.png";

	//this camera is used to render names
	this.camera2D = new LS.Camera({eye:[0,0,0],center:[0,0,-1]});
	this.createMeshes();

	this.colors = {
		selected: vec4.fromValues(1,1,1,1),
		node: vec4.fromValues(1,0.5,0,1),
		bone: vec4.fromValues(1,0,0.5,1)
	};

	this.settings = {
		render_grid: true,
		grid_scale: 1.0,
		grid_alpha: 0.5,
		grid_plane: "xz",
		render_names: false,
		render_skeletons: true,
		render_tree: false,
		render_components: true,
		render_null_nodes: true,
		render_axis: false,
		render_colliders: true,
		render_paths: true,
		render_origin: true,
		render_colliders_aabb: false
	};

	this._in_scene = false;
}

DebugRender.prototype.enable = function( scene )
{
	if(this._in_scene)
		return;
	scene = scene || LS.GlobalScene;
	LEvent.bind( scene, "afterRenderInstances", this.onRender, this );
	this._in_scene = scene;
}

DebugRender.prototype.disable = function( scene )
{
	if(!this._in_scene)
		return;
	LEvent.unbind( this._in_scene, "afterRenderInstances", this.onRender, this );
	this._in_scene = null;
}

DebugRender.prototype.onRender = function( e, render_settings )
{
	this.render( LS.Renderer._current_camera );
}

//we pass a callback to check if something is selected
DebugRender.prototype.render = function( camera, is_selected_callback, scene )
{
	var settings = this.settings;

	scene = scene || LS.GlobalScene;

	gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);
	gl.enable( gl.DEPTH_TEST );
	gl.disable(gl.BLEND);
	gl.disable( gl.CULL_FACE );
	gl.depthFunc( gl.LEQUAL );
	//gl.depthMask( false );
	var selected_node = null;

	if( settings.render_grid && settings.grid_alpha > 0 )
		this.renderGrid();

	if(settings.render_origin)
	{
		LS.Draw.setColor([0.3,0.3,0.3,1.0]);
		LS.Draw.push();
		LS.Draw.scale(0.01,0.01,0.01);
		LS.Draw.rotate(-90,[1,0,0]);
		gl.blendFunc(gl.SRC_ALPHA,gl.ONE);
		LS.Draw.renderText("Origin");
		gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);
		LS.Draw.pop();
	}

	if(settings.render_components)
	{
		//Node components
		for(var i = 0, l = scene._nodes.length; i < l; ++i)
		{
			var node = scene._nodes[i];
			var is_node_selected = node._is_selected;
			selected_node = node;
			if(node.renderEditor)
				node.renderEditor( is_node_selected );
			for(var j = 0, l2 = node._components.length; j < l2; ++j)
			{
				var component = node._components[j];
				var is_component_selected = false;
				if(is_selected_callback)
					is_component_selected = is_selected_callback( component );
				if(component.renderEditor)
					component.renderEditor( is_node_selected, is_component_selected );
			}
		}
	}

	//render local things		
	var zero = vec3.create();
	for(var i = 0, l = scene._nodes.length; i < l; ++i)
	{
		var node = scene._nodes[i];
		if(node._is_root || !node.flags.visible ) 
			continue;

		var global = node.transform ? node.transform.getGlobalMatrixRef() : mat4.create();
		var pos = mat4.multiplyVec3( vec3.create(), global, zero ); //create a new one to store them

		if( settings.render_null_nodes)
		{
			if( node._is_selected )
				this.renderPoint( pos, true, this.colors.selected );
			else if( node._is_bone )
				this.renderPoint( pos, true, this.colors.bone );
			else
				this.renderPoint( pos, false, this.colors.node );
		}

		if(settings.render_names)
			this.renderText(pos, node.name, node._is_selected ? [0.94, 0.8, 0.4,1] : [0.8,0.8,0.8,0.9] );

		if (node._parentNode && node._parentNode.transform && (settings.render_tree || (settings.render_skeletons && node._is_bone && node._parentNode._is_bone)) )
		{
			this.renderLine( pos , node._parentNode.transform.getGlobalPosition(), this.colors.bone );
			//this.renderPoint( pos, true, this.colors.bone );
		}

		if(settings.render_axis)
		{
			LS.Draw.push();
			LS.Draw.multMatrix(global);
			LS.Draw.setColor([1,1,1,1]);
			LS.Draw.renderMesh( this.axis_mesh, gl.LINES );
			LS.Draw.pop();
		}
	}

	if(settings.render_colliders)
		this.renderColliders( scene );
	if(settings.render_paths)
		this.renderPaths( scene );

	//Render primitives (points, lines, text) ***********************

	if(this._points.length)
	{
		LS.Draw.setPointSize(4);
		LS.Draw.setColor([1,1,1,1]);
		LS.Draw.renderPoints( this._points, this._points_color );
		this._points.length = 0;
		this._points_color.length = 0;
	}

	if(this._points_nodepth.length)
	{
		LS.Draw.setPointSize(4);
		LS.Draw.setColor([1,1,1,1]);
		gl.disable( gl.DEPTH_TEST );
		LS.Draw.renderPoints( this._points_nodepth, this._points_color_nodepth );
		gl.enable( gl.DEPTH_TEST );
		this._points_nodepth.length = 0;
		this._points_color_nodepth.length = 0;
	}

	if(this._lines.length)
	{
		gl.disable( gl.DEPTH_TEST );
		LS.Draw.setColor([1,1,1,1]);
		LS.Draw.renderLines( this._lines, this._lines_color );
		gl.enable( gl.DEPTH_TEST );
		this._lines.length = 0;
		this._lines_color.length = 0;
	}

	if(this.debug_points.length)
	{
		LS.Draw.setPointSize(5);
		LS.Draw.setColor([1,0,1,1]);
		LS.Draw.renderPoints( this.debug_points );
	}

	//this require Canvas2DtoWebGL library
	if(settings.render_names && gl.start2D)
	{
		gl.disable( gl.DEPTH_TEST );
		var camera2D = this.camera2D;
		var viewport = gl.getViewport();
		camera2D.setOrthographic(0,viewport[2], 0,viewport[3], -1,1);
		camera2D.updateMatrices();
		gl.start2D();
		//gl.disable( gl.BLEND );
		gl.font = "14px Arial";
		var black_color = vec4.fromValues(0,0,0,0.5);

		for(var i = 0; i < this._names.length; ++i)
		{
			var pos2D = camera.project( this._names[i][1] );
			if(pos2D[2] < 0)
				continue;
			pos2D[2] = 0;

			var text_size = gl.measureText( this._names[i][0] );
			gl.fillColor = black_color;
			gl.fillRect( Math.floor(pos2D[0] + 10), viewport[3] - (Math.floor(pos2D[1] + 8)), text_size.width, text_size.height );
			gl.fillColor = this._names[i][2];
			gl.fillText( this._names[i][0], Math.floor(pos2D[0] + 10), viewport[3] - (Math.floor(pos2D[1] - 4) ) );
		}
		gl.finish2D();
		this._names.length = 0;
	}

	//DEBUG
	if(settings.render_axis && selected_node) //render axis for all nodes
	{
		LS.Draw.push();
		var Q = selected_node.transform.getGlobalRotation();
		var R = mat4.fromQuat( mat4.create(), Q );
		LS.Draw.setMatrix( R );
		LS.Draw.setColor([1,1,1,1]);
		LS.Draw.scale(10,10,10);
		LS.Draw.renderMesh( this.axis_mesh, gl.LINES );
		LS.Draw.pop();
	}

	gl.depthFunc( gl.LESS );
}

//this primitives are rendered after all the components editors are rendered
DebugRender.prototype.renderPoint = function( p, ignore_depth, c )
{
	c = c || [1,1,1,1];
	if(ignore_depth)
	{
		this._points_nodepth.push( p[0], p[1], p[2] );
		this._points_color_nodepth.push( c[0], c[1], c[2], c[3] );
	}
	else
	{
		this._points.push( p[0], p[1], p[2] );
		this._points_color.push( c[0], c[1], c[2], c[3] );
	}
}

DebugRender.prototype.renderLine = function( start, end, color )
{
	color = color || [1,1,1,1];
	this._lines.push( start, end );
	this._lines_color.push( color, color );
}

DebugRender.prototype.renderText = function( position, text, color )
{
	color = color || [1,1,1,1];
	this._names.push([text,position, color]);
}

DebugRender.prototype.renderGrid = function()
{
	var settings = this.settings;

	//textured grid
	if(!this.grid_shader)
	{
		//this.grid_shader = LS.Draw.createSurfaceShader("float PI2 = 6.283185307179586; return vec4( vec3( max(0.0, cos(pos.x * PI2 * 0.1) - 0.95) * 10.0 + max(0.0, cos(pos.z * PI2 * 0.1) - 0.95) * 10.0 ),1.0);");
		this.grid_shader = LS.Draw.createSurfaceShader("vec2 f = vec2(1.0/64.0,-1.0/64.0); float brightness = texture2D(u_texture, pos.xz + f).x * 0.6 + texture2D(u_texture, pos.xz * 0.1 + f ).x * 0.3 + texture2D(u_texture, pos.xz * 0.01 + f ).x * 0.2; brightness /= max(1.0,0.001 * length(u_camera_position.xz - pos.xz));vec4 color = u_color * vec4(vec3(1.0),brightness); if( abs(pos.x) < 0.1 ) color = mix(vec4(0.4,0.4,1.0,0.5),color,abs(pos.x/0.1)); if( abs(pos.z) < 0.1 ) color = mix(vec4(1.0,0.4,0.4,0.5),color,abs(pos.z/0.1)); return color;");
		//this.grid_shader = LS.Draw.createSurfaceShader("vec2 f = vec2(1.0/64.0,-1.0/64.0); float brightness = texture2D(u_texture, pos.xz + f).x * 0.6 + texture2D(u_texture, pos.xz * 0.1 + f ).x * 0.3 + texture2D(u_texture, pos.xz * 0.01 + f ).x * 0.2; brightness /= max(1.0,0.001 * length(u_camera_position.xz - pos.xz));vec4 color = u_color * vec4(vec3(1.0),brightness); return color;");
		this.grid_shader_xy = LS.Draw.createSurfaceShader("vec2 f = vec2(1.0/64.0,-1.0/64.0); float brightness = texture2D(u_texture, pos.xy + f).x * 0.6 + texture2D(u_texture, pos.xy * 0.1 + f ).x * 0.3 + texture2D(u_texture, pos.xy * 0.01 + f ).x * 0.2; brightness /= max(1.0,0.001 * length(u_camera_position.xy - pos.xy));vec4 color = u_color * vec4(vec3(1.0),brightness);  if( abs(pos.x) < 0.025 ) color *= vec4(0.4,1.0,0.4,1.0); if( abs(pos.y) < 0.025 ) color *= vec4(1.0,0.4,0.4,1.0); return color;");
		//this.grid_shader_xy = LS.Draw.createSurfaceShader("vec2 f = vec2(1.0/64.0,-1.0/64.0); float brightness = texture2D(u_texture, pos.xy + f).x * 0.6 + texture2D(u_texture, pos.xy * 0.1 + f ).x * 0.3 + texture2D(u_texture, pos.xy * 0.01 + f ).x * 0.2; brightness /= max(1.0,0.001 * length(u_camera_position.xy - pos.xy));return u_color * vec4(vec3(1.0),brightness);");
		this.grid_shader_yz = LS.Draw.createSurfaceShader("vec2 f = vec2(1.0/64.0,-1.0/64.0); float brightness = texture2D(u_texture, pos.yz + f).x * 0.6 + texture2D(u_texture, pos.yz * 0.1 + f ).x * 0.3 + texture2D(u_texture, pos.yz * 0.01 + f ).x * 0.2; brightness /= max(1.0,0.001 * length(u_camera_position.yz - pos.yz)); vec4 color = u_color * vec4(vec3(1.0),brightness);  if( abs(pos.y) < 0.025 ) color *= vec4(0.4, 0.4, 1.0, 1.0); if( abs(pos.z) < 0.025 ) color *= vec4(0.4,1.0,0.4,1.0); return color;");
		//this.grid_shader_yz = LS.Draw.createSurfaceShader("vec2 f = vec2(1.0/64.0,-1.0/64.0); float brightness = texture2D(u_texture, pos.yz + f).x * 0.6 + texture2D(u_texture, pos.yz * 0.1 + f ).x * 0.3 + texture2D(u_texture, pos.yz * 0.01 + f ).x * 0.2; brightness /= max(1.0,0.001 * length(u_camera_position.yz - pos.yz));return u_color * vec4(vec3(1.0),brightness);");
		this.grid_shader.uniforms({u_texture:0});

		if( this.grid_img && this.grid_img.loaded )
			this.grid_texture = GL.Texture.fromImage( this.grid_img, {format: gl.RGB, wrap: gl.REPEAT, anisotropic: 4, minFilter: gl.LINEAR_MIPMAP_LINEAR } );
		else
			this.grid_texture = GL.Texture.fromURL( this.grid_texture_url, {format: gl.RGB, wrap: gl.REPEAT, anisotropic: 4, minFilter: gl.LINEAR_MIPMAP_LINEAR } );
	}

	LS.Draw.push();

	if(settings.grid_plane == "xy")
		LS.Draw.rotate(90,1,0,0);
	else if(settings.grid_plane == "yz")
		LS.Draw.rotate(90,0,0,1);


	if(!this.grid_texture || this.grid_texture.ready === false)
	{
		var grid_scale = 1;			
		var grid_alpha = 1;
		//lines grid
		LS.Draw.setColor([0.2,0.2,0.2, grid_alpha * 0.75]);
		LS.Draw.scale( grid_scale , grid_scale , grid_scale );
		LS.Draw.renderMesh( this.grid_mesh, gl.LINES );
		LS.Draw.scale(10,10,10);
		LS.Draw.renderMesh( this.grid_mesh, gl.LINES );
	}
	else
	{
		//texture grid
		gl.enable(gl.BLEND);
		this.grid_texture.bind(0);
		gl.depthMask( false );
		LS.Draw.setColor([1,1,1, this.settings.grid_alpha ]);
		LS.Draw.translate( LS.Draw.camera_position[0], 0, LS.Draw.camera_position[2] ); //follow camera
		LS.Draw.scale( 10000, 10000, 10000 );
		LS.Draw.renderMesh( this.plane_mesh, gl.TRIANGLES, settings.grid_plane == "xy" ? this.grid_shader_xy : (settings.grid_plane == "yz" ? this.grid_shader_yz : this.grid_shader) );
		gl.depthMask( true );
	}

	LS.Draw.pop();
}

DebugRender.prototype.renderColliders = function( scene )
{
	scene = scene || LS.GlobalScene;
	if(!scene._colliders)
		return;

	LS.Draw.setColor([0.33,0.71,0.71,0.5]);

	for(var i = 0; i < scene._colliders.length; ++i)
	{
		var instance = scene._colliders[i];
		var oobb = instance.oobb;

		if(this.settings.render_colliders_aabb) //render AABB
		{
			var aabb = instance.aabb;
			LS.Draw.push();
			var center = BBox.getCenter(aabb);
			var halfsize = BBox.getHalfsize(aabb);
			LS.Draw.translate(center);
			//LS.Draw.setColor([0.33,0.71,0.71,0.5]);
			LS.Draw.renderWireBox(halfsize[0]*2,halfsize[1]*2,halfsize[2]*2);
			LS.Draw.pop();
		}

		LS.Draw.push();
		LS.Draw.multMatrix( instance.matrix );
		var halfsize = BBox.getHalfsize(oobb);

		if(instance.type == LS.PhysicsInstance.BOX)
		{
			LS.Draw.translate( BBox.getCenter(oobb) );
			LS.Draw.renderWireBox( halfsize[0]*2, halfsize[1]*2, halfsize[2]*2 );
		}
		else if(instance.type == LS.PhysicsInstance.SPHERE)
		{
			//Draw.scale(,halfsize[0],halfsize[0]);
			LS.Draw.translate( BBox.getCenter(oobb) );
			LS.Draw.renderWireSphere( halfsize[0], 20 );
		}
		else if(instance.type == LS.PhysicsInstance.MESH)
		{
			var mesh = instance.mesh;
			if(mesh)
			{
				if(!mesh.indexBuffers["wireframe"])
					mesh.computeWireframe();
				LS.Draw.renderMesh(mesh, gl.LINES, null, "wireframe" );
			}
		}

		LS.Draw.pop();
	}
}

DebugRender.prototype.renderPaths = function( scene )
{
	scene = scene || LS.GlobalScene;

	if(!scene._paths)
		return;

	LS.Draw.setColor([0.7,0.6,0.3,0.5]);

	for(var i = 0; i < scene._paths.length; ++i)
	{
		var path = scene._paths[i];
		var points = path.samplePoints(0);
		LS.Draw.renderLines( points, null, true );
	}
}

DebugRender.prototype.createMeshes = function()
{
	//plane
	this.plane_mesh = GL.Mesh.plane({xz:true});

	//grid
	var dist = 10;
	var num = 10;
	var vertices = [];
	for(var i = -num; i <= num; i++)
	{
		vertices.push([i*dist,0,dist*num]);
		vertices.push([i*dist,0,-dist*num]);
		vertices.push([dist*num,0,i*dist]);
		vertices.push([-dist*num,0,i*dist]);
	}
	this.grid_mesh = GL.Mesh.load({vertices:vertices});

	//box
	vertices = new Float32Array([-1,1,1 , -1,1,-1, 1,1,-1, 1,1,1, -1,-1,1, -1,-1,-1, 1,-1,-1, 1,-1,1]);
	var triangles = new Uint16Array([0,1, 0,4, 0,3, 1,2, 1,5, 2,3, 2,6, 3,7, 4,5, 4,7, 6,7, 5,6 ]);
	this.box_mesh = GL.Mesh.load({vertices: vertices, lines:triangles });

	//circle
	this.circle_mesh = GL.Mesh.circle({size:1,slices:50});
	this.circle_empty_mesh = GL.Mesh.circle({size:1,slices:50,empty:1});
	this.sphere_mesh = GL.Mesh.icosahedron({size:1, subdivisions: 3});

	//dummy
	vertices = [];
	vertices.push([-dist*0.5,0,0],[+dist*0.5,0,0]);
	vertices.push([0,-dist*0.5,0],[0,+dist*0.5,0]);
	vertices.push([0,0,-dist*0.5],[0,0,+dist*0.5]);
	this.dummy_mesh = GL.Mesh.load({vertices:vertices});

	//box
	vertices = [];
	vertices.push([-1.0,1.0,1.0],[1.0,1.0,1.0],[-1.0,1.0,-1.0], [1.0,1.0,-1.0],[-1.0,-1.0,1.0], [1.0,-1.0,1.0],[-1.0,-1.0,-1.0], [1.0,-1.0,-1.0]);
	vertices.push([1.0,-1.0,1.0],[1.0,1.0,1.0],[1.0,-1.0,-1.0],[1.0,1.0,-1.0],[-1.0,-1.0,1.0],[-1.0,1.0,1.0],[-1.0,-1.0,-1.0],[-1.0,1.0,-1.0]);
	vertices.push([1.0,1.0,1.0],[1.0,1.0,-1.0],[1.0,-1.0,1.0],[1.0,-1.0,-1.0],[-1.0,1.0,1.0],[-1.0,1.0,-1.0],[-1.0,-1.0,1.0],[-1.0,-1.0,-1.0]);
	this.cube_mesh = GL.Mesh.load({vertices:vertices});

	for(var i = 1; i >= 0.0; i -= 0.02)
	{
		var f = ( 1 - 0.001/(i) )*2-1;
		vertices.push([-1.0,1.0,f],[1.0,1.0,f],[-1.0,-1.0,f], [1.0,-1.0,f]);
		vertices.push([1.0,-1.0,f],[1.0,1.0,f],[-1.0,-1.0,f],[-1.0,1.0,f]);
	}

	this.frustum_mesh = GL.Mesh.load({vertices:vertices});

	//cylinder
	this.cylinder_mesh = GL.Mesh.cylinder({radius:10,height:2});

	//axis
	vertices = [];
	var colors = [];
	dist = 2;
	vertices.push([0,0,0],[+dist*0.5,0,0]);
	colors.push([1,0,0,1],[1,0,0,1]);
	vertices.push([0,0,0],[0,+dist*0.5,0]);
	colors.push([0,1,0,1],[0,1,0,1]);
	vertices.push([0,0,0],[0,0,+dist*0.5]);
	colors.push([0,0,1,1],[0,0,1,1]);
	this.axis_mesh = GL.Mesh.load({vertices:vertices, colors: colors});

	//top
	vertices = [];
	vertices.push([0,0,0],[0,+dist*0.5,0]);
	vertices.push([0,+dist*0.5,0],[0.1*dist,+dist*0.4,0]);
	vertices.push([0,+dist*0.5,0],[-0.1*dist,+dist*0.4,0]);
	this.top_line_mesh = GL.Mesh.load({vertices:vertices});

	//front
	vertices = [];
	vertices.push([0,0,0],[0,0,+dist*0.5]);
	vertices.push([0,0,+dist*0.5],[0,0.1*dist,+dist*0.4]);
	vertices.push([0,0,+dist*0.5],[0,-0.1*dist,+dist*0.4]);
	this.front_line_mesh = GL.Mesh.load({vertices:vertices});
}

LS.DebugRender = DebugRender;