ashton 0.0.1alpha → 0.0.2alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. data/LICENSE +21 -21
  2. data/README.md +95 -68
  3. data/Rakefile +41 -23
  4. data/examples/bloom_example.rb +59 -0
  5. data/examples/lighting_example.rb +127 -0
  6. data/examples/media/SmallStar.png +0 -0
  7. data/examples/media/Starfighter.png +0 -0
  8. data/examples/media/simple.png +0 -0
  9. data/examples/noise_example.rb +94 -0
  10. data/examples/outline_example.rb +86 -0
  11. data/examples/particle_emitter_example.rb +114 -0
  12. data/examples/pixelate_example.rb +51 -49
  13. data/examples/pixelated_texture_example.rb +69 -0
  14. data/examples/radial_blur_example.rb +60 -62
  15. data/examples/shader_image_example.rb +74 -41
  16. data/examples/{shockwave2_example.rb → shockwave_example.rb} +74 -75
  17. data/examples/stencil_shader_example.rb +104 -0
  18. data/examples/{framebuffer_example.rb → texture_render_example.rb} +53 -49
  19. data/examples/{tv_screen_and_noise_example.rb → tv_screen_and_static_example.rb} +59 -59
  20. data/ext/ashton/GLee.c +18170 -0
  21. data/ext/ashton/GLee.h +17647 -0
  22. data/ext/ashton/ashton.c +42 -0
  23. data/ext/ashton/ashton.h +31 -0
  24. data/ext/ashton/color.c +45 -0
  25. data/ext/ashton/color.h +25 -0
  26. data/ext/ashton/common.h +41 -0
  27. data/ext/ashton/extconf.rb +42 -0
  28. data/ext/ashton/fast_math.c +30 -0
  29. data/ext/ashton/fast_math.h +30 -0
  30. data/ext/ashton/font.c +8 -0
  31. data/ext/ashton/font.h +16 -0
  32. data/ext/ashton/gosu.c +18 -0
  33. data/ext/ashton/gosu.h +19 -0
  34. data/ext/ashton/image.c +8 -0
  35. data/ext/ashton/image.h +16 -0
  36. data/ext/ashton/particle_emitter.c +788 -0
  37. data/ext/ashton/particle_emitter.h +171 -0
  38. data/ext/ashton/pixel_cache.c +237 -0
  39. data/ext/ashton/pixel_cache.h +58 -0
  40. data/ext/ashton/shader.c +9 -0
  41. data/ext/ashton/shader.h +16 -0
  42. data/ext/ashton/texture.c +442 -0
  43. data/ext/ashton/texture.h +63 -0
  44. data/ext/ashton/window.c +8 -0
  45. data/ext/ashton/window.h +16 -0
  46. data/lib/ashton.rb +38 -26
  47. data/lib/ashton/1.9/ashton.so +0 -0
  48. data/lib/ashton/gosu_ext/color.rb +24 -11
  49. data/lib/ashton/gosu_ext/font.rb +58 -0
  50. data/lib/ashton/gosu_ext/gosu_module.rb +16 -0
  51. data/lib/ashton/gosu_ext/image.rb +95 -31
  52. data/lib/ashton/gosu_ext/window.rb +78 -35
  53. data/lib/ashton/image_stub.rb +32 -36
  54. data/lib/ashton/lighting/light_source.rb +146 -0
  55. data/lib/ashton/lighting/manager.rb +98 -0
  56. data/lib/ashton/mixins/version_checking.rb +23 -0
  57. data/lib/ashton/particle_emitter.rb +87 -0
  58. data/lib/ashton/pixel_cache.rb +24 -0
  59. data/lib/ashton/shader.rb +353 -35
  60. data/lib/ashton/shaders/bloom.frag +41 -0
  61. data/lib/ashton/shaders/color_inversion.frag +11 -0
  62. data/lib/ashton/{post_process → shaders}/contrast.frag +16 -16
  63. data/lib/ashton/{shader → shaders}/default.frag +22 -19
  64. data/lib/ashton/{shader → shaders}/default.vert +13 -13
  65. data/lib/ashton/shaders/fade.frag +14 -0
  66. data/lib/ashton/shaders/grayscale.frag +15 -0
  67. data/lib/ashton/shaders/include/classicnoise2d.glsl +113 -0
  68. data/lib/ashton/shaders/include/classicnoise3d.glsl +177 -0
  69. data/lib/ashton/shaders/include/classicnoise4d.glsl +302 -0
  70. data/lib/ashton/{include/simplex.glsl → shaders/include/noise2d.glsl} +70 -63
  71. data/lib/ashton/shaders/include/noise3d.glsl +102 -0
  72. data/lib/ashton/shaders/include/noise4d.glsl +128 -0
  73. data/lib/ashton/shaders/include/rand.glsl +5 -0
  74. data/lib/ashton/shaders/lighting/distort.frag +57 -0
  75. data/lib/ashton/shaders/lighting/draw_shadows.frag +60 -0
  76. data/lib/ashton/shaders/lighting/shadow_blur.frag +60 -0
  77. data/lib/ashton/shaders/mezzotint.frag +22 -0
  78. data/lib/ashton/shaders/multitexture2.vert +19 -0
  79. data/lib/ashton/shaders/outline.frag +45 -0
  80. data/lib/ashton/{post_process → shaders}/pixelate.frag +48 -48
  81. data/lib/ashton/shaders/radial_blur.frag +63 -0
  82. data/lib/ashton/shaders/sepia.frag +26 -0
  83. data/lib/ashton/{post_process/shockwave2.frag → shaders/shockwave.frag} +38 -35
  84. data/lib/ashton/shaders/signed_distance_field.frag +80 -0
  85. data/lib/ashton/{post_process/noise.frag → shaders/static.frag} +25 -27
  86. data/lib/ashton/shaders/stencil.frag +27 -0
  87. data/lib/ashton/shaders/tv_screen.frag +23 -0
  88. data/lib/ashton/signed_distance_field.rb +151 -0
  89. data/lib/ashton/texture.rb +186 -0
  90. data/lib/ashton/version.rb +2 -2
  91. data/lib/ashton/window_buffer.rb +16 -0
  92. data/spec/ashton/ashton_spec.rb +22 -0
  93. data/spec/ashton/gosu_ext/color_spec.rb +34 -0
  94. data/spec/ashton/gosu_ext/font_spec.rb +57 -0
  95. data/spec/ashton/gosu_ext/gosu_spec.rb +11 -0
  96. data/spec/ashton/gosu_ext/image_spec.rb +66 -0
  97. data/spec/ashton/gosu_ext/window_spec.rb +71 -0
  98. data/spec/ashton/image_stub_spec.rb +46 -0
  99. data/spec/ashton/particle_emitter_spec.rb +123 -0
  100. data/spec/ashton/pixel_cache_spec.rb +153 -0
  101. data/spec/ashton/shader_spec.rb +152 -0
  102. data/spec/ashton/signed_distance_field_spec.rb +163 -0
  103. data/spec/ashton/texture_spec.rb +347 -0
  104. data/spec/helper.rb +12 -0
  105. metadata +159 -28
  106. data/examples/output/README.txt +0 -1
  107. data/lib/ashton/base_shader.rb +0 -172
  108. data/lib/ashton/framebuffer.rb +0 -183
  109. data/lib/ashton/post_process.rb +0 -83
  110. data/lib/ashton/post_process/default.vert +0 -9
  111. data/lib/ashton/post_process/fade.frag +0 -11
  112. data/lib/ashton/post_process/mezzotint.frag +0 -24
  113. data/lib/ashton/post_process/radial_blur.frag +0 -31
  114. data/lib/ashton/post_process/sepia.frag +0 -19
  115. data/lib/ashton/post_process/shockwave.frag +0 -40
  116. data/lib/ashton/post_process/tv_screen.frag +0 -32
