Browse Source

Particle editor/sample improvements.
Will likely be moving this out of samples and into tools in the near future.

sgrenier 12 năm trước cách đây
mục cha
commit
35f306e0c4

+ 1 - 1
samples/particles/game.config

@@ -2,7 +2,7 @@ window
 {
     title = Particles
     width = 1280
-    height = 720
+    height = 800
     fullscreen = false
     resizable = true
 }

+ 160 - 135
samples/particles/res/editor.form

@@ -3,6 +3,7 @@ form particleEditor
     theme = res/editor.theme
     autoWidth = true
     autoHeight = true
+    consumeInputEvents = false
 
 	// Vsync checkbox
 	checkBox vsync
@@ -15,108 +16,46 @@ form particleEditor
 		checked = true
 		consumeInputEvents = true
 	}
+ 
+    container leftSide
+    {
+        style = noBorder
+        layout = LAYOUT_VERTICAL
+        width = 160
+        autoHeight = true
+        consumeInputEvents = true
 
-	// Saving/loading
-	container buttons
-	{
-		style = noBorder
-		size = 200, 50
-        position = 163, 100
-		consumeInputEvents = false
-        alignment = ALIGN_TOP_HCENTER
-
-		button save
-		{
+        container saveLoad
+        {
+            style = basic
+            size = 160, 100
             consumeInputEvents = true
-			style = buttonStyle
-            width = 100
-			height = 50
-			text = Save
-		}
-
-        button load : save
-		{
-            x = 100
-			text = Load
-		}
-	}
+            layout = LAYOUT_VERTICAL
 
-	container leftSide
-	{
-        style = noBorder
-		width = 160
-		autoHeight = true
-		layout = LAYOUT_VERTICAL
+	        button save
+	        {
+                consumeInputEvents = true
+		        style = buttonStyle
+                width = 140
+		        height = 40
+                x = 100
+		        text = Save
+	        }
 
-		button reset
-		{
-            consumeInputEvents = true
-			style = buttonStyle
-            width = 160
-			height = 50
-			text = Reset
-		}
+            button load : save
+	        {
+                x = 200
+		        text = Load
+	        }
+        }
 
-		// Emission settings
-		container emission
-		{
-			style = basic
-			layout = LAYOUT_VERTICAL
-			size = 160, 210
-			consumeInputEvents = true
-
-			// Burst emission
-			button emit
-			{
-				style = buttonStyle
-				position = 0, 50
-				size = 140, 50
-				text = Emit
-			}
-
-			// Emission rate
-			slider emissionRate
-			{
-				style = noBorder
-				size = 140, 50
-				orientation = HORIZONTAL
-				min = 1
-				max = 500
-				value = 100
-				step = 0
-				text = Emission Rate
-				textAlignment = ALIGN_TOP_HCENTER
-				valueTextVisible = true
-				valueTextAlignment = ALIGN_BOTTOM_HCENTER
-				valueTextPrecision = 2            
-			}
-
-			slider burstSize : emissionRate
-			{
-				text = Burst Size
-				value = 20
-				max = 50
-				step = 1
-			}
-
-			// Start / Stop Emitter
-			checkBox started
-			{
-				style = iconNoBorder
-				size = 140, 40
-				imageSize = 35, 35
-				text = Running
-				checked = true
-			}
-		}
-        
         // Image settings
-		container image
-		{
-			style = basic
-			size = 160, 200
-			consumeInputEvents = true
-			layout = LAYOUT_VERTICAL
+        container image
+        {
+	        style = basic
+	        size = 160, 200
+	        consumeInputEvents = true
+	        layout = LAYOUT_VERTICAL
 
             label
             {
@@ -125,19 +64,19 @@ form particleEditor
                 text = Image
             }
 
-			image sprite
-			{
-				style = image
-				path = res/fire.png
-				size = 140, 140
-			}
+	        image sprite
+	        {
+		        style = image
+		        path = res/fire.png
+		        size = 140, 140
+	        }
 
             container imageSettings
             {
                 style = noBorder
-                size = 160, 220
+                size = 160, 230
                 consumeInputEvents = true
-			    layout = LAYOUT_VERTICAL
+		        layout = LAYOUT_VERTICAL
 
                 radioButton additive
                 {
@@ -238,36 +177,76 @@ form particleEditor
 
                 button updateFrames
                 {
-				    style = buttonStyle
-				    width = 140
-				    height = 30
-                    fontSize = 16
-				    text = Update
+			        style = buttonStyle
+			        width = 140
+			        height = 40
+			        text = Update
                 }
             }
-		}
+        }
 
-		// Camera Zoom
-		container zoom
-		{
-			style = noBorder
-			size = 160, 50
-			consumeInputEvents = true
-
-			button zoomIn
-			{
-				style = buttonStyle
-				size = 80, 50
-				text = Zoom  In
-			}
-
-			button zoomOut : zoomIn
-			{
-				position = 80, 0
-				text = Zoom Out
-			}
-		}
-	}
+        // Emission settings
+        container emission
+        {
+	        style = basic
+	        layout = LAYOUT_VERTICAL
+	        size = 160, 200
+	        consumeInputEvents = true
+
+	        // Burst emission
+	        button emit
+	        {
+		        style = buttonStyle
+		        position = 0, 50
+		        size = 140, 40
+		        text = Emit
+	        }
+
+	        // Emission rate
+	        slider emissionRate
+	        {
+		        style = noBorder
+		        size = 140, 50
+		        orientation = HORIZONTAL
+		        min = 1
+		        max = 500
+		        value = 100
+		        step = 1
+		        text = Emission Rate
+		        textAlignment = ALIGN_TOP_HCENTER
+		        valueTextVisible = true
+		        valueTextAlignment = ALIGN_BOTTOM_HCENTER
+		        valueTextPrecision = 2            
+	        }
+
+	        slider burstSize : emissionRate
+	        {
+		        text = Burst Size
+		        value = 20
+		        max = 50
+		        step = 1
+	        }
+
+	        // Start / Stop Emitter
+	        checkBox started
+	        {
+		        style = iconNoBorder
+		        size = 140, 40
+		        imageSize = 35, 35
+		        text = Running
+		        checked = true
+	        }
+        }
+
+        button reset
+	    {
+            consumeInputEvents = true
+		    style = buttonStyle
+            width = 160
+		    height = 40
+		    text = Reset
+	    }
+    }
 
     container particleProperties
     {
@@ -285,7 +264,7 @@ form particleEditor
         {
             style = title
             size = 150, 30
-            text = Size
+            text = Particle Size
         }
 
         slider startMin
@@ -294,9 +273,9 @@ form particleEditor
             size = 150, 50
             orientation = HORIZONTAL
             min = 0
-            max = 5
-            value = 1.5
-            step = 0
+            max = 10
+            value = 1
+            step = 0.1
             text = Min Begin
             textAlignment = ALIGN_TOP_HCENTER
             valueTextVisible = true
@@ -403,6 +382,30 @@ form particleEditor
         {
         }
 
+        // Position
+        label titlePosition : titleSize
+        {
+            text = Position
+        }
+
+        slider posX : startRed
+        {
+            min = -2
+            max = 2
+            step = 0.1
+            text = X
+        }
+
+        slider posY : posX
+        {
+            text = Y
+        }
+
+        slider posZ : posX
+        {
+            text = Z
+        }
+
         // Position variance
         label titlePositionVar : titleSize
         {
@@ -583,4 +586,26 @@ form particleEditor
             text = Max
         }
     }
+
+	// Camera Zoom
+	container zoom
+	{
+		style = noBorder
+		size = 160, 50
+		consumeInputEvents = true
+        alignment = ALIGN_BOTTOM_HCENTER
+
+		button zoomIn
+		{
+			style = buttonStyle
+			size = 80, 50
+			text = Zoom  In
+		}
+
+		button zoomOut : zoomIn
+		{
+			position = 80, 0
+			text = Zoom Out
+		}
+	}
 }

