CrossBrowdy - Examples

Advanced

Graphic rendering engine

This is an example of a simple graphic rendering engine:

index.html:

<!DOCTYPE html>
<html>
	<head>
		<meta http-equiv="content-type" content="text/html; charset=utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
		<title>Advanced: Graphic Rendering Engine - Example</title>
		<!-- Loads the needed CSS files: -->
		<link rel="stylesheet" type="text/css" href="main.css" />
		<!-- Loads FlashCanvas (Flash emulation) before CrossBrowdy. Needed also to use ExplorerCanvas (VML emulation) without problems: -->
		<script type="text/javascript" src="CrossBrowdy/CrossBase/audiovisual/image/canvas/FlashCanvas/pro/bin/flashcanvas.js"></script><!-- FlashCanvas/ExplorerCanvas do not support lazy load. -->
		<!-- Loads CrossBrowdy.js (main file): -->
		<script src="CrossBrowdy/CrossBrowdy.js" type="text/javascript" language="javascript"></script><!-- "type" and "language" parameters for legacy clients. -->
		<!-- Loads the other needed script files: -->
		<script src="main.js" type="text/javascript" language="javascript"></script>
	</head>
	<body>
		<canvas id="my_canvas">if you read this, canvas is not working</canvas><!-- Some emulation methods will require the canvas element created in HTML (not dynamically by JavaScript). -->
		<canvas id="my_canvas_buffer">if you read this, canvas is not working</canvas><!-- Some emulation methods will require the canvas element created in HTML (not dynamically by JavaScript). -->
		<br />
		<!-- The "CB_console" element will be used automatically in the case that the client does not support console: -->
		<div id="CB_console" style="visibility:hidden; overflow:scroll;">
			<span style="font-weight:bold;">Console:</span><br />
		</div>
		<div id="crossbrowdy_info"><a href="/guides#examples" target="_blank">CrossBrowdy.com</a> example</div>
	</body>
</html>

main.css:

