1 module pyramid;
2
3 import std.stdio;
4 import std.format;
5 import std.math;
6
7 import dlib.core.memory;
8 import dlib.container.array;
9 import dlib.math.vector;
10 import dlib.math.utils;
11 import dlib.image.color;
12 import dlib.geometry.ray;
13 import dlib.geometry.aabb;
14
15 import derelict.opengl.gl;
16 import derelict.opengl.glu;
17 import derelict.sdl.sdl;
18
19 import dgl.core.application;
20 import dgl.core.interfaces;
21 import dgl.core.event;
22 import dgl.graphics.shapes;
23 import dgl.graphics.material;
24 import dgl.graphics.tbcamera;
25 import dgl.graphics.axes;
26 import dgl.ui.font;
27 import dgl.ui.ftfont;
28 import dgl.ui.textline;
29
30 import dmech.world;
31 import dmech.geometry;
32 import dmech.rigidbody;
33 import dmech.shape;
34
35 import testbed.grid;
36 import testbed.physicsentity;
37
38 class TestApp: Application
39 {
40 TrackballCamera camera;
41 DynamicArray!Material materials;
42 DynamicArray!Drawable drawables;
43 Vector4f lightPosition;
44
45 Axes axes;
46 Grid grid;
47 ShapeBox sBox;
48
49 PhysicsWorld world;
50 GeomBox gFloor;
51 GeomBox gBox;
52
53 enum fixedTimeStep = 1.0 / 60.0;
54
55 Font font;
56 TextLine fpsText;
57
58 DynamicArray!PhysicsEntity entities;
59 PhysicsEntity selectedEntity;
60
61 float aspectRatio;
62 float fovY = 60;
63 float maxPickDistance = 1000.0f;
64 float dragDistance;
65
66 this()
67 {
68 super(800, 600, "dmech test");
69 clearColor = Color4f(0.2f, 0.2f, 0.2f);
70
71 camera = New!TrackballCamera();
72 camera.pitch(45.0f);
73 camera.turn(45.0f);
74 camera.setZoom(20.0f);
75
76 lightPosition = Vector4f(1, 1, 0.5, 0);
77
78 axes = New!Axes();
79 grid = New!Grid();
80
81 world = New!PhysicsWorld(null, 1000);
82
83 gFloor = New!GeomBox(world, Vector3f(40, 1, 40));
84 auto bFloor = world.addStaticBody(Vector3f(0, -1, 0));
85 world.addShapeComponent(bFloor, gFloor, Vector3f(0, 0, 0), 1.0f);
86
87 gBox = New!GeomBox(world, Vector3f(1, 1, 1)); // Physical shape
88 sBox = New!ShapeBox(Vector3f(1, 1, 1)); // Graphical shape
89
90 buildPyramid(7);
91
92 font = New!FreeTypeFont("data/fonts/droid/DroidSans.ttf", 18);
93
94 fpsText = New!TextLine(font, "FPS: 0", Vector2f(8, 8));
95 fpsText.color = Color4f(1, 1, 1);
96 }
97
98 void buildPyramid(uint pyramidSize)
99 {
100 float size = 1.0f;
101
102 float cubeHeight = 2.0f;
103
104 float width = size * 2.0f;
105 float height = cubeHeight;
106 float horizontal_spacing = 0.1f;
107 float veritcal_spacing = 0.1f;
108
109 foreach(i; 0..pyramidSize)
110 foreach(e; i..pyramidSize)
111 {
112 auto position = Vector3f(
113 (e - i * 0.5f) * (width + horizontal_spacing) - ((width + horizontal_spacing) * 5),
114 6.0f + (height + veritcal_spacing * 0.5f) + i * height + 0.26f,
115 0.0f);
116
117 PhysicsEntity entity = addBoxEntity(position);
118 Material mat = New!Material();
119 auto col = Color4f((randomUnitVector3!float + 0.5f).normalized);
120 mat.ambientColor = col;
121 mat.diffuseColor = col;
122 entity.material = mat;
123 materials.append(mat);
124 }
125 }
126
127 PhysicsEntity addBoxEntity(Vector3f pos)
128 {
129 auto bBox = world.addDynamicBody(pos, 0.0f);
130 auto scBox = world.addShapeComponent(bBox, gBox, Vector3f(0, 0, 0), 1.0f);
131 PhysicsEntity peBox = New!PhysicsEntity(sBox, bBox);
132 addDrawable(peBox);
133 entities.append(peBox);
134 return peBox;
135 }
136
137 void addDrawable(Drawable d)
138 {
139 drawables.append(d);
140 }
141
142 ~this()
143 {
144 camera.free();
145 foreach(m; materials.data)
146 m.free();
147 materials.free();
148 foreach(d; drawables.data)
149 d.free();
150 drawables.free();
151 entities.free();
152 axes.free();
153 grid.free();
154 sBox.free();
155
156 Delete(world);
157
158 font.free();
159 fpsText.free();
160 }
161
162 override void onKeyDown(int key)
163 {
164 super.onKeyDown(key);
165 }
166
167 int prevMouseX;
168 int prevMouseY;
169
170 override void onMouseButtonDown(int button)
171 {
172 if (button == SDL_BUTTON_LEFT)
173 {
174 Ray mray = mouseRay();
175 selectedEntity = pickEntity(mray);
176 if (selectedEntity)
177 dragDistance = distance(selectedEntity.rbody.position, camera.getPosition);
178 }
179 else if (button == SDL_BUTTON_MIDDLE)
180 {
181 prevMouseX = eventManager.mouseX;
182 prevMouseY = eventManager.mouseY;
183 }
184 else if (button == SDL_BUTTON_WHEELUP)
185 {
186 camera.zoom(1.0f);
187 }
188 else if (button == SDL_BUTTON_WHEELDOWN)
189 {
190 camera.zoom(-1.0f);
191 }
192 }
193
194 PhysicsEntity pickEntity(Ray r)
195 {
196 PhysicsEntity res = null;
197
198 float min_t = float.max;
199 foreach(e; entities.data)
200 {
201 AABB aabb = e.getAABB();
202 float t;
203 if (aabb.intersectsSegment(r.p0, r.p1, t))
204 {
205 if (t < min_t)
206 {
207 min_t = t;
208 res = e;
209 }
210 }
211 }
212
213 return res;
214 }
215
216 override void free()
217 {
218 Delete(this);
219 }
220
221 double time = 0.0;
222
223 override void onUpdate()
224 {
225 double dt = eventManager.deltaTime;
226
227 time += dt;
228 if (time >= fixedTimeStep)
229 {
230 time -= fixedTimeStep;
231 world.update(fixedTimeStep);
232 }
233
234 updateCamera();
235
236 if (eventManager.mouseButtonPressed[SDL_BUTTON_LEFT] && selectedEntity)
237 {
238 Vector3f dragPosition = cameraMousePoint(dragDistance);
239 selectedEntity.rbody.position = dragPosition;
240 }
241
242 fpsText.setText(format("FPS: %s", eventManager.fps));
243 }
244
245 override void onRedraw()
246 {
247 double dt = eventManager.deltaTime;
248
249 aspectRatio = cast(float)eventManager.windowWidth / cast(float)eventManager.windowHeight;
250
251 glMatrixMode(GL_PROJECTION);
252 glLoadIdentity();
253 gluPerspective(fovY, aspectRatio, 0.1, 1000.0);
254 glMatrixMode(GL_MODELVIEW);
255
256 glLoadIdentity();
257 glPushMatrix();
258 camera.bind(dt);
259
260 grid.draw(dt);
261 glDisable(GL_DEPTH_TEST);
262 axes.draw(dt);
263 glEnable(GL_DEPTH_TEST);
264
265 glEnable(GL_LIGHTING);
266 glEnable(GL_LIGHT0);
267 glLightfv(GL_LIGHT0, GL_POSITION, lightPosition.arrayof.ptr);
268
269 foreach(d; drawables.data)
270 d.draw(dt);
271 if (selectedEntity)
272 {
273 selectedEntity.useMaterial = false;
274 drawWireframe(selectedEntity, dt);
275 selectedEntity.useMaterial = true;
276 }
277 glDisable(GL_LIGHTING);
278
279 camera.unbind();
280 glPopMatrix();
281
282 glMatrixMode(GL_PROJECTION);
283 glLoadIdentity();
284 glOrtho(0, eventManager.windowWidth, 0, eventManager.windowHeight, 0, 1);
285 glMatrixMode(GL_MODELVIEW);
286
287 fpsText.draw(dt);
288 }
289
290 void updateCamera()
291 {
292 if (eventManager.mouseButtonPressed[SDL_BUTTON_MIDDLE] && eventManager.keyPressed[SDLK_LSHIFT])
293 {
294 float shift_x = (eventManager.mouseX - prevMouseX) * 0.1f;
295 float shift_y = (eventManager.mouseY - prevMouseY) * 0.1f;
296 Vector3f trans = camera.getUpVector * shift_y + camera.getRightVector * shift_x;
297 camera.translateTarget(trans);
298 }
299 else if (eventManager.mouseButtonPressed[SDL_BUTTON_MIDDLE] && eventManager.keyPressed[SDLK_LCTRL])
300 {
301 float shift_x = (eventManager.mouseX - prevMouseX);
302 float shift_y = (eventManager.mouseY - prevMouseY);
303 camera.zoom((shift_x + shift_y) * 0.1f);
304 }
305 else if (eventManager.mouseButtonPressed[SDL_BUTTON_MIDDLE])
306 {
307 float turn_m = (eventManager.mouseX - prevMouseX);
308 float pitch_m = -(eventManager.mouseY - prevMouseY);
309 camera.pitch(pitch_m);
310 camera.turn(turn_m);
311 }
312
313 prevMouseX = eventManager.mouseX;
314 prevMouseY = eventManager.mouseY;
315 }
316
317 void drawWireframe(Drawable drw, double dt)
318 {
319 glDisable(GL_DEPTH_TEST);
320 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
321 glDisable(GL_LIGHTING);
322 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
323 drw.draw(dt);
324 glEnable(GL_LIGHTING);
325 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
326 glEnable(GL_DEPTH_TEST);
327 }
328
329 Vector3f cameraPoint(float x, float y, float dist)
330 {
331 return camera.getPosition() + cameraDir(x, y) * dist;
332 }
333
334 Vector3f cameraMousePoint(float dist)
335 {
336 return cameraPoint(eventManager.mouseX, eventManager.windowHeight - eventManager.mouseY, dist);
337 }
338
339 Vector3f cameraDir(float x, float y)
340 {
341 Vector3f camDir = -camera.getDirectionVector();
342
343 float fovX = fovXfromY(fovY, aspectRatio);
344
345 float tfov1 = tan(fovY*PI/360.0f);
346 float tfov2 = tan(fovX*PI/360.0f);
347
348 Vector3f camUp = camera.getUpVector() * tfov1;
349 Vector3f camRight = -camera.getRightVector() * tfov2;
350
351 float width = 1.0f - 2.0f * x / eventManager.windowWidth;
352 float height = 1.0f - 2.0f * y / eventManager.windowHeight;
353
354 Vector3f m = camDir + camUp * height + camRight * width;
355 Vector3f dir = m.normalized;
356
357 return dir;
358 }
359
360 Ray cameraRay(float x, float y)
361 {
362 Vector3f camPos = camera.getPosition();
363 Ray r = Ray(camPos, camPos + cameraDir(x, y) * maxPickDistance);
364 return r;
365 }
366
367 Vector3f mouseDir()
368 {
369 return cameraDir(eventManager.mouseX, eventManager.windowHeight - eventManager.mouseY);
370 }
371
372 Ray mouseRay()
373 {
374 return cameraRay(eventManager.mouseX, eventManager.windowHeight - eventManager.mouseY);
375 }
376
377 Ray mouseRay(float x, float y)
378 {
379 return cameraRay(x, eventManager.windowHeight - y);
380 }
381 }
382
383 void main(string[] args)
384 {
385 loadLibraries();
386 writefln("Allocated memory at start: %s", allocatedMemory);
387 auto app = New!TestApp();
388 app.run();
389 Delete(app);
390 writefln("Allocated memory at end: %s", allocatedMemory);
391 }