+ 49 - 44
samples/particles/res/explosion.particle

@@ -1,44 +1,49 @@
-particle explosion
-{
-    // Sprite properties.
-    sprite
-    {
-        path = explosion.png
-        width = 64
-        height = 64
-        frameCount = 16
-        frameDuration = 25
-        frameRandomOffset = 4
-        looped = false
-        animated = true
-        blending = ADDITIVE
-    }
-    
-    // Emitter
-    particleCountMax = 5000
-    emissionRate = 50
-    orbitPosition = false
-    orbitVelocity = false
-    orbitAcceleration = false
-    ellipsoid = false
-
-    // Scalar properties.
-    sizeStartMin = 5
-    sizeStartMax = 5
-    sizeEndMin = 5
-    sizeEndMax = 5
-    energyMin = 1000
-    energyMax = 1200
-    rotationPerParticleSpeedMin = -0.5
-    rotationPerParticleSpeedMax = 0.5
-
-    // Vector properties.
-    colorStart = 1, 1, 1, 0.5
-    colorEnd = 1, 1, 1, 0
-    position = 0, 0, 0
-    positionVar = 2, 2, 2
-    velocity = 0, 0, 0.0
-    velocityVar = 6.0, 6.0, 6.0
-    acceleration = 0.0, 0.0, 0.0
-    accelerationVar = 0.0, 0.0, 0.0
-}
+particle explosion
+{
+    sprite
+    {
+        path = explosion.png
+        width = 64
+        height = 64
+        blending = ADDITIVE
+        animated = true
+        looped = false
+        frameCount = 16
+        frameRandomOffset = 4
+        frameDuration = 25
+    }
+
+    particleCountMax = 5000
+    emissionRate = 50
+    ellipsoid = false
+    orbitPosition = false
+    orbitVelocity = false
+    orbitAcceleration = false
+    sizeStartMin = 5
+    sizeStartMax = 5
+    sizeEndMin = 5
+    sizeEndMax = 5
+    energyMin = 1000
+    energyMax = 1200
+    colorStart = 1, 1, 1, 0.5
+    colorStartVar = 0, 0, 0, 0
+    colorEnd = 1, 1, 1, 0
+    colorEndVar = 0, 0, 0, 0
+    position = 0, 0, 0
+    positionVar = 2, 2, 2
+    velocity = 0, 0, 0
+    velocityVar = 6, 6, 6
+    acceleration = 0, 0, 0
+    accelerationVar = 0, 0, 0
+    rotationPerParticleSpeedMin = -0.5
+    rotationPerParticleSpeedMax = 0.5
+
+    editor
+    {
+        cameraTranslation = 0, 0, 0
+        cameraZoom = 0, 0, 19
+        cameraRotation = 0, 0, 0, 0
+        sizeMax = 5
+        energyMax = 5
+    }
+}