body { background-color:#aaddee; word-wrap:break-word; }
#crossbrowdy_info { position:fixed; bottom:2px; right:2px; }
#crossbrowdy_info a { color:#00aadd; }
#crossbrowdy_info a:hover { color:#0033aa; }
span { color:#aa0000; }
#my_canvas { position:absolute; left:0px; top:0px; }
#my_canvas_buffer { position:absolute; left:0px; top:0px; visibility:hidden; display:none; }

main.js:

var CB_REM_DEBUG_MESSAGES = true; //Shows debug messages.

//Adds the rendering engine module to CrossBrowdy:
CB_Modules.addNeededModule(CB_NAME, "RENDERING_ENGINE_MODULE", { "rendering_engine_module.js" : { load: true, mandatory: true, absolutePath: true } });

var FORCED_EMULATION_METHOD = null; //Forces an emulation mode which can be 'SILVERLIGHT', 'FLASH', 'DHTML' or 'VML' (testing purposes). Use null or undefined to disable it.

//If desired, sets the needed options to force emulation:
if (FORCED_EMULATION_METHOD)
{
	var CB_OPTIONS = { CrossBase: {} };
	CB_OPTIONS.CrossBase.CB_Canvas_PREFERRED_EMULATION_METHODS = [ FORCED_EMULATION_METHOD ];
	if (FORCED_EMULATION_METHOD === "SILVERLIGHT") { CB_OPTIONS.CrossBase.SLCANVAS_LOAD = true; }
	else if (FORCED_EMULATION_METHOD === "FLASH") { CB_OPTIONS.CrossBase.FLASHCANVAS_LOAD = true; } //Note: Flash emulation will not work if native canvas is supported.
	else if (FORCED_EMULATION_METHOD === "DHTML") { CB_OPTIONS.CrossBase.CANBOX_LOAD = true; }
	else if (FORCED_EMULATION_METHOD === "VML") { CB_OPTIONS.CrossBase.EXCANVAS_LOAD = CB_OPTIONS.CrossBase.CANVAS_TEXT_LOAD = true; }
}

var myREM = null; //It will store the CB_REM object.

CB_init(main); //It will call the "main" function when ready.


//This function will be called when CrossBrowdy is ready:
function main()
{
	//Function to execute when a canvas is created:
	var canvasLoaded = 0;
	var onLoadCanvas = function()
	{
		if (CB_REM.DEBUG_MESSAGES) { CB_console("Canvas '" + this.getId() + "' loaded! Mode used: " + this.getMode()); }

		canvasLoaded++;

		//Gets the "context" object to start working with the canvas:
		var canvasContext = this.getContext();
		if (!canvasContext) { CB_console("ERROR: canvas context could not be obtained! Drawing cannot be performed."); return; }

		//Stores the canvas in the 'canvases' object:
		canvases[this.getId()] = this;

		//If both canvas (normal and buffer) have been created, proceeds with the rendering:
		if (canvasLoaded >= 2)
		{
			//When the screen changes its size, both canvas will be re-adapted:
			CB_Screen.onResize(function() { canvases["my_canvas"].setWidth(CB_Screen.getWindowWidth()); canvases["my_canvas"].setHeight(CB_Screen.getWindowHeight()); canvases["my_canvas"].clear(); canvases["my_canvas"].disableAntiAliasing(); });
			CB_Screen.onResize(function() { canvases["my_canvas_buffer"].setWidth(CB_Screen.getWindowWidth()); canvases["my_canvas_buffer"].setHeight(CB_Screen.getWindowHeight()); canvases["my_canvas_buffer"].clear(); canvases["my_canvas_buffer"].disableAntiAliasing(); });

			//Clears both canvas:
			canvases["my_canvas"].clear();
			canvases["my_canvas_buffer"].clear();
			
			//Disables anti-aliasing to avoid problems with adjacent sprites:
			canvases["my_canvas"].disableAntiAliasing();
			canvases["my_canvas_buffer"].disableAntiAliasing();
			
			//Creates the sprites groups:
			var graphicSpritesSceneObject = createSpritesGroups();

			//Caches all needed images (performance purposes) and starts rendering sprites groups when all are loaded:
			myREM = new CB_REM();
			myREM.cacheImages
			(
				graphicSpritesSceneObject, //CB_GraphicSpritesSceneObject.
				undefined, //reload.
				function(imagesLoaded) //onLoad.
				{
					//Starts clearing the FPS (Frames Per Second) counter each second:
					myREM.startFPSCounter();

					//Show the FPS (Frames Per Second) every time there is a new value:
					myREM.onUpdatedFPS(function(FPS) { graphicSpritesSceneObject.getById("fps_group").getById("fps").src = "FPS: " + FPS; });
					
					//Processes the sprites groups:
					if (CB_REM.DEBUG_MESSAGES) { CB_console("Starts processing graphic sprites scene ('CB_GraphicSpritesScene' object) constantly..."); }
					processSpritesGroups(graphicSpritesSceneObject, canvases["my_canvas"], canvases["my_canvas"].getContext(), canvases["my_canvas_buffer"], canvases["my_canvas_buffer"].getContext());
				}
			);
		}
	};
	
	//Creates the canvases:
	var canvases = {};
	canvases["my_canvas"] = new CB_Canvas
	(
		"my_canvas", //canvasId. Unique required parameter.
		"2d", //contextType. NOTE: some emulation methods only support "2d". Default: "2d".
		CB_Screen.getWindowWidth(), //canvasWidth. Use 'CB_Screen.getWindowWidth()' for complete width. Default: CB_Canvas.WIDTH_DEFAULT.
		CB_Screen.getWindowHeight(), //canvasHeight. Use 'CB_Screen.getWindowHeight()' for complete height. Default: CB_Canvas.HEIGHT_DEFAULT.
		onLoadCanvas, //onLoad.
		function(error) { CB_console("Canvas object problem! Error: " + error); }, //onError.
		undefined, undefined, !!FORCED_EMULATION_METHOD, !!FORCED_EMULATION_METHOD //Forces emulation method.
	);
	canvases["my_canvas_buffer"] = new CB_Canvas
	(
		"my_canvas_buffer", //canvasId. Unique required parameter.
		"2d", //contextType. NOTE: some emulation methods only support "2d". Default: "2d".
		CB_Screen.getWindowWidth(), //canvasWidth. Use 'CB_Screen.getWindowWidth()' for complete width. Default: CB_Canvas.WIDTH_DEFAULT.
		CB_Screen.getWindowHeight(), //canvasHeight. Use 'CB_Screen.getWindowHeight()' for complete height. Default: CB_Canvas.HEIGHT_DEFAULT.
		onLoadCanvas, //onLoad.
		function(error) { CB_console("Canvas object problem! Error: " + error); }, //onError.
		undefined, undefined, !!FORCED_EMULATION_METHOD, !!FORCED_EMULATION_METHOD //Forces emulation method.
	);
}


//Creates the sprites groups:
function createSpritesGroups()
{
	//Defines the sprites groups information ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
	/*
		Note:
			1) Sprites groups, sprites and sub-sprites are always automatically ordered internally by their z-index (remember to use the 'setZIndex' method of each if you want to modify it). The elements of the scene will be rendered respecting this order.
			2) We will use 'duration' in the 'data' property to disable a sprite or sub-sprite (if it is set to null, it will never be disabled). If used in a 'CB_GraphicSprites' object, it will affect its current sprite being pointed.
			3) We will use the 'timeResetAndEnableAfter' in the 'data' property to enable again a sprite or sub-sprite (if it is set to null, it will never be enabled). It also resets the 'time' property of the sprite or sub-sprite. If used in a 'CB_GraphicSprites' object, it will affect its current sprite being pointed.
			4) We will use 'skipAfter' in the 'data' property of a sprite to jump to the next one (if it is set to null, it will never jump automatically). * If used in a 'CB_GraphicSprites' object, it will affect its current sprite being pointed.
			5) We can use 'beforeDrawing' and 'afterDrawing' callbacks defined in the 'data' object to be called before drawing an element or after doing it, respectively.
			6) We can set 'onlyUseInMap' to true in the 'data' object to just draw that element when it is being drawn as a part of a map (a map is an element whose 'srcType' equals to 'CB_GraphicSprites.SRC_TYPES.MAP').
			7) We can set 'loop' to true in the 'data' object to loop sprites infinitely instead of stopping at the last one.
			8) We can set 'positionAbsolute' to true in the 'data' object to do not have in mind the element's parent position to calculate the position of the element.
			10) We can set 'hidden' to true in the 'data' object to skip that element so it will not be drawn.
			10) To rotate an element, we can set the 'rotation' property in the 'data' object to rotate that element:
				* If the 'rotationUseDegrees' property is set to true, the value set in 'rotation' will be considered degrees, otherwise it will be considered radians.
				* To set the coordinates of the rotation axis (for rotation the canvas, internally), use the 'rotationX' and 'rotationY' properties. If any of them is not set, the rotation coordinate will be considered the center (horizontal or vertical) of the element.
			11) Some properties in the 'data' object can be either a static value or a callback function (as for example the 'style' property).
			12) The 'clearPreviousFirst' property in the 'data' object can be set to true to accomplish the following:
				* If it is a sprite, it will clean the space used by the previous sprite before drawing this sprite.
				* If it is a sub-sprite, it will clean the space that will be used by this sub-sprite.
				* If used in a 'CB_GraphicSprites' object, it will affect its current sprite being pointed.
	*/
	var spritesGroupsData =
	{
		//'my_sprites_groups_1' ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object). Some missing or non-valid properties will get a default value:
		id: "my_sprites_groups_1", //Identifier of the sprites groups object (also used for the 'CB_GraphicSpritesScene' object). Optional but recommended. It should be unique. By default, it is generated automatically.
		srcWidth: 40, //The value for the "srcWidth" property which will be used as default if not provided (or the provided one was wrong) in the given 'CB_GraphicSprites.SPRITES_OBJECT' objects. Default: CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
		srcHeight: 40, //The value for the "srcHeight" property which will be used as default if not provided (or the provided one was wrong) in the given 'CB_GraphicSprites.SPRITES_OBJECT' objects. Default: CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
		data: { skipAfter: null, duration: null, timeResetAndEnableAfter: null, loop: true, clearPreviousFirst: false }, //Object with any additional data desired which can be any kind. Default: { 'that' : CB_GraphicSprites.SPRITES_OBJECT, 'getThis' = function() { return this.that; } }.
		//Numeric array containing 'CB_GraphicSprites.SPRITES_OBJECT' objects with all the sprites groups that will be used (their "parent" property will be set to point the current 'CB_GraphicSpritesScene' object which contains them):
		spritesGroups:
		[
			//'bird_sprites' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "bird_sprites", //Identifier of the sprites group (also used for the 'CB_GraphicSprites' object). Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
				src: "img/bird_sprites.gif", //Source of origin. Can be a path or identifier of an image, text, bitmap, 3D object, etc. Optional but recommended. Default: this.parent.src || "".
				srcWidth: 38, //Width of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcWidth || CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
				srcHeight: 36, //Height of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcHeight || CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
				left: 300, //Left (horizontal) position in the destiny (inside the sprites group). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.LEFT_DEFAULT.
				width: 190, //Width of the destiny. Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
				height: 160, //Height of the destiny. Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
				//Object with any additional data desired which can be any kind:
				//NOTE: it will always have a "that" property pointing to the 'CB_GraphicSprites.SPRITES_OBJECT' object where it belongs to and a function in its "getThis" property returning the same value (added automatically).
				data: { skipAfter: 600, clearPreviousFirst: true, onlyUseInMap: false /* Set to true to only display in maps */ }, //Object with any additional data desired which can be any kind. Default: CB_combineJSON(this.parent.data, this.data) || this.parent.data || { 'that' : CB_GraphicSprites.SPRITES_OBJECT, 'getThis' = function() { return this.that; } }.
				//Numeric array containing 'CB_GraphicSprites.SPRITE_OBJECT' objects with all the sprites that will be used:
				sprites:
				[
					//'bird_sprite_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "bird_sprite_1", //Identifier for the sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
						//Numeric array containing 'CB_GraphicSprites.SUBSPRITE_OBJECT' objects with the sub-sprites that this sprite uses:
						subSprites:
						[
							//'bird_sprite_1_subsprite_1' ('CB_GraphicSprites.SUBSPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the parent sprite:
							{
								id: "bird_sprite_1_subsprite_1", //Identifier for the sub-sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
								src: "img/sol.gif",
								srcLeft: 0, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
								srcTop: 0, //Top (vertical) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcTop || CB_GraphicSprites.TOP_SOURCE_DEFAULT.
								srcWidth: 80, //Width of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcWidth || CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
								srcHeight: 80, //Height of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcHeight || CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
								left: 20, //Left (horizontal) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.LEFT_DEFAULT.
								top: 170, //Top (vertical) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.TOP_DEFAULT.
								width: 40, //Width of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
								height: 40, //Height of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
								data: { duration: 200, timeResetAndEnableAfter: 200 },
								zIndex: 2
							},
							//'bird_sprite_1_subsprite_2' ('CB_GraphicSprites.SUBSPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the parent sprite:
							{
								id: "bird_sprite_1_subsprite_2", //Identifier for the sub-sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
								src: "img/seta.gif",
								srcLeft: 0, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
								srcTop: 0, //Top (vertical) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcTop || CB_GraphicSprites.TOP_SOURCE_DEFAULT.
								srcWidth: 40, //Width of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcWidth || CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
								srcHeight: 40, //Height of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcHeight || CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
								top: 200, //Top (vertical) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.TOP_DEFAULT.
								width: 12, //Width of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
								height: 12, //Height of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
								data:
								{
									duration: null,
									beforeDrawing:
										function(element, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, x, y, mapElement, onDrawn) //Called before drawing the element.
										{
											this.height = this.width = this.width === 12 ? 15 : 12;
											if (drawingMap)
											{
												this.data._alternating = !this.data._alternating;
												this._attributes.width = this.data._alternating ? 60 : 50;
												this._attributes.height = this.data._alternating ? 90 : 80;
											}
											return this; //Same as 'element'. Must return the element to draw.
										},
									afterDrawing:
										function(element, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, x, y, mapElement, onDrawn) //Called after drawing the element.
										{
											//Here we could perform any tasks.
										}
								}
							}
						]
					},
					//'bird_sprite_2' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "bird_sprite_2", //Identifier for the sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
						srcLeft: 38, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
						//Numeric array containing 'CB_GraphicSprites.SUBSPRITE_OBJECT' objects with the sub-sprites that this sprite uses:
						subSprites:
						[
							//'bird_sprite_2_subsprite_1' ('CB_GraphicSprites.SUBSPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the parent sprite:
							{
								id: "bird_sprite_2_subsprite_1", //Identifier for the sub-sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
								src: "img/sol.gif",
								srcLeft: 0, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
								srcTop: 0, //Top (vertical) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcTop || CB_GraphicSprites.TOP_SOURCE_DEFAULT.
								srcWidth: 80, //Width of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcWidth || CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
								srcHeight: 80, //Height of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcHeight || CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
								left: 20, //Left (horizontal) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.LEFT_DEFAULT.
								top: 170, //Top (vertical) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.TOP_DEFAULT.
								width: 40, //Width of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
								height: 40, //Height of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
								data: { duration: 200, timeResetAndEnableAfter: 200 },
								zIndex: 2
							},
							//'bird_sprite_2_subsprite_1' ('CB_GraphicSprites.SUBSPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the parent sprite:
							{
								id: "bird_sprite_2_subsprite_2", //Identifier for the sub-sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
								src: "img/seta.gif",
								srcLeft: 0, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
								srcTop: 0, //Top (vertical) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcTop || CB_GraphicSprites.TOP_SOURCE_DEFAULT.
								srcWidth: 40, //Width of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcWidth || CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
								srcHeight: 40, //Height of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcHeight || CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
								top: 200, //Top (vertical) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.TOP_DEFAULT.
								width: 12, //Width of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
								height: 12, //Height of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
								data: { duration: null }
							}
						]
					},
					//'bird_sprite_3' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "bird_sprite_3", //Identifier for the sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
						srcLeft: 38 * 2, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
						//Numeric array containing 'CB_GraphicSprites.SUBSPRITE_OBJECT' objects with the sub-sprites that this sprite uses:
						subSprites:
						[
							//'bird_sprite_2_subsprite_1' ('CB_GraphicSprites.SUBSPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the parent sprite:
							{
								id: "bird_sprite_3_subsprite_1", //Identifier for the sub-sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
								src: "img/sol.gif",
								srcLeft: 0, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
								srcTop: 0, //Top (vertical) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcTop || CB_GraphicSprites.TOP_SOURCE_DEFAULT.
								srcWidth: 80, //Width of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcWidth || CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
								srcHeight: 80, //Height of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcHeight || CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
								left: 20, //Left (horizontal) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.LEFT_DEFAULT.
								top: 170, //Top (vertical) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.TOP_DEFAULT.
								width: 40, //Width of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
								height: 40, //Height of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
								data: { duration: 200, timeResetAndEnableAfter: 200 },
								zIndex: 2
							},
							//'bird_sprite_2_subsprite_1' ('CB_GraphicSprites.SUBSPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the parent sprite:
							{
								id: "bird_sprite_3_subsprite_2", //Identifier for the sub-sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
								src: "img/seta.gif",
								srcLeft: 0, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
								srcTop: 0, //Top (vertical) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcTop || CB_GraphicSprites.TOP_SOURCE_DEFAULT.
								srcWidth: 40, //Width of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcWidth || CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
								srcHeight: 40, //Height of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcHeight || CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
								top: 200, //Top (vertical) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.TOP_DEFAULT.
								width: 12, //Width of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
								height: 12, //Height of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
								data: { duration: null }
							}
						]
					},
					//'bird_sprite_4' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "bird_sprite_4", //Identifier for the sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
						srcLeft: 38 * 3, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
						//Numeric array containing 'CB_GraphicSprites.SUBSPRITE_OBJECT' objects with the sub-sprites that this sprite uses:
						subSprites:
						[
							//'bird_sprite_2_subsprite_1' ('CB_GraphicSprites.SUBSPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the parent sprite:
							{
								id: "bird_sprite_4_subsprite_1", //Identifier for the sub-sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
								src: "img/sol.gif",
								srcLeft: 0, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
								srcTop: 0, //Top (vertical) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcTop || CB_GraphicSprites.TOP_SOURCE_DEFAULT.
								srcWidth: 80, //Width of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcWidth || CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
								srcHeight: 80, //Height of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcHeight || CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
								left: 20, //Left (horizontal) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.LEFT_DEFAULT.
								top: 170, //Top (vertical) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.TOP_DEFAULT.
								width: 40, //Width of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
								height: 40, //Height of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
								data: { duration: 200, timeResetAndEnableAfter: 200 },
								zIndex: 2
							},
							//'bird_sprite_2_subsprite_4' ('CB_GraphicSprites.SUBSPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the parent sprite:
							{
								id: "bird_sprite_4_subsprite_2", //Identifier for the sub-sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
								src: "img/seta.gif",
								srcLeft: 0, //Left (horizontal) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcLeft || CB_GraphicSprites.LEFT_SOURCE_DEFAULT.
								srcTop: 0, //Top (vertical) position in the original source (having in mind its real width and height). Unit agnostic (only numeric values allowed). Default: this.parent.srcTop || CB_GraphicSprites.TOP_SOURCE_DEFAULT.
								srcWidth: 40, //Width of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcWidth || CB_GraphicSprites.WIDTH_SOURCE_DEFAULT.
								srcHeight: 40, //Height of the original source. Unit agnostic (only numeric values allowed). Default: this.parent.srcHeight || CB_GraphicSprites.HEIGHT_SOURCE_DEFAULT.
								top: 200, //Top (vertical) position in the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.TOP_DEFAULT.
								width: 12, //Width of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
								height: 12, //Height of the destiny (inside the sprite). Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
								data: { duration: null }
							}
						]
					}
				]
			},
			//'panda_sprites' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "panda_sprites", //Identifier of the sprites group (also used for the 'CB_GraphicSprites' object). Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
				width: 80, //Width of the destiny. Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
				height: 80, //Height of the destiny. Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
				zIndex: 3, //The z-index for the destiny. Only numeric values allowed. Default: this.parent.zIndex || CB_GraphicSprites.ZINDEX_DEFAULT.
				//Object with any additional data desired which can be any kind:
				//NOTE: it will always have a "that" property pointing to the 'CB_GraphicSprites.SPRITES_OBJECT' object where it belongs to and a function in its "getThis" property returning the same value (added automatically).
				data: { skipAfter: 500 }, //Object with any additional data desired which can be any kind. Default: CB_combineJSON(this.parent.data, this.data) || this.parent.data || { 'that' : CB_GraphicSprites.SPRITES_OBJECT, 'getThis' = function() { return this.that; } }.
				//Numeric array containing 'CB_GraphicSprites.SPRITE_OBJECT' objects with all the sprites that will be used:
				sprites:
				[
					//'panda_sprites_sprite_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "panda_sprites_sprite_1", //Identifier for the sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
						src: "img/panda_1.gif" //Source of origin. Can be a path or identifier of an image, text, bitmap, 3D object, etc. Optional but recommended. Default: this.parent.src || "".
					},
					//'panda_sprites_sprite_2' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "panda_sprites_sprite_2", //Identifier for the sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
						src: "img/panda_2.gif" //Source of origin. Can be a path or identifier of an image, text, bitmap, 3D object, etc. Optional but recommended. Default: this.parent.src || "".
					}
				]
			},
			//'ranisima_sprites' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "ranisima_sprites", //Identifier of the sprites group (also used for the 'CB_GraphicSprites' object). Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
				src: "img/ranisima.gif", //Source of origin. Can be a path or identifier of an image, text, bitmap, 3D object, etc. Optional but recommended. Default: this.parent.src || "".
				left: 110, //Left (horizontal) position in the destiny (inside the sprites group). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.LEFT_DEFAULT.
				top: 80, //Top (vertical) position in the destiny (inside the sprites group). Unit agnostic (only numeric values allowed). Default: CB_GraphicSprites.TOP_DEFAULT.
				width: 160, //Width of the destiny. Unit agnostic (only numeric values allowed). Default: this.parent.width || CB_GraphicSprites.WIDTH_DEFAULT.
				height: 160, //Height of the destiny. Unit agnostic (only numeric values allowed). Default: this.parent.height || CB_GraphicSprites.HEIGHT_DEFAULT.
				zIndex: 2, //The z-index for the destiny. Only numeric values allowed. Default: this.parent.zIndex || CB_GraphicSprites.ZINDEX_DEFAULT.
				data:
				{
					rotation: 0,
					rotationX: undefined,
					rotationY: undefined,
					rotationUseDegrees: true,
					duration: 1000,
					timeResetAndEnableAfter: 250,
					clearPreviousFirst: true,
					opacity: 0.2,
					beforeDrawing:
						function(element, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, x, y, mapElement, onDrawn) //Called before drawing the element.
						{
							element.data.rotation++;
							return element;
						}
				},
				//Numeric array containing 'CB_GraphicSprites.SPRITE_OBJECT' objects with all the sprites that will be used:
				sprites:
				[
					//'ranisima_sprites_sprite_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "ranisima_sprites_sprite_1" //Identifier for the sprite. Optional but recommended. It should be unique. If not provided, it will be calculated automatically.
					}
				]
			},
			//'text_group' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "text_group",
				srcType: CB_GraphicSprites.SRC_TYPES.TEXT,
				zIndex: 3,
				data: { skipAfter: 2000, clearPreviousFirst: true },
				sprites:
				[
					//'text_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "text_1",
						src: "Hello, CrossBrowdy!",
						left: 100,
						top: 24,
						data:
						{
							fontSize: "26px",
							fontFamily: "courier",
							style: "#ffff00",
							fontStyle: "normal",
							fontVariant: "normal",
							fontWeight: "bold",
							caption: null,
							smallCaption: null,
							icon: null,
							menu: null,
							messageBox: null,
							statusBar: null
						}
					},
					//'text_2' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "text_2",
						src: "Stroke text",
						left: 100,
						top: 60,
						data:
						{
							fontSize: "48px",
							fontFamily: "arial",
							style: "#ff0000",
							stroke: true,
							fontStyle: "italic",
							fontVariant: "small-caps",
							fontWeight: "lighter",
							caption: null,
							smallCaption: null,
							icon: null,
							menu: null,
							messageBox: null,
							statusBar: null
						}
					}
				]
			},
			//'fps_group' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "fps_group",
				srcType: CB_GraphicSprites.SRC_TYPES.TEXT,
				zIndex: 3,
				sprites:
				[
					//'fps' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "fps",
						src: "FPS: Calculating...",
						left: 110,
						top: 0,
						data:
						{
							fontSize: "26px",
							fontFamily: "courier",
							style: "#aa00dd",
							fontStyle: "normal",
							fontVariant: "normal",
							fontWeight: "bold",
							caption: null,
							smallCaption: null,
							icon: null,
							menu: null,
							messageBox: null,
							statusBar: null
						}
					}
				]
			},
			//'pixel_group' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "pixel_group",
				srcType: CB_GraphicSprites.SRC_TYPES.PIXEL,
				sprites:
				[
					//'pixel_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "pixel_1",
						left: 85,
						top: 25,
						data:
						{
							style: "#ff00ff",
						},
						subSprites:
						[
							{
								id: "pixel_1_subsprite_1",
								top: 2
							},
							{
								id: "pixel_1_subsprite_2",
								top: 4
							},
							{
								id: "pixel_1_subsprite_3",
								top: 6
							},
							{
								id: "pixel_1_subsprite_4",
								top: 8
							}
						]
					}
				]
			},
			//'rectangle_group' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "rectangle_group",
				srcType: CB_GraphicSprites.SRC_TYPES.RECTANGLE,
				sprites:
				[
					//'rectangle_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "rectangle_1",
						left: 400,
						top: 170,
						width: 60,
						height: 60,
						data:
						{
							style: "#00aa00"
						},
						subSprites:
						[
							{
								id: "rectangle_1_subsprite_1",
								left: 70,
								data:
								{
									style:
									function(element, canvasContext, canvasBufferContext, userBuffer)
									{
										var gradient = canvasContext.createLinearGradient(element.left, element.top, element.left + element.width, element.top + element.height);
										gradient.addColorStop(0, "#aa0000");
										gradient.addColorStop(1, "#aaaa00");
										return gradient;
									}
								}
							},
							{
								id: "rectangle_1_subsprite_2",
								left: 140,
								data:
								{
									stroke: true,
									style:
									function(element, canvasContext, canvasBufferContext, userBuffer)
									{
										var gradient = canvasContext.createLinearGradient(element.left, element.top, element.left + element.width, element.top + element.height);
										gradient.addColorStop(0, "#00aa00");
										gradient.addColorStop(1, "#00ffaa");
										return gradient;
									}
								}
							},
							{
								id: "rectangle_1_subsprite_3",
								left: 210,
								data:
								{
									style:
									function(element, canvasContext, canvasBufferContext, userBuffer)
									{
										var src = "img/seta.gif";
										var image = CB_REM._IMAGES_CACHE[src];
										if (!image)
										{
											image = new Image()
											image.src = src;
										}
										return canvasContext.createPattern(image, 'repeat');
									}
								}
							}
						]
					}
				]
			},
			//'circle_group' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "circle_group",
				srcType: CB_GraphicSprites.SRC_TYPES.CIRCLE,
				data: { radius: 30, style: "#00aa00", opacity: 0.5 },
				sprites:
				[
					//'circle_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "circle_1",
						left: 35,
						top: 120,
						subSprites:
						[
							{
								id: "circle_1_subsprite_1",
								top: 70,
								data:
								{
									style:
									function(element, canvasContext, canvasBufferContext, userBuffer)
									{
										var gradient = canvasContext.createRadialGradient(element.left, element.top, 10, element.left + 10, element.top + 10, 80);
										gradient.addColorStop(0, "red");
										gradient.addColorStop(0.5, "black");
										return gradient;
									}
								}
							},
							{
								id: "circle_1_subsprite_2",
								top: 140,
								data: { style: "#aa0000", stroke: true }
							}
						]
					}
				]
			},
			//'arc_group' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "arc_group",
				srcType: CB_GraphicSprites.SRC_TYPES.ARC,
				data: { radius: 10, startAngle: 2, endAngle: 11, style: "#00aa00" },
				sprites:
				[
					//'arc_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "arc_1",
						left: 80,
						top: 150,
						subSprites:
						[
							{
								id: "arc_1_subsprite_1",
								top: 30
							},
							{
								id: "arc_1_subsprite_2",
								top: 60,
								data: { style: "#aa0000", stroke: true }
							}
						]
					}
				]
			},
			//'ellipse_group' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "ellipse_group",
				srcType: CB_GraphicSprites.SRC_TYPES.ELLIPSE,
				data: { radiusX: 3, radiusY: 8, ellipseRotation: 20, startAngle: 2, endAngle: 11, anticlockwise: false, style: "#00aa00" },
				sprites:
				[
					//'ellipse_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "ellipse_1",
						left: 350,
						top: 100,
						subSprites:
						[
							{
								id: "ellipse_1_subsprite_1",
								top: 20
							},
							{
								id: "ellipse_1_subsprite_2",
								top: 40,
								data: { style: "#aa0000", stroke: true }
							}
						]
					}
				]
			},
			//'triangle_group' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "triangle_group",
				srcType: CB_GraphicSprites.SRC_TYPES.TRIANGLE,
				data: { x1: 3, y1: 8, x2: 20, y2: 2, style: "#00aa00" }, //The (x1, y1) and (x2, y2) points are relative to the element left and top.
				sprites:
				[
					//'triangle_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "triangle_1",
						left: 370,
						top: 190,
						subSprites:
						[
							{
								id: "triangle_1_subsprite_1",
								top: 20
							},
							{
								id: "triangle_1_subsprite_2",
								top: 40,
								data: { style: "#aa0000", stroke: true }
							}
						]
					}
				]
			},
			//'segment_group' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "segment_group",
				srcType: CB_GraphicSprites.SRC_TYPES.SEGMENT,
				width: 10,
				data: { x1: 3, y1: 8, x2: 200, y2: 20, style: "#00aa00" }, //The (x1, y1) and (x2, y2) points are relative to the element left and top.
				sprites:
				[
					//'segment_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "segment_1",
						left: 370,
						top: 250,
						subSprites:
						[
							{
								id: "segment_1_subsprite_1",
								top: 20
							},
							{
								id: "segment_1_subsprite_2",
								top: 40,
								data: { style: "#aa0000" }
							}
						]
					}
				]
			},
			//'bezier_curve_group' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "bezier_curve_group",
				srcType: CB_GraphicSprites.SRC_TYPES.BEZIER_CURVE,
				width: 5,
				data: { x1: 0, y1: 50, x2: 220, y2: 20, x3: 100, y3: 10, style: "#00aa00" }, //The (x1, y1), (x2, y2) and (x3, y3) points are relative to the element left and top.
				sprites:
				[
					//'bezier_curve_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "bezier_curve_1",
						left: 0,
						top: 280,
						subSprites:
						[
							{
								id: "bezier_curve_1_subsprite_1",
								top: 30
							},
							{
								id: "bezier_curve_1_subsprite_2",
								top: 60,
								data: { style: "#aa0000" }
							}
						]
					}
				]
			},
			//'quadratic_bezier_curve_group' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "quadratic_bezier_curve_group",
				srcType: CB_GraphicSprites.SRC_TYPES.QUADRATIC_BEZIER_CURVE,
				width: 2,
				data: { x1: 0, y1: 50, x2: 220, y2: 20, style: "#00aa00" }, //The (x1, y1) and (x2, y2) points are relative to the element left and top.
				sprites:
				[
					//'quadratic_bezier_curve_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "quadratic_bezier_curve_1",
						left: 150,
						top: 250,
						subSprites:
						[
							{
								id: "quadratic_bezier_curve_1_subsprite_1",
								top: 30
							},
							{
								id: "quadratic_bezier_curve_1_subsprite_2",
								top: 60,
								data: { style: "#aa0000" }
							}
						]
					}
				]
			},
			//'map_group' ('CB_GraphicSprites.SPRITES_OBJECT' object). Some missing or non-valid properties will will be inherited from the parent ('CB_GraphicSpritesScene.SPRITES_GROUPS_OBJECT' object):
			{
				id: "map_group",
				src: //Map with string aliases:
				[
					[ "#", "$", "@", "#", "", "@", "", "|", "!" ],
					[ 3, "%", "#", "@", "#", "@", "!", "", 1 ]
				],
				srcType: CB_GraphicSprites.SRC_TYPES.MAP,
				left: 20,
				top: 400,
				width: 20,
				height: 20,
				data:
				{
					//References sprites or sub-sprites by their index or identifier. Define a "parentId" (parent identifier of the 'CB_GraphicSprites' object or of the 'CB_GraphicSprites.SPRITE_OBJECT' object) to improve performance.
					elementsData:
					{
						//Each property name is an alias which can be used in the map (in the "src" property).
						"1" : { index: 1, leftDependsOnPreviousElement: true, topDependsOnPreviousElement: true, parentId: "circle_1" }, //Has in mind the position of the last element to calculate its left and top.
						"3" : { index: 3, leftDependsOnPreviousElement: true, topDependsOnUpperElement: true, parentId: "bird_sprites" }, //Has in mind the position of the last element to calculate its left but to calculate its top has in mind the element above.
						"|":
						{
							id: "bird_sprites",
							leftDependsOnPreviousElement: true, //Has in mind the left position of the last element to calculate its left.
							topDependsOnPreviousElement: true //Has in mind the top position of the last element to calculate its top.
						},
						"@": { id: "panda_sprites_sprite_1" },
						"#": { id: "panda_sprites_sprite_2" },
						"$": //If defined left and/or top, the position will always be that one (fixed).
						{
							id: "bird_sprite_2_subsprite_1",
							width: 120,
							height: 50,
							left: function(alias, element, elementData, elementMapParent, elementLoopLeftNext, x, y) { return 500; }, //It can be either a number or a function returning a number. The function will be called before drawing the element (sprite or sub-sprite) in each loop.
							top: 0, //It can be either a number or a function returning a number. The function will be called before drawing the element (sprite or sub-sprite) in each loop.
 							//Renews the internal cache by creating a new copy of the cached element ('CB_GraphicSprites', 'CB_GraphicSprites.SPRITE_OBJECT' or 'CB_GraphicSprites.SUBSPRITE_OBJECT') every time it is rendered:
							renewCache: true
						},
						"%":
						{
							id: "bird_sprite_1",
 							//Renews the internal cache by creating a new copy of the cached element ('CB_GraphicSprites', 'CB_GraphicSprites.SPRITE_OBJECT' or 'CB_GraphicSprites.SUBSPRITE_OBJECT') every time it is rendered:
							renewCache: true,
							destroyOnRewnew: true //Destroys the previous stored element before renewing the internal cache.
						},
						"!":
						{
							id: "bird_sprite_1_subsprite_2",
							width: function(alias, element, elementData, elementMapParent, elementLoopWidthDefault, x, y) { return 50; }, //It can be either a number or a function returning a number. The function will be called before drawing the element (sprite or sub-sprite) in each loop.
							height: 80, //It can be either a number or a function returning a number. The function will be called before drawing the element (sprite or sub-sprite) in each loop.
							leftDependsOnPreviousElement: true, //Has in mind the left position of the last element to calculate its left.
							topDependsOnPreviousElement: true, //Has in mind the top position of the last element to calculate its top.
 							//Renews the internal cache by creating a new copy of the cached element ('CB_GraphicSprites', 'CB_GraphicSprites.SPRITE_OBJECT' or 'CB_GraphicSprites.SUBSPRITE_OBJECT') every time it is rendered:
							renewCache: true,
						}
					},
					elementsWidth: 40, //It can be either a number or a function returning a number. The function will be called before drawing the element (sprite or sub-sprite) in each loop.
					elementsHeight: function(alias, element, elementData, elementMapParent, elementLoopHeightDefault, x, y) { return 50; }, //It can be either a number or a function returning a number. The function will be called before drawing the element (sprite or sub-sprite) in each loop.
					/*
						Note:
							The "elementsLeft" and "elementsTop" properties can also be defined and can be either a number or a function returning a number. The function will be called before drawing the element (sprite or sub-sprite) in each loop.
							Uncomment them try:
					*/
					//elementsLeft: function(alias, element, elementData, elementMapParent, elementLoopLeftNext, x, y) { return 200; },
					//elementsTop: 20,
					skipAfter: 5000
				},
				sprites:
				[
					//'map_1' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "map_1"
					},
					//'map_2' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "map_2",
						src:
						[
							[ "", "%", "@", "#", "@", "#", "!" ],
							[ "$", "@", "#", "@", "|", "" ]
						]
					},
					//'map_3' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "map_3",
						left: 120,
						top: 100
					},
					//'map_4' ('CB_GraphicSprites.SPRITE_OBJECT' object). Some missing or non-valid properties will be inherited from the sprites group:
					{
						id: "map_4",
						left: 120,
						top: 100,
						src:
						[
							[ "", "%", "@", "#", "@", "#", "!" ],
							[ "$", "@", "#", "@", "|", "" ]
						]
					}
				]
			}
		]
	 };

	//Creates the graphic sprites object:
	return new CB_GraphicSpritesScene(spritesGroupsData);	
}