@@ -0,0 +1,42 @@
1
+ /*
2
+ * module Ashton
3
+ *
4
+ * Ruby module extension for Gosu, implementing shaders and textures.
5
+ *
6
+ */
7
+
8
+ #include "ashton.h"
9
+
10
+ VALUE rb_mAshton;
11
+
12
+ void Init_ashton()
13
+ {
14
+ rb_mAshton = rb_define_module("Ashton");
15
+
16
+ srand((float)time(NULL));
17
+
18
+ Init_Gosu();
19
+
20
+ Init_Ashton_Texture(rb_mAshton);
21
+ Init_Ashton_ParticleEmitter(rb_mAshton);
22
+ Init_Ashton_Shader(rb_mAshton);
23
+ Init_Ashton_PixelCache(rb_mAshton);
24
+ //Init_Ashton_WindowBuffer(rb_mAshton);
25
+
26
+
27
+ rb_define_singleton_method(rb_mAshton, "fast_sin", Ashton_fast_sin, 1);
28
+ rb_define_singleton_method(rb_mAshton, "fast_cos", Ashton_fast_cos, 1);
29
+ rb_define_method(rb_mAshton, "fast_sin", Ashton_fast_sin, 1);
30
+ rb_define_method(rb_mAshton, "fast_cos", Ashton_fast_cos, 1);
31
+ }
32
+
33
+ VALUE Ashton_fast_sin(VALUE self, VALUE angle)
34
+ {
35
+ return rb_float_new(fast_sin_deg(NUM2DBL(angle)));
36
+ }
37
+
38
+ VALUE Ashton_fast_cos(VALUE self, VALUE angle)
39
+ {
40
+ return rb_float_new(fast_cos_deg(NUM2DBL(angle)));
41
+ }
42
+
@@ -0,0 +1,31 @@
1
+ /*
2
+ * module Ashton
3
+ *
4
+ * Ruby module extension for Gosu, implementing shaders and textures.
5
+ *
6
+ */
7
+
8
+
9
+ #ifndef ASHTON_H
10
+ #define ASHTON_H
11
+
12
+ #include <ruby.h>
13
+ #include <time.h>
14
+
15
+ #include "common.h"
16
+ #include "fast_math.h"
17
+
18
+ #include "gosu.h"
19
+
20
+ #include "texture.h"
21
+ #include "particle_emitter.h"
22
+ #include "pixel_cache.h"
23
+ #include "shader.h"
24
+ //#include "window_buffer.h"
25
+
26
+ void Init_ashton();
27
+ VALUE Ashton_fast_sin(VALUE self, VALUE angle);
28
+ VALUE Ashton_fast_cos(VALUE self, VALUE angle);
29
+
30
+ #endif // ASHTON_H
31
+
@@ -0,0 +1,45 @@
1
+ #include "color.h"
2
+
3
+ VALUE rb_cColor;
4
+
5
+ void Init_Gosu_Color(VALUE module)
6
+ {
7
+ rb_cColor = rb_define_class_under(module, "Color", rb_cObject);
8
+
9
+ rb_define_singleton_method(rb_cColor, "from_opengl", Gosu_Color_from_opengl, 1);
10
+
11
+ rb_define_method(rb_cColor, "to_opengl", Gosu_Color_to_opengl, 0);
12
+ rb_define_method(rb_cColor, "to_i", Gosu_Color_to_i, 0);
13
+ }
14
+
15
+ VALUE Gosu_Color_from_opengl(VALUE klass, VALUE array)
16
+ {
17
+ VALUE red = INT2NUM(round(NUM2DBL(rb_ary_entry(array, 0)) * 255.0));
18
+ VALUE green = INT2NUM(round(NUM2DBL(rb_ary_entry(array, 1)) * 255.0));
19
+ VALUE blue = INT2NUM(round(NUM2DBL(rb_ary_entry(array, 2)) * 255.0));
20
+ VALUE alpha = INT2NUM(round(NUM2DBL(rb_ary_entry(array, 3)) * 255.0));
21
+
22
+ return rb_funcall(klass, rb_intern("rgba"), 4, red, green, blue, alpha);
23
+ }
24
+
25
+ VALUE Gosu_Color_to_opengl(VALUE self)
26
+ {
27
+ VALUE array = rb_ary_new();
28
+
29
+ rb_ary_push(array, rb_float_new(NUM2INT(rb_funcall(self, rb_intern("red"), 0)) / 255.0));
30
+ rb_ary_push(array, rb_float_new(NUM2INT(rb_funcall(self, rb_intern("green"), 0)) / 255.0));
31
+ rb_ary_push(array, rb_float_new(NUM2INT(rb_funcall(self, rb_intern("blue"), 0)) / 255.0));
32
+ rb_ary_push(array, rb_float_new(NUM2INT(rb_funcall(self, rb_intern("alpha"), 0)) / 255.0));
33
+
34
+ return array;
35
+ }
36
+
37
+ VALUE Gosu_Color_to_i(VALUE self)
38
+ {
39
+ uint argb = (NUM2UINT(rb_funcall(self, rb_intern("alpha"), 0)) << 24) +
40
+ (NUM2UINT(rb_funcall(self, rb_intern("red"), 0)) << 16) +
41
+ (NUM2UINT(rb_funcall(self, rb_intern("green"), 0)) << 8) +
42
+ NUM2UINT(rb_funcall(self, rb_intern("blue"), 0));
43
+
44
+ return UINT2NUM(argb);
45
+ }
@@ -0,0 +1,25 @@
1
+ /*
2
+ * class Gosu::Color
3
+ *
4
+ *
5
+ */
6
+
7
+
8
+ #ifndef GOSU_COLOR_H
9
+ #define GOSU_COLOR_H
10
+
11
+ #include <math.h>
12
+
13
+ #include "common.h"
14
+
15
+ void Init_Gosu_Color(VALUE module);
16
+
17
+ // Singleton methods
18
+ VALUE Gosu_Color_from_opengl(VALUE klass, VALUE array);
19
+
20
+ // Methods
21
+ VALUE Gosu_Color_to_opengl(VALUE self);
22
+ VALUE Gosu_Color_to_i(VALUE self);
23
+
24
+ #endif // GOSU_COLOR_H
25
+
@@ -0,0 +1,41 @@
1
+ #ifndef COMMON_H
2
+ #define COMMON_H
3
+
4
+ #include <ruby.h>
5
+ #include <stdbool.h>
6
+
7
+ #include "GLee.h"
8
+
9
+ typedef unsigned char uchar;
10
+ typedef unsigned int uint;
11
+ typedef unsigned long ulong;
12
+
13
+ // Colour based on integer values (0..255)
14
+ typedef struct _color_i
15
+ {
16
+ uchar red, green, blue, alpha;
17
+ } Color_i;
18
+
19
+
20
+ // Colour based on float values (0.0..1.0)
21
+ typedef struct _color_f
22
+ {
23
+ float red, green, blue, alpha;
24
+ } Color_f;
25
+
26
+ #define SYMBOL(STR) ID2SYM(rb_intern(STR))
27
+
28
+ // Global variables for each Gosu module/class.
29
+ extern VALUE rb_mGosu;
30
+ extern VALUE rb_cColor;
31
+ extern VALUE rb_cFont;
32
+ extern VALUE rb_cImage;
33
+ extern VALUE rb_cWindow;
34
+
35
+ // Global variables for each Ashton module/class.
36
+ extern VALUE rb_mAshton;
37
+ extern VALUE rb_cPixelCache;
38
+ extern VALUE rb_cShader;
39
+ extern VALUE rb_cTexture;
40
+
41
+ #endif // COMMON_H
@@ -0,0 +1,42 @@
1
+ require 'mkmf'
2
+
3
+ RUBY_VERSION =~ /(\d+.\d+)/
4
+ extension_name = "ashton/#{$1}/ashton"
5
+
6
+ dir_config(extension_name)
7
+
8
+ case RUBY_PLATFORM
9
+ when /darwin/
10
+ # Everyone on OSX has plenty of OpenGL to go around.
11
+ $LDFLAGS << " -framework OpenGL"
12
+
13
+ when /win32|mingw/
14
+ gl_path = File.expand_path("../vendor/gl", __FILE__)
15
+ $LDFLAGS << %{ -L"#{gl_path}/lib"}
16
+ $CFLAGS << %{ -I"#{gl_path}/include"}
17
+ exit unless have_library('opengl32.lib', 'glVertex3d') || have_library('opengl32')
18
+
19
+ exit unless have_header 'GL/gl.h'
20
+
21
+ else
22
+ $LDFLAGS << " -lGL"
23
+
24
+ # You are on Linux, so everything is hunky dory!
25
+ exit unless have_library 'GL'
26
+ end
27
+
28
+ # 1.9 compatibility
29
+ $CFLAGS << ' -DRUBY_19' if RUBY_VERSION =~ /^1.9/
30
+
31
+ # let's use a nicer C (rather than C90)
32
+ $CFLAGS << " -std=gnu99"
33
+
34
+ # Make it possible to use a debugger.
35
+ #$CFLAGS << " -g -O0"
36
+
37
+ # Stop getting annoying warnings for valid C99 code.
38
+ $warnflags.gsub!('-Wdeclaration-after-statement', '') if $warnflags
39
+ $warnflags << ' -Wall' # Let's be good boys and girls!
40
+
41
+ create_header
42
+ create_makefile extension_name
@@ -0,0 +1,30 @@
1
+ #include "fast_math.h"
2
+
3
+ float sin_lookup[NUM_LOOKUP_VALUES];
4
+
5
+ //
6
+ void initialize_fast_math()
7
+ {
8
+ for(int i = 0; i < NUM_LOOKUP_VALUES; i++)
9
+ {
10
+ float angle = (float)i / LOOKUPS_PER_DEGREE;
11
+ sin_lookup[i] = sin(DEGREES_TO_RADIANS(angle + LOOKUP_PRECISION));
12
+ }
13
+
14
+ // Ensure the cardinal directions are 100% accurate.
15
+ for (int i = 0; i <= 270; i += 90)
16
+ {
17
+ sin_lookup[i * LOOKUPS_PER_DEGREE] = sin(DEGREES_TO_RADIANS(i));
18
+ }
19
+ }
20
+
21
+ float fast_sin_deg(float degrees)
22
+ {
23
+ // Normalize to 0..360 (i.e. 0..2PI)
24
+ degrees = fmod(degrees, 360.0f);
25
+ if(degrees < 0.0f) degrees += 360.0f;
26
+
27
+ int index = (int)(degrees * LOOKUPS_PER_DEGREE);
28
+
29
+ return sin_lookup[index % NUM_LOOKUP_VALUES];
30
+ }
@@ -0,0 +1,30 @@
1
+ // Lookup table based sin/cos using degrees.
2
+ //
3
+ // MUST call initialize_fast_math() before using the fast_ functions.
4
+
5
+ #ifndef FAST_MATH_H
6
+ #define FAST_MATH_H
7
+
8
+ #include <math.h>
9
+ #include <stdio.h>
10
+
11
+ #define DEGREES_TO_RADIANS(ANGLE) ((ANGLE - 90) * (M_PI / 180.0f))
12
+
13
+ #define LOOKUPS_PER_DEGREE 10
14
+
15
+ // Enough for values from 0..360 inclusive
16
+ #define NUM_LOOKUP_VALUES (360 * LOOKUPS_PER_DEGREE)
17
+
18
+ #define LOOKUP_PRECISION (1.0f / LOOKUPS_PER_DEGREE)
19
+
20
+ extern float sin_lookup[NUM_LOOKUP_VALUES];
21
+
22
+ void initialize_fast_math();
23
+
24
+ // sin implementation using lookup table, accepting degrees rather than radians.
25
+ float fast_sin_deg(float degrees);
26
+
27
+ // cos implementation using lookup table, accepting degrees rather than radians.
28
+ #define fast_cos_deg(degrees) fast_sin_deg((degrees) + 90.0f)
29
+
30
+ #endif // FAST_MATH_H
data/ext/ashton/font.c ADDED
@@ -0,0 +1,8 @@
1
+ #include "font.h"
2
+
3
+ VALUE rb_cFont;
4
+
5
+ void Init_Gosu_Font(VALUE module)
6
+ {
7
+ rb_cFont = rb_define_class_under(module, "Font", rb_cObject);
8
+ }
data/ext/ashton/font.h ADDED
@@ -0,0 +1,16 @@
1
+ /*
2
+ * class Gosu::Font
3
+ *
4
+ *
5
+ */
6
+
7
+
8
+ #ifndef GOSU_FONT_H
9
+ #define GOSU_FONT_H
10
+
11
+ #include "common.h"
12
+
13
+ void Init_Gosu_Font(VALUE module);
14
+
15
+ #endif // GOSU_FONT_H
16
+
data/ext/ashton/gosu.c ADDED
@@ -0,0 +1,18 @@
1
+ /*
2
+ * module Gosu being extended.
3
+ *
4
+ */
5
+
6
+ #include "gosu.h"
7
+
8
+ VALUE rb_mGosu;
9
+
10
+ void Init_Gosu()
11
+ {
12
+ rb_mGosu = rb_define_module("Gosu");
13
+
14
+ Init_Gosu_Color(rb_mGosu);
15
+ Init_Gosu_Font(rb_mGosu);
16
+ Init_Gosu_Image(rb_mGosu);
17
+ Init_Gosu_Window(rb_mGosu);
18
+ }
data/ext/ashton/gosu.h ADDED
@@ -0,0 +1,19 @@
1
+ /*
2
+ * module Gosu being extended.
3
+ *
4
+ */
5
+
6
+
7
+ #ifndef GOSU_H
8
+ #define GOSU_H
9
+
10
+ #include "common.h"
11
+
12
+ #include "color.h"
13
+ #include "font.h"
14
+ #include "image.h"
15
+ #include "window.h"
16
+
17
+ void Init_Gosu();
18
+
19
+ #endif // GOSU_H
@@ -0,0 +1,8 @@
1
+ #include "image.h"
2
+
3
+ VALUE rb_cImage;
4
+
5
+ void Init_Gosu_Image(VALUE module)
6
+ {
7
+ rb_cImage = rb_define_class_under(module, "Image", rb_cObject);
8
+ }
@@ -0,0 +1,16 @@
1
+ /*
2
+ * class Gosu::Image
3
+ *
4
+ *
5
+ */
6
+
7
+
8
+ #ifndef GOSU_IMAGE_H
9
+ #define GOSU_IMAGE_H
10
+
11
+ #include "common.h"
12
+
13
+ void Init_Gosu_Image(VALUE module);
14
+
15
+ #endif // GOSU_IMAGE_H
16
+
@@ -0,0 +1,788 @@
1
+ #include "particle_emitter.h"
2
+
3
+ // Helpers.
4
+ inline static float randf();
5
+ inline static float deviate(Range * range);
6
+ static bool color_changes(ParticleEmitter* emitter);
7
+ static bool texture_changes(ParticleEmitter* emitter);
8
+
9
+ static void update_particle(ParticleEmitter* emitter, Particle* particle, const float delta);
10
+ static void update_vbo(ParticleEmitter* emitter);
11
+ static void draw_vbo(ParticleEmitter* emitter);
12
+
13
+ static Vertex2d* write_particle_vertices(Vertex2d* vertex,
14
+ Particle* particle,
15
+ const uint width, const uint height);
16
+ static uint write_vertices_for_particles(Vertex2d* vertex,
17
+ Particle* first, Particle* last,
18
+ const uint width, const uint height);
19
+
20
+ static Vertex2d* write_particle_texture_coords(Vertex2d* texture_coord,
21
+ TextureInfo* texture_info);
22
+ static void write_texture_coords_for_particles(Vertex2d* texture_coord,
23
+ Particle* first, Particle* last,
24
+ TextureInfo* texture_info);
25
+ static void write_texture_coords_for_all_particles(Vertex2d *texture_coord,
26
+ TextureInfo* texture_info,
27
+ const uint num_particles);
28
+
29
+ static Color_i* write_particle_colors(Color_i* color_out, Color_f* color_in);
30
+ static void write_colors_for_particles(Color_i* color,
31
+ Particle* first, Particle* last);
32
+
33
+ static uint color_to_argb(Color_f* color);
34
+
35
+ static void init_vbo(ParticleEmitter* emitter);
36
+ static VALUE particle_emitter_allocate(VALUE klass);
37
+ static void particle_emitter_mark(ParticleEmitter* emitter);
38
+ static void particle_emitter_free(ParticleEmitter* emitter);
39
+
40
+
41
+ // === GETTERS & SETTERS ===
42
+ GET_SET_EMITTER_DATA(x, rb_float_new, (float)NUM2DBL);
43
+ GET_SET_EMITTER_DATA(y, rb_float_new, (float)NUM2DBL);
44
+ GET_SET_EMITTER_DATA(z, rb_float_new, (float)NUM2DBL);
45
+ GET_SET_EMITTER_DATA(gravity, rb_float_new, (float)NUM2DBL);
46
+
47
+ GET_SET_EMITTER_DATA_RANGE(center_x, rb_float_new, (float)NUM2DBL);
48
+ GET_SET_EMITTER_DATA_RANGE(center_y, rb_float_new, (float)NUM2DBL);
49
+
50
+ GET_SET_EMITTER_DATA_RANGE(angular_velocity, rb_float_new, (float)NUM2DBL);
51
+ GET_SET_EMITTER_DATA_RANGE(fade, rb_float_new, (float)NUM2DBL);
52
+ GET_SET_EMITTER_DATA_RANGE(friction, rb_float_new, (float)NUM2DBL);
53
+ GET_SET_EMITTER_DATA_RANGE(offset, rb_float_new, (float)NUM2DBL);
54
+ GET_SET_EMITTER_DATA_RANGE(scale, rb_float_new, (float)NUM2DBL);
55
+ GET_SET_EMITTER_DATA_RANGE(speed, rb_float_new, (float)NUM2DBL);
56
+ GET_SET_EMITTER_DATA_RANGE(time_to_live, rb_float_new, (float)NUM2DBL);
57
+ GET_SET_EMITTER_DATA_RANGE(zoom, rb_float_new, (float)NUM2DBL);
58
+
59
+ GET_EMITTER_DATA(max_particles, max_particles, UINT2NUM);
60
+ GET_EMITTER_DATA(count, count, UINT2NUM);
61
+
62
+ // Special case of interval, since that should also alter the time until emission.
63
+
64
+ //
65
+ VALUE Ashton_ParticleEmitter_set_interval_min(VALUE self, VALUE value)
66
+ {
67
+ EMITTER();
68
+ emitter->interval.min = (float)NUM2DBL(value);
69
+ emitter->time_until_emit = deviate(&emitter->interval);
70
+ return value;
71
+ }
72
+
73
+ //
74
+ VALUE Ashton_ParticleEmitter_get_interval_min(VALUE self)
75
+ {
76
+ EMITTER();
77
+ return rb_float_new(emitter->interval.min);
78
+ }
79
+
80
+ //
81
+ VALUE Ashton_ParticleEmitter_set_interval_max(VALUE self, VALUE value)
82
+ {
83
+ EMITTER();
84
+ emitter->interval.max = (float)NUM2DBL(value);
85
+ emitter->time_until_emit = deviate(&emitter->interval);
86
+ return value;
87
+ }
88
+
89
+ //
90
+ VALUE Ashton_ParticleEmitter_get_interval_max(VALUE self)
91
+ {
92
+ EMITTER();
93
+ return rb_float_new(emitter->interval.max);
94
+ }
95
+
96
+ // ----------------------------------------
97
+ void Init_Ashton_ParticleEmitter(VALUE module)
98
+ {
99
+ initialize_fast_math(); // Needed to save HUGE amount of time calculating sin/cos all the time!
100
+
101
+ VALUE rb_cParticleEmitter = rb_define_class_under(module, "ParticleEmitter", rb_cObject);
102
+
103
+ rb_define_alloc_func(rb_cParticleEmitter, particle_emitter_allocate);
104
+
105
+ rb_define_method(rb_cParticleEmitter, "initialize_", Ashton_ParticleEmitter_init, 4);
106
+
107
+ // Getters & Setters
108
+ DEFINE_METHOD_GET_SET(x);
109
+ DEFINE_METHOD_GET_SET(y);
110
+ DEFINE_METHOD_GET_SET(z);
111
+ DEFINE_METHOD_GET_SET(gravity);
112
+
113
+ DEFINE_METHOD_GET_SET_RANGE(angular_velocity);
114
+ DEFINE_METHOD_GET_SET_RANGE(center_x);
115
+ DEFINE_METHOD_GET_SET_RANGE(center_y);
116
+ DEFINE_METHOD_GET_SET_RANGE(fade);
117
+ DEFINE_METHOD_GET_SET_RANGE(friction);
118
+ DEFINE_METHOD_GET_SET_RANGE(interval);
119
+ DEFINE_METHOD_GET_SET_RANGE(offset);
120
+ DEFINE_METHOD_GET_SET_RANGE(scale);
121
+ DEFINE_METHOD_GET_SET_RANGE(speed);
122
+ DEFINE_METHOD_GET_SET_RANGE(time_to_live);
123
+ DEFINE_METHOD_GET_SET_RANGE(zoom);
124
+
125
+ // Getters
126
+ rb_define_method(rb_cParticleEmitter, "count", Ashton_ParticleEmitter_get_count, 0);
127
+ rb_define_method(rb_cParticleEmitter, "max_particles", Ashton_ParticleEmitter_get_max_particles, 0);
128
+ rb_define_method(rb_cParticleEmitter, "color_argb", Ashton_ParticleEmitter_get_color_argb, 0);
129
+
130
+ // Setters
131
+ rb_define_method(rb_cParticleEmitter, "color_argb=", Ashton_ParticleEmitter_set_color_argb, 1);
132
+
133
+ // Public methods.
134
+ rb_define_method(rb_cParticleEmitter, "draw", Ashton_ParticleEmitter_draw, 0);
135
+ rb_define_method(rb_cParticleEmitter, "emit", Ashton_ParticleEmitter_emit, 0);
136
+ rb_define_method(rb_cParticleEmitter, "update", Ashton_ParticleEmitter_update, 1);
137
+
138
+ rb_define_protected_method(rb_cParticleEmitter, "shader", Ashton_ParticleEmitter_get_shader, 0);
139
+ rb_define_protected_method(rb_cParticleEmitter, "shader=", Ashton_ParticleEmitter_set_shader, 1);
140
+
141
+ rb_define_protected_method(rb_cParticleEmitter, "image", Ashton_ParticleEmitter_get_image, 0);
142
+ rb_define_protected_method(rb_cParticleEmitter, "image=", Ashton_ParticleEmitter_set_image, 1);
143
+ }
144
+
145
+ // ----------------------------------------
146
+ // Ashton::ParticleEmitter#initialize
147
+ VALUE Ashton_ParticleEmitter_init(VALUE self, VALUE x, VALUE y, VALUE z, VALUE max_particles)
148
+ {
149
+ EMITTER();
150
+
151
+ emitter->x = (float)NUM2DBL(x);
152
+ emitter->y = (float)NUM2DBL(y);
153
+ emitter->z = (float)NUM2DBL(z);
154
+
155
+ // Create space for all the particles we'll ever need!
156
+ emitter->max_particles = NUM2UINT(max_particles);
157
+
158
+ init_vbo(emitter);
159
+
160
+ emitter->particles = ALLOC_N(Particle, emitter->max_particles);
161
+ memset(emitter->particles, 0, emitter->max_particles * sizeof(Particle));
162
+
163
+ return self;
164
+ }
165
+
166
+ //
167
+ static void init_vbo(ParticleEmitter* emitter)
168
+ {
169
+ if(!GL_ARB_vertex_buffer_object)
170
+ {
171
+ rb_raise(rb_eRuntimeError, "Ashton::ParticleEmitter requires GL_ARB_vertex_buffer_object, which is not supported by your OpenGL");
172
+ }
173
+
174
+ int num_vertices = emitter->max_particles * VERTICES_IN_PARTICLE;
175
+
176
+ emitter->color_array = ALLOC_N(Color_i, num_vertices);
177
+ emitter->color_array_offset = 0;
178
+
179
+ emitter->texture_coords_array = ALLOC_N(Vertex2d, num_vertices);
180
+ emitter->texture_coords_array_offset = sizeof(Color_i) * num_vertices;
181
+
182
+ emitter->vertex_array = ALLOC_N(Vertex2d, num_vertices);
183
+ emitter->vertex_array_offset = (sizeof(Color_i) + sizeof(Vertex2d)) * num_vertices;
184
+
185
+ // Create the VBO, but don't upload any data yet.
186
+ int data_size = (sizeof(Color_i) + sizeof(Vertex2d) + sizeof(Vertex2d)) * num_vertices;
187
+ glGenBuffersARB(1, &emitter->vbo_id);
188
+ glBindBufferARB(GL_ARRAY_BUFFER_ARB, emitter->vbo_id);
189
+ glBufferDataARB(GL_ARRAY_BUFFER_ARB, data_size, NULL, GL_STREAM_DRAW_ARB);
190
+
191
+ // Check the buffer was actually created.
192
+ int buffer_size = 0;
193
+ glGetBufferParameterivARB(GL_ARRAY_BUFFER_ARB, GL_BUFFER_SIZE_ARB, &buffer_size);
194
+ if(buffer_size != data_size)
195
+ {
196
+ rb_raise(rb_eRuntimeError, "Failed to create a VBO [%d bytes] to hold emitter data.", data_size);
197
+ }
198
+
199
+ glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
200
+
201
+ return;
202
+ }
203
+
204
+ // ----------------------------------------
205
+ VALUE Ashton_ParticleEmitter_set_shader(VALUE self, VALUE shader)
206
+ {
207
+ EMITTER();
208
+ emitter->rb_shader = shader;
209
+ return shader;
210
+ }
211
+
212
+ // ----------------------------------------
213
+ VALUE Ashton_ParticleEmitter_get_shader(VALUE self)
214
+ {
215
+ EMITTER();
216
+ return emitter->rb_shader;
217
+ }
218
+
219
+ // ----------------------------------------
220
+ VALUE Ashton_ParticleEmitter_get_image(VALUE self)
221
+ {
222
+ EMITTER();
223
+ return emitter->rb_image;
224
+ }
225
+
226
+ // ----------------------------------------
227
+ // Update the texture coordinates when a new image is chosen.
228
+ VALUE Ashton_ParticleEmitter_set_image(VALUE self, VALUE image)
229
+ {
230
+ EMITTER();
231
+
232
+ emitter->rb_image = image;
233
+
234
+ // Pixel size of image.
235
+ emitter->width = NUM2UINT(rb_funcall(image, rb_intern("width"), 0));
236
+ emitter->height = NUM2UINT(rb_funcall(image, rb_intern("height"), 0));
237
+
238
+ // Fill the array with all the same coords (won't be used if the image changes dynamically).
239
+ VALUE tex_info = rb_funcall(image, rb_intern("gl_tex_info"), 0);
240
+ emitter->texture_info.id = FIX2UINT(rb_funcall(tex_info, rb_intern("tex_name"), 0));
241
+ emitter->texture_info.left = (float)NUM2DBL(rb_funcall(tex_info, rb_intern("left"), 0));
242
+ emitter->texture_info.right = (float)NUM2DBL(rb_funcall(tex_info, rb_intern("right"), 0));
243
+ emitter->texture_info.top = (float)NUM2DBL(rb_funcall(tex_info, rb_intern("top"), 0));
244
+ emitter->texture_info.bottom = (float)NUM2DBL(rb_funcall(tex_info, rb_intern("bottom"), 0));
245
+
246
+ write_texture_coords_for_all_particles(emitter->texture_coords_array,
247
+ &emitter->texture_info,
248
+ emitter->max_particles);
249
+
250
+ // Push whole array to graphics card.
251
+ glBindBufferARB(GL_ARRAY_BUFFER_ARB, emitter->vbo_id);
252
+
253
+ glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, emitter->texture_coords_array_offset,
254
+ sizeof(Vertex2d) * VERTICES_IN_PARTICLE * emitter->max_particles,
255
+ emitter->texture_coords_array);
256
+
257
+ glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
258
+
259
+ return image;
260
+ }
261
+
262
+ // ----------------------------------------
263
+ static VALUE particle_emitter_allocate(VALUE klass)
264
+ {
265
+ ParticleEmitter* emitter = ALLOC(ParticleEmitter);
266
+ memset(emitter, 0, sizeof(ParticleEmitter));
267
+
268
+ return Data_Wrap_Struct(klass, particle_emitter_mark, particle_emitter_free, emitter);
269
+ }
270
+
271
+ // ----------------------------------------
272
+ static void particle_emitter_mark(ParticleEmitter* emitter)
273
+ {
274
+ if(!NIL_P(emitter->rb_shader)) rb_gc_mark(emitter->rb_shader);
275
+ rb_gc_mark(emitter->rb_image);
276
+ }
277
+
278
+ // ----------------------------------------
279
+ // Deallocate data structure and its contents.
280
+ static void particle_emitter_free(ParticleEmitter* emitter)
281
+ {
282
+ glDeleteBuffersARB(1, &emitter->vbo_id);
283
+ xfree(emitter->color_array);
284
+ xfree(emitter->texture_coords_array);
285
+ xfree(emitter->vertex_array);
286
+
287
+ xfree(emitter->particles);
288
+ xfree(emitter);
289
+ }
290
+
291
+ // === HELPERS ===
292
+
293
+ // ----------------------------------------
294
+ // Simple random numbers used by #deviate (0.0 <= randf() < 1.0)
295
+ inline static float randf()
296
+ {
297
+ return (float)rand() / RAND_MAX;
298
+ }
299
+
300
+ // ----------------------------------------
301
+ // Deviate a value from a median value within a range.
302
+ inline static float deviate(Range * range)
303
+ {
304
+ if(isfinite(range->min) && isfinite(range->max))
305
+ {
306
+ float deviation = (range->max - range->min) / 2.0;
307
+ return range->min + deviation + randf() * deviation - randf() * deviation;
308
+ }
309
+ else
310
+ {
311
+ return range->max;
312
+ }
313
+ }
314
+
315
+ // ----------------------------------------
316
+ static Vertex2d* write_particle_vertices(Vertex2d* vertex, Particle* particle,
317
+ const uint width, const uint height)
318
+ {
319
+ // Totally ripped this code from Gosu :$
320
+ float sizeX = width * particle->scale;
321
+ float sizeY = height * particle->scale;
322
+
323
+ float offsX = fast_sin_deg(particle->angle);
324
+ float offsY = fast_cos_deg(particle->angle);
325
+
326
+ float distToLeftX = +offsY * sizeX * particle->center_x;
327
+ float distToLeftY = -offsX * sizeX * particle->center_x;
328
+ float distToRightX = -offsY * sizeX * (1 - particle->center_x);
329
+ float distToRightY = +offsX * sizeX * (1 - particle->center_x);
330
+ float distToTopX = +offsX * sizeY * particle->center_y;
331
+ float distToTopY = +offsY * sizeY * particle->center_y;
332
+ float distToBottomX = -offsX * sizeY * (1 - particle->center_y);
333
+ float distToBottomY = -offsY * sizeY * (1 - particle->center_y);
334
+
335
+ vertex->x = particle->x + distToLeftX + distToTopX;
336
+ vertex->y = particle->y + distToLeftY + distToTopY;
337
+ vertex++;
338
+
339
+ vertex->x = particle->x + distToRightX + distToTopX;
340
+ vertex->y = particle->y + distToRightY + distToTopY;
341
+ vertex++;
342
+
343
+ vertex->x = particle->x + distToRightX + distToBottomX;
344
+ vertex->y = particle->y + distToRightY + distToBottomY;
345
+ vertex++;
346
+
347
+ vertex->x = particle->x + distToLeftX + distToBottomX;
348
+ vertex->y = particle->y + distToLeftY + distToBottomY;
349
+ vertex++;
350
+
351
+ return vertex;
352
+ }
353
+
354
+ // ----------------------------------------
355
+ // Calculate the vertices for all active particles
356
+ static uint write_vertices_for_particles(Vertex2d *vertex,
357
+ Particle* first, Particle* last,
358
+ const uint width, const uint height)
359
+ {
360
+ int num_particles_written = 0;
361
+
362
+ for(Particle* particle = first; particle <= last; particle++)
363
+ {
364
+ if(particle->time_to_live > 0)
365
+ {
366
+ vertex = write_particle_vertices(vertex, particle, width, height);
367
+ num_particles_written++;
368
+ }
369
+ }
370
+
371
+ return num_particles_written;
372
+ }
373
+
374
+ // ----------------------------------------
375
+ static Vertex2d* write_particle_texture_coords(Vertex2d* texture_coord,
376
+ TextureInfo* texture_info)
377
+ {
378
+ texture_coord->x = texture_info->left;
379
+ texture_coord->y = texture_info->top;
380
+ texture_coord++;
381
+
382
+ texture_coord->x = texture_info->right;
383
+ texture_coord->y = texture_info->top;
384
+ texture_coord++;
385
+
386
+ texture_coord->x = texture_info->right;
387
+ texture_coord->y = texture_info->bottom;
388
+ texture_coord++;
389
+
390
+ texture_coord->x = texture_info->left;
391
+ texture_coord->y = texture_info->bottom;
392
+ texture_coord++;
393
+
394
+ return texture_coord;
395
+ }
396
+
397
+ // ----------------------------------------
398
+ // Write out texture coords, assuming image is animated.
399
+ static void write_texture_coords_for_particles(Vertex2d *texture_coord,
400
+ Particle* first, Particle* last,
401
+ TextureInfo * texture_info)
402
+ {
403
+ for(Particle* particle = first; particle <= last; particle++)
404
+ {
405
+ if(particle->time_to_live > 0)
406
+ {
407
+ texture_coord = write_particle_texture_coords(texture_coord, texture_info);
408
+ }
409
+ }
410
+ }
411
+
412
+ // ----------------------------------------
413
+ // Write all texture coords, assuming the image isn't animated.
414
+ static void write_texture_coords_for_all_particles(Vertex2d *texture_coord,
415
+ TextureInfo * texture_info,
416
+ const uint num_particles)
417
+ {
418
+ for(uint i = 0; i < num_particles; i++)
419
+ {
420
+ texture_coord = write_particle_texture_coords(texture_coord, texture_info);
421
+ }
422
+ }
423
+
424
+ // ----------------------------------------
425
+ static Color_i* write_particle_colors(Color_i* color_out, Color_f* color_in)
426
+ {
427
+ // Convert the color from float to int (1/4 the data size).
428
+ Color_i color;
429
+ color.red = color_in->red * 255;
430
+ color.green = color_in->green * 255;
431
+ color.blue = color_in->blue * 255;
432
+ color.alpha = color_in->alpha * 255;
433
+
434
+ *color_out = color;
435
+ color_out++;
436
+ *color_out = color;
437
+ color_out++;
438
+ *color_out = color;
439
+ color_out++;
440
+ *color_out = color;
441
+ color_out++;
442
+
443
+ return color_out;
444
+ }
445
+
446
+ // ----------------------------------------
447
+ static void write_colors_for_particles(Color_i *color,
448
+ Particle* first, Particle* last)
449
+ {
450
+ for(Particle* particle = first; particle <= last; particle++)
451
+ {
452
+ if(particle->time_to_live > 0)
453
+ {
454
+ color = write_particle_colors(color, &particle->color);
455
+ }
456
+ }
457
+ }
458
+
459
+ // --------------------------------------
460
+ // Is the colour animated (e.g. is fade set)?
461
+ static bool color_changes(ParticleEmitter* emitter)
462
+ {
463
+ return ((emitter->fade.min != 0.0) || (emitter->fade.max != 0.0));
464
+ }
465
+
466
+ // --------------------------------------
467
+ // Is the texture animated?
468
+ static bool texture_changes(ParticleEmitter* emitter)
469
+ {
470
+ return false;
471
+ }
472
+
473
+ // --------------------------------------
474
+ static void update_vbo(ParticleEmitter* emitter)
475
+ {
476
+ // Ensure that drawing order is correct by drawing in order of creation...
477
+
478
+ // First, we draw all those from after the current, going up to the last one.
479
+ Particle* first = &emitter->particles[emitter->next_particle_index];
480
+ Particle* last = &emitter->particles[emitter->max_particles - 1];
481
+ if(color_changes(emitter))
482
+ {
483
+ write_colors_for_particles(emitter->color_array,
484
+ first, last);
485
+ }
486
+ if(texture_changes(emitter))
487
+ {
488
+ write_texture_coords_for_particles(emitter->texture_coords_array,
489
+ first, last,
490
+ &emitter->texture_info);
491
+ }
492
+ uint num_particles_written = write_vertices_for_particles(emitter->vertex_array,
493
+ first, last,
494
+ emitter->width, emitter->height);
495
+
496
+ // When we copy the second half of the particles, we want to start writing further on.
497
+ uint offset = num_particles_written * VERTICES_IN_PARTICLE;
498
+
499
+ // Then go from the first to the current.
500
+ if(emitter->next_particle_index > 0)
501
+ {
502
+ Particle* first = &emitter->particles[0];
503
+ Particle* last = &emitter->particles[emitter->next_particle_index - 1];
504
+ if(color_changes(emitter))
505
+ {
506
+ write_colors_for_particles(&emitter->color_array[offset],
507
+ first, last);
508
+ }
509
+
510
+ if(texture_changes(emitter))
511
+ {
512
+ write_texture_coords_for_particles(&emitter->texture_coords_array[offset],
513
+ first, last,
514
+ &emitter->texture_info);
515
+ }
516
+
517
+ write_vertices_for_particles(&emitter->vertex_array[offset],
518
+ first, last,
519
+ emitter->width, emitter->height);
520
+ }
521
+
522
+ // Upload the data, but only as much as we are actually using.
523
+ glBindBufferARB(GL_ARRAY_BUFFER_ARB, emitter->vbo_id);
524
+ if(color_changes(emitter))
525
+ {
526
+ glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, emitter->color_array_offset,
527
+ sizeof(Color_i) * VERTICES_IN_PARTICLE * emitter->count,
528
+ emitter->color_array);
529
+ }
530
+
531
+ if(texture_changes(emitter))
532
+ {
533
+ glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, emitter->texture_coords_array_offset,
534
+ sizeof(Vertex2d) * VERTICES_IN_PARTICLE * emitter->count,
535
+ emitter->texture_coords_array);
536
+ }
537
+
538
+ glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, emitter->vertex_array_offset,
539
+ sizeof(Vertex2d) * VERTICES_IN_PARTICLE * emitter->count,
540
+ emitter->vertex_array);
541
+
542
+ glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
543
+ }
544
+
545
+ // --------------------------------------
546
+ static void draw_vbo(ParticleEmitter* emitter)
547
+ {
548
+ glEnable(GL_BLEND);
549
+ glEnable(GL_TEXTURE_2D);
550
+ glBindTexture(GL_TEXTURE_2D, emitter->texture_info.id);
551
+
552
+ glBindBufferARB(GL_ARRAY_BUFFER_ARB, emitter->vbo_id);
553
+
554
+ // Only use colour array if colours are dynamic. Otherwise a single colour setting is enough.
555
+ if(color_changes(emitter))
556
+ {
557
+ glEnableClientState(GL_COLOR_ARRAY);
558
+ glColorPointer(4, GL_UNSIGNED_BYTE, 0, (void*)emitter->color_array_offset);
559
+ }
560
+ else
561
+ {
562
+ glColor4fv((GLfloat*)&emitter->color);
563
+ }
564
+
565
+ // Always use the texture array, even if it is static.
566
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
567
+ glTexCoordPointer(2, GL_FLOAT, 0, (void*)emitter->texture_coords_array_offset);
568
+
569
+ // Vertex array will always be dynamic.
570
+ glEnableClientState(GL_VERTEX_ARRAY);
571
+ glVertexPointer(2, GL_FLOAT, 0, (void*)emitter->vertex_array_offset);
572
+
573
+ glDrawArrays(GL_QUADS, 0, emitter->count * VERTICES_IN_PARTICLE);
574
+
575
+ if(color_changes(emitter)) glDisableClientState(GL_COLOR_ARRAY);
576
+ glDisableClientState(GL_TEXTURE_COORD_ARRAY);
577
+ glDisableClientState(GL_VERTEX_ARRAY);
578
+
579
+ glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
580
+ }
581
+
582
+ // ----------------------------------------
583
+ static VALUE draw_block(VALUE yield_value, VALUE self, int argc, VALUE argv[])
584
+ {
585
+ EMITTER();
586
+
587
+ if(!NIL_P(emitter->rb_shader))
588
+ {
589
+ rb_funcall(emitter->rb_shader, rb_intern("image="), 1, emitter->rb_image);
590
+ rb_funcall(emitter->rb_shader, rb_intern("color="), 1, UINT2NUM(color_to_argb(&emitter->color)));
591
+ }
592
+
593
+ draw_vbo(emitter);
594
+
595
+ return Qnil;
596
+ }
597
+
598
+ // ----------------------------------------
599
+ // Convert Color structure into 0xAARRGGBB value.
600
+ static uint color_to_argb(Color_f* color)
601
+ {
602
+ uint argb = ((((uint)(color->alpha * 255.0)) & 0xff) << 24) +
603
+ ((((uint)(color->red * 255.0)) & 0xff) << 16) +
604
+ ((((uint)(color->green * 255.0)) & 0xff) << 8) +
605
+ (((uint)(color->blue * 255.0)) & 0xff);
606
+
607
+ return argb;
608
+ }
609
+
610
+ // === Getters & setters ===
611
+
612
+ // ----------------------------------------
613
+ // #color
614
+ VALUE Ashton_ParticleEmitter_get_color_argb(VALUE self)
615
+ {
616
+ EMITTER();
617
+
618
+ uint color = color_to_argb(&emitter->color);
619
+
620
+ return UINT2NUM(color);
621
+ }
622
+
623
+ // ----------------------------------------
624
+ // #color=
625
+ VALUE Ashton_ParticleEmitter_set_color_argb(VALUE self, VALUE color)
626
+ {
627
+ EMITTER();
628
+
629
+ uint argb = NUM2UINT(color);
630
+
631
+ emitter->color.alpha = ((argb >> 24) & 0xff) / 255.0;
632
+ emitter->color.red = ((argb >> 16) & 0xff) / 255.0;
633
+ emitter->color.green = ((argb >> 8) & 0xff) / 255.0;
634
+ emitter->color.blue = (argb & 0xff) / 255.0;
635
+
636
+ return color;
637
+ }
638
+
639
+ // === METHODS ===
640
+
641
+ // ----------------------------------------
642
+ // #draw
643
+ VALUE Ashton_ParticleEmitter_draw(VALUE self)
644
+ {
645
+ EMITTER();
646
+
647
+ if(emitter->count == 0) return Qnil;
648
+
649
+ VALUE window = rb_gv_get("$window");
650
+ VALUE z = rb_float_new(emitter->z);
651
+
652
+ // Enable the shader, if provided.
653
+ if(!NIL_P(emitter->rb_shader)) rb_funcall(emitter->rb_shader, rb_intern("enable"), 1, z);
654
+
655
+ // Run the actual drawing operation at the correct Z-order.
656
+ rb_block_call(window, rb_intern("gl"), 1, &z,
657
+ RUBY_METHOD_FUNC(draw_block), self);
658
+
659
+ // Disable the shader, if provided.
660
+ if(!NIL_P(emitter->rb_shader)) rb_funcall(emitter->rb_shader, rb_intern("disable"), 1, z);
661
+
662
+ return Qnil;
663
+ }
664
+
665
+ // ----------------------------------------
666
+ // #emit
667
+ // Generate a single particle.
668
+ VALUE Ashton_ParticleEmitter_emit(VALUE self)
669
+ {
670
+ EMITTER();
671
+
672
+ // Find the first dead particle in the heap, or overwrite the oldest one.
673
+ Particle* particle = &emitter->particles[emitter->next_particle_index];
674
+
675
+ // If we are replacing an old one, remove it from the count and clear it to fresh.
676
+ if(particle->time_to_live > 0)
677
+ {
678
+ // Kill off and replace one with time to live :(
679
+ memset(particle, 0, sizeof(Particle));
680
+ }
681
+ else
682
+ {
683
+ emitter->count++; // Dead or never been used.
684
+ }
685
+
686
+ // Lets move the index onto the next one, or loop around.
687
+ emitter->next_particle_index = (emitter->next_particle_index + 1) % emitter->max_particles;
688
+
689
+ // Which way will the particle move?
690
+ float movement_angle = randf() * 360;
691
+ float speed = deviate(&emitter->speed);
692
+
693
+ // How far away from the origin will the particle spawn?
694
+ float offset = deviate(&emitter->offset);
695
+ float position_angle = randf() * 360;
696
+
697
+ particle->angle = position_angle; // TODO: Which initial facing?
698
+ particle->x = emitter->x + fast_sin_deg(position_angle) * offset;
699
+ particle->y = emitter->y + fast_cos_deg(position_angle) * offset;
700
+ particle->velocity_x = sin(movement_angle) * speed;
701
+ particle->velocity_y = cos(movement_angle) * speed;
702
+
703
+ particle->color = emitter->color;
704
+
705
+ particle->angular_velocity = deviate(&emitter->angular_velocity);
706
+ particle->center_x = deviate(&emitter->center_x);
707
+ particle->center_y = deviate(&emitter->center_y);
708
+ particle->fade = deviate(&emitter->fade);
709
+ particle->friction = deviate(&emitter->friction);
710
+ particle->scale = deviate(&emitter->scale);
711
+ particle->time_to_live = deviate(&emitter->time_to_live);
712
+ particle->zoom = deviate(&emitter->zoom);
713
+
714
+ return Qnil;
715
+ }
716
+
717
+ // ----------------------------------------
718
+ static void update_particle(ParticleEmitter* emitter, Particle* particle,
719
+ const float delta)
720
+ {
721
+ // Apply friction
722
+ particle->velocity_x *= 1.0 - particle->friction * delta;
723
+ particle->velocity_y *= 1.0 - particle->friction * delta;
724
+
725
+ // Gravity.
726
+ particle->velocity_y += emitter->gravity * delta;
727
+
728
+ // Move
729
+ particle->x += particle->velocity_x * delta;
730
+ particle->y += particle->velocity_y * delta;
731
+
732
+ // Rotate.
733
+ particle->angle += particle->angular_velocity * delta;
734
+
735
+ // Resize.
736
+ particle->scale += particle->zoom * delta;
737
+
738
+ // Fade out.
739
+ particle->color.alpha -= (particle->fade / 255.0) * delta;
740
+
741
+ particle->time_to_live -= delta;
742
+
743
+ // Die if out of time, invisible or shrunk to nothing.
744
+ if((particle->time_to_live <= 0) ||
745
+ (particle->color.alpha <= 0) ||
746
+ (particle->scale <= 0))
747
+ {
748
+ particle->time_to_live = 0;
749
+ emitter->count -= 1;
750
+ }
751
+ }
752
+
753
+ // ----------------------------------------
754
+ // #update(delta)
755
+ VALUE Ashton_ParticleEmitter_update(VALUE self, VALUE delta)
756
+ {
757
+ EMITTER();
758
+
759
+ float _delta = (float)NUM2DBL(delta);
760
+ if(_delta < 0.0) rb_raise(rb_eArgError, "delta must be >= 0");
761
+
762
+ if(emitter->count > 0)
763
+ {
764
+ Particle* particle = emitter->particles;
765
+ Particle* last = &emitter->particles[emitter->max_particles - 1];
766
+ for(; particle <= last; particle++)
767
+ {
768
+ // Ignore particles that are already dead.
769
+ if(particle->time_to_live > 0)
770
+ {
771
+ update_particle(emitter, particle, _delta);
772
+ }
773
+ }
774
+ }
775
+
776
+ // Time to emit one (or more) new particles?
777
+ emitter->time_until_emit -= _delta;
778
+ while(emitter->time_until_emit <= 0)
779
+ {
780
+ rb_funcall(self, rb_intern("emit"), 0);
781
+ emitter->time_until_emit += deviate(&emitter->interval);
782
+ }
783
+
784
+ // Copy all the current data onto the graphics card.
785
+ if(emitter->count > 0) update_vbo(emitter);
786
+
787
+ return Qnil;
788
+ }