bulldog_physics 0.1.0
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.
- data/.document +5 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +22 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +25 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/bulldog_physics.gemspec +92 -0
- data/lib/Particles/particle.rb +75 -0
- data/lib/Particles/particle_anchored_spring.rb +42 -0
- data/lib/Particles/particle_cable.rb +52 -0
- data/lib/Particles/particle_contact.rb +121 -0
- data/lib/Particles/particle_contact_generator.rb +28 -0
- data/lib/Particles/particle_contact_resolver.rb +49 -0
- data/lib/Particles/particle_drag.rb +30 -0
- data/lib/Particles/particle_force_generator.rb +23 -0
- data/lib/Particles/particle_force_registration.rb +15 -0
- data/lib/Particles/particle_force_registry.rb +50 -0
- data/lib/Particles/particle_gravity.rb +31 -0
- data/lib/Particles/particle_ground_contacts.rb +43 -0
- data/lib/Particles/particle_link.rb +41 -0
- data/lib/Particles/particle_particle_contacts.rb +48 -0
- data/lib/Particles/particle_rod.rb +58 -0
- data/lib/Particles/particle_spring.rb +44 -0
- data/lib/Particles/particle_world.rb +116 -0
- data/lib/Particles/projectile.rb +15 -0
- data/lib/bulldog_physics.rb +43 -0
- data/lib/examples/GlStuff/gl_utility.rb +21 -0
- data/lib/examples/GlStuff/lighting.rb +35 -0
- data/lib/examples/GlStuff/material.rb +24 -0
- data/lib/examples/GlStuff/terrain.rb +216 -0
- data/lib/examples/simple_game.rb +289 -0
- data/lib/matrix3.rb +242 -0
- data/lib/matrix4.rb +230 -0
- data/lib/quaternion.rb +86 -0
- data/lib/vector3.rb +155 -0
- data/test/helper.rb +18 -0
- data/test/test_bulldog_physics.rb +7 -0
- metadata +190 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
module BulldogPhysics
|
2
|
+
module Particles
|
3
|
+
module Collisions
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
# Keeps track of a set of particles, and provides the means to update them all.
|
8
|
+
class ParticleWorld
|
9
|
+
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
|
14
|
+
attr_accessor :resolver
|
15
|
+
|
16
|
+
attr_accessor :max_contacts
|
17
|
+
attr_accessor :calculate_iterations
|
18
|
+
|
19
|
+
|
20
|
+
public
|
21
|
+
|
22
|
+
attr_accessor :contact_generators
|
23
|
+
attr_accessor :contacts
|
24
|
+
attr_accessor :particles
|
25
|
+
attr_accessor :registry
|
26
|
+
|
27
|
+
# Creates a new particle simulator that can handle up to the
|
28
|
+
# given number of contacts per frame. You can also optionally
|
29
|
+
# give a number of contact-resolution iterations to use. If you * don’t give a number of iterations, then twice the number of
|
30
|
+
# contacts will be used.
|
31
|
+
def initialize(max_contacts = 100, iterations = 1)
|
32
|
+
@max_contacts = max_contacts
|
33
|
+
@calculate_iterations = iterations
|
34
|
+
@particles = Array.new
|
35
|
+
@contacts = Array.new
|
36
|
+
@contact_generators = Array.new
|
37
|
+
@registry = ParticleForceRegistry.new
|
38
|
+
@resolver = ParticleContactResolver.new
|
39
|
+
end
|
40
|
+
|
41
|
+
# Initializes the world for a simulation frame. This clears * the force accumulators for particles in the world. After
|
42
|
+
# calling this, the particles can have their forces for this * frame added.
|
43
|
+
def start_frame()
|
44
|
+
|
45
|
+
|
46
|
+
@particles.each do |particle|
|
47
|
+
particle.clearAccumulator()
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Calls each of the registered contact generators to report * their contacts.
|
52
|
+
# Returns the number of generated contacts.
|
53
|
+
def generate_contacts()
|
54
|
+
limit = @max_contacts
|
55
|
+
|
56
|
+
#if @contacts.size == 0
|
57
|
+
# return 0
|
58
|
+
#end
|
59
|
+
|
60
|
+
|
61
|
+
for g in @contact_generators
|
62
|
+
used = g.add_contact(@contacts, limit)
|
63
|
+
|
64
|
+
|
65
|
+
limit -= used
|
66
|
+
# We’ve run out of contacts to fill. This means we’re missing // contacts.
|
67
|
+
if limit <= 0
|
68
|
+
break
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
#return number of contacts used
|
73
|
+
return @max_contacts - limit
|
74
|
+
end
|
75
|
+
|
76
|
+
# Integrates all the particles in this world forward in time
|
77
|
+
# by the given duration.
|
78
|
+
def integrate(duration)
|
79
|
+
@particles.each{|p| p.integrate(duration)}
|
80
|
+
end
|
81
|
+
|
82
|
+
def run_physics(duration)
|
83
|
+
|
84
|
+
# check for colliding and hitting floor
|
85
|
+
|
86
|
+
@contacts.clear
|
87
|
+
|
88
|
+
@registry.update_forces(duration)
|
89
|
+
|
90
|
+
|
91
|
+
integrate(duration)
|
92
|
+
|
93
|
+
used_contacts = generate_contacts()
|
94
|
+
|
95
|
+
if(used_contacts > 0)
|
96
|
+
if(@calculate_iterations > 0)
|
97
|
+
@resolver.iterations = used_contacts * 2
|
98
|
+
@resolver.resolve_contacts(@contacts, used_contacts, duration)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
#@resolver.resolve_contacts(@contacts, @contacts.size, duration)
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
def add_gravity
|
107
|
+
gravity = ParticleGravity.new(Vector3.new(0, -0.5, 0))
|
108
|
+
@particles.each{|part| @registry.add(part, gravity)}
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# make life easier by including all Physics files here
|
2
|
+
require 'rubygems'
|
3
|
+
require 'require_all'
|
4
|
+
|
5
|
+
|
6
|
+
class VirtualMethodCalledError < RuntimeError
|
7
|
+
attr :name
|
8
|
+
def initialize(name)
|
9
|
+
super("Virtual function '#{name}' called")
|
10
|
+
@name = name
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Module
|
15
|
+
def virtual(*methods)
|
16
|
+
methods.each do |m|
|
17
|
+
define_method(m) {
|
18
|
+
raise VirtualMethodCalledError, m
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require './vector3.rb'
|
25
|
+
require './matrix3.rb'
|
26
|
+
require './matrix4.rb'
|
27
|
+
require './quaternion.rb'
|
28
|
+
|
29
|
+
module BulldogPhysics
|
30
|
+
|
31
|
+
def localToWorld(local, transform)
|
32
|
+
return tarnsform.transform(local)
|
33
|
+
end
|
34
|
+
|
35
|
+
def worldToLocal(world, transform)
|
36
|
+
inverseTransform = Matrix4.new
|
37
|
+
inverseTransform.setInverse(transform)
|
38
|
+
return inverseTransform.transform(world)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
require_all 'Particles'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module GlStuff
|
2
|
+
class GlUtility
|
3
|
+
|
4
|
+
def self.draw_line(ax, ay, bx, by, width, r, g, b, a)
|
5
|
+
glDisable(GL_TEXTURE_2D);
|
6
|
+
#glEnable(GL_BLEND);
|
7
|
+
#glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
8
|
+
glColor4ub( r, g, b, a);
|
9
|
+
|
10
|
+
glLineWidth(width);
|
11
|
+
glBegin(GL_LINES);
|
12
|
+
glVertex2i( ax, ay);
|
13
|
+
glVertex2i( bx, by);
|
14
|
+
glEnd();
|
15
|
+
|
16
|
+
#glDisable(GL_BLEND);
|
17
|
+
glEnable(GL_TEXTURE_2D);
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module GlStuff
|
2
|
+
|
3
|
+
class Lighting
|
4
|
+
|
5
|
+
attr_accessor :ambient, :diffuse, :position, :lmodel_ambient, :no_mat, :mat_ambient, :mat_ambient_color, :mat_diffuse,
|
6
|
+
:mat_specular, :no_shininess, :low_shininess, :high_shininess, :mat_emission, :local_view
|
7
|
+
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@ambient = [ 0.0, 1.0, 0.0, 1.0 ]
|
11
|
+
@diffuse = [ 1.0, 1.0, 1.0, 1.0 ]
|
12
|
+
@position = [ 1.0, 5.0, 1.0, 0.0 ]
|
13
|
+
@lmodel_ambient = [ 0.4, 0.4, 0.4, 1.0 ]
|
14
|
+
@no_mat = [ 0.0, 0.0, 0.0, 1.0 ]
|
15
|
+
@mat_ambient = [ 0.7, 0.7, 0.7, 1.0 ]
|
16
|
+
@mat_ambient_color = [ 0.8, 0.8, 0.2, 1.0 ]
|
17
|
+
@mat_diffuse = [ 0.1, 0.5, 0.0, 1.0 ]
|
18
|
+
@mat_specular = [ 1.0, 1.0, 1.0, 1.0 ]
|
19
|
+
@no_shininess = [ 0.0 ]
|
20
|
+
@low_shininess = [ 5.0 ]
|
21
|
+
@high_shininess = [ 100.0 ]
|
22
|
+
@mat_emission = [0.3, 0.2, 0.2, 0.0]
|
23
|
+
@local_view = [ 0.0 ]
|
24
|
+
end
|
25
|
+
|
26
|
+
def setup
|
27
|
+
glEnable(GL_LIGHTING)
|
28
|
+
glEnable(GL_LIGHT0)
|
29
|
+
glShadeModel(GL_SMOOTH);
|
30
|
+
glLightfv(GL_LIGHT0, GL_POSITION, @position)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module GlStuff
|
2
|
+
class Material
|
3
|
+
|
4
|
+
attr_accessor :ambient, :diffuse, :specular, :shininess, :emission
|
5
|
+
|
6
|
+
def initialize()
|
7
|
+
@ambient = [ 0.7, 0.7, 0.7, 1.0 ]
|
8
|
+
@ambient_color = [ 0.8, 0.8, 0.2, 1.0 ]
|
9
|
+
@diffuse = [ 0.5, 0.5, 0.5, 1.0 ]
|
10
|
+
@specular = [ 1.0, 1.0, 1.0, 1.0 ]
|
11
|
+
@shininess = [ 0.0 ] # 0 is noe, 100 would be hide
|
12
|
+
@emission = [0.3, 0.2, 0.2, 0.0]
|
13
|
+
end
|
14
|
+
|
15
|
+
def setup_material
|
16
|
+
glMaterial(GL_FRONT, GL_AMBIENT, @ambient)
|
17
|
+
glMaterial(GL_FRONT, GL_DIFFUSE, @diffuse)
|
18
|
+
glMaterial(GL_FRONT, GL_SPECULAR, @specular)
|
19
|
+
glMaterial(GL_FRONT, GL_SHININESS, @shininess)
|
20
|
+
glMaterial(GL_FRONT, GL_EMISSION, @emission)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
class Terrain
|
2
|
+
|
3
|
+
attr_accessor :height_map_image, :terrain_grid_width, :terrain_grid_length, :terrain_normals
|
4
|
+
|
5
|
+
def initialize(surface)
|
6
|
+
@height_map_image = surface
|
7
|
+
@terrain_normals = Array.new
|
8
|
+
load_from_surface()
|
9
|
+
#compute_normals()
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_from_surface
|
13
|
+
mode = @height_map_image.depth
|
14
|
+
puts mode
|
15
|
+
@terrain_grid_width = @height_map_image.width
|
16
|
+
@terrain_grid_length = @height_map_image.height
|
17
|
+
@terrain_heights = Array.new( @terrain_grid_width * @terrain_grid_length)
|
18
|
+
pixels = @height_map_image.pixels()
|
19
|
+
#puts pixels
|
20
|
+
puts pixels.size
|
21
|
+
for i in 0..@terrain_grid_length-1
|
22
|
+
for j in 0..@terrain_grid_width-1
|
23
|
+
#aux = mode * (i * @terrain_grid_width + j)
|
24
|
+
#index = aux + (mode - 1)
|
25
|
+
#puts@height_map_image.get_at(i,j)[1]
|
26
|
+
#if index < pixels.size
|
27
|
+
point_height = @height_map_image.get_at(i,j)[0] / 255.0
|
28
|
+
#puts point_height
|
29
|
+
#end
|
30
|
+
@terrain_heights[i * @terrain_grid_width + j] = point_height
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
for i in 0..10
|
35
|
+
#puts @terrain_heights[i * 1000]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def terrain_cross_product(x1, z1, x2, z2, x3, z3)
|
40
|
+
v1 = v2 = aux = Vector3.new
|
41
|
+
|
42
|
+
v1.x = (x2 - x1 )
|
43
|
+
v1.y = -@terrain_heights[z1 * @terrain_grid_width + x1] +
|
44
|
+
@terrain_heights[z2 * @terrain_grid_width + x2]
|
45
|
+
v1.z = (z2 - z1)
|
46
|
+
|
47
|
+
v2.x = (x3 - x1)
|
48
|
+
v2.y = -@terrain_heights[z1 * @terrain_grid_width + x1] +
|
49
|
+
@terrain_heights[z3 * @terrain_grid_width + x3]
|
50
|
+
v2.z = (z3 - z1)
|
51
|
+
|
52
|
+
v1 -= v1.crossProduct(v2)
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_display_list(x_offset, y_offset, z_offset)
|
56
|
+
|
57
|
+
start_w = (@terrain_grid_width / 2.0) - @terrain_grid_width
|
58
|
+
start_l = (-@terrain_grid_length / 2.0) + @terrain_grid_length
|
59
|
+
|
60
|
+
terrain_dl = glGenLists(1)
|
61
|
+
|
62
|
+
glNewList(terrain_dl, GL_COMPILE)
|
63
|
+
|
64
|
+
for i in 0..(@terrain_grid_length - 2)
|
65
|
+
glBegin(GL_TRIANGLE_STRIP)
|
66
|
+
for j in 0..(@terrain_grid_width - 2)
|
67
|
+
|
68
|
+
unless @terrain_normals.empty?
|
69
|
+
glNormal3f(@terrain_normals[3*((i+1)*@terrain_grid_width)],
|
70
|
+
@terrain_normals[3*((i+1)*@terrain_grid_width)+1],
|
71
|
+
@terrain_normals[3*((i+1)*@terrain_grid_width)+2] )
|
72
|
+
end
|
73
|
+
glVertex3f(
|
74
|
+
start_w + j,
|
75
|
+
@terrain_heights[(i+1)*@terrain_grid_width + (j)],
|
76
|
+
start_l - (i+1)
|
77
|
+
)
|
78
|
+
|
79
|
+
unless @terrain_normals.empty?
|
80
|
+
glNormal3f(@terrain_normals[3*(i+@terrain_grid_width + j)],
|
81
|
+
@terrain_normals[3*(i+@terrain_grid_width + j)+1],
|
82
|
+
@terrain_normals[3*(i+@terrain_grid_width + j)+2] )
|
83
|
+
end
|
84
|
+
|
85
|
+
aux = 3 * ((i+1) * @terrain_grid_length + j)
|
86
|
+
glVertex3f( (start_w + j),
|
87
|
+
@terrain_heights[(i * @terrain_grid_width) + j],
|
88
|
+
(start_l - i)
|
89
|
+
)
|
90
|
+
end
|
91
|
+
glEnd()
|
92
|
+
end
|
93
|
+
|
94
|
+
glEndList()
|
95
|
+
|
96
|
+
terrain_dl
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def terrain_scale(minnum, maxnum)
|
101
|
+
if minnum > maxnum
|
102
|
+
aux = minnum
|
103
|
+
minnum = maxnum
|
104
|
+
maxnum = aux
|
105
|
+
end
|
106
|
+
|
107
|
+
amp = maxnum - minnum
|
108
|
+
|
109
|
+
total = @terrain_grid_width * @terrain_grid_length
|
110
|
+
|
111
|
+
min_val = @terrain_heights.min
|
112
|
+
max_val = @terrain_heights.max
|
113
|
+
for i in 0..total-2
|
114
|
+
height = (@terrain_heights[i] - min_val) / (max_val - min_val)
|
115
|
+
if height == nil
|
116
|
+
next
|
117
|
+
end
|
118
|
+
@terrain_heights[i] = (height * amp) - minnum
|
119
|
+
#puts @terrain_heights[i]
|
120
|
+
end
|
121
|
+
|
122
|
+
compute_normals()
|
123
|
+
end
|
124
|
+
|
125
|
+
def compute_normals
|
126
|
+
norm1,norm2 = Vector3.new, Vector3.new
|
127
|
+
@terrain_normals = Array.new(@terrain_grid_width * @terrain_grid_length * 3)
|
128
|
+
|
129
|
+
return if @terrain_normals.nil?
|
130
|
+
|
131
|
+
|
132
|
+
|
133
|
+
for i in 0..@terrain_grid_length - 1
|
134
|
+
for j in 0..@terrain_grid_width - 1
|
135
|
+
norm1.clear
|
136
|
+
norm2.clear
|
137
|
+
if i == 0 && j == 0
|
138
|
+
norm1 = terrain_cross_product(0,0, 0,1, 1,0)
|
139
|
+
norm1.normalize()
|
140
|
+
elsif j == @terrain_grid_width-1 && i == @terrain_grid_length-1
|
141
|
+
norm1 = terrain_cross_product(j,i, j,i-1, j-1,i)
|
142
|
+
norm1.normalize()
|
143
|
+
elsif j == 0 && i == @terrain_grid_length-1
|
144
|
+
norm1 = terrain_cross_product(j,i, j,i-1, j+1, i)
|
145
|
+
norm1.normalize()
|
146
|
+
elsif j == @terrain_grid_width - 1 && i == 0
|
147
|
+
norm1 = terrain_cross_product(j,i, j,i+1, j-1,i)
|
148
|
+
elsif i == 0
|
149
|
+
norm1 = terrain_cross_product(j,0, j-1,0, j,1)
|
150
|
+
norm1.normalize
|
151
|
+
norm2 = terrain_cross_product(j,0, j,1, j+1,0)
|
152
|
+
norm2.normalize
|
153
|
+
norm1 += norm2
|
154
|
+
elsif j == 0
|
155
|
+
norm1 = terrain_cross_product(0,i, 1,i, 0,i-1)
|
156
|
+
norm1.normalize
|
157
|
+
norm2 = terrain_cross_product(j,0, j,1, j+1,0)
|
158
|
+
norm2.normalize
|
159
|
+
norm1 += norm2
|
160
|
+
elsif i == @terrain_grid_length-1
|
161
|
+
norm1 = terrain_cross_product(j,i, j,i-1, j+1,i);
|
162
|
+
norm1.normalize
|
163
|
+
norm2 = terrain_cross_product(j,i, j+1,i, j,i-1);
|
164
|
+
norm2.normalize
|
165
|
+
norm1 += norm2
|
166
|
+
elsif (j == @terrain_grid_width-1)
|
167
|
+
norm1 = terrain_cross_product(j,i, j,i-1, j-1,i);
|
168
|
+
norm1.normalize
|
169
|
+
norm2 = terrain_cross_product(j,i, j-1,i, j,i+1);
|
170
|
+
norm2.normalize
|
171
|
+
norm1 += norm2;
|
172
|
+
else
|
173
|
+
norm1 = terrain_cross_product(j,i, j-1,i, j-1,i+1).unit
|
174
|
+
norm2 = terrain_cross_product(j,i, j-1,i+1, j,i+1).unit
|
175
|
+
norm1 += norm2;
|
176
|
+
|
177
|
+
norm2 = terrain_cross_product(j,i, j,i+1, j+1,i+1).unit
|
178
|
+
norm1 += norm2;
|
179
|
+
|
180
|
+
norm2 = terrain_cross_product(j,i, j+1,i+1, j+1,i).unit
|
181
|
+
norm1 += norm2
|
182
|
+
|
183
|
+
norm2 = terrain_cross_product(j,i, j+1,i, j+1,i-1).unit
|
184
|
+
norm1 += norm2
|
185
|
+
|
186
|
+
norm2 = terrain_cross_product(j,i, j+1,i-1, j,i-1).unit
|
187
|
+
norm1 += norm2
|
188
|
+
|
189
|
+
norm2 = terrain_cross_product(j,i, j,i-1, j-1,i-1).unit
|
190
|
+
norm1 += norm2
|
191
|
+
|
192
|
+
norm2 = terrain_cross_product(j,i, j-1,i-1, j-1,i).unit
|
193
|
+
norm1 += norm2
|
194
|
+
end
|
195
|
+
|
196
|
+
norm1.normalize
|
197
|
+
norm1.z = - norm1.z
|
198
|
+
|
199
|
+
component = 0
|
200
|
+
for k in 0..3
|
201
|
+
case k
|
202
|
+
when 0
|
203
|
+
component = norm1.x
|
204
|
+
when 1
|
205
|
+
component = norm1.y
|
206
|
+
when 2
|
207
|
+
component = norm1.z
|
208
|
+
end
|
209
|
+
@terrain_normals[3*(i*@terrain_grid_width + j) + k] = component;
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
end
|