//Processes the sprites groups:
var processSpritesGroupsTimer = null;
function processSpritesGroups(graphicSpritesSceneObject, CB_CanvasObject, CB_CanvasObjectContext, CB_CanvasObjectBuffer, CB_CanvasObjectBufferContext)
{
	//Renders the scene:
	myREM.renderGraphicScene
	(
		graphicSpritesSceneObject, //graphicSpritesSceneObject. Mandatory. The 'CB_GraphicSpritesScene' object to render.
		//data:
		{
			"CB_CanvasObject": CB_CanvasObject, //Main canvas. Mandatory.
			"CB_CanvasObjectContext": CB_CanvasObjectContext, //Context of the main canvas. Mandatory.
			"CB_CanvasObjectBuffer": CB_CanvasObjectBuffer, //Buffer canvas. Mandatory if "useBuffer" is set to true.
			"CB_CanvasObjectBufferContext": CB_CanvasObjectBufferContext //Context of the buffer canvas. Optional. Mandatory if "useBuffer" is set to true.
		},
		!FORCED_EMULATION_METHOD && CB_REM.BUFFER_RECOMMENDED, //useBuffer. Optional. Default: false. Defines whether to use canvas buffer.
		true //alternateBuffer. Optional. Default: false. Defines whether to alternate visibility between canvas or not (if not, it will copy the buffer canvas content to the visible canvas always).
	);

	//Calls itself again:
	processSpritesGroupsTimer = CB_symmetricCall //Note: we could also use 'requestAnimationFrame'.
	(
		function() { processSpritesGroups(graphicSpritesSceneObject, CB_CanvasObject, CB_CanvasObjectContext, CB_CanvasObjectBuffer, CB_CanvasObjectBufferContext); },
		16, //About 60 FPS (Frames Per Second).
		"processSpritesGroupsTimerId"
	);
}