+ 49 - 39
samples/particles/res/fire.particle

@@ -1,39 +1,49 @@
-particle fire
-{
-    sprite
-    {
-		path = fire.png
-        width = 256
-        height = 256
-	    blending = ADDITIVE
-        animated = false
-		looped = false
-        frameCount = 3
-	    frameRandomOffset = 3
-        frameDuration = 0
-    }
-
-    particleCountMax = 5000
-    emissionRate = 300
-    ellipsoid = true
-    orbitPosition = true
-    orbitVelocity = true
-    orbitAcceleration = false
-    sizeStartMin = 1.5
-    sizeStartMax = 2
-    sizeEndMin = 0.5
-    sizeEndMax = 1.0
-    energyMin = 750
-    energyMax = 1000
-    colorStart = 1, 0.2, 0, 1
-    colorStartVar = 0.25, 0, 0, 0
-    colorEnd = 0, 0, 0, 0
-    position = 0, 0, 0
-    positionVar = 1, 1, 1
-    velocity = 0.0, 10, 0.0
-    velocityVar = 4, 6, 4
-    acceleration = 0.0, 2, 0.0
-    accelerationVar = 3, 0.5, 3
-    rotationPerParticleSpeedMin = -1.5
-    rotationPerParticleSpeedMax = 1.5
-}
+particle fire
+{
+    sprite
+    {
+        path = fire.png
+        width = 256
+        height = 256
+        blending = ADDITIVE
+        animated = false
+        looped = false
+        frameCount = 3
+        frameRandomOffset = 3
+        frameDuration = 0
+    }
+
+    particleCountMax = 5000
+    emissionRate = 300
+    ellipsoid = true
+    orbitPosition = true
+    orbitVelocity = true
+    orbitAcceleration = false
+    sizeStartMin = 1.5
+    sizeStartMax = 2
+    sizeEndMin = 0.5
+    sizeEndMax = 1
+    energyMin = 750
+    energyMax = 1000
+    colorStart = 1, 0.2, 0, 1
+    colorStartVar = 0.25, 0, 0, 0
+    colorEnd = 0, 0, 0, 0
+    colorEndVar = 0, 0, 0, 0
+    position = 0, 0, 0
+    positionVar = 1, 1, 1
+    velocity = 0, 10, 0
+    velocityVar = 4, 6, 4
+    acceleration = 0, 2, 0
+    accelerationVar = 3, 0.5, 3
+    rotationPerParticleSpeedMin = -1.5
+    rotationPerParticleSpeedMax = 1.5
+
+    editor
+    {
+        cameraTranslation = 0.22, 2.7, 0
+        cameraZoom = 0, 0, 13.3
+        cameraRotation = 0, 0, 0, 0
+        sizeMax = 5
+        energyMax = 5
+    }
+}

