Explorar el Código

Merge branch 'master' into dev-cubemap-reflection

Jhonny Göransson hace 9 meses
padre
commit
b8dfbc059c
Se han modificado 50 ficheros con 5312 adiciones y 27 borrados
  1. 14 0
      .github/ISSUE_TEMPLATE/1-request_an_example.md
  2. 20 0
      .github/ISSUE_TEMPLATE/2-feature_request.md
  3. 33 0
      .github/ISSUE_TEMPLATE/3-bug_report.md
  4. BIN
      assets/images/fireworks/fw_circle_01.png
  5. BIN
      assets/images/fireworks/fw_light_01.png
  6. BIN
      assets/images/fireworks/fw_star_01.png
  7. BIN
      assets/images/fireworks/fw_trace_01.png
  8. BIN
      assets/images/fireworks/fw_trace_02.png
  9. BIN
      assets/images/pattern_circle.png
  10. 28 0
      assets/models/kenney_prototype-kit/License.txt
  11. BIN
      assets/models/kenney_prototype-kit/Textures/colormap.png
  12. BIN
      assets/models/kenney_prototype-kit/crate-color.glb
  13. BIN
      assets/models/kenney_prototype-kit/crate.glb
  14. BIN
      assets/models/kenney_prototype-kit/target-a-round.glb
  15. BIN
      assets/models/kenney_prototype-kit/weapon-sword.glb
  16. 15 0
      assets/sprites.atlas
  17. 6 5
      examples/_main/examples.lua
  18. 24 0
      examples/_main/loader.go
  19. 99 0
      examples/material/screenspace/screenspace.collection
  20. 65 0
      examples/material/screenspace/screenspace.fp
  21. 49 0
      examples/material/screenspace/screenspace.material
  22. 24 0
      examples/material/screenspace/screenspace.md
  23. 22 0
      examples/material/screenspace/screenspace.script
  24. 47 0
      examples/material/screenspace/screenspace.vp
  25. 15 14
      examples/material/unlit/unlit.collection
  26. 4 3
      examples/material/unlit/unlit.md
  27. 0 4
      examples/material/unlit/unlit.script
  28. 3 0
      examples/movement/look_at/look_at.md
  29. 163 0
      examples/movement/look_rotation/look_rotation.collection
  30. 14 0
      examples/movement/look_rotation/look_rotation.md
  31. 64 0
      examples/movement/look_rotation/look_rotation.script
  32. 89 0
      examples/particles/fireworks/fireworks.collection
  33. 16 0
      examples/particles/fireworks/fireworks.md
  34. 94 0
      examples/particles/fireworks/fireworks.script
  35. 4 0
      examples/particles/fireworks/fw_splat_blue.go
  36. 1134 0
      examples/particles/fireworks/fw_splat_blue.particlefx
  37. 4 0
      examples/particles/fireworks/fw_splat_green.go
  38. 1150 0
      examples/particles/fireworks/fw_splat_green.particlefx
  39. 4 0
      examples/particles/fireworks/fw_splat_red.go
  40. 1150 0
      examples/particles/fireworks/fw_splat_red.particlefx
  41. 4 0
      examples/particles/fireworks/fw_trail_blue.go
  42. 273 0
      examples/particles/fireworks/fw_trail_blue.particlefx
  43. 4 0
      examples/particles/fireworks/fw_trail_green.go
  44. 273 0
      examples/particles/fireworks/fw_trail_green.particlefx
  45. 4 0
      examples/particles/fireworks/fw_trail_red.go
  46. 273 0
      examples/particles/fireworks/fw_trail_red.particlefx
  47. 53 0
      examples/render/orbit_camera/orbit_camera.collection
  48. 22 0
      examples/render/orbit_camera/orbit_camera.md
  49. 51 0
      examples/render/orbit_camera/orbit_camera.script
  50. 1 1
      game.project

+ 14 - 0
.github/ISSUE_TEMPLATE/1-request_an_example.md

@@ -0,0 +1,14 @@
+---
+name: Request an example
+about: Suggest an example for the collection of examples
+title: ''
+labels: example
+assignees: ''
+
+---
+
+**Describe the example you'd like**
+A clear and concise description of what you want to have. The goal is to illustrate one feature in one example, so try to be as specific as possible.
+
+**Additional context (optional)**
+Here you can specify references (screenshots, links) of what you would like to see in the example.

+ 20 - 0
.github/ISSUE_TEMPLATE/2-feature_request.md

@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: feature request
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context (optional)**
+Add any other context or screenshots about the feature request here.

+ 33 - 0
.github/ISSUE_TEMPLATE/3-bug_report.md

@@ -0,0 +1,33 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots (optional)**
+If applicable, add screenshots to help explain your problem.
+
+**Hardware (optional)**
+ - Device: [e.g. iPhone6]
+ - OS: [e.g. iOS]
+ - Browser [e.g. chrome, safari]
+ - Version [e.g. 22]
+
+**Additional context (optional)**
+Add any other context about the problem here.

BIN
assets/images/fireworks/fw_circle_01.png


BIN
assets/images/fireworks/fw_light_01.png


BIN
assets/images/fireworks/fw_star_01.png


BIN
assets/images/fireworks/fw_trace_01.png


BIN
assets/images/fireworks/fw_trace_02.png


BIN
assets/images/pattern_circle.png


+ 28 - 0
assets/models/kenney_prototype-kit/License.txt

@@ -0,0 +1,28 @@
+	
+
+	Prototype Kit (1.0)
+
+	Created/distributed by Kenney (www.kenney.nl)
+	Creation date: 28-08-2024 09:59
+	
+			------------------------------
+
+	License: (Creative Commons Zero, CC0)
+	http://creativecommons.org/publicdomain/zero/1.0/
+
+	You can use this content for personal, educational, and commercial purposes.
+
+	Support by crediting 'Kenney' or 'www.kenney.nl' (this is not a requirement)
+
+			------------------------------
+
+	• Website : www.kenney.nl
+	• Donate  : www.kenney.nl/donate
+
+	• Patreon : patreon.com/kenney
+	
+	Follow on social media for updates:
+
+	• Twitter:   twitter.com/KenneyNL
+	• Instagram: instagram.com/kenney_nl
+	• Mastodon:  mastodon.gamedev.place/@kenney

BIN
assets/models/kenney_prototype-kit/Textures/colormap.png


BIN
assets/models/kenney_prototype-kit/crate-color.glb


BIN
assets/models/kenney_prototype-kit/crate.glb


BIN
assets/models/kenney_prototype-kit/target-a-round.glb


BIN
assets/models/kenney_prototype-kit/weapon-sword.glb


+ 15 - 0
assets/sprites.atlas