rendering_engine.js:

//Properties:
CB_REM.IMAGES_CACHE_ENABLED = true; //Determines whether to use images cache or not.
CB_REM.BUFFER_RECOMMENDED = true; //Determines whether using buffer is recommended or not. This value will be calculated and changed automatically when the module initializes (as it is only recommended when native canvas is supported or for some emulation methods).
CB_REM.prototype.FPS = 0; //Stores the FPS counter (cleared each second).
CB_REM.prototype._FPS_timeout = null; //Timeout for each call to the 'CB_REM#_FPS_clear' method (which clears the FPS counter).
CB_REM.prototype._onUpdatedFPS = null; //Callback to run when the FPS have been counted (each second).


//Initialization function for the main static properties (it will be called automatically when the module loads):
CB_REM._init = function()
{
	CB_REM.BUFFER_RECOMMENDED = CB_Client.supportsCanvas() || CB_Canvas.bestEmulation() === "DHTML"; //Using buffer is only recommended when native canvas is supported or for some emulation methods (as some emulation methods flicker more or do not work at all with buffer).
	if (CB_REM.DEBUG_MESSAGES) { CB_console("RENDERING_ENGINE_MODULE recommends using buffer: " + (CB_REM.BUFFER_RECOMMENDED ? "Yes" : "No")); }
}


//Initialization function for the main instance properties (it will be called automatically by the constructor):
CB_REM.prototype._init = function()
{
	return this;
}


//Stops counting the FPS:
CB_REM.prototype.stopFPSCounter = function()
{
	clearTimeout(this._FPS_timeout);
}


//Clears the FPS (Frames Per Second) counter (called automatically):
CB_REM.prototype.startFPSCounter = function()
{
	clearTimeout(this._FPS_timeout);
	if (typeof(this._onUpdatedFPS) === "function") { this._onUpdatedFPS.call(this, this.FPS); }
	this.FPS = 0;
	var that = this;
	this._FPS_timeout = setTimeout(function() { that.startFPSCounter.call(that); }, 1000);
}


//Sets a callback to run when FPS is refreshed (before clearing it):
CB_REM.prototype.onUpdatedFPS = function(callback)
{
	this._onUpdatedFPS = callback;
}


//Stores an image in the internal cache:
CB_REM._IMAGES_CACHE = {}; //It will keep the images cached (read-only).
CB_REM.prototype.cacheImage = function(element, reload, onLoad)
{
	//If we do not want to reload and the image is already in the internal cache, calls the 'onLoad' function (if any) and exits:
	if (!reload && CB_REM._IMAGES_CACHE[element.src]) { if (typeof(onLoad) === "function") { onLoad(element, image); } return; }
	
	//Creates and loads the image from the element (sprite or sub-sprite):
	var image = new Image();
	image.onload = function()
	{
		//Loads the image in the internal cache:
		CB_REM._IMAGES_CACHE[element.src] = image;
		
		//Calls the 'onLoad' function (if any):
		if (typeof(onLoad) === "function") { onLoad(element, image); }
	};
	image.src = element.src;
}


//Stores the needed images in the internal cache (performance purposes):
CB_REM.prototype._cacheImages_timeout = null; //Keeps internal timeout for 'CB_REM#cacheImages' (read-only).
CB_REM.prototype.cacheImages = function(CB_GraphicSpritesSceneObject, reload, onLoad)
{
	clearTimeout(this._cacheImages_timeout);
	
	var imagesNeeded = 0;
	var imagesLoaded = 0;
	var imageOnLoad = function(element, image) { imagesLoaded++; };
	
	//Loops each 'CB_GraphicSprites' object in the 'CB_GraphicSpritesScene' object:
	var that = this;
	CB_GraphicSpritesSceneObject.forEach //Same as 'CB_GraphicSpritesSceneObject.forEachGraphicSprites', 'CB_GraphicSpritesSceneObject.executeAll' and 'CB_GraphicSpritesSceneObject.executeFunctionAll'.
	(
		function(graphicSprites, position, graphicSpritesArray, delay) //functionEach.
		{
			//Loops each sprite:
			graphicSprites.forEach
			(
				function(sprite, position, graphicSpritesArray, delay) //functionEach.
				{
					if (sprite.srcType === CB_GraphicSprites.SRC_TYPES.IMAGE)
					{
						that.cacheImage(sprite, reload, imageOnLoad);
						imagesNeeded++;
					}
					
					//Loops each sub-sprite:
					sprite.forEach
					(
						function(subSprite, position, graphicSpritesArray, delay) //functionEach.
						{
							if (subSprite.srcType === CB_GraphicSprites.SRC_TYPES.IMAGE)
							{
								that.cacheImage(subSprite, reload, imageOnLoad);
								imagesNeeded++;
							}
						}
					);
				}
			)
		},
		true //Loops using z-index order (ascending order).
	);

	//Starts checking the number of loaded images and calls 'onLoad' when finishes:
	var checkImagesLoaded = function()
	{
		if (imagesLoaded >= imagesNeeded) { if (typeof(onLoad) === "function") { onLoad(imagesLoaded); } return; } //Calls 'onLoad' and finishes.
		else { this._cacheImages_timeout = setTimeout(checkImagesLoaded, 1); } //Continues checking.
	};
	checkImagesLoaded();
}


//Processes the sprites groups (a 'CB_GraphicSprites' object):
CB_REM.prototype.renderGraphicScene_lastCB_GraphicSpritesSceneObject = null; //Keeps the last given "CB_GraphicSpritesSceneObject" when the 'renderGraphicScene' method was called the last time (it could be modified internally).
CB_REM.prototype.renderGraphicScene_lastData = null; //Keeps the last given "data" when the 'renderGraphicScene' method was called the last time (it could be modified internally).
CB_REM.prototype.renderGraphicScene = function(CB_GraphicSpritesSceneObject, data, useBuffer, alternateBuffer)
{
	//If we do not use buffer or we want to use buffer but alternating between canvas (their visibility):
	if (!useBuffer || useBuffer && alternateBuffer && data.CB_CanvasObject.isBuffer === true)
	{
		data.CB_CanvasObject.isBuffer = false;
		data.CB_CanvasObjectBuffer.isBuffer = true;
		
		var CB_CanvasObjectBackup = data.CB_CanvasObject;
		data.CB_CanvasObject = data.CB_CanvasObjectBuffer;
		data.CB_CanvasObjectBuffer = CB_CanvasObjectBackup;
		
		var CB_CanvasObjectContextBackup = data.CB_CanvasObjectContext;
		data.CB_CanvasObjectContext = data.CB_CanvasObjectBufferContext;
		data.CB_CanvasObjectBufferContext = CB_CanvasObjectContextBackup;
	}
	else
	{
		data.CB_CanvasObject.isBuffer = true;
		data.CB_CanvasObjectBuffer.isBuffer = false;
	}
	
	//Saves the context (reduces flickering):
	data.CB_CanvasObjectBufferContext.save();

	//Sets the text baseline at top:
	data.CB_CanvasObjectBufferContext.textBaseline = "top"; //Needed for clearing previous text.

	//Clears the canvas:
	data.CB_CanvasObjectBuffer.clear(false);

	//Perform an action (execute a function) for each 'CB_GraphicSprites' object (being able to introduce a delay between each call):
	var that = this;
	CB_GraphicSpritesSceneObject.forEach //Same as 'CB_GraphicSpritesSceneObject.forEachGraphicSprites', 'CB_GraphicSpritesSceneObject.executeAll' and 'CB_GraphicSpritesSceneObject.executeFunctionAll'.
	(
		function(graphicSprites, position, graphicSpritesArray, delay) //functionEach.
		{
			that.drawGraphicSpritesObject(this, data.CB_CanvasObjectBufferContext, data.CB_CanvasObjectContext, useBuffer, CB_GraphicSpritesSceneObject);
		},
		true //Loops using z-index order (ascending order).
	);

	//Restores the context (reduces flickering):
	data.CB_CanvasObjectBufferContext.restore();
	
	//If we are using buffer, shows the buffer and hides the other:
	if (useBuffer)
	{
		//If we do not want to alternate between canvas (their visibility):
		if (!alternateBuffer)
		{
			//Copies the content from the canvas buffer to the displaying one:
			data.CB_CanvasObjectContext.save();
			data.CB_CanvasObject.clear(false);
			data.CB_CanvasObjectContext.drawImage(data.CB_CanvasObjectBuffer.get(), 0, 0);
			data.CB_CanvasObjectContext.restore();
		}
		//...otherwise, if we want to alternate between canvas (their visibility):
		else
		{
			//Shows the canvas buffer and hides the other one:
			CB_Elements.show
			(
				data.CB_CanvasObjectBuffer.get(), //element.
				"block", //displayValue.
				false, //checkValues.
				false, //computed.
				//As soon as it is shown, hides the other canvas:
				function(element) //onShow.
				{
					CB_Elements.hide
					(
						data.CB_CanvasObject.get(), //element.
						false, //checkValues.
						false, //computed.
						//As soon as it is hidden, gets its content copied from the showing canvas:
						function(element) //onHide.
						{
							data.CB_CanvasObjectContext.save();
							//data.CB_CanvasObjectContext.textBaseline = "top"; //Needed for clearing previous text.
							data.CB_CanvasObject.clear(true);
							data.CB_CanvasObjectContext.drawImage(data.CB_CanvasObjectBuffer.get(), 0, 0);
							data.CB_CanvasObjectContext.restore();
						}
					);
				}
			);
		}
	}
	
	this.FPS++; //Increments FPS counter (erased each second).
	
	//Saves the last parameters used (they could have been modified from the given ones):
	this.renderGraphicScene_lastCB_GraphicSpritesSceneObject = CB_GraphicSpritesSceneObject;
	this.renderGraphicScene_lastData = data;
}