+ 23 - 0
samples/particles/res/grid.material

@@ -0,0 +1,23 @@
+material grid
+{
+    technique
+    {
+        pass 0
+        {
+            // shaders
+            vertexShader = res/shaders/colored-unlit.vert
+            fragmentShader = res/shaders/colored-unlit.frag
+			defines = VERTEX_COLOR
+
+             // uniforms
+            u_worldViewProjectionMatrix = WORLD_VIEW_PROJECTION_MATRIX
+            
+            // render state
+            renderState
+            {
+                cullFace = false
+                depthTest = true
+            }
+        }
+    }
+}

+ 49 - 37
samples/particles/res/smoke.particle

@@ -1,37 +1,49 @@
-particle chimney-smoke
-{
-    sprite 
-    {
-		path = smoke.png
-        width = 64
-        height = 64
-        blending = ADDITIVE
-		animated = false
-        looped = false
-		frameCount = 1
-	    frameRandomOffset = 0
-        frameDuration = 0
-    }
-
-    particleCountMax = 5000
-    emissionRate = 20
-    ellipsoid = false
-    sizeStartMin = 3.5
-    sizeStartMax = 3.5
-    sizeEndMin = 15
-    sizeEndMax = 15
-    energyMin = 4000
-    energyMax = 5000
-    colorStart = 0.5, 0.5, 0.5, 1
-    colorStartVar = 0, 0, 0, 0
-    colorEnd = 1, 0, 0, 0
-    colorEndVar = 0, 0.0, 0.0, 0.0
-    position = 0, 0, 0
-    positionVar = 0, 0, 0
-    velocity = 2.5, 5, 0
-    velocityVar = 0, 0, 0
-    acceleration = -10, 5, 0
-    accelerationVar = 2.5, 5, 0
-    rotationPerParticleSpeedMin = -1.5
-    rotationPerParticleSpeedMax = 1.5
-}
+particle smoke
+{
+    sprite
+    {
+        path = smoke.png
+        width = 64
+        height = 64
+        blending = ADDITIVE
+        animated = false
+        looped = false
+        frameCount = 1
+        frameRandomOffset = 0
+        frameDuration = 0
+    }
+
+    particleCountMax = 5000
+    emissionRate = 20
+    ellipsoid = false
+    orbitPosition = false
+    orbitVelocity = false
+    orbitAcceleration = false
+    sizeStartMin = 3.5
+    sizeStartMax = 3.5
+    sizeEndMin = 15
+    sizeEndMax = 15
+    energyMin = 4000
+    energyMax = 5000
+    colorStart = 0.5, 0.5, 0.5, 1
+    colorStartVar = 0, 0, 0, 0
+    colorEnd = 1, 0, 0, 0
+    colorEndVar = 0, 0, 0, 0
+    position = 0, 0, 0
+    positionVar = 0, 0, 0
+    velocity = 2.5, 5, 0
+    velocityVar = 0, 0, 0
+    acceleration = -10, 5, 0
+    accelerationVar = 2.5, 5, 0
+    rotationPerParticleSpeedMin = -1.5
+    rotationPerParticleSpeedMax = 1.5
+
+    editor
+    {
+        cameraTranslation = -0.0200001, 4.06, 0
+        cameraZoom = 0, 0, 16.8
+        cameraRotation = 0, 0, 0, 0
+        sizeMax = 5
+        energyMax = 5
+    }
+}