@@ -73,6 +73,21 @@ images {
 images {
   image: "/assets/images/confetti/confetti_rectangle.png"
 }
+images {
+  image: "/assets/images/fireworks/fw_star_01.png"
+}
+images {
+  image: "/assets/images/fireworks/fw_circle_01.png"
+}
+images {
+  image: "/assets/images/fireworks/fw_light_01.png"
+}
+images {
+  image: "/assets/images/fireworks/fw_trace_01.png"
+}
+images {
+  image: "/assets/images/fireworks/fw_trace_02.png"
+}
 images {
   image: "/assets/images/bar_round_large_grey.png"
 }

+ 6 - 5
examples/_main/examples.lua

@@ -4,7 +4,7 @@ local examples = {}
 
 examples["basics"] = { "message_passing", "parent_child", "z_order" }
 examples["factory"] = { "basic", "bullets", "dynamic" }
-examples["movement"] = { "simple_move", "follow", "move_to", "move_forward", "movement_speed", "look_at" }
+examples["movement"] = { "simple_move", "follow", "move_to", "move_forward", "movement_speed", "look_at", { name = "look_rotation", nobg = true } }
 examples["physics"] = { "dynamic", "kinematic", "raycast", "trigger", "hinge_joint", "pendulum", "knockback"}
 examples["animation"] = { "euler_rotation", "spinner", "flipbook", "chained_tween", "basic_tween", "spine", "cursor", "easing" }
 examples["gui"] = {
@@ -16,10 +16,10 @@ examples["gui"] = {
 }
 examples["input"] = { "move", "text", "down_duration", "mouse_and_touch" }
 examples["model"] = { { name = "cubemap", nobg = true } }
-examples["material"] = { "vertexcolor", { name = "unlit", nobg = true }, "uvgradient", "noise" }
-examples["particles"] = { "confetti", "particlefx", "modifiers", "fire_and_smoke" }
+examples["material"] = { "vertexcolor", { name = "unlit", nobg = true }, "uvgradient", "noise", { name = "screenspace", nobg = true } }
+examples["particles"] = { "confetti", "particlefx", "modifiers", "fire_and_smoke", "fireworks" }
 examples["sound"] = { "music", "fade_in_out", "panning" }
-examples["render"] = { "camera", "screen_to_world" }
+examples["render"] = { "camera", { name = "orbit_camera", nobg = true }, "screen_to_world" }
 examples["debug"] = { "physics", "profile" }
 examples["collection"] = { "proxy", "splash", "timestep" }
 examples["sprite"] = { "size", "tint", "flip", "bunnymark" }
@@ -32,6 +32,7 @@ local categories = {}
 for category,_ in pairs(examples) do
 	categories[#categories + 1] = category
 end
+table.sort(categories)
 
 for category,examples_in_category in pairs(examples) do
 	for id,example in pairs(examples_in_category) do
@@ -62,4 +63,4 @@ end
 
 
 
-return M
+return M

+ 24 - 0
examples/_main/loader.go

@@ -442,6 +442,12 @@ embedded_components {
   data: "collection: \"/examples/particles/confetti/confetti.collection\"\n"
   ""
 }
+embedded_components {
+  id: "particles/fireworks"
+  type: "collectionproxy"
+  data: "collection: \"/examples/particles/fireworks/fireworks.collection\"\n"
+  ""
+}
 embedded_components {
   id: "gui/healthbar"
   type: "collectionproxy"
@@ -454,3 +460,21 @@ embedded_components {
   data: "collection: \"/examples/model/cubemap/cubemap.collection\"\n"
   ""
 }
+embedded_components {
+  id: "render/orbit_camera"
+  type: "collectionproxy"
+  data: "collection: \"/examples/render/orbit_camera/orbit_camera.collection\"\n"
+  ""
+}
+embedded_components {
+  id: "movement/look_rotation"
+  type: "collectionproxy"
+  data: "collection: \"/examples/movement/look_rotation/look_rotation.collection\"\n"
+  ""
+}
+embedded_components {
+  id: "material/screenspace"
+  type: "collectionproxy"
+  data: "collection: \"/examples/material/screenspace/screenspace.collection\"\n"
+  ""
+}

+ 99 - 0
examples/material/screenspace/screenspace.collection

@@ -0,0 +1,99 @@
+name: "screenspace"
+scale_along_z: 1
+embedded_instances {
+  id: "camera"
+  data: "components {\n"
+  "  id: \"main\"\n"
+  "  component: \"/examples/material/screenspace/screenspace.script\"\n"
+  "}\n"
+  "components {\n"
+  "  id: \"orbit_camera\"\n"
+  "  component: \"/examples/render/orbit_camera/orbit_camera.script\"\n"
+  "  properties {\n"
+  "    id: \"offset\"\n"
+  "    value: \"0.0, 0.25, 0.0\"\n"
+  "    type: PROPERTY_TYPE_VECTOR3\n"
+  "  }\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"camera\"\n"
+  "  type: \"camera\"\n"
+  "  data: \"aspect_ratio: 1.0\\n"
+  "fov: 0.7854\\n"
+  "near_z: 0.1\\n"
+  "far_z: 1000.0\\n"
+  "auto_aspect_ratio: 1\\n"
+  "\"\n"
+  "}\n"
+  ""
+  rotation {
+    x: -0.25881904
+    w: 0.9659258
+  }
+}
+embedded_instances {
+  id: "root"
+  children: "crate"
+  children: "crate_selected"
+  data: ""
+}
+embedded_instances {
+  id: "crate"
+  data: "embedded_components {\n"
+  "  id: \"model\"\n"
+  "  type: \"model\"\n"
+  "  data: \"mesh: \\\"/assets/models/kenney_prototype-kit/crate-color.glb\\\"\\n"
+  "name: \\\"{{NAME}}\\\"\\n"
+  "materials {\\n"
+  "  name: \\\"colormap\\\"\\n"
+  "  material: \\\"/examples/material/screenspace/screenspace.material\\\"\\n"
+  "  textures {\\n"
+  "    sampler: \\\"texture0\\\"\\n"
+  "    texture: \\\"/assets/models/kenney_prototype-kit/Textures/colormap.png\\\"\\n"
+  "  }\\n"
+  "  textures {\\n"
+  "    sampler: \\\"texture_pattern\\\"\\n"
+  "    texture: \\\"/assets/images/pattern_circle.png\\\"\\n"
+  "  }\\n"
+  "}\\n"
+  "\"\n"
+  "}\n"
+  ""
+  position {
+    x: 0.5
+  }
+  rotation {
+    y: 0.6087614
+    w: 0.7933533
+  }
+}
+embedded_instances {
+  id: "crate_selected"
+  data: "embedded_components {\n"
+  "  id: \"model\"\n"
+  "  type: \"model\"\n"
+  "  data: \"mesh: \\\"/assets/models/kenney_prototype-kit/crate-color.glb\\\"\\n"
+  "name: \\\"{{NAME}}\\\"\\n"
+  "materials {\\n"
+  "  name: \\\"colormap\\\"\\n"
+  "  material: \\\"/examples/material/screenspace/screenspace.material\\\"\\n"
+  "  textures {\\n"
+  "    sampler: \\\"texture0\\\"\\n"
+  "    texture: \\\"/assets/models/kenney_prototype-kit/Textures/colormap.png\\\"\\n"
+  "  }\\n"
+  "  textures {\\n"
+  "    sampler: \\\"texture_pattern\\\"\\n"
+  "    texture: \\\"/assets/images/pattern_circle.png\\\"\\n"
+  "  }\\n"
+  "}\\n"
+  "\"\n"
+  "}\n"
+  ""
+  position {
+    x: -0.5
+  }
+  rotation {
+    y: 0.6087614
+    w: 0.7933533
+  }
+}

+ 65 - 0
examples/material/screenspace/screenspace.fp

@@ -0,0 +1,65 @@
+#version 140
+
+// Inputs should match the vertex shader's outputs.
+in vec2 var_texcoord0;
+in vec4 var_screen_texcoord;
+
+// The color texture.
+uniform lowp sampler2D texture0;
+// The pattern texture.
+uniform lowp sampler2D texture_pattern;
+
+// The user defined uniforms.
+uniform user_fp
+{
+    // pattern_opts.x - alpha, default 1.0 (set 0.0 to disable the screen space effect).
+    // pattern_opts.y - scale, default 30.0.
+    // pattern_opts.z - offset by x, default 0.0.
+    // pattern_opts.w - rotation in radians.
+    vec4 pattern_opts;
+
+    // The screen size, used to calculate the aspect ratio.
+    vec4 screen_size;
+};
+
+// The final color of the fragment.
+out lowp vec4 final_color;
+
+// Rotate 2D vector "v" by the "a" angle in radians
+vec2 rotate(vec2 v, float a)
+{
+    float s = sin(a);
+    float c = cos(a);
+    return mat2(c, s, -s, c) * v;
+}
+
+void main()
+{
+    // Sample the color texture at the fragment's texture coordinates.
+    vec4 color = texture(texture0, var_texcoord0.xy);
+
+    // Counteract the perspective correction and scale the coords.
+    vec2 pattern_coord = (var_screen_texcoord.xy / var_screen_texcoord.w) * pattern_opts.y;
+    // + Correct the aspect ratio
+    float aspect = screen_size.x / screen_size.y;
+    pattern_coord.x *= aspect;
+    // + Offset the grid horizontally
+    pattern_coord.x += pattern_opts.z;
+    // + Rotate
+    pattern_coord = rotate(pattern_coord, pattern_opts.w);
+
+    // Output the sampled color
+    if (pattern_opts.x > 0.0)
+    {
+        // Sample the pattern at the screen space texture coordinates.
+        vec4 pattern_color = texture(texture_pattern, pattern_coord);
+
+        // Blend the colors: (sRGBA*1) + (dRGBA*(1-sA))
+        final_color = pattern_color * pattern_opts.x + color * (1.0 - (pattern_color.a * pattern_opts.x));
+    }
+    else
+    {
+        // No pattern, just output the color.
+        final_color = color;
+    }
+}

+ 49 - 0
examples/material/screenspace/screenspace.material

@@ -0,0 +1,49 @@
+name: "screenspace"
+tags: "model"
+vertex_program: "/examples/material/screenspace/screenspace.vp"
+fragment_program: "/examples/material/screenspace/screenspace.fp"
+vertex_space: VERTEX_SPACE_LOCAL
+vertex_constants {
+  name: "mtx_world"
+  type: CONSTANT_TYPE_WORLD
+}
+vertex_constants {
+  name: "mtx_view"
+  type: CONSTANT_TYPE_VIEW
+}
+vertex_constants {
+  name: "mtx_proj"
+  type: CONSTANT_TYPE_PROJECTION
+}
+fragment_constants {
+  name: "pattern_opts"
+  type: CONSTANT_TYPE_USER
+  value {
+    x: 0.5
+    y: 30.0
+  }
+}
+fragment_constants {
+  name: "screen_size"
+  type: CONSTANT_TYPE_USER
+  value {
+    x: 1920.0
+    y: 1080.0
+  }
+}
+samplers {
+  name: "texture0"
+  wrap_u: WRAP_MODE_CLAMP_TO_EDGE
+  wrap_v: WRAP_MODE_CLAMP_TO_EDGE
+  filter_min: FILTER_MODE_MIN_LINEAR
+  filter_mag: FILTER_MODE_MAG_LINEAR
+  max_anisotropy: 0.0
+}
+samplers {
+  name: "texture_pattern"
+  wrap_u: WRAP_MODE_REPEAT
+  wrap_v: WRAP_MODE_REPEAT
+  filter_min: FILTER_MODE_MIN_LINEAR
+  filter_mag: FILTER_MODE_MAG_LINEAR
+  max_anisotropy: 0.0
+}

+ 24 - 0
examples/material/screenspace/screenspace.md

@@ -0,0 +1,24 @@
+---
+name: Screenspace (3D)
+title: Screenspace
+brief: This example shows how to create a custom material with two textures that blend together to create a pattern effect using screen space coordinates.
+scripts: screenspace.script, screenspace.vp, screenspace.fp
+---
+
+In this example, we create a new material for 3D models in which we convert vertex coordinates to screenspace to get a special effect. It may be called "surface fill", "screenspace fill" and is used, most often in combination with outlines, to highlight objects in 3D games or indicate their status. 
+
+We added two game objects and two models to which we assigned our new `screenspace` material. The material is based on [`unlit`](/examples/material/unlit/), but in it:
+- vertex shader: we added a conversion of the clip space position to the screen position to pass that value to the fragment shader.
+- fragment shader: we added sampling the color based on screenspace coordinates and blending into the final output color.
+- material properties: we added a new sampler to set a second texture to be used as a pattern, and user-defined uniforms to control the fragment shader.
+
+The last important thing is to pass the screen size to the shader to adjust the aspect ratio:
+
+```lua
+local w, h = window.get_size()
+go.set("#model", "screen_size", vmath.vector4(w, h, 0, 0))
+```
+
+To activate a perspective camera and to have camera controls, we added the `orbit_camera.script` script from the [Orbit Camera (3D)](/examples/render/orbit_camera/orbit_camera/) example.
+
+The shaders are written in GLSL 1.40, which is available from Defold 1.9.2. The model used in this example is from Kenney's [Prototype Pack](https://kenney.nl/assets/prototype-kit), licensed under CC0.

+ 22 - 0
examples/material/screenspace/screenspace.script

@@ -0,0 +1,22 @@
+function init(self)
+	self.time = 0 -- for pattern animation
+
+	-- The model with the pattern - we enabled the effect, 0.5 is the intensity (alpha)
+	go.set("/crate_selected#model", "pattern_opts.x", 0.5)
+	-- + add 70 degrees to the rotation
+	go.set("/crate_selected#model", "pattern_opts.w", math.rad(70))
+
+	-- The normal model - the 0.0 value disables the effect
+	go.set("/crate#model", "pattern_opts.x", 0)
+end
+
+function update(self, dt)
+	-- Animate the pattern by changing the z value
+	self.time = self.time - dt
+	go.set("/crate_selected#model", "pattern_opts.z", self.time)
+
+	-- The shader uses the screen size to calculate the aspect ratio.
+	-- In a real game, you'd set this in the render script globally for all materials.
+	local w, h = window.get_size()
+	go.set("/crate_selected#model", "screen_size", vmath.vector4(w, h, 0, 0))
+end

+ 47 - 0
examples/material/screenspace/screenspace.vp

@@ -0,0 +1,47 @@
+#version 140
+
+// The model's vertex position and texture coordinates.
+in vec4 position;
+in vec2 texcoord0;
+
+// The projection, view and world matrices.
+uniform general_vp
+{
+    mat4 mtx_world;
+    mat4 mtx_view;
+    mat4 mtx_proj;
+};
+
+// The output of a vertex shader are passed to the fragment shader.
+// The texture coordinates of the vertex.
+out vec2 var_texcoord0;
+
+// The screen texture coordinates of the vertex.
+out vec4 var_screen_texcoord;
+
+// Converts the clip space position to the screen position.
+vec4 clip_to_screen(vec4 pos)
+{
+    // Position is [-w,w], convert to [-0.5w,0.5w]
+    vec4 o = pos * 0.5;
+
+    // Convert from [-0.5w + 0.5w,0.5w + 0.5w] to [0,w]
+    o.xy = vec2(o.x, o.y) + o.w;
+
+    // Keep "zw" as it is
+    o.zw = pos.zw;
+    return o;
+}
+
+void main()
+{
+    // Pass the texture coordinates to the fragment shader.
+    var_texcoord0 = texcoord0;
+
+    // Transform the vertex position to clip space.
+    vec4 vertex_pos = mtx_proj * mtx_view * mtx_world * vec4(position.xyz, 1.0);
+    gl_Position = vertex_pos;
+
+    // Convert the clip space position to the screen position and pass the value to the fragment shader.
+    var_screen_texcoord = clip_to_screen(vertex_pos);
+}

+ 15 - 14
examples/material/unlit/unlit.collection

@@ -21,7 +21,21 @@ embedded_instances {
 }
 embedded_instances {
   id: "camera"
-  data: "embedded_components {\n"
+  data: "components {\n"
+  "  id: \"orbit_camera\"\n"
+  "  component: \"/examples/render/orbit_camera/orbit_camera.script\"\n"
+  "  properties {\n"
+  "    id: \"zoom\"\n"
+  "    value: \"7.0\"\n"
+  "    type: PROPERTY_TYPE_NUMBER\n"
+  "  }\n"
+  "  properties {\n"
+  "    id: \"offset\"\n"
+  "    value: \"0.0, 0.5, 0.0\"\n"
+  "    type: PROPERTY_TYPE_VECTOR3\n"
+  "  }\n"
+  "}\n"
+  "embedded_components {\n"
   "  id: \"camera\"\n"
   "  type: \"camera\"\n"
   "  data: \"aspect_ratio: 1.0\\n"
@@ -32,11 +46,6 @@ embedded_instances {
   "\"\n"
   "}\n"
   ""
-  position {
-    x: 3.543836
-    y: 3.619002
-    z: 3.104797
-  }
   rotation {
     x: -0.26769933
     y: 0.3956773
@@ -44,11 +53,3 @@ embedded_instances {
     w: 0.8700313
   }
 }
-embedded_instances {
-  id: "main"
-  data: "components {\n"
-  "  id: \"unlit\"\n"
-  "  component: \"/examples/material/unlit/unlit.script\"\n"
-  "}\n"
-  ""
-}

+ 4 - 3
examples/material/unlit/unlit.md

@@ -1,13 +1,14 @@
 ---
+name: Unlit (3D)
 title: Unlit
 brief: This example demonstrates how to create and apply an custom non-lit material to a 3D model.
-scripts: unlit.script, unlit.vp, unlit.fp
+scripts: unlit.vp, unlit.fp
 ---
 
 In industry-established terms, a material that is not affected by lighting is called "unlit" or "non-lit". It is used to create retro-style graphics or for effects that should not depend on lighting (headlights, lamps).
 
-This example contains a game object with a model that has an `unlit` material applied to it. The material is assigned custom vertex and fragment shaders. To set up a perspective camera, a camera is added to the collection, which is enabled in the script via the `acquire_camera_focus` message when the collection loads.
+This example contains a game object with a model that has an `unlit` material applied to it. The material is assigned custom vertex and fragment shaders. The shader is very simple and just transfers the texture color to the model. This is an excellent starting point for creating new materials and for creating effects that do not depend on lighting. The shaders are written in GLSL 1.40, which is available from Defold 1.9.2.
 
-The unlit shader is very simple and just transfers the texture color to the model. This is an excellent starting point for creating new materials and for creating effects that do not depend on lighting. The shaders are written in GLSL 1.40, which is available from Defold 1.9.2.
+To activate a perspective camera and to have camera controls, we added the `orbit_camera.script` script from the [Orbit Camera (3D)](/examples/render/orbit_camera/orbit_camera/) example.
 
 The model used in this example is from Kenney's [Train Pack](https://kenney.nl/assets/train-kit), licensed under CC0.

+ 0 - 4
examples/material/unlit/unlit.script

@@ -1,4 +0,0 @@
-function init(self)
-	msg.post("/camera", "acquire_camera_focus")
-	msg.post("@render:", "use_camera_projection")
-end

+ 3 - 0
examples/movement/look_at/look_at.md

@@ -1,7 +1,10 @@
 ---
+name: Look at
 title: Look at
 brief: This example shows how to rotate a game object to look at the mouse cursor
 scripts: look_at.script
 ---
 
 This example shows how to rotate a game object to look at the mouse cursor. It reads the mouse position in `on_input` and uses the mathematical function `math.atan2(x, y)` to calculate the angle between the ray to the point to look at and the positive x-axis. This angle is used to set the rotation of the game object to always look at the mouse position. 
+
+The example is suitable for the movement in two dimensions, for platformers or top-down games. For 3D objects, check out the [next example](/examples/movement/look_rotation/).

+ 163 - 0
examples/movement/look_rotation/look_rotation.collection

@@ -0,0 +1,163 @@
+name: "look_rotation"
+scale_along_z: 1
+embedded_instances {
+  id: "main"
+  data: "components {\n"
+  "  id: \"look_rotation\"\n"
+  "  component: \"/examples/movement/look_rotation/look_rotation.script\"\n"
+  "}\n"
+  "components {\n"
+  "  id: \"orbit_camera\"\n"
+  "  component: \"/examples/render/orbit_camera/orbit_camera.script\"\n"
+  "  properties {\n"
+  "    id: \"zoom\"\n"
+  "    value: \"6.0\"\n"
+  "    type: PROPERTY_TYPE_NUMBER\n"
+  "  }\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"camera\"\n"
+  "  type: \"camera\"\n"
+  "  data: \"aspect_ratio: 1.0\\n"
+  "fov: 0.7854\\n"
+  "near_z: 0.1\\n"
+  "far_z: 1000.0\\n"
+  "auto_aspect_ratio: 1\\n"
+  "\"\n"
+  "}\n"
+  ""
+}
+embedded_instances {
+  id: "sword"
+  data: "embedded_components {\n"
+  "  id: \"model\"\n"
+  "  type: \"model\"\n"
+  "  data: \"mesh: \\\"/assets/models/kenney_prototype-kit/weapon-sword.glb\\\"\\n"
+  "name: \\\"{{NAME}}\\\"\\n"
+  "materials {\\n"
+  "  name: \\\"colormap\\\"\\n"
+  "  material: \\\"/examples/material/unlit/unlit.material\\\"\\n"
+  "  textures {\\n"
+  "    sampler: \\\"texture0\\\"\\n"
+  "    texture: \\\"/assets/models/kenney_prototype-kit/Textures/colormap.png\\\"\\n"
+  "  }\\n"
+  "}\\n"
+  "\"\n"
+  "  position {\n"
+  "    y: -0.05\n"
+  "  }\n"
+  "  rotation {\n"
+  "    y: -0.70710677\n"
+  "    w: 0.70710677\n"
+  "  }\n"
+  "}\n"
+  ""
+}
+embedded_instances {
+  id: "target2"
+  data: "embedded_components {\n"
+  "  id: \"model\"\n"
+  "  type: \"model\"\n"
+  "  data: \"mesh: \\\"/assets/models/kenney_prototype-kit/target-a-round.glb\\\"\\n"
+  "name: \\\"{{NAME}}\\\"\\n"
+  "materials {\\n"
+  "  name: \\\"colormap\\\"\\n"
+  "  material: \\\"/examples/material/unlit/unlit.material\\\"\\n"
+  "  textures {\\n"
+  "    sampler: \\\"texture0\\\"\\n"
+  "    texture: \\\"/assets/models/kenney_prototype-kit/Textures/colormap.png\\\"\\n"
+  "  }\\n"
+  "}\\n"
+  "\"\n"
+  "  position {\n"
+  "    y: -0.25\n"
+  "  }\n"
+  "}\n"
+  ""
+  position {
+    x: 1.0
+    y: 1.0
+    z: -2.0
+  }
+  rotation {
+    x: 0.10821981
+    y: 0.488148
+    z: 0.1874422
+    w: 0.84549713
+  }
+  scale3 {
+    x: 2.0
+  }
+}
+embedded_instances {
+  id: "target3"
+  data: "embedded_components {\n"
+  "  id: \"model\"\n"
+  "  type: \"model\"\n"
+  "  data: \"mesh: \\\"/assets/models/kenney_prototype-kit/target-a-round.glb\\\"\\n"
+  "name: \\\"{{NAME}}\\\"\\n"
+  "materials {\\n"
+  "  name: \\\"colormap\\\"\\n"
+  "  material: \\\"/examples/material/unlit/unlit.material\\\"\\n"
+  "  textures {\\n"
+  "    sampler: \\\"texture0\\\"\\n"
+  "    texture: \\\"/assets/models/kenney_prototype-kit/Textures/colormap.png\\\"\\n"
+  "  }\\n"
+  "}\\n"
+  "\"\n"
+  "  position {\n"
+  "    y: -0.25\n"
+  "  }\n"
+  "}\n"
+  ""
+  position {
+    x: -1.5
+    y: -0.5
+    z: -1.0
+  }
+  rotation {
+    x: -0.12094011
+    y: 0.9186313
+    z: -0.049097624
+    w: 0.37293348
+  }
+  scale3 {
+    x: 2.0
+  }
+}
+embedded_instances {
+  id: "target1"
+  data: "embedded_components {\n"
+  "  id: \"model\"\n"
+  "  type: \"model\"\n"
+  "  data: \"mesh: \\\"/assets/models/kenney_prototype-kit/target-a-round.glb\\\"\\n"
+  "name: \\\"{{NAME}}\\\"\\n"
+  "materials {\\n"
+  "  name: \\\"colormap\\\"\\n"
+  "  material: \\\"/examples/material/unlit/unlit.material\\\"\\n"
+  "  textures {\\n"
+  "    sampler: \\\"texture0\\\"\\n"
+  "    texture: \\\"/assets/models/kenney_prototype-kit/Textures/colormap.png\\\"\\n"
+  "  }\\n"
+  "}\\n"
+  "\"\n"
+  "  position {\n"
+  "    y: -0.25\n"
+  "  }\n"
+  "}\n"
+  ""
+  position {
+    x: 1.0
+    y: -1.0
+    z: 1.0
+  }
+  rotation {
+    x: 0.1071506
+    y: -0.3398383
+    z: -0.28096747
+    w: 0.891115
+  }
+  scale3 {
+    x: 2.0
+  }
+}

+ 14 - 0
examples/movement/look_rotation/look_rotation.md

@@ -0,0 +1,14 @@
+---
+name: Look rotation (3D)
+title: Look rotation
+brief: This example shows how to rotate a game object to look at the object in 3D space.
+scripts: look_rotation.script
+---
+
+This example shows how to orient a game object to look at the target game object in 3D space. For this purpose, we created the function `quat_look_rotation` (also called `LookRotation` or `looking_at` in the industry). This function creates a rotation matrix from the forward and upwards vectors and then converts it to a quaternion. The function also handles the case where no upwards direction is specified, using the default (0, 1, 0) in that case.
+
+Note: to properly apply the resulting rotation, you must remember that your game object must face backwards to the "z" axis, i.e. in Defold the "forward" direction is vector (0, 0, -1).
+
+In this demo you can rotate the camera by holding down the mouse button. And also switch "targets" by pressing any key.
+
+The models used in this example are from Kenney's [Prototype Kit](https://kenney.nl/assets/prototype-kit), licensed under CC0.

+ 64 - 0
examples/movement/look_rotation/look_rotation.script

@@ -0,0 +1,64 @@
+--- Creates a rotation with the specified forward and upwards directions.
+-- @param forward vector3 The forward direction.
+-- @param upwards vector3|nil The upwards direction.
+-- @return quat The rotation.
+local function quat_look_rotation(forward, upwards)
+	-- If no upwards direction is specified, use the default (0, 1, 0)
+	upwards = upwards or vmath.vector3(0, 1, 0)
+
+	-- No zero vectors
+	if vmath.length_sqr(forward) < 0.0000000001 or vmath.length_sqr(upwards) < 0.0000000001 then
+		return vmath.quat()
+	end
+
+	-- Create a rotation matrix from the forward and upwards vectors
+	local matrix = vmath.matrix4_look_at(vmath.vector3(0), forward, upwards)
+
+	-- Convert the matrix to a quaternion and return it
+	return vmath.conj(vmath.quat_matrix4(matrix))
+end
+
+local function next_target(self)
+	self.target = (self.target or 0) + 1
+	if self.target > #self.targets then
+		self.target = 1
+	end
+
+	local target_id = self.targets[self.target]
+
+	local from = go.get_position("/sword")
+	local to = go.get_position(target_id)
+
+	self.target_rotation = quat_look_rotation(to - from)
+end
+
+function init(self)
+	-- Acquire input focus to receive input events
+	msg.post(".", "acquire_input_focus")
+
+	-- List of target objects
+	self.targets = {
+		"/target1",
+		"/target2",
+		"/target3"
+	}
+
+	-- Set the initial target
+	next_target(self)
+end
+
+function update(self, dt)
+	-- If a target rotation is set, smoothly rotate the sword to face the target
+	if self.target_rotation then
+		-- Important: we must use vmath.slerp to animate quaternions
+		local q = vmath.slerp(0.15, go.get_rotation("/sword"), self.target_rotation)
+		go.set_rotation(q, "/sword")
+	end
+end
+
+function on_input(self, action_id, action)
+	-- If the action is pressed (any key or mouse button), set the next target
+	if action.pressed then
+		next_target(self)
+	end
+end

+ 89 - 0
examples/particles/fireworks/fireworks.collection

@@ -0,0 +1,89 @@
+name: "fireworks"
+scale_along_z: 0
+embedded_instances {
+  id: "gameobject"
+  data: "components {\n"
+  "  id: \"script\"\n"
+  "  component: \"/examples/particles/fireworks/fireworks.script\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"blue_trail_factory\"\n"
+  "  type: \"factory\"\n"
+  "  data: \"prototype: \\\"/examples/particles/fireworks/fw_trail_blue.go\\\"\\n"
+  "\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"green_trail_factory\"\n"
+  "  type: \"factory\"\n"
+  "  data: \"prototype: \\\"/examples/particles/fireworks/fw_trail_green.go\\\"\\n"
+  "\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"red_trail_factory\"\n"
+  "  type: \"factory\"\n"
+  "  data: \"prototype: \\\"/examples/particles/fireworks/fw_trail_red.go\\\"\\n"
+  "\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"blue_splat_factory\"\n"
+  "  type: \"factory\"\n"
+  "  data: \"prototype: \\\"/examples/particles/fireworks/fw_splat_blue.go\\\"\\n"
+  "\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"red_splat_factory\"\n"
+  "  type: \"factory\"\n"
+  "  data: \"prototype: \\\"/examples/particles/fireworks/fw_splat_red.go\\\"\\n"
+  "\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"green_splat_factory\"\n"
+  "  type: \"factory\"\n"
+  "  data: \"prototype: \\\"/examples/particles/fireworks/fw_splat_green.go\\\"\\n"
+  "\"\n"
+  "}\n"
+  ""
+  position {
+    x: 360.0
+    y: 50.0
+  }
+}
+embedded_instances {
+  id: "instructions"
+  data: "embedded_components {\n"
+  "  id: \"label\"\n"
+  "  type: \"label\"\n"
+  "  data: \"size {\\n"
+  "  x: 128.0\\n"
+  "  y: 32.0\\n"
+  "}\\n"
+  "color {\\n"
+  "  x: 0.0\\n"
+  "  y: 0.5647059\\n"
+  "  z: 0.99215686\\n"
+  "}\\n"
+  "outline {\\n"
+  "  x: 1.0\\n"
+  "  y: 1.0\\n"
+  "  z: 1.0\\n"
+  "}\\n"
+  "shadow {\\n"
+  "  x: 1.0\\n"
+  "  y: 1.0\\n"
+  "  z: 1.0\\n"
+  "}\\n"
+  "text: \\\"Tap to start random firework\\\"\\n"
+  "font: \\\"/assets/text48.font\\\"\\n"
+  "material: \\\"/builtins/fonts/label.material\\\"\\n"
+  "\"\n"
+  "  scale {\n"
+  "    x: 0.5\n"
+  "    y: 0.5\n"
+  "  }\n"
+  "}\n"
+  ""
+  position {
+    x: 360.0
+    y: 38.0
+  }
+}

+ 16 - 0
examples/particles/fireworks/fireworks.md

@@ -0,0 +1,16 @@
+---
+title: Particle effect example - fireworks
+brief: This example shows a fireworks effect made with particles.
+scripts: fireworks.script
+---
+
+This effect consists of two particle effects: trail and bang. In this example there are three different colors, which could be easily changed in particle emitters settings.
+
+
+The main script `fireworks.script` spawns the fireworks trail particlefx on startup or when any key is pressed or the mouse button is clicked. It also has a timer that spawns the particlefx in a loop with a 3 second delay. 
+
+To start effect:
+- add factories for splat and trail particles;
+- call "start_fireworks" method with parameters (time, start point, speed vector).
+
+Images for particles are taken from Kenney Particle Pack.

+ 94 - 0
examples/particles/fireworks/fireworks.script

@@ -0,0 +1,94 @@
+local colors = {"red", "green", "blue"} -- list of existing fireworks colors
+local instances = {} -- list of active fireworks instances
+
+local _tension = 0.9 -- emulation of air tension. More value leads to faster deceleration
+local _gravity = 980 -- gravity, measuring in mm/sq.s.
+
+local function start_fireworks(trail_id, bang_id, start_pos, speed, time, gravity, tension)
+	go.set_position(start_pos, trail_id)
+	particlefx.play(trail_id)
+	local m = {
+		update = function(self, dt)
+			if self.time > 0 then
+				local prev_pos = vmath.vector3(self.start_pos)
+				self.start_pos.x = self.start_pos.x + self.speed.x*dt
+				self.start_pos.y = self.start_pos.y + self.speed.y*dt
+
+				local triangle = vmath.vector3(prev_pos.x - start_pos.x, prev_pos.y - start_pos.y, 0)
+				local angle = math.atan2(triangle.y, triangle.x)
+				self.speed.x = self.speed.x - self.speed.x * self.tension*dt
+				self.speed.y = self.speed.y - self.speed.y * self.tension*dt - self.gravity*dt
+
+				go.set_position(self.start_pos, self.trail)
+				go.set_rotation(vmath.quat_rotation_z(angle+math.pi/2), self.trail)
+				if self.time > 0 then
+					go.set_scale(self.time, self.trail)
+				end
+
+			elseif self.time <= 0 and not self.is_stopped then
+				self.is_stopped = true
+				particlefx.stop(self.trail, { clear = true })
+				go.set_position(self.start_pos, self.bang)
+				particlefx.play(self.bang)
+			elseif self.time <= -1.5 and self.bang  then
+				go.delete(self.bang)
+				self.bang = nil
+			end
+			self.time = self.time - dt
+		end,
+		start_pos = start_pos,
+		time = time,
+		speed = speed,
+		gravity = gravity or _gravity,
+		tension = tension or _tension,
+		trail = trail_id,
+		bang = bang_id
+	}
+
+	return m
+end
+
+local function single_shot()
+	if #instances > 5 then
+		return
+	end
+	local color = colors[math.random(1, #colors)]
+	local splat = factory.create("#"..color.."_splat_factory")
+	local trail = factory.create("#"..color.."_trail_factory")
+
+	local strength = 1000+math.random()*600 		-- scalar value of speed
+	local angle = (-0.2+math.random()*0.4)*math.pi	-- angle beetween central vertical line and trail
+	
+	local pos = vmath.vector3(360-math.sin(angle)*350, 0, 0)
+	local speed = vmath.vector3(strength*math.sin(angle), strength*math.cos(angle), 0)
+	table.insert(instances, 
+		start_fireworks(trail, splat, 
+			pos, speed, 
+			1.2+math.random()*0.5
+		)
+	)
+end
+
+function init(self)
+	single_shot()
+
+	timer.delay(3, true, single_shot) 
+
+	msg.post(".", "acquire_input_focus")
+end
+
+function update(self, dt)
+	for i, val in ipairs(instances) do
+		if not val.bang then
+			table.remove(instances, i)
+		else
+			val:update(dt)
+		end
+	end
+end
+
+function on_input(self, action_id, action)
+	if action.pressed then 
+		single_shot()
+	end
+end

+ 4 - 0
examples/particles/fireworks/fw_splat_blue.go

@@ -0,0 +1,4 @@
+components {
+  id: "tile_splat_blue"
+  component: "/examples/particles/fireworks/fw_splat_blue.particlefx"
+}

+ 1134 - 0
examples/particles/fireworks/fw_splat_blue.particlefx

@@ -0,0 +1,1134 @@
+emitters {
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.2
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_trace_01"
+  material: "/builtins/materials/particlefx.material"
+  particle_orientation: PARTICLE_ORIENTATION_MOVEMENT_DIRECTION
+  max_particle_count: 150
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 10000.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_X
+    points {
+      y: 20.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_Y
+    points {
+      y: 20.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+    spread: 0.5
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SPEED
+    points {
+      y: 200.0
+    }
+    spread: 20.0
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 3.0
+    }
+    spread: 0.5
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 0.12941177
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 0.3019608
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.7019608
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_Y
+    points {
+      y: -0.5
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 0.42859232
+      t_x: 0.024429077
+      t_y: 0.99970156
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 0.12233837
+    }
+    points {
+      x: 0.08273616
+      y: 2.0447288
+      t_x: 0.14027841
+      t_y: -0.9901121
+    }
+    points {
+      x: 0.26107493
+      y: 0.40814364
+      t_x: 0.27199933
+      t_y: -0.96229744
+    }
+    points {
+      x: 0.38371336
+      y: -0.036952645
+      t_x: 0.9262474
+      t_y: 0.3769161
+    }
+    points {
+      x: 0.7171824
+      y: 0.16613448
+      t_x: 0.64932483
+      t_y: 0.76051116
+    }
+    points {
+      x: 1.0
+      y: -0.20678474
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 10.92268
+    }
+    points {
+      x: 0.105944626
+      y: 6.334795
+      t_x: 0.34915915
+      t_y: -0.93706346
+    }
+    points {
+      x: 0.30114007
+      y: 2.4454107
+      t_x: 0.14935292
+      t_y: -0.98878396
+    }
+    points {
+      x: 1.0
+      y: 1.5742594
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 15.942318
+    }
+    points {
+      x: 0.08151466
+      y: 4.3566136
+      t_x: 0.039131545
+      t_y: -0.9992341
+    }
+    points {
+      x: 0.51758957
+      y: -0.68891436
+      t_x: 0.96546835
+      t_y: -0.26052028
+    }
+    points {
+      x: 0.77581435
+      y: 0.08814634
+      t_x: 0.91469765
+      t_y: 0.40413892
+    }
+    points {
+      x: 1.0
+      y: -0.5702326
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_RADIAL
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: 0.0
+      }
+    }
+    properties {
+      key: MODIFIER_KEY_MAX_DISTANCE
+      points {
+        y: 30.0
+      }
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_ACCELERATION
+    rotation {
+      z: 1.0
+      w: 6.123234E-17
+    }
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: 50.0
+      }
+    }
+  }
+  duration_spread: 0.1
+  stretch_with_velocity: true
+}
+emitters {
+  id: "emitter1"
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.2
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_star_01"
+  material: "/builtins/materials/particlefx.material"
+  particle_orientation: PARTICLE_ORIENTATION_ANGULAR_VELOCITY
+  max_particle_count: 30
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 10000.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_X
+    points {
+      y: 60.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_Y
+    points {
+      y: 60.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_Z
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+    spread: 0.3
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SPEED
+    points {
+      y: 300.0
+    }
+    spread: 20.0
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 3.0
+    }
+    spread: 2.0
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 0.12941177
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 0.3019608
+    }
+    spread: 0.1
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.7019608
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ROTATION
+    points {
+      y: 0.0
+    }
+    spread: 90.0
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ANGULAR_VELOCITY
+    points {
+      y: 10.0
+    }
+    spread: 5.0
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 7.6625104
+    }
+    points {
+      x: 1.0
+      y: 10.478123
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+    points {
+      x: 1.0
+      y: 0.86500204
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+    points {
+      x: 1.0
+      y: 2.7463124
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+    points {
+      x: 1.0
+      y: 10.386523
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 0.11957385
+      t_x: 0.9949079
+      t_y: 0.10078809
+    }
+    points {
+      x: 0.20659609
+      y: 1.245942
+      t_x: 0.23804364
+      t_y: -0.97125447
+    }
+    points {
+      x: 0.6756515
+      y: 0.6910322
+      t_x: 0.7841117
+      t_y: -0.6206198
+    }
+    points {
+      x: 1.0
+      y: 0.015711093
+      t_x: 0.56921566
+      t_y: -0.82218826
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ANGULAR_VELOCITY
+    points {
+      y: 15.0
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_ACCELERATION
+    rotation {
+      z: 1.0
+      w: 6.123234E-17
+    }
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: 10.0
+      }
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_RADIAL
+    rotation {
+      z: 1.0
+      w: 6.123234E-17
+    }
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: -250.0
+        t_x: 0.003265428
+        t_y: -0.9999947
+      }
+      points {
+        x: 1.0
+        y: -8.693188
+        t_x: 7.5198716E-4
+        t_y: 0.9999997
+      }
+    }
+    properties {
+      key: MODIFIER_KEY_MAX_DISTANCE
+      points {
+        y: 500.0
+      }
+    }
+  }
+  stretch_with_velocity: true
+}
+emitters {
+  id: "emitter4"
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.2
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_trace_01"
+  material: "/builtins/materials/particlefx.material"
+  blend_mode: BLEND_MODE_ADD
+  particle_orientation: PARTICLE_ORIENTATION_MOVEMENT_DIRECTION
+  max_particle_count: 150
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 10000.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_X
+    points {
+      y: 20.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_Y
+    points {
+      y: 20.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+    spread: 0.1
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SPEED
+    points {
+      y: 300.0
+    }
+    points {
+      x: 0.11107492
+      y: 33.759693
+      t_x: 0.004660093
+      t_y: -0.99998915
+    }
+    points {
+      x: 1.0
+      y: 10.3574295
+    }
+    spread: 20.0
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 3.0
+    }
+    spread: 0.5
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 0.050980393
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 0.29803923
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_Y
+    points {
+      y: -0.5
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 0.42859232
+      t_x: 0.024429077
+      t_y: 0.99970156
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: -0.0012211404
+    }
+    points {
+      x: 0.08175896
+      y: 2.0447288
+      t_x: 0.14027841
+      t_y: -0.9901121
+    }
+    points {
+      x: 0.18749742
+      y: 0.12487222
+      t_x: 0.27199933
+      t_y: -0.96229744
+    }
+    points {
+      x: 0.6348273
+      y: -0.031716812
+      t_x: 0.29903722
+      t_y: 0.95424145
+    }
+    points {
+      x: 1.0
+      y: 0.009376409
+      t_x: 0.3381512
+      t_y: -0.9410918
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 6.6410265
+    }
+    points {
+      x: 0.10692182
+      y: 4.3885884
+      t_x: 0.34915915
+      t_y: -0.93706346
+    }
+    points {
+      x: 1.0
+      y: 0.22707473
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 15.942318
+    }
+    points {
+      x: 0.08151466
+      y: 4.3566136
+      t_x: 0.039131545
+      t_y: -0.9992341
+    }
+    points {
+      x: 0.5713355
+      y: 0.29830235
+      t_x: 0.96546835
+      t_y: -0.26052028
+    }
+    points {
+      x: 1.0
+      y: -0.5702326
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_ACCELERATION
+    rotation {
+      z: 1.0
+      w: 6.123234E-17
+    }
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: 100.0
+      }
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_RADIAL
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: 0.0
+      }
+    }
+    properties {
+      key: MODIFIER_KEY_MAX_DISTANCE
+      points {
+        y: 30.0
+      }
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+  duration_spread: 0.1
+  stretch_with_velocity: true
+}
+emitters {
+  id: "emitter2"
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.1
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_circle_01"
+  material: "/builtins/materials/particlefx.material"
+  blend_mode: BLEND_MODE_ADD
+  max_particle_count: 2
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 60.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 0.12941177
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 0.3019608
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.7019608
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_X
+    points {
+      y: 2.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_Y
+    points {
+      y: 2.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 0.0050403317
+      t_x: 0.07567246
+      t_y: 0.9971327
+    }
+    points {
+      x: 0.13013029
+      y: 1.0516063
+      t_x: 0.22336559
+      t_y: 0.9747347
+    }
+    points {
+      x: 0.31872964
+      y: 0.12317026
+      t_x: 0.44993034
+      t_y: -0.89306366
+    }
+    points {
+      x: 0.6131107
+      y: -0.03477873
+      t_x: 0.18202555
+      t_y: -0.9832938
+    }
+    points {
+      x: 0.8332248
+      y: 0.0568189
+      t_x: 0.8397494
+      t_y: 0.5429742
+    }
+    points {
+      x: 1.0
+      y: 0.0
+      t_x: 0.99995637
+      t_y: -0.009341509
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+}
+emitters {
+  id: "emitter3"
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.1
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_light_01"
+  material: "/builtins/materials/particlefx.material"
+  max_particle_count: 1
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 1000.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 60.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 0.19
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 0.3019608
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.7019608
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_X
+    points {
+      y: 2.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_Y
+    points {
+      y: 2.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 9.531683E-5
+      t_x: 0.48855478
+      t_y: 0.8725332
+    }
+    points {
+      x: 0.014885994
+      y: 1.0171621
+      t_x: 0.06842353
+      t_y: 0.99765635
+    }
+    points {
+      x: 0.31872964
+      y: 0.12317026
+      t_x: 0.44993034
+      t_y: -0.89306366
+    }
+    points {
+      x: 0.5886808
+      y: -0.99066126
+      t_x: 0.18202555
+      t_y: -0.9832938
+    }
+    points {
+      x: 0.83127034
+      y: -0.3613797
+      t_x: 0.8397494
+      t_y: 0.5429742
+    }
+    points {
+      x: 1.0
+      y: 0.0
+      t_x: 0.99995637
+      t_y: -0.009341509
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 1.0156251
+    }
+    points {
+      x: 0.2022469
+      y: 3.2772617
+    }
+    points {
+      x: 0.38703388
+      y: 1.1423699
+      t_x: 0.3538746
+      t_y: -0.9352929
+    }
+    points {
+      x: 1.0
+      y: 0.014935664
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 1.0156251
+    }
+    points {
+      x: 0.2027355
+      y: 3.3074327
+      t_x: 0.92783904
+      t_y: -0.3729809
+    }
+    points {
+      x: 0.38931102
+      y: 1.1156069
+      t_x: 0.26103032
+      t_y: -0.9653306
+    }
+    points {
+      x: 1.0
+      y: 0.0
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+}
+emitters {
+  id: "emitter5"
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.1
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_light_01"
+  material: "/builtins/materials/particlefx.material"
+  blend_mode: BLEND_MODE_ADD
+  max_particle_count: 1
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 1000.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 30.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 0.12941177
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 0.3019608
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.7019608
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_X
+    points {
+      y: 2.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_Y
+    points {
+      y: 2.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 9.531683E-5
+      t_x: 0.48855478
+      t_y: 0.8725332
+    }
+    points {
+      x: 0.014885994
+      y: 1.0171621
+      t_x: 0.06842353
+      t_y: 0.99765635
+    }
+    points {
+      x: 0.31872964
+      y: 0.12317026
+      t_x: 0.44993034
+      t_y: -0.89306366
+    }
+    points {
+      x: 0.5886808
+      y: -0.99066126
+      t_x: 0.18202555
+      t_y: -0.9832938
+    }
+    points {
+      x: 0.83127034
+      y: -0.3613797
+      t_x: 0.8397494
+      t_y: 0.5429742
+    }
+    points {
+      x: 1.0
+      y: 0.0
+      t_x: 0.99995637
+      t_y: -0.009341509
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 1.0156251
+    }
+    points {
+      x: 0.2022469
+      y: 3.2772617
+    }
+    points {
+      x: 0.38703388
+      y: 1.1423699
+      t_x: 0.3538746
+      t_y: -0.9352929
+    }
+    points {
+      x: 1.0
+      y: 0.014935664
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 1.0156251
+    }
+    points {
+      x: 0.2027355
+      y: 3.3074327
+      t_x: 0.92783904
+      t_y: -0.3729809
+    }
+    points {
+      x: 0.38931102
+      y: 1.1156069
+      t_x: 0.26103032
+      t_y: -0.9653306
+    }
+    points {
+      x: 1.0
+      y: 0.0
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+}

+ 4 - 0
examples/particles/fireworks/fw_splat_green.go

@@ -0,0 +1,4 @@
+components {
+  id: "tile_splat_green"
+  component: "/examples/particles/fireworks/fw_splat_green.particlefx"
+}

+ 1150 - 0
examples/particles/fireworks/fw_splat_green.particlefx

@@ -0,0 +1,1150 @@
+emitters {
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.2
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_trace_01"
+  material: "/builtins/materials/particlefx.material"
+  particle_orientation: PARTICLE_ORIENTATION_MOVEMENT_DIRECTION
+  max_particle_count: 150
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 10000.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_X
+    points {
+      y: 20.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_Y
+    points {
+      y: 20.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+    spread: 0.5
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SPEED
+    points {
+      y: 200.0
+    }
+    spread: 20.0
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 3.0
+    }
+    spread: 0.5
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 0.2039216
+    }
+    spread: 0.2
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 1.0
+    }
+    spread: 0.05
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.039215688
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_Y
+    points {
+      y: -0.5
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 0.42859232
+      t_x: 0.024429077
+      t_y: 0.99970156
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 0.12233837
+    }
+    points {
+      x: 0.08273616
+      y: 2.0447288
+      t_x: 0.14027841
+      t_y: -0.9901121
+    }
+    points {
+      x: 0.26107493
+      y: 0.40814364
+      t_x: 0.27199933
+      t_y: -0.96229744
+    }
+    points {
+      x: 0.38371336
+      y: -0.036952645
+      t_x: 0.9262474
+      t_y: 0.3769161
+    }
+    points {
+      x: 0.7171824
+      y: 0.16613448
+      t_x: 0.64932483
+      t_y: 0.76051116
+    }
+    points {
+      x: 1.0
+      y: -0.20678474
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 10.92268
+    }
+    points {
+      x: 0.105944626
+      y: 6.334795
+      t_x: 0.34915915
+      t_y: -0.93706346
+    }
+    points {
+      x: 0.30114007
+      y: 2.4454107
+      t_x: 0.14935292
+      t_y: -0.98878396
+    }
+    points {
+      x: 1.0
+      y: 1.5742594
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 15.942318
+    }
+    points {
+      x: 0.08151466
+      y: 4.3566136
+      t_x: 0.039131545
+      t_y: -0.9992341
+    }
+    points {
+      x: 0.51758957
+      y: -0.68891436
+      t_x: 0.96546835
+      t_y: -0.26052028
+    }
+    points {
+      x: 0.77581435
+      y: 0.08814634
+      t_x: 0.91469765
+      t_y: 0.40413892
+    }
+    points {
+      x: 1.0
+      y: -0.5702326
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_RADIAL
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: 0.0
+      }
+    }
+    properties {
+      key: MODIFIER_KEY_MAX_DISTANCE
+      points {
+        y: 30.0
+      }
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_ACCELERATION
+    rotation {
+      z: 1.0
+      w: 6.123234E-17
+    }
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: 50.0
+      }
+    }
+  }
+  duration_spread: 0.1
+  stretch_with_velocity: true
+}
+emitters {
+  id: "emitter1"
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.2
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_star_01"
+  material: "/builtins/materials/particlefx.material"
+  particle_orientation: PARTICLE_ORIENTATION_ANGULAR_VELOCITY
+  max_particle_count: 30
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 10000.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_X
+    points {
+      y: 60.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_Y
+    points {
+      y: 60.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_Z
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+    spread: 0.3
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SPEED
+    points {
+      y: 300.0
+    }
+    spread: 20.0
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 3.0
+    }
+    spread: 2.0
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 0.2
+    }
+    spread: 0.3
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 1.0
+    }
+    spread: 0.1
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.1
+    }
+    spread: 0.05
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ROTATION
+    points {
+      y: 0.0
+    }
+    spread: 90.0
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ANGULAR_VELOCITY
+    points {
+      y: 10.0
+    }
+    spread: 5.0
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 7.6625104
+    }
+    points {
+      x: 1.0
+      y: 10.478123
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+    points {
+      x: 1.0
+      y: 11.4782715
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+    points {
+      x: 1.0
+      y: 2.8707824
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+    points {
+      x: 1.0
+      y: 2.8707824
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 0.11957385
+      t_x: 0.9949079
+      t_y: 0.10078809
+    }
+    points {
+      x: 0.20659609
+      y: 1.245942
+      t_x: 0.23804364
+      t_y: -0.97125447
+    }
+    points {
+      x: 0.6756515
+      y: 0.6910322
+      t_x: 0.7841117
+      t_y: -0.6206198
+    }
+    points {
+      x: 1.0
+      y: 0.015711093
+      t_x: 0.56921566
+      t_y: -0.82218826
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ANGULAR_VELOCITY
+    points {
+      y: 15.0
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_ACCELERATION
+    rotation {
+      z: 1.0
+      w: 6.123234E-17
+    }
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: 10.0
+      }
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_RADIAL
+    rotation {
+      z: 1.0
+      w: 6.123234E-17
+    }
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: -250.0
+        t_x: 0.003265428
+        t_y: -0.9999947
+      }
+      points {
+        x: 1.0
+        y: -8.693188
+        t_x: 7.5198716E-4
+        t_y: 0.9999997
+      }
+    }
+    properties {
+      key: MODIFIER_KEY_MAX_DISTANCE
+      points {
+        y: 500.0
+      }
+    }
+  }
+  stretch_with_velocity: true
+}
+emitters {
+  id: "emitter4"
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.2
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_trace_01"
+  material: "/builtins/materials/particlefx.material"
+  blend_mode: BLEND_MODE_ADD
+  particle_orientation: PARTICLE_ORIENTATION_MOVEMENT_DIRECTION
+  max_particle_count: 150
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 10000.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_X
+    points {
+      y: 20.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_Y
+    points {
+      y: 20.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+    spread: 0.1
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SPEED
+    points {
+      y: 300.0
+    }
+    points {
+      x: 0.11107492
+      y: 33.759693
+      t_x: 0.004660093
+      t_y: -0.99998915
+    }
+    points {
+      x: 1.0
+      y: 10.3574295
+    }
+    spread: 20.0
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 3.0
+    }
+    spread: 0.5
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 0.2
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.2
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_Y
+    points {
+      y: -0.5
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 0.42859232
+      t_x: 0.024429077
+      t_y: 0.99970156
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+    points {
+      x: 1.0
+      y: 0.60587674
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 0.21502538
+    }
+    points {
+      x: 1.0
+      y: 1.6762574
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 0.24819332
+    }
+    points {
+      x: 1.0
+      y: 1.7094253
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: -0.0012211404
+    }
+    points {
+      x: 0.08175896
+      y: 2.0447288
+      t_x: 0.14027841
+      t_y: -0.9901121
+    }
+    points {
+      x: 0.1985342
+      y: -1.6654266
+      t_x: 0.27199933
+      t_y: -0.96229744
+    }
+    points {
+      x: 0.63583064
+      y: -0.031716812
+      t_x: 0.29903722
+      t_y: 0.95424145
+    }
+    points {
+      x: 1.0
+      y: 0.009376409
+      t_x: 0.3381512
+      t_y: -0.9410918
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 6.6410265
+    }
+    points {
+      x: 0.10692182
+      y: 4.3885884
+      t_x: 0.34915915
+      t_y: -0.93706346
+    }
+    points {
+      x: 1.0
+      y: 0.22707473
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 15.942318
+    }
+    points {
+      x: 0.08151466
+      y: 4.3566136
+      t_x: 0.039131545
+      t_y: -0.9992341
+    }
+    points {
+      x: 0.5713355
+      y: 0.29830235
+      t_x: 0.96546835
+      t_y: -0.26052028
+    }
+    points {
+      x: 1.0
+      y: -0.5702326
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_ACCELERATION
+    rotation {
+      z: 1.0
+      w: 6.123234E-17
+    }
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: 100.0
+      }
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_RADIAL
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: 0.0
+      }
+    }
+    properties {
+      key: MODIFIER_KEY_MAX_DISTANCE
+      points {
+        y: 30.0
+      }
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+  duration_spread: 0.1
+  stretch_with_velocity: true
+}
+emitters {
+  id: "emitter2"
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.1
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_circle_01"
+  material: "/builtins/materials/particlefx.material"
+  blend_mode: BLEND_MODE_ADD
+  max_particle_count: 2
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 60.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 0.2039216
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.039215688
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_X
+    points {
+      y: 2.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_Y
+    points {
+      y: 2.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 0.0050403317
+      t_x: 0.07567246
+      t_y: 0.9971327
+    }
+    points {
+      x: 0.13013029
+      y: 1.0516063
+      t_x: 0.22336559
+      t_y: 0.9747347
+    }
+    points {
+      x: 0.31872964
+      y: 0.12317026
+      t_x: 0.44993034
+      t_y: -0.89306366
+    }
+    points {
+      x: 0.6131107
+      y: -0.03477873
+      t_x: 0.18202555
+      t_y: -0.9832938
+    }
+    points {
+      x: 0.8332248
+      y: 0.0568189
+      t_x: 0.8397494
+      t_y: 0.5429742
+    }
+    points {
+      x: 1.0
+      y: 0.0
+      t_x: 0.99995637
+      t_y: -0.009341509
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+}
+emitters {
+  id: "emitter3"
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.1
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_light_01"
+  material: "/builtins/materials/particlefx.material"
+  max_particle_count: 1
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 1000.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 60.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 0.4313726
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.30980393
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_X
+    points {
+      y: 2.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_Y
+    points {
+      y: 2.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 9.531683E-5
+      t_x: 0.48855478
+      t_y: 0.8725332
+    }
+    points {
+      x: 0.014885994
+      y: 1.0171621
+      t_x: 0.06842353
+      t_y: 0.99765635
+    }
+    points {
+      x: 0.31872964
+      y: 0.12317026
+      t_x: 0.44993034
+      t_y: -0.89306366
+    }
+    points {
+      x: 0.5886808
+      y: -0.99066126
+      t_x: 0.18202555
+      t_y: -0.9832938
+    }
+    points {
+      x: 0.83127034
+      y: -0.3613797
+      t_x: 0.8397494
+      t_y: 0.5429742
+    }
+    points {
+      x: 1.0
+      y: 0.0
+      t_x: 0.99995637
+      t_y: -0.009341509
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 1.0156251
+    }
+    points {
+      x: 0.2022469
+      y: 3.2772617
+    }
+    points {
+      x: 0.38703388
+      y: 1.1423699
+      t_x: 0.3538746
+      t_y: -0.9352929
+    }
+    points {
+      x: 1.0
+      y: 0.014935664
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 1.0156251
+    }
+    points {
+      x: 0.2027355
+      y: 3.3074327
+      t_x: 0.92783904
+      t_y: -0.3729809
+    }
+    points {
+      x: 0.38931102
+      y: 1.1156069
+      t_x: 0.26103032
+      t_y: -0.9653306
+    }
+    points {
+      x: 1.0
+      y: 0.0
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+}
+emitters {
+  id: "emitter5"
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.1
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_light_01"
+  material: "/builtins/materials/particlefx.material"
+  blend_mode: BLEND_MODE_ADD
+  max_particle_count: 1
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 1000.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 30.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 0.4313726
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.30980393
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_X
+    points {
+      y: 2.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_Y
+    points {
+      y: 2.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 9.531683E-5
+      t_x: 0.48855478
+      t_y: 0.8725332
+    }
+    points {
+      x: 0.014885994
+      y: 1.0171621
+      t_x: 0.06842353
+      t_y: 0.99765635
+    }
+    points {
+      x: 0.31872964
+      y: 0.12317026
+      t_x: 0.44993034
+      t_y: -0.89306366
+    }
+    points {
+      x: 0.5886808
+      y: -0.99066126
+      t_x: 0.18202555
+      t_y: -0.9832938
+    }
+    points {
+      x: 0.83127034
+      y: -0.3613797
+      t_x: 0.8397494
+      t_y: 0.5429742
+    }
+    points {
+      x: 1.0
+      y: 0.0
+      t_x: 0.99995637
+      t_y: -0.009341509
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 1.0156251
+    }
+    points {
+      x: 0.2022469
+      y: 3.2772617
+    }
+    points {
+      x: 0.38703388
+      y: 1.1423699
+      t_x: 0.3538746
+      t_y: -0.9352929
+    }
+    points {
+      x: 1.0
+      y: 0.014935664
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 1.0156251
+    }
+    points {
+      x: 0.2027355
+      y: 3.3074327
+      t_x: 0.92783904
+      t_y: -0.3729809
+    }
+    points {
+      x: 0.38931102
+      y: 1.1156069
+      t_x: 0.26103032
+      t_y: -0.9653306
+    }
+    points {
+      x: 1.0
+      y: 0.0
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+}

+ 4 - 0
examples/particles/fireworks/fw_splat_red.go

@@ -0,0 +1,4 @@
+components {
+  id: "tile_splat_red"
+  component: "/examples/particles/fireworks/fw_splat_red.particlefx"
+}

+ 1150 - 0
examples/particles/fireworks/fw_splat_red.particlefx

@@ -0,0 +1,1150 @@
+emitters {
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.2
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_trace_01"
+  material: "/builtins/materials/particlefx.material"
+  particle_orientation: PARTICLE_ORIENTATION_MOVEMENT_DIRECTION
+  max_particle_count: 150
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 10000.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_X
+    points {
+      y: 20.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_Y
+    points {
+      y: 20.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+    spread: 0.5
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SPEED
+    points {
+      y: 200.0
+    }
+    spread: 20.0
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 3.0
+    }
+    spread: 0.5
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 1.0
+    }
+    spread: 0.2
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 0.20392157
+    }
+    spread: 0.05
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.039215688
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_Y
+    points {
+      y: -0.5
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 0.42859232
+      t_x: 0.024429077
+      t_y: 0.99970156
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 0.12233837
+    }
+    points {
+      x: 0.08273616
+      y: 2.0447288
+      t_x: 0.14027841
+      t_y: -0.9901121
+    }
+    points {
+      x: 0.26107493
+      y: 0.40814364
+      t_x: 0.27199933
+      t_y: -0.96229744
+    }
+    points {
+      x: 0.38371336
+      y: -0.036952645
+      t_x: 0.9262474
+      t_y: 0.3769161
+    }
+    points {
+      x: 0.7171824
+      y: 0.16613448
+      t_x: 0.64932483
+      t_y: 0.76051116
+    }
+    points {
+      x: 1.0
+      y: -0.20678474
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 10.92268
+    }
+    points {
+      x: 0.105944626
+      y: 6.334795
+      t_x: 0.34915915
+      t_y: -0.93706346
+    }
+    points {
+      x: 0.30114007
+      y: 2.4454107
+      t_x: 0.14935292
+      t_y: -0.98878396
+    }
+    points {
+      x: 1.0
+      y: 1.5742594
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 15.942318
+    }
+    points {
+      x: 0.08151466
+      y: 4.3566136
+      t_x: 0.039131545
+      t_y: -0.9992341
+    }
+    points {
+      x: 0.51758957
+      y: -0.68891436
+      t_x: 0.96546835
+      t_y: -0.26052028
+    }
+    points {
+      x: 0.77581435
+      y: 0.08814634
+      t_x: 0.91469765
+      t_y: 0.40413892
+    }
+    points {
+      x: 1.0
+      y: -0.5702326
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_RADIAL
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: 0.0
+      }
+    }
+    properties {
+      key: MODIFIER_KEY_MAX_DISTANCE
+      points {
+        y: 30.0
+      }
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_ACCELERATION
+    rotation {
+      z: 1.0
+      w: 6.123234E-17
+    }
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: 50.0
+      }
+    }
+  }
+  duration_spread: 0.1
+  stretch_with_velocity: true
+}
+emitters {
+  id: "emitter1"
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.2
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_star_01"
+  material: "/builtins/materials/particlefx.material"
+  particle_orientation: PARTICLE_ORIENTATION_ANGULAR_VELOCITY
+  max_particle_count: 30
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 10000.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_X
+    points {
+      y: 60.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_Y
+    points {
+      y: 60.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_Z
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+    spread: 0.3
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SPEED
+    points {
+      y: 300.0
+    }
+    spread: 20.0
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 3.0
+    }
+    spread: 2.0
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 1.0
+    }
+    spread: 0.3
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 0.2
+    }
+    spread: 0.1
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.1
+    }
+    spread: 0.05
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ROTATION
+    points {
+      y: 0.0
+    }
+    spread: 90.0
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ANGULAR_VELOCITY
+    points {
+      y: 10.0
+    }
+    spread: 5.0
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 7.6625104
+    }
+    points {
+      x: 1.0
+      y: 10.478123
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+    points {
+      x: 1.0
+      y: 11.4782715
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+    points {
+      x: 1.0
+      y: 2.8707824
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+    points {
+      x: 1.0
+      y: 2.8707824
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 0.11957385
+      t_x: 0.9949079
+      t_y: 0.10078809
+    }
+    points {
+      x: 0.20659609
+      y: 1.245942
+      t_x: 0.23804364
+      t_y: -0.97125447
+    }
+    points {
+      x: 0.6756515
+      y: 0.6910322
+      t_x: 0.7841117
+      t_y: -0.6206198
+    }
+    points {
+      x: 1.0
+      y: 0.015711093
+      t_x: 0.56921566
+      t_y: -0.82218826
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ANGULAR_VELOCITY
+    points {
+      y: 15.0
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_ACCELERATION
+    rotation {
+      z: 1.0
+      w: 6.123234E-17
+    }
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: 10.0
+      }
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_RADIAL
+    rotation {
+      z: 1.0
+      w: 6.123234E-17
+    }
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: -250.0
+        t_x: 0.003265428
+        t_y: -0.9999947
+      }
+      points {
+        x: 1.0
+        y: -8.693188
+        t_x: 7.5198716E-4
+        t_y: 0.9999997
+      }
+    }
+    properties {
+      key: MODIFIER_KEY_MAX_DISTANCE
+      points {
+        y: 500.0
+      }
+    }
+  }
+  stretch_with_velocity: true
+}
+emitters {
+  id: "emitter4"
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.2
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_trace_01"
+  material: "/builtins/materials/particlefx.material"
+  blend_mode: BLEND_MODE_ADD
+  particle_orientation: PARTICLE_ORIENTATION_MOVEMENT_DIRECTION
+  max_particle_count: 150
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 10000.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_X
+    points {
+      y: 20.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_SIZE_Y
+    points {
+      y: 20.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+    spread: 0.1
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SPEED
+    points {
+      y: 300.0
+    }
+    points {
+      x: 0.11107492
+      y: 33.759693
+      t_x: 0.004660093
+      t_y: -0.99998915
+    }
+    points {
+      x: 1.0
+      y: 10.3574295
+    }
+    spread: 20.0
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 3.0
+    }
+    spread: 0.5
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 0.2
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.2
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_Y
+    points {
+      y: -0.5
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 0.42859232
+      t_x: 0.024429077
+      t_y: 0.99970156
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+    points {
+      x: 1.0
+      y: 0.60587674
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 0.21502538
+    }
+    points {
+      x: 1.0
+      y: 1.6762574
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 0.24819332
+    }
+    points {
+      x: 1.0
+      y: 1.7094253
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: -0.0012211404
+    }
+    points {
+      x: 0.08175896
+      y: 2.0447288
+      t_x: 0.14027841
+      t_y: -0.9901121
+    }
+    points {
+      x: 0.1985342
+      y: -1.6654266
+      t_x: 0.27199933
+      t_y: -0.96229744
+    }
+    points {
+      x: 0.63583064
+      y: -0.031716812
+      t_x: 0.29903722
+      t_y: 0.95424145
+    }
+    points {
+      x: 1.0
+      y: 0.009376409
+      t_x: 0.3381512
+      t_y: -0.9410918
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 6.6410265
+    }
+    points {
+      x: 0.10692182
+      y: 4.3885884
+      t_x: 0.34915915
+      t_y: -0.93706346
+    }
+    points {
+      x: 1.0
+      y: 0.22707473
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 15.942318
+    }
+    points {
+      x: 0.08151466
+      y: 4.3566136
+      t_x: 0.039131545
+      t_y: -0.9992341
+    }
+    points {
+      x: 0.5713355
+      y: 0.29830235
+      t_x: 0.96546835
+      t_y: -0.26052028
+    }
+    points {
+      x: 1.0
+      y: -0.5702326
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_ACCELERATION
+    rotation {
+      z: 1.0
+      w: 6.123234E-17
+    }
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: 100.0
+      }
+    }
+  }
+  modifiers {
+    type: MODIFIER_TYPE_RADIAL
+    properties {
+      key: MODIFIER_KEY_MAGNITUDE
+      points {
+        y: 0.0
+      }
+    }
+    properties {
+      key: MODIFIER_KEY_MAX_DISTANCE
+      points {
+        y: 30.0
+      }
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+  duration_spread: 0.1
+  stretch_with_velocity: true
+}
+emitters {
+  id: "emitter2"
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.1
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_circle_01"
+  material: "/builtins/materials/particlefx.material"
+  blend_mode: BLEND_MODE_ADD
+  max_particle_count: 2
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 60.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 0.20392157
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.039215688
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_X
+    points {
+      y: 2.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_Y
+    points {
+      y: 2.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 0.0050403317
+      t_x: 0.07567246
+      t_y: 0.9971327
+    }
+    points {
+      x: 0.13013029
+      y: 1.0516063
+      t_x: 0.22336559
+      t_y: 0.9747347
+    }
+    points {
+      x: 0.31872964
+      y: 0.12317026
+      t_x: 0.44993034
+      t_y: -0.89306366
+    }
+    points {
+      x: 0.6131107
+      y: -0.03477873
+      t_x: 0.18202555
+      t_y: -0.9832938
+    }
+    points {
+      x: 0.8332248
+      y: 0.0568189
+      t_x: 0.8397494
+      t_y: 0.5429742
+    }
+    points {
+      x: 1.0
+      y: 0.0
+      t_x: 0.99995637
+      t_y: -0.009341509
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+}
+emitters {
+  id: "emitter3"
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.1
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_light_01"
+  material: "/builtins/materials/particlefx.material"
+  max_particle_count: 1
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 1000.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 60.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 0.43137255
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.30980393
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_X
+    points {
+      y: 2.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_Y
+    points {
+      y: 2.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 9.531683E-5
+      t_x: 0.48855478
+      t_y: 0.8725332
+    }
+    points {
+      x: 0.014885994
+      y: 1.0171621
+      t_x: 0.06842353
+      t_y: 0.99765635
+    }
+    points {
+      x: 0.31872964
+      y: 0.12317026
+      t_x: 0.44993034
+      t_y: -0.89306366
+    }
+    points {
+      x: 0.5886808
+      y: -0.99066126
+      t_x: 0.18202555
+      t_y: -0.9832938
+    }
+    points {
+      x: 0.83127034
+      y: -0.3613797
+      t_x: 0.8397494
+      t_y: 0.5429742
+    }
+    points {
+      x: 1.0
+      y: 0.0
+      t_x: 0.99995637
+      t_y: -0.009341509
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 1.0156251
+    }
+    points {
+      x: 0.2022469
+      y: 3.2772617
+    }
+    points {
+      x: 0.38703388
+      y: 1.1423699
+      t_x: 0.3538746
+      t_y: -0.9352929
+    }
+    points {
+      x: 1.0
+      y: 0.014935664
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 1.0156251
+    }
+    points {
+      x: 0.2027355
+      y: 3.3074327
+      t_x: 0.92783904
+      t_y: -0.3729809
+    }
+    points {
+      x: 0.38931102
+      y: 1.1156069
+      t_x: 0.26103032
+      t_y: -0.9653306
+    }
+    points {
+      x: 1.0
+      y: 0.0
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+}
+emitters {
+  id: "emitter5"
+  mode: PLAY_MODE_ONCE
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    z: 0.1
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_light_01"
+  material: "/builtins/materials/particlefx.material"
+  blend_mode: BLEND_MODE_ADD
+  max_particle_count: 1
+  type: EMITTER_TYPE_CIRCLE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 1000.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 1.6
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 30.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 0.43137255
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.30980393
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_X
+    points {
+      y: 2.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_STRETCH_FACTOR_Y
+    points {
+      y: 2.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 9.531683E-5
+      t_x: 0.48855478
+      t_y: 0.8725332
+    }
+    points {
+      x: 0.014885994
+      y: 1.0171621
+      t_x: 0.06842353
+      t_y: 0.99765635
+    }
+    points {
+      x: 0.31872964
+      y: 0.12317026
+      t_x: 0.44993034
+      t_y: -0.89306366
+    }
+    points {
+      x: 0.5886808
+      y: -0.99066126
+      t_x: 0.18202555
+      t_y: -0.9832938
+    }
+    points {
+      x: 0.83127034
+      y: -0.3613797
+      t_x: 0.8397494
+      t_y: 0.5429742
+    }
+    points {
+      x: 1.0
+      y: 0.0
+      t_x: 0.99995637
+      t_y: -0.009341509
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 1.0156251
+    }
+    points {
+      x: 0.2022469
+      y: 3.2772617
+    }
+    points {
+      x: 0.38703388
+      y: 1.1423699
+      t_x: 0.3538746
+      t_y: -0.9352929
+    }
+    points {
+      x: 1.0
+      y: 0.014935664
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 1.0156251
+    }
+    points {
+      x: 0.2027355
+      y: 3.3074327
+      t_x: 0.92783904
+      t_y: -0.3729809
+    }
+    points {
+      x: 0.38931102
+      y: 1.1156069
+      t_x: 0.26103032
+      t_y: -0.9653306
+    }
+    points {
+      x: 1.0
+      y: 0.0
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+}

+ 4 - 0
examples/particles/fireworks/fw_trail_blue.go

@@ -0,0 +1,4 @@
+components {
+  id: "trail_blue"
+  component: "/examples/particles/fireworks/fw_trail_blue.particlefx"
+}

+ 273 - 0
examples/particles/fireworks/fw_trail_blue.particlefx

@@ -0,0 +1,273 @@
+emitters {
+  mode: PLAY_MODE_LOOP
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    y: -36.0
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_trace_02"
+  material: "/builtins/materials/particlefx.material"
+  particle_orientation: PARTICLE_ORIENTATION_MOVEMENT_DIRECTION
+  max_particle_count: 150
+  type: EMITTER_TYPE_CONE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 512.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 0.3
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SPEED
+    points {
+      y: 100.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 100.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 0.12941177
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 0.3019608
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.7019608
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 0.5190731
+    }
+    points {
+      x: 1.0
+      y: 0.0035842706
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 0.0
+      t_x: 0.07194582
+      t_y: 0.99740857
+    }
+    points {
+      x: 0.11320755
+      y: 0.99277455
+      t_x: 0.99418455
+      t_y: 0.10768964
+    }
+    points {
+      x: 0.38487023
+      y: 0.12991524
+      t_x: 0.8252495
+      t_y: -0.5647683
+    }
+    points {
+      x: 1.0
+      y: 7.7859504E-4
+      t_x: 0.9999786
+      t_y: -0.0065419343
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ANGULAR_VELOCITY
+    points {
+      y: 1.0
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+}
+emitters {
+  id: "emitter1"
+  mode: PLAY_MODE_LOOP
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    y: -19.0
+    z: 0.5
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_trace_02"
+  material: "/builtins/materials/particlefx.material"
+  blend_mode: BLEND_MODE_ADD
+  particle_orientation: PARTICLE_ORIENTATION_MOVEMENT_DIRECTION
+  max_particle_count: 128
+  type: EMITTER_TYPE_CONE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 500.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 0.3
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SPEED
+    points {
+      y: 100.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 50.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 0.5190731
+    }
+    points {
+      x: 1.0
+      y: 0.0035842706
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 0.0
+      t_x: 0.07194582
+      t_y: 0.99740857
+    }
+    points {
+      x: 0.11320755
+      y: 0.99277455
+      t_x: 0.99418455
+      t_y: 0.10768964
+    }
+    points {
+      x: 0.38487023
+      y: 0.12991524
+      t_x: 0.8252495
+      t_y: -0.5647683
+    }
+    points {
+      x: 1.0
+      y: 7.7859504E-4
+      t_x: 0.9999786
+      t_y: -0.0065419343
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ANGULAR_VELOCITY
+    points {
+      y: 1.0
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+}

+ 4 - 0
examples/particles/fireworks/fw_trail_green.go

@@ -0,0 +1,4 @@
+components {
+  id: "trail_green"
+  component: "/examples/particles/fireworks/fw_trail_green.particlefx"
+}

+ 273 - 0
examples/particles/fireworks/fw_trail_green.particlefx

@@ -0,0 +1,273 @@
+emitters {
+  mode: PLAY_MODE_LOOP
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    y: -36.0
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_trace_02"
+  material: "/builtins/materials/particlefx.material"
+  particle_orientation: PARTICLE_ORIENTATION_MOVEMENT_DIRECTION
+  max_particle_count: 150
+  type: EMITTER_TYPE_CONE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 512.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 0.3
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SPEED
+    points {
+      y: 100.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 100.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 0.2
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.3
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 0.5190731
+    }
+    points {
+      x: 1.0
+      y: 0.0035842706
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 0.0
+      t_x: 0.07194582
+      t_y: 0.99740857
+    }
+    points {
+      x: 0.11320755
+      y: 0.99277455
+      t_x: 0.99418455
+      t_y: 0.10768964
+    }
+    points {
+      x: 0.38487023
+      y: 0.12991524
+      t_x: 0.8252495
+      t_y: -0.5647683
+    }
+    points {
+      x: 1.0
+      y: 7.7859504E-4
+      t_x: 0.9999786
+      t_y: -0.0065419343
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ANGULAR_VELOCITY
+    points {
+      y: 1.0
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+}
+emitters {
+  id: "emitter1"
+  mode: PLAY_MODE_LOOP
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    y: -19.0
+    z: 0.5
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_trace_02"
+  material: "/builtins/materials/particlefx.material"
+  blend_mode: BLEND_MODE_ADD
+  particle_orientation: PARTICLE_ORIENTATION_MOVEMENT_DIRECTION
+  max_particle_count: 128
+  type: EMITTER_TYPE_CONE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 500.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 0.3
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SPEED
+    points {
+      y: 100.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 50.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 0.5190731
+    }
+    points {
+      x: 1.0
+      y: 0.0035842706
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 0.0
+      t_x: 0.07194582
+      t_y: 0.99740857
+    }
+    points {
+      x: 0.11320755
+      y: 0.99277455
+      t_x: 0.99418455
+      t_y: 0.10768964
+    }
+    points {
+      x: 0.38487023
+      y: 0.12991524
+      t_x: 0.8252495
+      t_y: -0.5647683
+    }
+    points {
+      x: 1.0
+      y: 7.7859504E-4
+      t_x: 0.9999786
+      t_y: -0.0065419343
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ANGULAR_VELOCITY
+    points {
+      y: 1.0
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+}

+ 4 - 0
examples/particles/fireworks/fw_trail_red.go

@@ -0,0 +1,4 @@
+components {
+  id: "trail_red"
+  component: "/examples/particles/fireworks/fw_trail_red.particlefx"
+}

+ 273 - 0
examples/particles/fireworks/fw_trail_red.particlefx

@@ -0,0 +1,273 @@
+emitters {
+  mode: PLAY_MODE_LOOP
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    y: -36.0
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_trace_02"
+  material: "/builtins/materials/particlefx.material"
+  particle_orientation: PARTICLE_ORIENTATION_MOVEMENT_DIRECTION
+  max_particle_count: 150
+  type: EMITTER_TYPE_CONE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 512.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 0.3
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SPEED
+    points {
+      y: 100.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 100.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 0.4
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 0.05
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 0.5190731
+    }
+    points {
+      x: 1.0
+      y: 0.0035842706
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 0.0
+      t_x: 0.07194582
+      t_y: 0.99740857
+    }
+    points {
+      x: 0.11320755
+      y: 0.99277455
+      t_x: 0.99418455
+      t_y: 0.10768964
+    }
+    points {
+      x: 0.38487023
+      y: 0.12991524
+      t_x: 0.8252495
+      t_y: -0.5647683
+    }
+    points {
+      x: 1.0
+      y: 7.7859504E-4
+      t_x: 0.9999786
+      t_y: -0.0065419343
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ANGULAR_VELOCITY
+    points {
+      y: 1.0
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+}
+emitters {
+  id: "emitter1"
+  mode: PLAY_MODE_LOOP
+  duration: 1.0
+  space: EMISSION_SPACE_WORLD
+  position {
+    y: -19.0
+    z: 0.5
+  }
+  tile_source: "/assets/sprites.atlas"
+  animation: "fw_trace_02"
+  material: "/builtins/materials/particlefx.material"
+  blend_mode: BLEND_MODE_ADD
+  particle_orientation: PARTICLE_ORIENTATION_MOVEMENT_DIRECTION
+  max_particle_count: 128
+  type: EMITTER_TYPE_CONE
+  properties {
+    key: EMITTER_KEY_SPAWN_RATE
+    points {
+      y: 500.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_LIFE_TIME
+    points {
+      y: 0.3
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SPEED
+    points {
+      y: 100.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_SIZE
+    points {
+      y: 50.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_RED
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  properties {
+    key: EMITTER_KEY_PARTICLE_ALPHA
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_SCALE
+    points {
+      y: 0.5190731
+    }
+    points {
+      x: 1.0
+      y: 0.0035842706
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_RED
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_GREEN
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_BLUE
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ALPHA
+    points {
+      y: 0.0
+      t_x: 0.07194582
+      t_y: 0.99740857
+    }
+    points {
+      x: 0.11320755
+      y: 0.99277455
+      t_x: 0.99418455
+      t_y: 0.10768964
+    }
+    points {
+      x: 0.38487023
+      y: 0.12991524
+      t_x: 0.8252495
+      t_y: -0.5647683
+    }
+    points {
+      x: 1.0
+      y: 7.7859504E-4
+      t_x: 0.9999786
+      t_y: -0.0065419343
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_X
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_STRETCH_FACTOR_Y
+    points {
+      y: 1.0
+    }
+  }
+  particle_properties {
+    key: PARTICLE_KEY_ANGULAR_VELOCITY
+    points {
+      y: 1.0
+    }
+  }
+  size_mode: SIZE_MODE_AUTO
+}

+ 53 - 0
examples/render/orbit_camera/orbit_camera.collection

@@ -0,0 +1,53 @@
+name: "orbit_camera"
+scale_along_z: 1
+embedded_instances {
+  id: "crate"
+  data: "embedded_components {\n"
+  "  id: \"model\"\n"
+  "  type: \"model\"\n"
+  "  data: \"mesh: \\\"/assets/models/kenney_prototype-kit/crate.glb\\\"\\n"
+  "name: \\\"{{NAME}}\\\"\\n"
+  "materials {\\n"
+  "  name: \\\"colormap\\\"\\n"
+  "  material: \\\"/examples/material/unlit/unlit.material\\\"\\n"
+  "  textures {\\n"
+  "    sampler: \\\"texture0\\\"\\n"
+  "    texture: \\\"/assets/models/kenney_prototype-kit/Textures/colormap.png\\\"\\n"
+  "  }\\n"
+  "}\\n"
+  "\"\n"
+  "}\n"
+  ""
+  position {
+    y: -0.25
+  }
+}
+embedded_instances {
+  id: "camera"
+  data: "components {\n"
+  "  id: \"main\"\n"
+  "  component: \"/examples/render/orbit_camera/orbit_camera.script\"\n"
+  "}\n"
+  "embedded_components {\n"
+  "  id: \"camera\"\n"
+  "  type: \"camera\"\n"
+  "  data: \"aspect_ratio: 1.0\\n"
+  "fov: 0.7854\\n"
+  "near_z: 0.1\\n"
+  "far_z: 1000.0\\n"
+  "auto_aspect_ratio: 1\\n"
+  "\"\n"
+  "}\n"
+  ""
+  position {
+    x: 2.0
+    y: 2.0
+    z: 2.0
+  }
+  rotation {
+    x: -0.27781594
+    y: 0.36497167
+    z: 0.11507513
+    w: 0.88111955
+  }
+}

+ 22 - 0
examples/render/orbit_camera/orbit_camera.md

@@ -0,0 +1,22 @@
+---
+name: Orbit Camera (3D)
+title: Orbit Camera
+brief: This example demonstrates how to create script to control a 3D camera with the mouse. Scroll wheel is used to zoom in and out.
+scripts: orbit_camera.script
+---
+
+In this example, we create a script to control a 3D camera using the mouse and mouse scroll wheel.
+
+We added two objects to the collection: a camera (`/camera`) and an object (`/crate`) that we will explore. In the `camera` object, we added the `orbit_camera.script` - the script that controls the camera. The properties defined in the script are:
+- `zoom`: the initial zoom level.
+- `zoom_speed`: the speed of the zoom.
+- `rotation_speed`: the speed of the rotation.
+- `offset`: the offset of the camera from the origin. Use it to move the camera away from the origin.
+
+During `init`, the script sets up the camera projection, acquires input focus, and establishes starting values for yaw, pitch, and zoom. 
+
+In the `update` loop, the script smoothly interpolates camera rotation and zoom (note: `vmath.lerp` is used and it doesn't depend on the delta time, so the camera will move at different speed on different devices), calculates the camera's rotation and position based on current yaw, pitch, and zoom values, and then updates the camera's position and rotation accordingly. This creates a fluid, responsive camera movement!
+
+The function `on_input` handles user input to control the camera. As the user moves the mouse or touches the screen, the script adjusts the yaw and pitch values, allowing the camera to rotate around its focal point. Additionally, it responds to mouse wheel input, adjusting the zoom level to move the camera closer to or further from the center point.
+
+The model used in this example is from Kenney's [Prototype Pack](https://kenney.nl/assets/prototype-kit), licensed under CC0.

+ 51 - 0
examples/render/orbit_camera/orbit_camera.script

@@ -0,0 +1,51 @@
+-- The initial zoom level
+go.property("zoom", 3)
+-- The speed of the zoom
+go.property("zoom_speed", 0.1)
+-- The speed of the rotation
+go.property("rotation_speed", 0.5)
+-- The offset of the camera from the origin
+go.property("offset", vmath.vector3(0, 0, 0))
+
+function init(self)
+	-- Set the camera projection to be used
+	msg.post("@render:", "use_camera_projection")
+	-- Acquire input focus to receive input events
+	msg.post(".", "acquire_input_focus")
+
+	-- Initialize start values
+	self.yaw = go.get(".", "euler.y")
+	self.pitch = go.get(".", "euler.x")
+	self.zoom_offset = 0
+	self.current_yaw = self.yaw
+	self.current_pitch = self.pitch
+	self.current_zoom = self.zoom_offset
+end
+
+function update(self, dt)
+	-- Animate camera rotation and zoom
+	self.current_yaw = vmath.lerp(0.15, self.current_yaw, self.yaw)
+	self.current_pitch = vmath.lerp(0.15, self.current_pitch, self.pitch)
+	self.current_zoom = vmath.lerp(0.15, self.current_zoom, self.zoom_offset)
+
+	-- Calculate rotation and position
+	local camera_yaw = vmath.quat_rotation_y(math.rad(self.current_yaw))
+	local camera_pitch = vmath.quat_rotation_x(math.rad(self.current_pitch))
+	local camera_rotation = camera_yaw * camera_pitch
+	local camera_position = self.offset + vmath.rotate(camera_rotation, vmath.vector3(0, 0, self.zoom + self.current_zoom))
+
+	-- Set camera position and rotation
+	go.set_position(camera_position)
+	go.set_rotation(camera_rotation)
+end
+
+function on_input(self, action_id, action)
+	if action_id == hash("touch") and not action.pressed then
+		self.yaw   = self.yaw   - action.dx * self.rotation_speed
+		self.pitch = self.pitch + action.dy * self.rotation_speed
+	elseif action_id == hash("wheel_up") then
+		self.zoom_offset = self.zoom_offset - self.zoom * self.zoom_speed
+	elseif action_id == hash("wheel_down") then
+		self.zoom_offset = self.zoom_offset + self.zoom * self.zoom_speed
+	end
+end

+ 1 - 1
game.project

@@ -2,7 +2,7 @@
 title = Defold-examples
 version = 0.1
 custom_resources = examples/resource/modify_atlas/resources
-dependencies#0 = https://github.com/defold/extension-spine/archive/refs/tags/3.5.0.zip
+dependencies#0 = https://github.com/defold/extension-spine/archive/refs/tags/3.5.3.zip
 
 [bootstrap]
 main_collection = /examples/main.collectionc