//Advances to the next sprite until the current one is pointed and returns it:
CB_REM.prototype.getCurrentSprite = function(CB_GraphicSpritesObject, returnValueOnFail, canvasContext, drawingMap)
{
	if (!CB_GraphicSpritesObject) { return returnValueOnFail; }
	
	var sprite = sprite = CB_GraphicSpritesObject.getCurrent(); //Gets the current sprite (the one indicated by the pointer).
	
	if (!sprite) { return returnValueOnFail; }
	
	if (sprite._timeDisappeared && sprite.data.timeResetAndEnableAfter !== null && sprite._timeDisappeared + sprite.data.timeResetAndEnableAfter < CB_Device.getTiming()) { sprite.setDisabled(false); sprite.setTime(); sprite._timeDisappeared = null; } //Resets the time property.
	if (!sprite._timeDisappeared && sprite.data.duration !== null && sprite.getTimeElapsed() > sprite.data.duration) { sprite.setDisabled(true); sprite._timeDisappeared = CB_Device.getTiming(); this.clearElementSpace(sprite, canvasContext); } //Disables the sprite and clears it from the canvas..
	
	var spritesLength = CB_GraphicSpritesObject.getSprites(false, []).length;
	if (spritesLength > 0)
	{
		var pointerCurrent = CB_GraphicSpritesObject.getPointer();
		var pointerNext = pointerCurrent + 1;
		if (sprite.data.loop) { pointerNext %= spritesLength; }
		else if (pointerNext >= spritesLength) { pointerNext = spritesLength - 1; }
		while (spritesLength > 0 && pointerNext !== pointerCurrent && (sprite === null || sprite.isDisabled() || sprite.data.skipAfter !== null && sprite.getTimeElapsed() > sprite.data.skipAfter || sprite.data.duration !== null && sprite.getTimeElapsed() > sprite.data.duration || !drawingMap && sprite.data.onlyUseInMap))
		{
			sprite = CB_GraphicSpritesObject.get(pointerNext, null);
			if (sprite._timeDisappeared && sprite.data.timeResetAndEnableAfter !== null && sprite._timeDisappeared + sprite.data.timeResetAndEnableAfter < CB_Device.getTiming()) { sprite.setDisabled(false); sprite.setTime(); sprite._timeDisappeared = null; } //Resets the time property.
			if (!sprite._timeDisappeared && sprite.data.duration !== null && sprite.getTimeElapsed() > sprite.data.duration) { sprite.setDisabled(true); sprite._timeDisappeared = CB_Device.getTiming(); } //Disables the sprite.
			pointerNext++;
			if (sprite.data.loop) { pointerNext %= spritesLength; }
			else if (pointerNext >= spritesLength) { break; }
		}
		
		if (sprite !== null && sprite.position !== pointerCurrent)
		{
			CB_GraphicSpritesObject.setPointer(sprite.position);
			sprite._clearPreviousFirstPerformed = false;
		}
	}
	return sprite || returnValueOnFail;
}


//Draws the current sprite of a 'CB_GraphicSprites' object:
CB_REM.prototype.drawGraphicSpritesObject = function(CB_GraphicSpritesObject, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, attributesSpritesGroup, attributesSprite, attributesSubSprites, usingRelativePosition, onDrawSprite, onDrawSubSprite)
{
	if (!CB_GraphicSpritesObject) { return false; }

	if (attributesSpritesGroup) { CB_GraphicSpritesObject.spritesGroup = CB_combineArraysOrObjects(CB_GraphicSpritesObject.spritesGroup, attributesSpritesGroup, false, true); }
	
	var sprite = this.getCurrentSprite(CB_GraphicSpritesObject, null, canvasContext, drawingMap); //Gets the current sprite (advancing/looping the pointer if needed).
	
	//Draws the current sprite:
	return this.drawSprite(sprite, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, undefined, attributesSprite, attributesSubSprites, usingRelativePosition, onDrawSprite, onDrawSubSprite);
}


//Draws a sprite:
CB_REM.prototype.drawSprite = function(sprite, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, attributesSpritesGroup, attributesSprite, attributesSubSprites, usingRelativePosition, onDrawSprite, onDrawSubSprite)
{
	if (sprite === null) { return false; }
	
	if (sprite.time === 0) { sprite.setTime(); } //If not set, sets the current time in the "time" property.
	
	if (!sprite._timeDisappeared && sprite.data.duration !== null && sprite.getTimeElapsed() > sprite.data.duration)
	{
		this.clearElementSpace(sprite, canvasContext);
		return false;
	} 

	if (attributesSpritesGroup) { sprite.parent = CB_combineArraysOrObjects(sprite.parent, attributesSpritesGroup, false, true); }
	if (attributesSprite) { sprite = CB_combineArraysOrObjects(sprite, attributesSprite, false, true); }

	//Draws the sprite:
	var drawnSprite = this.drawElement(sprite, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, onDrawSprite);
	
	var calculateAttributesSubSprites = false;
	if (drawingMap && typeof(attributesSubSprites) !== "object" || attributesSubSprites === null) { calculateAttributesSubSprites = true; }
	
	if (usingRelativePosition) { sprite._usingRelativePosition = true; }
	
	//If it has sub-sprites, loops through them and draws them:
	if (sprite.subSprites && sprite.subSprites.length)
	{
		var that = this;
		sprite.forEach //Same as 'sprite.executeAll' and 'sprite.executeFunctionAll'.
		(
			function(subSprite, position, graphicSpritesArray, delay)
			{
				if (calculateAttributesSubSprites)
				{
					attributesSubSprites = {};

					if (!isNaN(sprite.width) && !isNaN(subSprite._widthOriginal && !isNaN(sprite._widthOriginal)) && sprite._widthOriginal > 0)
					{
						attributesSubSprites.width = subSprite._widthOriginal / sprite._widthOriginal * sprite.width;
					}
					else { attributesSubSprites.width = subSprite.width; }
					
					if (!isNaN(sprite.height) && !isNaN(subSprite._heightOriginal && !isNaN(sprite._heightOriginal)) && sprite._heightOriginal > 0)
					{
						attributesSubSprites.height = subSprite._heightOriginal / sprite._heightOriginal * sprite.height;
					}
					else { attributesSubSprites.height = subSprite.height; }
					
					if (!isNaN(subSprite._leftOriginal) && !isNaN(sprite._leftOriginal) && !isNaN(sprite.width) && !isNaN(sprite._widthOriginal) && sprite._widthOriginal > 0)
					{
						attributesSubSprites.left = (subSprite._leftOriginal - sprite._leftOriginal) * (sprite.width / sprite._widthOriginal);
					}
					else { attributesSubSprites.left = subSprite.left; }

					if (!isNaN(subSprite._topOriginal) && !isNaN(sprite._topOriginal) && !isNaN(sprite.height) && !isNaN(sprite._heightOriginal) && sprite._heightOriginal > 0)
					{
						attributesSubSprites.top = (subSprite._topOriginal - sprite._topOriginal) * (sprite.height / sprite._heightOriginal);
					}
					else { attributesSubSprites.top = subSprite.top; }
					
					attributesSubSprites.width = parseInt(attributesSubSprites.width);
					attributesSubSprites.height = parseInt(attributesSubSprites.height);
					attributesSubSprites.left = parseInt(attributesSubSprites.left);
					attributesSubSprites.top = parseInt(attributesSubSprites.top);
				}
				
				that.drawSubSprite(this, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, undefined, undefined, attributesSubSprites, calculateAttributesSubSprites ? false : usingRelativePosition, onDrawSubSprite); //Draws the sub-sprite.
			},
			true //Loops using z-index order (ascending order).
		);
	}
	
	return drawnSprite;
}


//Draws a sub-sprite:
CB_REM.prototype.drawSubSprite = function(subSprite, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, attributesSpritesGroup, attributesSprite, attributesSubSprites, usingRelativePosition, onDrawSubSprite)
{
	if (subSprite === null) { return; }
	
	if (subSprite.time === 0) { subSprite.setTime(); } //If not set, sets the current time in the "time" property.
	
	if (subSprite._timeDisappeared && subSprite.data.timeResetAndEnableAfter !== null && subSprite._timeDisappeared + subSprite.data.timeResetAndEnableAfter < CB_Device.getTiming())
	{
		subSprite.setDisabled(false);
		subSprite.setTime(); //Resets the time property of the sub-sprite object.
		subSprite._timeDisappeared = null;
	}

	//If the sub-sprite is disabled, exits:
	if (subSprite.isDisabled()) { return; }
	
	//The "duration" for sub-sprites indicates when the sub-sprite must be disabled:
	if (!subSprite._timeDisappeared && subSprite.data.duration !== null && subSprite.getTimeElapsed() > subSprite.data.duration)
	{
		//Disables the sub-sprite and exits:
		subSprite.setDisabled(true);
		subSprite._timeDisappeared = CB_Device.getTiming();
		this.clearElementSpace(subSprite, canvasContext);
		return;
	}

	if (attributesSpritesGroup) { subSprite.parent.parent = CB_combineArraysOrObjects(subSprite.parent.parent, attributesSpritesGroup, false, true); }
	if (attributesSprite) { subSprite.parent = CB_combineArraysOrObjects(subSprite.parent, attributesSprite, false, true); }	
	if (attributesSubSprites) { subSprite = CB_combineArraysOrObjects(subSprite, attributesSubSprites, false, true); }

	if (usingRelativePosition) { subSprite._usingRelativePosition = true; }

	//Draws the sub-sprite:
	return this.drawElement(subSprite, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, onDrawSubSprite);
}


//Clears a desired part:
CB_REM.prototype.clearElementSpace = function(element, canvasContext)
{
	if (typeof(element) === "undefined" || element === null) { return; }
	
	//If the element is an image:
	if (element.srcType === CB_GraphicSprites.SRC_TYPES.IMAGE) { canvasContext.clearRect(element._leftRelative, element._topRelative, element.width, element.height); }
	//...otherwise, if it is text:
	else if (element.srcType === CB_GraphicSprites.SRC_TYPES.TEXT)
	{
		var lineHeight = parseFloat(element.data.fontSize) || element.height;
		var textLines = (element.src + "").split("\n");
		var textTop = null;
		for (var x = 0; x < textLines.length; x++)
		{
			textTop = element._topRelative + x * lineHeight;
			canvasContext.clearRect(element._leftRelative, textTop, parseFloat(canvasContext.measureText(textLines[x]).width) || element.width, lineHeight);
		}
	}
}


//Clears a previous element:
CB_REM.prototype.clearPreviousElement = function(element, canvasContext, useBuffer)
{
	if (useBuffer) { return; } //There is no need to clean previous things if we are using buffer.
	if (typeof(element) === "undefined" || element === null) { return; }
	else if (element._clearPreviousFirstPerformed) { return; }
	this.clearElementSpace(typeof(element.getPrevious) === "function" ? element.getPrevious() : element, canvasContext);
	element._clearPreviousFirstPerformed = true;
}