+ 202 - 40
samples/particles/src/ParticlesGame.cpp

@@ -1,25 +1,107 @@
 #include "ParticlesGame.h"
-#ifdef WIN32
-#include <Windows.h>
-#include <Commdlg.h>
-#endif
 
 // Declare our game instance.
 ParticlesGame game;
 
 #define DEFAULT_PARTICLE_EMITTER "res/fire.particle"
 
-const static float PARTICLE_SIZE_MAX = 30.0f; //5.0f, 30.0f, 30.0f;
-const static float EMIT_RATE_MAX = 500.0f; //500, 100, 100;;
 const float INPUT_SENSITIVITY = 0.05f;
-const float PANNING_SENSITIVITY = 0.05f;
+const float PANNING_SENSITIVITY = 0.01f;
 const float ROTATE_SENSITIVITY = 0.25f;
 const Vector4 BACKGROUND_COLOR = Vector4::zero();
+const float INITIAL_ZOOM = 6.0f;
 
 ParticlesGame::ParticlesGame() : _scene(NULL), _panning(false), _rotating(false), _zooming(false)
 {
 }
 
+void addGrid(unsigned int lineCount)
+{
+    float z = -1;
+
+    // There needs to be an odd number of lines
+    lineCount |= 1;
+    const unsigned int pointCount = lineCount * 4;
+    const unsigned int verticesSize = pointCount * (3 + 3);  // (3 (position(xyz) + 3 color(rgb))
+
+    std::vector<float> vertices;
+    vertices.resize(verticesSize);
+
+    const float gridLength = (float)(lineCount / 2);
+    float value = -gridLength;
+    for (unsigned int i = 0; i < verticesSize; ++i)
+    {
+        // Default line color is dark grey
+        Vector4 color(0.3f, 0.3f, 0.3f, 1.0f);
+
+        // Every 10th line is brighter grey
+        if (((int)value) % 10 == 0)
+        {
+            color.set(0.45f, 0.45f, 0.45f, 1.0f);
+        }
+
+        // The Z axis is blue
+        if (value == 0.0f)
+        {
+            color.set(0.15f, 0.15f, 0.7f, 1.0f);
+        }
+
+        // Build the lines
+        vertices[i] = value;
+        vertices[++i] = -gridLength;
+        vertices[++i] = z;
+        vertices[++i] = color.x;
+        vertices[++i] = color.y;
+        vertices[++i] = color.z;
+
+        vertices[++i] = value;
+        vertices[++i] = gridLength;
+        vertices[++i] = z;
+        vertices[++i] = color.x;
+        vertices[++i] = color.y;
+        vertices[++i] = color.z;
+
+        // The X axis is red
+        if (value == 0.0f)
+        {
+            color.set(0.7f, 0.15f, 0.15f, 1.0f);
+        }
+        vertices[++i] = -gridLength;
+        vertices[++i] = value;
+        vertices[++i] = z;
+        vertices[++i] = color.x;
+        vertices[++i] = color.y;
+        vertices[++i] = color.z;
+
+        vertices[++i] = gridLength;
+        vertices[++i] = value;
+        vertices[++i] = z;
+        vertices[++i] = color.x;
+        vertices[++i] = color.y;
+        vertices[++i] = color.z;
+
+        value += 1.0f;
+    }
+    VertexFormat::Element elements[] =
+    {
+        VertexFormat::Element(VertexFormat::POSITION, 3),
+        VertexFormat::Element(VertexFormat::COLOR, 3)
+    };
+    Mesh* mesh = Mesh::createMesh(VertexFormat(elements, 2), pointCount, false);
+    if (mesh == NULL)
+        return;
+
+    mesh->setPrimitiveType(Mesh::LINES);
+    mesh->setVertexData(&vertices[0], 0, pointCount);
+
+    Model* model = Model::create(mesh);
+    model->setMaterial("res/grid.material");
+    SAFE_RELEASE(mesh);
+
+    Scene::getScene()->addNode("grid")->setModel(model);
+    model->release();
+}
+
 void ParticlesGame::initialize()
 {
     // Display the gameplay splash screen
@@ -42,16 +124,15 @@ void ParticlesGame::initialize()
     _cameraParent->addChild(cameraNode);
     Camera* camera = Camera::createPerspective(45.0f, (float)getWidth() / (float)getHeight(), 0.25f, 1000.0f);
     cameraNode->setCamera(camera);
-    cameraNode->setTranslation(0.0f, 0.0f, 40.0f);
+    cameraNode->setTranslation(0.0f, 0.0f, INITIAL_ZOOM);
     _scene->setActiveCamera(camera);
     SAFE_RELEASE(camera);
 
+    addGrid(61);
+
     // Create a font for drawing the framerate.
     _font = Font::create("res/arial.gpb");
 
-    // Load preset emitters.
-    loadEmitters();
-
     // Load the form for editing ParticleEmitters.
     _form = Form::create("res/editor.form");
     _form->setConsumeInputEvents(false);
@@ -135,6 +216,9 @@ void ParticlesGame::initialize()
     _save->addListener(this, Listener::RELEASE);
     _load->addListener(this, Listener::RELEASE);
     _burstSize->addListener(this, Listener::VALUE_CHANGED);
+    _form->getControl("posX")->addListener(this, Listener::VALUE_CHANGED);
+    _form->getControl("posY")->addListener(this, Listener::VALUE_CHANGED);
+    _form->getControl("posZ")->addListener(this, Listener::VALUE_CHANGED);
     _posVarX->addListener(this, Listener::VALUE_CHANGED);
     _posVarY->addListener(this, Listener::VALUE_CHANGED);
     _posVarZ->addListener(this, Listener::VALUE_CHANGED);
@@ -169,18 +253,25 @@ void ParticlesGame::initialize()
     _form->getControl("updateFrames")->addListener(this, Listener::CLICK);
     
     // Hide save/load buttons for non-windows platforms until we implement picking dialogs for others
-#ifndef WIN32
-    _form->getControl("save")->setVisible(false);
-    _form->getControl("load")->setVisible(false);
+#ifdef WIN32
+    _form->getControl("zoomIn")->setVisible(false);
+    _form->getControl("zoomOut")->setVisible(false);
+#else
+    _form->getControl("saveLoad")->setVisible(false);
     _form->getControl("image")->setVisible(false);
 #endif
 
-    // Apply default emitter values to the UI.
-    emitterChanged();
+    // Load preset emitters.
+    loadEmitters();
 
     updateImageControl();
 }
 
