ashton 0.0.1alpha → 0.0.2alpha

Sign up to get free protection for your applications and to get access to all the features.
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
+ }