//Function to call when the element has been drawn (internal usage):
CB_REM._elementDrawn = function(element, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, x, y, mapElement, onDrawn, avoidAfterDrawing)
{
	//If there is a 'onDrawn' callback set, calls it:
	if (typeof(onDrawn) === "function") { onDrawn.call(element, element, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, x, y, mapElement, onDrawn); }

	//If set, calls the 'afterDrawing' callback:
	if (!avoidAfterDrawing && element && element.data && typeof(element.data.afterDrawing) === "function")
	{
		element.data.afterDrawing.call(element, element, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, x, y, mapElement, onDrawn);
	}
};


//Draws an element (sprite or sub-sprite):
CB_REM._ELEMENT_ATTRIBUTES_EMPTY = { left: 0, top: 0, width: 0, height: 0 }; //Empty element attributes, used to clean them (internal usage).
CB_REM.prototype.drawElement_currentMap = null; //It will store a two-dimensional array representing map being drawn or the last one drawn, with the elements.
CB_REM.prototype._onDrawMapElement = null; //It will store the 'onDrawn' callback for each map element (internal usage).
CB_REM.prototype.drawElement = function(element, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, onDrawn, avoidAfterDrawing)
{
	if (!element || typeof(element.isDisabled) !== "function" || element.isDisabled() || !element.data || element.data.hidden) { return false; }
	
	//If not desired, it will not be drawn:
	if (!drawingMap && element.data.onlyUseInMap) { return false; }

	//If desired, clears the space it will use before drawing it:
	if (element.data.clearPreviousFirst) { this.clearPreviousElement(element, canvasContext, useBuffer); }

	//Gets the desired style (can be a function):
	var style = typeof(element.data.style) === "function" ? element.data.style.call(element, element, canvasContext, canvasBufferContext, useBuffer) : element.data.style;
	
	//Applies the desired options:
	canvasContext.globalCompositeOperation = element.data.globalCompositeOperation || "source-over";
	canvasContext.globalAlpha = !isNaN(parseFloat(element.data.opacity)) ? parseFloat(element.data.opacity) : 1;

	//Calculates and sets left and top position relative to the parent element(s):
	element._leftRelative = element.left;
	element._topRelative = element.top;
	if (!element._usingRelativePosition && !element.data.positionAbsolute)
	{
		var parentLoop = element;
		while (parentLoop = parentLoop.parent)
		{
			if (parentLoop.left) { element._leftRelative += parentLoop.left; }
			if (parentLoop.top) { element._topRelative += parentLoop.top; }
		}
	}

	//If set, calls the 'beforeDrawing' callback (unless we are drawing a map as map elements will use this callback in their loop):
	if (!drawingMap && typeof(element.data.beforeDrawing) === "function")
	{
		element = element.data.beforeDrawing.call(element, element, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, false);
		if (!element || typeof(element.isDisabled) !== "function" || element.isDisabled()) { return false; }
	}

	//If we want to rotate the element, proceeds:
	var rotated = false;
	if (element.data.rotation)
	{
		canvasContext.save();
		
		var translationX = null;
		if (!isNaN(parseFloat(element.data.rotationX))) { translationX = parseFloat(element.data.rotationX); }
		else { translationX = element._leftRelative + element.width / 2; element._leftRelative = element.width / 2 * -1; }
		var translationY = null;
		if (!isNaN(parseFloat(element.data.rotationY))) { translationY = parseFloat(element.data.rotationY); }
		else { translationY = element._topRelative + element.height / 2; element._topRelative = element.height / 2 * -1; }
		
		canvasContext.translate(translationX, translationY);
		canvasContext.rotate(element.data.rotationUseDegrees && element.data.rotation !== 0 ? element.data.rotation * Math.PI / 180 : element.data.rotation);
		
		rotated = true;
	}

	//If the element is an image:
	var drawn = true; //Defines whether the element was finally drawn or not.
	switch (element.srcType)
	{
		//Image:
		case CB_GraphicSprites.SRC_TYPES.IMAGE:
			if (!element.src) { return false; } //Exists if there is no source.
			var image = CB_REM._IMAGES_CACHE[element.src];
			if (CB_REM.IMAGES_CACHE_ENABLED && image)
			{
				this.drawImage(image, element.srcLeft, element.srcTop, element.srcWidth, element.srcHeight, element._leftRelative, element._topRelative, element.width, element.height, canvasContext, null);
			}
			else
			{
				image = new Image();
				image.onload = function()
				{
					this.drawImage(image, element.srcLeft, element.srcTop, element.srcWidth, element.srcHeight, element._leftRelative, element._topRelative, element.width, element.height, canvasContext, null);
					
					//If we are not drawing a map (as map elements will manage callbacks in their loop):
					if (!drawingMap)
					{
						//If there is a 'onDrawn' callback set, calls it:
						if (typeof(onDrawn) === "function") { onDrawn.call(element, element, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap); }
					
						//If set, calls the 'afterDrawing' callback:
						if (typeof(element.data.afterDrawing) === "function")
						{
							element.data.afterDrawing.call(element, element, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap);
						}
					}
				};
				image.src = element.src;
				CB_REM._IMAGES_CACHE[element.src] = image;
				drawn = false;
			}
			break;
		//Text:
		case CB_GraphicSprites.SRC_TYPES.TEXT:
			if (!CB_isString(element.src) && isNaN(element.src) || element.src === null) { return; } //Exists if there is no source (but allows 0 value).
			canvasContext.font = 
							(element.data.fontStyle ? element.data.fontStyle + " " : "") + (element.data.fontVariant ? element.data.fontVariant + " " : "") + (element.data.fontWeight ? element.data.fontWeight + " " : "") +
							(element.data.fontSize ? element.data.fontSize + " " : "") +  (element.data.fontFamily ? element.data.fontFamily + " " : "") + (element.data.caption ? element.data.caption + " " : "") +
							(element.data.icon ? element.data.icon + " " : "") + (element.data.menu ? element.data.menu + " " : "") + (element.data.messageBox ? element.data.messageBox + " " : "") +
							(element.data.smallCaption ? element.data.smallCaption + " " : "") + (element.data.statusBar ? element.data.statusBar + " " : "");
			var lineHeight = parseFloat(element.data.fontSize) || element.height;
			var textLines = (element.src + "").split("\n");
			var textTop = null;

			if (element.data.stroke)
			{
				canvasContext.lineWidth = element.data.lineWidth || 1;
				canvasContext.strokeStyle = style;
				for (var x = 0; x < textLines.length; x++)
				{
					textTop = element._topRelative + x * lineHeight;
					canvasContext.strokeText(textLines[x], element._leftRelative, textTop);
				}
			}
			else
			{
				canvasContext.fillStyle = style;
				for (var x = 0; x < textLines.length; x++)
				{
					textTop = element._topRelative + x * lineHeight;
					canvasContext.fillText(textLines[x], element._leftRelative, textTop);
				}
			}
			break;
		//Segment (finite line):
		case CB_GraphicSprites.SRC_TYPES.SEGMENT:
			canvasContext.lineWidth = element.data.lineWidth || element.width || 1;
			canvasContext.strokeStyle = style;
			canvasContext.beginPath();
			canvasContext.moveTo(element._leftRelative + element.data.x1, element._topRelative + element.data.y1);
			canvasContext.lineTo(element._leftRelative + element.data.x2, element._topRelative + element.data.y2);
			canvasContext.closePath();
			canvasContext.stroke(); 
			break;
		//Pixel (1x1 rectangle):
		case CB_GraphicSprites.SRC_TYPES.PIXEL:
			canvasContext.fillStyle = style;
			canvasContext.fillRect(element._leftRelative, element._topRelative, 1, 1);
			break;
		//Rectangle:
		case CB_GraphicSprites.SRC_TYPES.RECTANGLE:
			if (element.data.stroke)
			{
				canvasContext.lineWidth = element.data.lineWidth || 1;
				canvasContext.strokeStyle = style;
				canvasContext.strokeRect(element._leftRelative, element._topRelative, element.width, element.height);
			}
			else
			{
				canvasContext.fillStyle = style;
				canvasContext.fillRect(element._leftRelative, element._topRelative, element.width, element.height);
			}
			break;
		//Circle:
		case CB_GraphicSprites.SRC_TYPES.CIRCLE:
			if (element.data.stroke)
			{
				canvasContext.lineWidth = element.data.lineWidth || 1;
				canvasContext.strokeStyle = style;
				canvasContext.beginPath();
				canvasContext.arc(element._leftRelative, element._topRelative, element.data.radius, 0, Math.PI * 2, true);
				canvasContext.closePath();
				canvasContext.stroke();
			}
			else
			{
				canvasContext.fillStyle = style;
				canvasContext.beginPath();
				canvasContext.arc(element._leftRelative, element._topRelative, element.data.radius, 0, Math.PI * 2, true);
				canvasContext.closePath();
				canvasContext.fill();
			}
			break;
		//Arc:
		case CB_GraphicSprites.SRC_TYPES.ARC:
			if (element.data.stroke)
			{
				canvasContext.lineWidth = element.data.lineWidth || 1;
				canvasContext.strokeStyle = style;
				canvasContext.beginPath();
				canvasContext.arc(element._leftRelative, element._topRelative, element.data.radius, element.data.startAngle, element.data.endAngle, true);
				canvasContext.closePath();
				canvasContext.stroke();
			}
			else
			{
				canvasContext.fillStyle = style;
				canvasContext.beginPath();
				canvasContext.arc(element._leftRelative, element._topRelative, element.data.radius, element.data.startAngle, element.data.endAngle, true);
				canvasContext.closePath();
				canvasContext.fill();
			}
			break;
		//Ellipse:
		case CB_GraphicSprites.SRC_TYPES.ELLIPSE:
			if (element.data.stroke)
			{
				canvasContext.lineWidth = element.data.lineWidth || 1;
				canvasContext.strokeStyle = style;
				canvasContext.beginPath();
				canvasContext.ellipse(element._leftRelative, element._topRelative, element.data.radiusX, element.data.radiusY, element.data.ellipseRotation || 0, element.data.startAngle, element.data.endAngle, element.data.anticlockwise);
				canvasContext.closePath();
				canvasContext.stroke();
			}
			else
			{
				canvasContext.fillStyle = style;
				canvasContext.beginPath();
				canvasContext.ellipse(element._leftRelative, element._topRelative, element.data.radiusX, element.data.radiusY, element.data.ellipseRotation || 0, element.data.startAngle, element.data.endAngle, element.data.anticlockwise);
				canvasContext.closePath();
				canvasContext.fill();
			}
			break;
		//Triangle:
		case CB_GraphicSprites.SRC_TYPES.TRIANGLE:
			if (element.data.stroke)
			{
				canvasContext.lineWidth = element.data.lineWidth || 1;
				canvasContext.strokeStyle = style;
				canvasContext.beginPath();
				canvasContext.moveTo(element._leftRelative, element._topRelative);
				canvasContext.lineTo(element._leftRelative + element.data.x1, element._topRelative + element.data.y1);
				canvasContext.lineTo(element._leftRelative + element.data.x2, element._topRelative + element.data.y2);
				canvasContext.closePath();
				canvasContext.stroke();
			}
			else
			{
				canvasContext.fillStyle = style;
				canvasContext.beginPath();
				canvasContext.moveTo(element._leftRelative, element._topRelative);
				canvasContext.lineTo(element._leftRelative + element.data.x1, element._topRelative + element.data.y1);
				canvasContext.lineTo(element._leftRelative + element.data.x2, element._topRelative + element.data.y2);
				canvasContext.closePath();
				canvasContext.fill();
			}
			break;
		//Bezier curve:
		case CB_GraphicSprites.SRC_TYPES.BEZIER_CURVE:
			canvasContext.lineWidth = element.data.lineWidth || element.width || 1;
			canvasContext.strokeStyle = style;
			canvasContext.beginPath();
			canvasContext.moveTo(element._leftRelative, element._topRelative);
			canvasContext.bezierCurveTo(element._leftRelative + element.data.x1, element._topRelative + element.data.y1, element._leftRelative + element.data.x2, element._topRelative + element.data.y2, element._leftRelative + element.data.x3, element._topRelative + element.data.y3);
			canvasContext.stroke(); 
			break;
		//Quadratic Bezier curve:
		case CB_GraphicSprites.SRC_TYPES.QUADRATIC_BEZIER_CURVE:
			canvasContext.lineWidth = element.data.lineWidth || element.width || 1;
			canvasContext.strokeStyle = style;
			canvasContext.beginPath();
			canvasContext.moveTo(element._leftRelative, element._topRelative);
			canvasContext.quadraticCurveTo(element._leftRelative + element.data.x1, element._topRelative + element.data.y1, element._leftRelative + element.data.x2, element._topRelative + element.data.y2);
			canvasContext.stroke(); 
			break;
		//Map (bitmap, image map, etc.):
		case CB_GraphicSprites.SRC_TYPES.MAP:
			if (element.src.length && element.data.elementsData && CB_GraphicSpritesSceneObject)
			{
				//If there is still not cache for this map, creates it:
				if (typeof(CB_REM._MAP_ELEMENTS_CACHE[element.id]) === "undefined" || CB_REM._MAP_ELEMENTS_CACHE[element.id] === null)
				{
					CB_REM._createCacheMapElement(element.id, CB_GraphicSpritesSceneObject);
				}
				
				var CB_GraphicSpritesSceneObjectCopy = CB_REM._MAP_ELEMENTS_CACHE[element.id].CB_GraphicSpritesSceneObject;
				
				var elementLoopData = null;
				var elementIndexOrId = null;
				var elementLoop = null;
				var elementsWidthDefault = element.width / element.src.length;
				var elementsHeightDefault = element.height / element.src.length;
				var elementLoopLeftNext = null;
				var elementLoopTopNext = null;
				this.drawElement_currentMap = [];
				var lastElementX = -1;
				var lastElementY = -1;
				for (var y = 0; y < element.src.length; y++)
				{
					this.drawElement_currentMap[lastElementY + 1] = [];
					for (var x = 0; x < element.src[y].length; x++)
					{
						if (!element.src[y][x]) { continue; }
						elementLoopData = element.data.elementsData[element.src[y][x]] || null;
						if (elementLoopData === null) { continue; }
						elementIndexOrId = elementLoopData.id || (!isNaN(parseInt(elementLoopData.index)) ? parseInt(elementLoopData.index) : null);
						if (elementIndexOrId === null) { continue; }

						//Tries to get the map element:
						elementLoop = CB_REM._getMapElement(elementIndexOrId, elementLoopData, element, CB_GraphicSpritesSceneObjectCopy, x, y, elementsWidthDefault, elementsHeightDefault);
						
						if (elementLoop === null) { continue; }
						
						//Calculates the width, height, left and top attributes:
						elementLoop._attributes = elementLoop._attributes || {};
						elementLoop._attributes.width = elementLoop.width;
						elementLoop._attributes.height = elementLoop.height;
						elementLoop._attributes.left =
							element._leftRelative +
							(
								typeof(element.data.elementsLeft) === "function" ? element.data.elementsLeft(element.src[y][x], elementLoop, elementLoopData, element, elementLoopLeftNext, x, y) :
								!isNaN(parseFloat(element.data.elementsLeft)) ? parseFloat(element.data.elementsLeft) :
								typeof(elementLoopData.left) === "function" ? elementLoopData.left(element.src[y][x], elementLoop, elementLoopData, element, elementLoopLeftNext, x, y) :
								(
									!isNaN(parseFloat(elementLoopData.left)) ? parseFloat(elementLoopData.left) :
									elementLoopData.leftDependsOnPreviousElement ? elementLoopLeftNext || 0 :
									x * elementLoop.width
								)
							);
						elementLoopLeftNext = elementLoop._attributes.left - element._leftRelative + elementLoop.width;

						elementLoop._attributes.top =
							element._topRelative +
							(
								typeof(element.data.elementsTop) === "function" ? element.data.elementsTop(element.src[y][x], elementLoop, elementLoopData, element, elementLoopTopNext, x, y) :
								!isNaN(parseFloat(element.data.elementsTop)) ? parseFloat(element.data.elementsTop) :
								typeof(elementLoopData.top) === "function" ? elementLoopData.top(element.src[y][x], elementLoop, elementLoopData, element, elementLoopTopNext, x, y) :
								(
									!isNaN(parseFloat(elementLoopData.top)) ? parseFloat(elementLoopData.top) :
									elementLoopData.topDependsOnPreviousElement ? elementLoopTopNext || 0 :
									elementLoopData.topDependsOnUpperElement ?
									(
										(
										lastElementY === -1 && lastElementX !== -1 && typeof(this.drawElement_currentMap[0][lastElementX]) !== "undefined" && this.drawElement_currentMap[0][lastElementX] !== null ? this.drawElement_currentMap[0][lastElementX]._attributes.top + this.drawElement_currentMap[0][lastElementX]._attributes.height || 0 :
										lastElementY !== -1 && typeof(this.drawElement_currentMap[lastElementY][lastElementX + 1]) !== "undefined" && this.drawElement_currentMap[lastElementY][lastElementX + 1] !== null ? this.drawElement_currentMap[lastElementY][lastElementX + 1]._attributes.top + this.drawElement_currentMap[lastElementY][lastElementX + 1]._attributes.height || 0 :
										0) - element._topRelative
									) : y * elementLoop.height
								)
							);

						//If set, calls the 'beforeDrawing' callback:
						if (elementLoop.data && typeof(elementLoop.data.beforeDrawing) === "function")
						{
							elementLoop = elementLoop.data.beforeDrawing.call(elementLoop, elementLoop, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, true, x, y, element);
							if (!elementLoop || typeof(elementLoop.isDisabled) !== "function" || elementLoop.isDisabled()) { continue; }
						}

						this._onDrawMapElement = function() { CB_REM._elementDrawn(elementLoop, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, true, x, y, element, undefined, true); }

						//Draws the element:
						if (elementLoop.isSprites) //The element is a 'CB_GraphicSprites' object.
						{
							this.drawGraphicSpritesObject(elementLoop, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObjectCopy, true, CB_REM._ELEMENT_ATTRIBUTES_EMPTY, elementLoop._attributes, undefined, elementLoopData.leftDependsOnPreviousElement, this._onDrawMapElement);
						}
						else if (elementLoop.isSprite) //The element is a sprite ('CB_GraphicSprites.SPRITE_OBJECT' object).
						{
							this.drawSprite(elementLoop, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObjectCopy, true, CB_REM._ELEMENT_ATTRIBUTES_EMPTY, elementLoop._attributes, undefined, elementLoopData.leftDependsOnPreviousElement, this._onDrawMapElement);
						}
						else //The element is a sub-sprite ('CB_GraphicSprites.SUBSPRITE_OBJECT' object).
						{
							this.drawSubSprite(elementLoop, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObjectCopy, true, CB_REM._ELEMENT_ATTRIBUTES_EMPTY, CB_REM._ELEMENT_ATTRIBUTES_EMPTY, elementLoop._attributes, elementLoopData.leftDependsOnPreviousElement, this._onDrawMapElement);
						}
						
						//Stores the current element in the cached map:
						this.drawElement_currentMap[lastElementY + 1][++lastElementX] = elementLoop;
						
						elementLoopTopNext = elementLoop._attributes.top - element._topRelative;
					}
					
					if (elementLoop === null) { continue; }
					else
					{
						lastElementX = -1;
						lastElementY++;
						elementLoopLeftNext = null;
						elementLoopTopNext += elementLoop.height;
					}
				}
			}
			else { drawn = false; }
			break;
	}

	//If the element has a rotation, restores the canvas (as the canvas itself rotated, indeed):
	if (rotated)
	{
		canvasContext.translate(translationX * -1, translationY * -1);
		canvasContext.restore();
	}

	//If it was drawn, calls the 'onDrawn' and 'element.data.afterDrawing' callbacks (if any):
	if (drawn)
	{
		CB_REM._elementDrawn(element, canvasContext, canvasBufferContext, useBuffer, CB_GraphicSpritesSceneObject, drawingMap, undefined, undefined, undefined, onDrawn, avoidAfterDrawing);
	}
	
	return drawn;
}