+#ifdef WIN32
+#include <Windows.h>
+#include <Commdlg.h>
+#endif
+
 std::string ParticlesGame::openFile(const char* title, const char* filterDescription, const char* filterExtension)
 {
 #ifdef WIN32
@@ -257,6 +348,13 @@ std::string toString(const Vector3& v)
     return s.str();
 }
 
+std::string toString(const Quaternion& q)
+{
+    std::ostringstream s;
+    s << q.x << ", " << q.y << ", " << q.z << ", " << q.w;
+    return s.str();
+}
+
 std::string toString(bool b)
 {
     return b ? "true" : "false";
@@ -324,6 +422,10 @@ void ParticlesGame::saveFile()
     std::string textureDir = FileSystem::getDirectoryName(texturePath.c_str());
     texturePath = texturePath.substr(textureDir.length());
 
+    // Get camera rotation as axis-angle
+    Vector3 cameraAxis;
+    float cameraAngle = MATH_RAD_TO_DEG(_cameraParent->getRotation().toAxisAngle(&cameraAxis));
+
     // Write out a properties file
     std::ostringstream s;
     s << 
@@ -366,6 +468,15 @@ void ParticlesGame::saveFile()
         "    accelerationVar = " << toString(e->getAccelerationVariance()) << "\n" <<
         "    rotationPerParticleSpeedMin = " << e->getRotationPerParticleSpeedMin() << "\n" <<
         "    rotationPerParticleSpeedMax = " << e->getRotationPerParticleSpeedMax() << "\n" <<
+        "\n" <<
+        "    editor\n" <<
+        "    {\n" <<
+        "        cameraTranslation = " << toString(_cameraParent->getTranslation()) << "\n" <<
+        "        cameraZoom = " << toString(_scene->getActiveCamera()->getNode()->getTranslation()) << "\n" <<
+        "        cameraRotation = " << toString(cameraAxis) << ", " << cameraAngle << "\n" <<
+        "        sizeMax = " << _startMax->getMax() << "\n" <<
+        "        energyMax = " << _energyMax->getMax() << "\n" <<
+        "    }\n"
         "}\n";
 
     std::string text = s.str();
@@ -377,6 +488,8 @@ void ParticlesGame::saveFile()
 
 void ParticlesGame::controlEvent(Control* control, EventType evt)
 {
+    std::string id = control->getId();
+
     // Handle UI events.
     ParticleEmitter* emitter = _particleEmitterNode->getParticleEmitter();
     switch(evt)
@@ -458,6 +571,24 @@ void ParticlesGame::controlEvent(Control* control, EventType evt)
         {
             emitter->setEmissionRate(_emissionRate->getValue());
         }
+        else if (id == "posX")
+        {
+            Vector3 pos(emitter->getPosition());
+            pos.x = ((Slider*)control)->getValue();
+            emitter->setPosition(pos, emitter->getPositionVariance());
+        }
+        else if (id == "posY")
+        {
+            Vector3 pos(emitter->getPosition());
+            pos.y = ((Slider*)control)->getValue();
+            emitter->setPosition(pos, emitter->getPositionVariance());
+        }
+        else if (id == "posZ")
+        {
+            Vector3 pos(emitter->getPosition());
+            pos.z = ((Slider*)control)->getValue();
+            emitter->setPosition(pos, emitter->getPositionVariance());
+        }
         else if (control == _posVarX)
         {
             Vector3 posVar = emitter->getPositionVariance();
@@ -621,22 +752,22 @@ void ParticlesGame::controlEvent(Control* control, EventType evt)
         {
             Game::getInstance()->setVsync(_vsync->isChecked());
         }
-        else if (strcmp(control->getId(), "additive") == 0)
+        else if (id == "additive")
         {
             if (((RadioButton*)control)->isSelected())
                 emitter->setTextureBlending(ParticleEmitter::BLEND_ADDITIVE);
         }
-        else if (strcmp(control->getId(), "transparent") == 0)
+        else if (id == "transparent")
         {
             if (((RadioButton*)control)->isSelected())
                 emitter->setTextureBlending(ParticleEmitter::BLEND_TRANSPARENT);
         }
-        else if (strcmp(control->getId(), "multiply") == 0)
+        else if (id == "multiply")
         {
             if (((RadioButton*)control)->isSelected())
                 emitter->setTextureBlending(ParticleEmitter::BLEND_MULTIPLIED);
         }
-        else if (strcmp(control->getId(), "opaque") == 0)
+        else if (id == "opaque")
         {
             if (((RadioButton*)control)->isSelected())
                 emitter->setTextureBlending(ParticleEmitter::BLEND_OPAQUE);
@@ -645,9 +776,8 @@ void ParticlesGame::controlEvent(Control* control, EventType evt)
     case Listener::CLICK:
         if (control == _reset)
         {
-            // Re-load the current emitter.
-            _particleEmitterNode->setParticleEmitter(NULL);
-            emitter = _particleEmitter = ParticleEmitter::create(_url.c_str());
+            // Re-load the current emitter and reset the view
+            _particleEmitter = ParticleEmitter::create(_url.c_str());
             emitterChanged();
         }
         else if (control == _emit)
@@ -656,11 +786,11 @@ void ParticlesGame::controlEvent(Control* control, EventType evt)
             unsigned int burstSize = (unsigned int)_burstSize->getValue();
             emitter->emitOnce(burstSize);
         }
-        else if (strcmp(control->getId(), "sprite") == 0)
+        else if (id == "sprite")
         {
             updateTexture();
         }
-        else if (strcmp(control->getId(), "updateFrames") == 0)
+        else if (id == "updateFrames")
         {
             updateFrames();
         }
@@ -696,8 +826,8 @@ void ParticlesGame::controlEvent(Control* control, EventType evt)
             std::string filename = openFile("Select Particle File", "Particle Files", "particle");
             if (filename.length() > 0)
             {
-                _particleEmitter = ParticleEmitter::create(filename.c_str());
                 _url = filename;
+                _particleEmitter = ParticleEmitter::create(_url.c_str());
                 emitterChanged();
             }
             Game::getInstance()->resume();
@@ -791,11 +921,10 @@ void ParticlesGame::render(float elapsedTime)
 
 bool ParticlesGame::drawScene(Node* node, void* cookie)
 {
-    ParticleEmitter* emitter = node->getParticleEmitter();
-    if (emitter)
-    {
-        emitter->draw();
-    }
+    if (node->getModel())
+        node->getModel()->draw();
+    if (node->getParticleEmitter())
+        node->getParticleEmitter()->draw();
     return true;
 }
 
@@ -947,6 +1076,8 @@ void ParticlesGame::loadEmitters()
 
     _particleEmitterNode = _scene->addNode("Particle Emitter");
     _particleEmitterNode->setTranslation(0.0f, 0.0f, 0.0f);
+
+    emitterChanged();
 }
 
 void ParticlesGame::emitterChanged()
@@ -973,21 +1104,14 @@ void ParticlesGame::emitterChanged()
     _endBlue->setValue(emitter->getColorEnd().z);
     _endAlpha->setValue(emitter->getColorEnd().w);
 
-    _startMin->setMax(PARTICLE_SIZE_MAX);
     _startMin->setValue(emitter->getSizeStartMin());
-
-    _startMax->setMax(PARTICLE_SIZE_MAX);
     _startMax->setValue(emitter->getSizeStartMax());
-    
-    _endMin->setMax(PARTICLE_SIZE_MAX);
     _endMin->setValue(emitter->getSizeEndMin());
-    _endMax->setMax(PARTICLE_SIZE_MAX);
     _endMax->setValue(emitter->getSizeEndMax());
 
     _energyMin->setValue(emitter->getEnergyMin());
     _energyMax->setValue(emitter->getEnergyMax());
 
-    _emissionRate->setMax(EMIT_RATE_MAX);
     _emissionRate->setValue(emitter->getEmissionRate());
 
     char txt[25];
@@ -1036,9 +1160,46 @@ void ParticlesGame::emitterChanged()
     _rotationSpeedMin->setValue(emitter->getRotationSpeedMin());
     _rotationSpeedMax->setValue(emitter->getRotationSpeedMax());
 
-    emitter->start();
-
+    // Update our image control
     updateImageControl();
+
+    // Parse editor section of particle properties
+    Properties* p = Properties::create(_url.c_str());
+    Properties* ns = p->getNamespace("editor", true);
+    if (ns)
+    {
+        Vector3 v3;
+        if (ns->getVector3("cameraTranslation", &v3))
+        {
+            _cameraParent->setTranslation(v3);
+        }
+        if (ns->getVector3("cameraZoom", &v3))
+        {
+            _scene->getActiveCamera()->getNode()->setTranslation(v3);
+        }
+        Quaternion q;
+        if (ns->getQuaternionFromAxisAngle("cameraRotation", &q))
+        {
+            _cameraParent->setRotation(q);
+        }
+        float f;
+        if ((f = ns->getFloat("sizeMax")) != 0.0f)
+        {
+            _startMin->setMax(f);
+            _startMax->setMax(f);
+            _endMin->setMax(f);
+            _endMax->setMax(f);
+        }
+        if ((f = ns->getFloat("energyMax")) != 0.0f)
+        {
+            _energyMin->setMax(f);
+            _energyMax->setMax(f);
+        }
+    }
+    SAFE_DELETE(p);
+
+    // Start the emitter
+    emitter->start();
 }
 
 void ParticlesGame::drawSplash(void* param)
@@ -1064,6 +1225,7 @@ void ParticlesGame::resizeEvent(unsigned int width, unsigned int height)
 {
     setViewport(gameplay::Rectangle(width, height));
     _form->setSize(width, height);
+    _scene->getActiveCamera()->setAspectRatio((float)getWidth() / (float)getHeight());
 }
 
 void ParticlesGame::updateTexture()