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 }