//Draws an image from an element (sprite or sub-sprite):
CB_REM.prototype.drawImage = function(image, srcLeft, srcTop, srcWidth, srcHeight, left, top, width, height, canvasContext, canvasBufferContext)
{
	canvasContext.drawImage(image, srcLeft, srcTop, srcWidth, srcHeight, left, top, width, height);
	if (typeof(canvasBufferContext) !== "undefined" && canvasBufferContext !== null) //Received the canvas buffer's context because it was called through the "onload" event of the image and the current canvas displaying could be the buffer one.
	{
		canvasBufferContext.drawImage(image, srcLeft, srcTop, srcWidth, srcHeight, left, top, width, height);
	}
}


//Creates (or resets) and returns the cache for a map which contains the elements and the copy of the given 'CB_GraphicSpritesScene' object:
CB_REM._MAP_ELEMENTS_CACHE = {}; //Stores a cache with elements which are sprite or sub-sprite objects (read-only).
CB_REM._createCacheMapElement = function(elementId, CB_GraphicSpritesSceneObject)
{
	CB_REM._MAP_ELEMENTS_CACHE[elementId] = {};
	CB_REM._MAP_ELEMENTS_CACHE[elementId].CB_GraphicSpritesSceneObject = CB_GraphicSpritesSceneObject.getCopy(false, false);
	CB_REM._MAP_ELEMENTS_CACHE[elementId].CB_GraphicSpritesSceneObject.forEach //Keeps some of the original attributes.
	(
		function (CB_GraphicSpritesObject)
		{
			if (!CB_GraphicSpritesObject) { return; }
			CB_GraphicSpritesObject._widthOriginal = CB_GraphicSpritesObject.width;
			CB_GraphicSpritesObject._heightOriginal = CB_GraphicSpritesObject.height;
			CB_GraphicSpritesObject._leftOriginal = CB_GraphicSpritesObject.left;
			CB_GraphicSpritesObject._topOriginal = CB_GraphicSpritesObject.top;
			CB_GraphicSpritesObject.forEach
			(
				function (sprite)
				{
					if (!sprite) { return; }
					sprite._widthOriginal = sprite.width;
					sprite._heightOriginal = sprite.height;
					sprite._leftOriginal = sprite.left;
					sprite._topOriginal = sprite.top;
					sprite.forEach
					(
						function (subSprite)
						{
							if (!subSprite) { return; }
							subSprite._widthOriginal = subSprite.width;
							subSprite._heightOriginal = subSprite.height;
							subSprite._leftOriginal = subSprite.left;
							subSprite._topOriginal = subSprite.top;
						}
					);
				}
			);
		}
	);
	CB_REM._MAP_ELEMENTS_CACHE[elementId].elements = {};
	return CB_REM._MAP_ELEMENTS_CACHE[elementId];
}


//Gets a map element from the internal cache or searches it in the given 'CB_GraphicSpritesScene' object (and caches the element):
CB_REM._getMapElement = function(elementIndexOrId, elementData, mapElement, CB_GraphicSpritesSceneObject, mapX, mapY, elementsWidthDefault, elementsHeightDefault)
{
	var element = null;
	
	var elementCacheIndex = elementData.parentId ? elementData.parentId + "_" + elementIndexOrId : elementIndexOrId;
	
	//If we want to renew the cache for this element, tries to find it in the cache:
	if (!elementData.renewCache)
	{
		element = CB_REM._MAP_ELEMENTS_CACHE[mapElement.id].elements[elementCacheIndex] || null;
	}
	
	//If found in the cache, returns it and exits:
	if (element !== null) { return element; }
	//...otherwise, finds the element in the 'CB_GraphicSpritesScene' object and caches it:
	else
	{
		element = CB_REM._findElement(elementIndexOrId, elementData, mapElement, CB_GraphicSpritesSceneObject);
		if (element !== null)
		{
			//Calculates the width and height attributes:
			element.width =
				typeof(elementData.width) === "function" ? elementData.width(mapElement.src[mapY][mapX], element, elementData, mapElement, elementsWidthDefault, mapX, mapY) :
				!isNaN(parseFloat(elementData.width)) ? parseFloat(elementData.width) :
				typeof(mapElement.data.elementsWidth) === "function" ? mapElement.data.elementsWidth(mapElement.src[mapY][mapX], element, elementData, mapElement, elementsWidthDefault, mapX, mapY) :
				!isNaN(parseFloat(mapElement.data.elementsWidth)) ? parseFloat(mapElement.data.elementsWidth) :
				element.width ||
				elementsWidthDefault,
			element.height =
				typeof(elementData.height) === "function" ? elementData.height(mapElement.src[mapY][mapX], element, elementData, mapElement, elementsHeightDefault, mapX, mapY) :
				!isNaN(parseFloat(elementData.height)) ? parseFloat(elementData.height) :
				typeof(mapElement.data.elementsHeight) === "function" ? mapElement.data.elementsHeight(mapElement.src[mapY][mapX], element, elementData, mapElement, elementsHeightDefault, mapX, mapY) :
				!isNaN(mapElement.data.elementsHeight) ? parseFloat(mapElement.data.elementsHeight) :
				element.height ||
				elementsHeightDefault

			//Stores the element in the cache:
			if (elementData.destroyOnRewnew && CB_REM._MAP_ELEMENTS_CACHE[mapElement.id].elements[elementCacheIndex] !== element)
			{
				if (typeof(CB_REM._MAP_ELEMENTS_CACHE[mapElement.id].elements[elementCacheIndex]) !== "undefined" && CB_REM._MAP_ELEMENTS_CACHE[mapElement.id].elements[elementCacheIndex] !== null)
				{
					if (typeof(CB_REM._MAP_ELEMENTS_CACHE[mapElement.id].elements[elementCacheIndex].destructor) === "function")
					{
						CB_REM._MAP_ELEMENTS_CACHE[mapElement.id].elements[elementCacheIndex].destructor();
					}
				}
			}
			CB_REM._MAP_ELEMENTS_CACHE[mapElement.id].elements[elementCacheIndex] = element;
		}
	}
	
	return element;
}


