function SceneEditor(parent, closeable, container, index)
{
	TabComponent.call(this, parent, closeable, container, index, Locale.scene, Global.FILE_PATH + "icons/misc/scene.png");

	var self = this;

	/**
	 * Rendering canvas element where the scene is presented.
	 *
	 * @attribute canvas
	 * @type {RendererCanvas}
	 */
	this.canvas = new RendererCanvas(this, Editor.getRendererConfig());
	this.canvas.onResize = function(width, height)
	{
		if (self.scene !== null)
		{
			self.scene.resize(width, height);
		}
	};
	this.canvas.resetCanvas = function()
	{
		RendererCanvas.prototype.resetCanvas.call(this);

		self.transform.setCanvas(this.canvas);
		self.mouse.setCanvas(this.canvas);

		this.canvas.ondragover = Component.preventDefault;
		this.canvas.ondrop = function(event)
		{
			event.preventDefault();

			var uuid = event.dataTransfer.getData("uuid");
			var draggedObject = DragBuffer.get(uuid);

			var canvas = this;
			var rect = canvas.getBoundingClientRect();

			var position = new Vector2(event.clientX - rect.left, event.clientY - rect.top);
			var normalized = new Vector2(position.x / self.canvas.size.x * 2.0 - 1.0, -2.0 * position.y / self.canvas.size.y + 1.0);
			self.raycaster.setFromCamera(normalized, self.camera);

			var intersections = self.raycaster.intersectObjects(self.scene.children, true);

			// Auxiliar method to copy details from a object to a destination
			function copyDetails(destination, object)
			{
				destination.name = object.name;
				destination.visible = object.visible;
				destination.castShadow = object.castShadow;
				destination.receiveShadow = object.receiveShadow;
				destination.frustumCulled = object.frustumCulled;
				destination.renderOrder = object.renderOrder;
				destination.matrixAutoUpdate = object.matrixAutoUpdate;
				destination.position.copy(object.position);
				destination.scale.copy(object.scale);
				destination.quaternion.copy(object.quaternion);
			}

			// Auxiliar method to attach textures to objects
			function attachTexture(texture, object)
			{
				var material = null;
				if (object instanceof Mesh || object instanceof SkinnedMesh)
				{
					material = new MeshStandardMaterial({map: texture, color: 0xFFFFFF, roughness: 0.6, metalness: 0.2});
					material.name = texture.name;
				}
				else if (object instanceof Line)
				{
					material = new LineBasicMaterial({color: 0xFFFFFF});
					material.name = texture.name;
				}
				else if (object instanceof Points)
				{
					material = new PointsMaterial({map: texture, color: 0xFFFFFF});
					material.name = texture.name;
				}
				else if (object instanceof Sprite)
				{
					material = new SpriteMaterial({map: texture, color: 0xFFFFFF});
					material.name = texture.name;
				}

				Editor.addAction(new ActionBundle(
					[
						new AddResourceAction(material, Editor.program, "materials"), 
						new ChangeAction(object, "material", material)
					]));
			}

			// Dragged resource
			if (draggedObject !== null)
			{
				// Object intersected
				if (intersections.length > 0)
				{
					var object = intersections[0].object;

					// Material
					if (draggedObject instanceof Material)
					{
						// Sprite material
						if (draggedObject instanceof SpriteMaterial)
						{
							if (object instanceof Sprite)
							{
								Editor.addAction(new ChangeAction(object, "material", draggedObject));
							}
						}
						// Points material
						else if (draggedObject instanceof PointsMaterial)
						{
							if (object instanceof Points)
							{
								Editor.addAction(new ChangeAction(object, "material", draggedObject));
							}
							else if (object.geometry !== undefined)
							{
								var newObject = new Points(object.geometry, draggedObject);
								copyDetails(newObject, object);
								Editor.addAction(new SwapAction(object, newObject, true));
							}
						}
						// Line material
						else if (draggedObject instanceof LineBasicMaterial)
						{
							if (object instanceof Line)
							{
								Editor.addAction(new ChangeAction(object, "material", draggedObject));
							}
							else if (object.geometry !== undefined)
							{
								var newObject = new Line(object.geometry, draggedObject);
								copyDetails(newObject, object);
								Editor.addAction(new SwapAction(object, newObject, true));
							}
						}
						// Shader material
						else if (draggedObject instanceof ShaderMaterial)
						{
							if (object.material !== undefined)
							{
								Editor.addAction(new ChangeAction(object, "material", draggedObject));
							}
						}
						// Mesh material
						else
						{
							if (object instanceof Mesh)
							{
								Editor.addAction(new ChangeAction(object, "material", draggedObject));
							}
							else if (object.geometry !== undefined)
							{
								var newObject = new Mesh(object.geometry, draggedObject);
								copyDetails(newObject, object);
								Editor.addAction(new SwapAction(object, newObject, true));
							}
						}
					}
					// Cubemap
					else if (draggedObject.isCubeTexture === true)
					{
						if (object.material instanceof Material)
						{
							Editor.addAction(new ChangeAction(object.material, "envMap", draggedObject));
							self.canvas.reloadContext();
						}
					}
					// Texture
					else if (draggedObject instanceof Texture)
					{
						attachTexture(draggedObject, object);
					}
					// Image
					else if (draggedObject instanceof Image)
					{
						attachTexture(new Texture(draggedObject), object);
					}
					// Video
					else if (draggedObject instanceof Video)
					{
						attachTexture(new VideoTexture(draggedObject), object);
					}
					// Font
					else if (draggedObject instanceof Font)
					{
						if (object.font !== undefined)
						{
							object.setFont(draggedObject);
							Editor.updateObjectsViewsGUI();
						}
					}
					// Geometry
					else if (draggedObject instanceof Geometry || draggedObject instanceof BufferGeometry)
					{
						if (object instanceof Mesh || object instanceof Points || object instanceof Line)
						{
							Editor.addAction(new ChangeAction(object, "geometry", draggedObject));
						}
					}
				}

				// Create audio emitter
				if (draggedObject instanceof Audio)
				{
					var audio = new AudioEmitter(draggedObject);
					audio.name = draggedObject.name;
					Editor.addObject(audio);
				}
			}
			// Dragged file
			else if (event.dataTransfer.files.length > 0)
			{
				var files = event.dataTransfer.files;

				for (var i = 0; i < files.length; i++)
				{
					var file = files[i];

					// Check if mouse intersects and object
					if (intersections.length > 0)
					{
						var object = intersections[0].object;

						// Image
						if (Image.fileIsImage(file))
						{
							Loaders.loadTexture(file, function(texture)
							{
								attachTexture(texture, object);
							});
						}
						// Video
						else if (Video.fileIsVideo(file))
						{
							Loaders.loadVideoTexture(file, function(texture)
							{
								attachTexture(texture, object);
							});
						}
						// Font
						else if (Font.fileIsFont(file))
						{
							if (object.font !== undefined)
							{
								Loaders.loadFont(file, function(font)
								{
									object.setFont(font);
								});
							}
						}
					}
					
					// Model
					if (Model.fileIsModel(file))
					{
						Loaders.loadModel(file);
					}
				}
			}

		};
	};

	/**
	 * Keyboard input object.
	 *
	 * @attribute keyboard
	 * @type {Keyboard}
	 */
	this.keyboard = new Keyboard();

	/** 
	 * Mouse input object
	 *
	 * It is attached to the window object to capture movement outside of the tab division.
	 *
	 * @attribute mouse
	 * @type {Mouse}
	 */
	this.mouse = new Mouse(window, true);

	/** 
	 * Raycaster object used for object picking.
	 *
	 * @attribute raycaster
	 * @type {Raycaster}
	 */
	this.raycaster = new Raycaster();

	/**
	 * Normalized mouse coordinates for raycasting.
	 *
	 * @attribute normalized
	 * @type {Vector2}
	 */
	this.normalized = new Vector2();

	/**
	 * Scene being edited in this tab.
	 *
	 * Can also be a regular 3D object.
	 *
	 * @attribute scene
	 * @type {Object3D}
	 */
	this.scene = null;

	/** 
	 * The tool bar contains the selector for the transform tools and object placing icons.
	 *
	 * @attribute sideBar
	 * @type {SideBar}
	 */
	this.sideBar = new SideBar(this);
	
	/**
	 * Camera orientation cube.
	 *
	 * Used to preview the orientation of the editor camera.
	 *
	 * @attribute orientation
	 * @type {OrientationCube}
	 */
	this.orientation = new OrientationCube();

	/**
	 * Helper scene stored the object and editor preview objects.
	 *
	 * @attribute helperScene
	 * @type {Scene}
	 */
	this.helperScene = new Scene();
	this.helperScene.matrixAutoUpdate = false;

	/**
	 * Grid helper configured to match editor settings.
	 *
	 * @attribute gridHelper
	 * @type {GridHelper}
	 */
	this.gridHelper = new GridHelper(Editor.settings.editor.gridSize, Editor.settings.editor.gridSpacing, 0x888888);
	this.gridHelper.visible = Editor.settings.editor.gridEnabled;
	this.helperScene.add(this.gridHelper);

	/**
	 * Axes helper configured to match editor settings.
	 *
	 * @attribute axisHelper
	 * @type {AxesHelper}
	 */
	this.axisHelper = new AxesHelper(Editor.settings.editor.gridSize);
	this.axisHelper.material.depthWrite = false;
	this.axisHelper.material.transparent = true;
	this.axisHelper.material.opacity = 1.0;
	this.axisHelper.visible = Editor.settings.editor.axisEnabled;
	this.helperScene.add(this.axisHelper);

	/**
	 * Object helper container.
	 *
	 * @attribute objectHelper
	 * @type {Group}
	 */
	this.objectHelper = new Group();
	this.objectHelper.matrixAutoUpdate = true;
	this.helperScene.add(this.objectHelper);

	/**
	 * Group where the object manipulation tools are drawn
	 *
	 * @attribute toolScene
	 * @type {Scene}
	 */
	this.toolScene = new Scene();
	this.toolScene.matrixAutoUpdate = false;

	/**
	 * Editor manipulation mode.
	 *
	 * @attribute mode
	 * @type {number}
	 */
	this.mode = SceneEditor.SELECT;

	/** 
	 * Transform controls tool.
	 *
	 * @attribute transform
	 * @type {TransformControls}
	 */
	this.transform = new TransformControls(this.camera, null, this.mouse);
	this.transform.visible = false;
	this.toolScene.add(this.transform);

	/**
	 * Camera object used to visualize the scene.
	 *
	 * This object is attached to the scene as the defaultCamera, allowing it to be used for runtime when there is no default camera.
	 *
	 * Can be a an OrthographicCamera or PerspectiveCamera dependeing on the cameraMode value.
	 *
	 * @attribute camera
	 * @type {Camera}
	 */
	this.camera = null;
	
	/** 
	 * Camera controls object used to manipulate the camera position.
	 *
	 * Can be EditorFreeControls, EditorOrbitControls or EditorPlanarControls.
	 *
	 * @attribute controls
	 * @type {Group}
	 */
	this.controls = null;
	this.controlsMode = -1;
	this.setCameraMode(SceneEditor.PERSPECTIVE);

	/**
	 * Transformation controls mode can be local or world.
	 *
	 * @attribute transformationSpace
	 * @type {DropdownList}
	 */
	this.transformationSpace = new DropdownList(this);
	this.transformationSpace.size.set(60, 30);
	this.transformationSpace.position.set(145, 5);
	this.transformationSpace.updatePosition(Component.BOTTOM_RIGHT);
	this.transformationSpace.updateSize();
	this.transformationSpace.addValue(Locale.local, TransformControls.LOCAL);
	this.transformationSpace.addValue(Locale.world, TransformControls.WORLD);
	this.transformationSpace.element.style.opacity = 0.5;
	this.transformationSpace.setOnChange(function()
	{
		var space = self.transformationSpace.getValue();
		Editor.settings.editor.transformationSpace = space;
		self.transform.space = space;
	});
	this.transformationSpace.element.onmouseenter = function()
	{
		this.style.opacity = 1.0;
	};
	this.transformationSpace.element.onmouseleave = function()
	{
		this.style.opacity = 0.5;
	};

	/**
	 * Dropdown to select the world navigation mode to use.
	 *
	 * @attribute navigation
	 * @type {DropdownList}
	 */
	this.navigation = new DropdownList(this);
	this.navigation.setAltText(Locale.cameraNavigation);
	this.navigation.size.set(100, 30);
	this.navigation.position.set(40, 5);
	this.navigation.updatePosition(Component.BOTTOM_RIGHT);
	this.navigation.updateSize();
	this.navigation.addValue(Locale.firstPerson, Settings.FIRST_PERSON);
	this.navigation.addValue(Locale.orbit, Settings.ORBIT);
	this.navigation.addValue(Locale.left, Settings.PLANAR_LEFT);
	this.navigation.addValue(Locale.right, Settings.PLANAR_RIGHT);
	this.navigation.addValue(Locale.front, Settings.PLANAR_FRONT);
	this.navigation.addValue(Locale.back, Settings.PLANAR_BACK);
	this.navigation.addValue(Locale.top, Settings.PLANAR_TOP);
	this.navigation.addValue(Locale.bottom, Settings.PLANAR_BOTTOM);
	this.navigation.element.style.opacity = 0.5;
	this.navigation.setOnChange(function()
	{
		Editor.settings.editor.navigation = self.navigation.getValue();
		self.updateCameraControls(Editor.settings.editor.navigation);
	});
	this.navigation.element.onmouseenter = function()
	{
		this.style.opacity = 1.0;
	};
	this.navigation.element.onmouseleave = function()
	{
		this.style.opacity = 0.5;
	};

	/**
	 * Button to toggle snap to grid functionality
	 *
	 * @method snapGridButton
	 * @type {ButtonIcon}
	 */
	this.snapGridButton = new ButtonIcon(this);
	this.snapGridButton.position.set(5, 40);
	this.snapGridButton.size.set(30, 30);
	this.snapGridButton.setImage(Global.FILE_PATH + "icons/misc/" + (Editor.settings.editor.snap ? "grid" : "freemove") + ".png");
	this.snapGridButton.setAltText(Locale.toggleSnapToGrid);
	this.snapGridButton.setImageScale(0.8, 0.8);
	this.snapGridButton.updateSize();
	this.snapGridButton.updatePosition(Component.BOTTOM_RIGHT);
	this.snapGridButton.element.style.borderRadius = "5px";
	this.snapGridButton.updateSyles({backgroundColor: "var(--panel-color)", opacity: 0.5}, {backgroundColor: "var(--panel-color)", opacity: 1.0});
	this.snapGridButton.setOnClick(function()
	{
		Editor.settings.editor.snap = !Editor.settings.editor.snap;
		self.transform.snap = Editor.settings.editor.snap;

		self.snapGridButton.setImage(Global.FILE_PATH + "icons/misc/" + (Editor.settings.editor.snap ? "grid" : "freemove") + ".png");
	});

	/**
	 * Button to toggle the camera mode between ORTHOGRAPHIC and PERSPECTIVE.
	 *
	 * @method cameraButton
	 * @type {ButtonIcon}
	 */
	this.cameraButton = new ButtonIcon(this);
	this.cameraButton.position.set(5, 5);
	this.cameraButton.size.set(30, 30);
	this.cameraButton.setImage(Global.FILE_PATH + "icons/misc/3d.png");
	this.cameraButton.setAltText(Locale.cameraMode);
	this.cameraButton.setImageScale(0.8, 0.8);
	this.cameraButton.updateSize();
	this.cameraButton.updatePosition(Component.BOTTOM_RIGHT);
	this.cameraButton.updateSyles({backgroundColor: "var(--panel-color)", opacity: 0.5}, {backgroundColor: "var(--panel-color)", opacity: 1.0});
	this.cameraButton.element.style.borderRadius = "5px";
	this.cameraButton.setOnClick(function()
	{
		self.setCameraMode();

		if (self.cameraMode === SceneEditor.ORTHOGRAPHIC)
		{
			self.cameraButton.setImage(Global.FILE_PATH + "icons/misc/2d.png");
		}
		else if (self.cameraMode === SceneEditor.PERSPECTIVE)
		{
			self.cameraButton.setImage(Global.FILE_PATH + "icons/misc/3d.png");
		}
	});

	/**
	 * The editor tool bar is used to select tool used to manipulate objects.
	 *
	 * @attribute toolBar
	 * @type {ToolBar}
	 */
	this.toolBar = new ToolBar(this);
	this.toolBar.setMode(Component.BOTTOM_LEFT);

	/**
	 * Event manager to handley keyboard shortcuts.
	 *
	 * @attribute manager
	 * @type {EventManager}
	 */
	this.manager = new EventManager();
	this.manager.add(document.body, "keydown", function(event)
	{
		var key = event.keyCode;

		if (event.ctrlKey)
		{
			if (self.container.focused)
			{
				if (key === Keyboard.NUM1)
				{
					self.selectTool(SceneEditor.SELECT);
				}
				else if (key === Keyboard.NUM2)
				{
					self.selectTool(SceneEditor.MOVE);
				}
				else if (key === Keyboard.NUM3)
				{
					self.selectTool(SceneEditor.SCALE);
				}
				else if (key === Keyboard.NUM4)
				{
					self.selectTool(SceneEditor.ROTATE);
				}
				else if (key === Keyboard.F)
				{
					self.focusObject();
				}
				else if (key === Keyboard.C)
				{
					Editor.copyObject();
				}
				else if (key === Keyboard.V)
				{
					Editor.pasteObject();
				}
				else if (key === Keyboard.X)
				{
					Editor.cutObject();
				}
			}
		}
	});

	this.canvas.resetCanvas();
}
The scene editor is the core of the nunuStudio editor.

It is used to edit the scenes and apply changes to the objects using helper objects.

@class SceneEditor
@extends {TabComponent}