CB_REM._findElement = function(elementIndexOrId, elementData, mapElement, CB_GraphicSpritesSceneObject)
{
	var element = null;
	
	//Tries to find the ID or index among the 'CB_GraphicSprites' objects which are in 'CB_GraphicSpritesScene' object:
	if (!elementData.parentId || elementData.parentId === CB_GraphicSpritesSceneObject.id)
	{
		if (elementData.id) { element = CB_GraphicSpritesSceneObject.getById(elementIndexOrId, null); }
		else { element = CB_GraphicSpritesSceneObject.get(elementIndexOrId, null); }
	}
	
	if (element !== null && elementData.renewCache) //'CB_GraphicSprites' object found.
	{
		//Makes a copy of the 'CB_GraphicSprites' to renew it:
		element = element.getCopy(false, false);
		element.parent.spritesGroups.items[element.position] = element;
		element.parent.spritesGroups.itemsByZIndex[element.positionByZIndex] = element;
	}
	else
	{
		//Loops each 'CB_GraphicSprites' object in the 'CB_GraphicSpritesScene' object:
		CB_GraphicSpritesSceneObject.forEach
		(
			function(graphicSprites, position, graphicSpritesArray, delay) //functionEach.
			{
				if (element !== null) { return; } //Element (sprite or sub-sprite) found.
				
				//Tries to find the ID or index among the sprites ('CB_GraphicSprites.SPRITES_OBJECT' objects) which are in the current 'CB_GraphicSprites' object:
				if (!elementData.parentId || elementData.parentId === graphicSprites.id)
				{
					if (elementData.id) { element = graphicSprites.getById(elementIndexOrId, null); }
					else { element = graphicSprites.get(elementIndexOrId, null); }
				}
				
				if (element !== null) //Element (sprite) found.
				{
					element = CB_copyObject(element);
					element.subSprites = CB_Arrays.copy(element.subSprites);
					element.subSpritesByZIndex = CB_Arrays.copy(element.subSpritesByZIndex);
					element.forEach
					(
						function(subSprite, index)
						{
							//element.subSprites[index] = CB_copyObject(subSprite);
							//element.subSpritesByZIndex[subSprite.positionByZIndex] = element.subSprites[index];
							element.subSprites[index].parent = element;
							element.subSprites[index].container = element.container;
						}
					);
					element.parent.sprites[element.position] = element;
					element.parent.spritesByZIndex[element.positionByZIndex] = element;
					return;
				}
				
				//Loops each sprite:
				graphicSprites.forEach
				(
					function(sprite, position, graphicSpritesArray, delay) //functionEach.
					{
						if (element !== null) { return; } //Element (sub-sprite) found.
						
						//Tries to find the ID or index among the sub-sprites ('CB_GraphicSprites.SUBSPRITES_OBJECT' objects) which are in the current 'CB_GraphicSprites.SPRITES_OBJECT' object:
						if (!elementData.parentId || elementData.parentId === sprite.id)
						{
							if (elementData.id) { element = sprite.getById(elementIndexOrId, null); }
							else { element = sprite.get(elementIndexOrId, null); }
						}
						
						if (element !== null) //Element (sub-sprite) found.
						{
							element = CB_copyObject(element);
							element.parent.subSprites[element.position] = element;
							element.parent.subSpritesByZIndex[element.positionByZIndex] = element;
							return;
						}
					},
					true //Loops using z-index order (ascending order).
				);
			},
			true //Loops using z-index order (ascending order).
		);
	}

	return element;
}



//Defines the properties that will be returned by the CB_REM#getData method by default (if no "propertiesToKeepGraphicSpritesSceneObject" is given):
CB_REM.getData_filterProperties_DEFAULT_PROPERTIES =
{
	spritesScene: ["id", "spritesGroups"],
	spritesGroups: ["id", "byReference_DEFAULT", "src", "srcType", "srcLeft", "left", "srcTop", "top", "srcWidth", "width", "srcHeight", "height", "zIndex", "data", /*"spritesGroups",*/ "items"],
	sprites: ["id", "byReference_DEFAULT", "spritesGroup", "zIndex", "pointer", "pointerPrevious", "time", "pointer", "pointerPrevious", "position", "positionByZIndex", "_attributes"],
	spritesGroup: ["id", "byReference_DEFAULT", "src", "srcType", "srcLeft", "left", "srcTop", "top", "srcWidth", "width", "srcHeight", "height", "zIndex", "data", "disabled", "sprites"],
	sprite: ["id", "byReference", "time", "src", "srcType", "srcLeft", "left", "srcTop", "top", "srcWidth", "width", "srcHeight", "height", "zIndex", "data", "disabled", "position", "positionByZIndex", "subSprites", "_timeDisappeared", "_usingRelativePosition", "_clearPreviousFirstPerformed", "_widthOriginal", "_heightOriginal", "_leftOriginal", "_topOriginal", "_attributes"],
	subSprite: ["id", "byReference", "time", "src", "srcType", "srcLeft", "left", "srcTop", "top", "srcWidth", "width", "srcHeight", "height", "zIndex", "data", "disabled", "position", "positionByZIndex", "_timeDisappeared", "_usingRelativePosition", "_attributes"]
};


//Returns the current data of the rendering engine:
CB_REM.prototype.getData_object = {};
CB_REM.prototype.getData = function(stringify, graphicSpritesSceneObject, avoidGraphicSpritesSceneData, propertiesToKeepGraphicSpritesSceneObject)
{
	//Updates the internal object with the data:
	this.getData_object.data = this.data;
	propertiesToKeepGraphicSpritesSceneObject = (typeof(propertiesToKeepGraphicSpritesSceneObject) === "object" && propertiesToKeepGraphicSpritesSceneObject !== null) ? propertiesToKeepGraphicSpritesSceneObject : CB_REM.getData_filterProperties_DEFAULT_PROPERTIES;
	this.getData_object.GraphicSpritesScene_data = !avoidGraphicSpritesSceneData && graphicSpritesSceneObject instanceof CB_GraphicSpritesScene ? graphicSpritesSceneObject.getCopy(false, false, false, true, propertiesToKeepGraphicSpritesSceneObject) : null;
	
	//Here we could also get other additional data from the rendering engine (as for example this.renderGraphicScene_lastCB_GraphicSpritesSceneObject, etc.)...
	
	//Returns the data (stringified if desired):
	return stringify ? JSON.stringify(this.getData_object) : this.getData_object; //It could be a good idea to stringify functions ('JSON.stringify' method does not).
}


//Restores the given data into the rendering engine:
CB_REM.prototype.setData = function(getDataObject, graphicSpritesSceneObject, avoidGraphicSpritesSceneData)
{
	if (typeof(getDataObject) === "undefined" || getDataObject === null || typeof(getDataObject) !== "object") { return false; }
	
	//Restores the given data:
	this.data = getDataObject.data;
	if (!avoidGraphicSpritesSceneData && getDataObject.GraphicSpritesScene_data instanceof CB_GraphicSpritesScene)
	{
		//Here we would restore the the graphic sprites object...
	}
	//Here we could also restore other additional data from the rendering engine...
	
	return true;
}


//TODO: A 'reset' method could be useful sometimes.


if (CB_REM.DEBUG_MESSAGES) { CB_console("rendering_engine.js inserted in the document"); }

rendering_engine_module.js:

//Constants and variables:
var CB_REM = function() { if (this === window || !(this instanceof CB_REM)) { return new CB_REM(); } return this._init(); }; //Rendering Engine Module main object and constructor.
CB_REM_PATH = CB_this.CB_REM_PATH || ""; //Path to the graphic rendering engine module.
CB_REM.DEBUG_MESSAGES = !!CB_this.CB_REM_DEBUG_MESSAGES; //Sets whether to show debug messages or not.


//Module basic configuration:
CB_Modules.modules["RENDERING_ENGINE_MODULE"] =
{
	//Name of the module:
	"name" : "RENDERING_ENGINE_MODULE",

	//Status (UNKNOWN, UNLOADED, LOADING, LOADED, READY or FAILED):
	"status" : CB_Modules.STATUSES.UNLOADED,

	//Function to call as soon as the module is called (before loading its files):
	"onCall" :
		function(scriptPathGiven)
		{
			if (CB_REM_PATH)
			{
				CB_Modules.modules["RENDERING_ENGINE_MODULE"].neededFiles = {};
				CB_Modules.modules["RENDERING_ENGINE_MODULE"].neededFiles[CB_REM_PATH + "rendering_engine.js"] = { load: true, mandatory: true, absolutePath: true };
			}
			
			if (CB_REM.DEBUG_MESSAGES) { CB_console("RENDERING_ENGINE_MODULE called"); }
			CB_Modules.setStatus("RENDERING_ENGINE_MODULE", CB_Modules.STATUSES.LOADED);
		},

	//Callback function to call when the module has been loaded successfully:
	"onLoad" :
		function(scriptPathGiven)
		{
			if (CB_REM.DEBUG_MESSAGES) { CB_console("RENDERING_ENGINE_MODULE loaded"); }
			
			//Sets the module ready when CrossBase module is ready:
			var checkCrossBaseLoaded =
				function()
				{
					//If CrossBase module is not ready yet:
					if (!CB_Modules.modules["CrossBase"] || CB_Modules.modules["CrossBase"].status !== CB_Modules.STATUSES.READY)
					{
						return setTimeout(checkCrossBaseLoaded, 1); //Calls this checking function again.
					}
					//...otherwise, if CrossBase module is ready, proceeds:
					else
					{
						CB_REM._init(); //Calls initialization function.
						CB_Modules.setStatus("RENDERING_ENGINE_MODULE", CB_Modules.STATUSES.READY); //Sets the RENDERING_ENGINE_MODULE as ready.
					}
				};
			checkCrossBaseLoaded();
		},

	//Callback function to call when the module is ready:
	"onReady" :
		function(scriptPathGiven)
		{
			if (CB_REM.DEBUG_MESSAGES) { CB_console("RENDERING_ENGINE_MODULE ready"); }
		},

	//Needed files:
	"neededFiles" :
		{
			//Filepaths:
			"rendering_engine.js" : { load: true, mandatory: true, absolutePath: true } //Needs to be loaded. Mandatory. Relative path.
		},

	//Credits:
	"credits" : "[CB] - RENDERING_ENGINE_MODULE by Joan Alba Maldonado" //Credits will be shown in the console when loading.
};


if (CB_REM.DEBUG_MESSAGES) { CB_console("rendering_engine_module.js (RENDERING_ENGINE_MODULE file) inserted in the document"); }
Additional file used (inside the "img" folder): bird_sprites.gif, panda_1.gif, panda_2.gif, ranisima.gif, seta.gif and sol.gif.

Try this example

You can check the Guides & Tutorials category as well as the API documentation in the case you need more information.

Go back to Guides & Tutorials

« PrevReturnNext »
CrossBrowdy by Joan Alba Maldonado