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(1000);
82         
83         gFloor = New!GeomBox(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(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         world.free();
155         sBox.free();
156         
157         gFloor.free();
158         gBox.free();
159         
160         font.free();
161         fpsText.free();
162     }
163     
164     override void onKeyDown(int key)
165     {
166         super.onKeyDown(key);
167     }
168     
169     int prevMouseX;
170     int prevMouseY;
171     
172     override void onMouseButtonDown(int button)
173     {
174         if (button == SDL_BUTTON_LEFT)
175         {
176             Ray mray = mouseRay();
177             selectedEntity = pickEntity(mray);
178             if (selectedEntity)
179                 dragDistance = distance(selectedEntity.rbody.position, camera.getPosition);
180         }
181         else if (button == SDL_BUTTON_MIDDLE)
182         {
183             prevMouseX = eventManager.mouseX;
184             prevMouseY = eventManager.mouseY;
185         }
186         else if (button == SDL_BUTTON_WHEELUP)
187         {
188             camera.zoom(1.0f);
189         }
190         else if (button == SDL_BUTTON_WHEELDOWN)
191         {
192             camera.zoom(-1.0f);
193         }
194     }
195     
196     PhysicsEntity pickEntity(Ray r)
197     {
198         PhysicsEntity res = null;
199 
200         float min_t = float.max;
201         foreach(e; entities.data)
202         {
203             AABB aabb = e.getAABB();
204             float t;
205             if (aabb.intersectsSegment(r.p0, r.p1, t))
206             {
207                 if (t < min_t)
208                 {
209                     min_t = t;
210                     res = e;
211                 }
212             }
213         }
214         
215         return res;
216     }
217     
218     override void free()
219     {
220         Delete(this);
221     }
222     
223     double time = 0.0;
224     
225     override void onUpdate()
226     {
227         double dt = eventManager.deltaTime;
228         
229         time += dt;
230         if (time >= fixedTimeStep)
231         {
232             time -= fixedTimeStep;
233             world.update(fixedTimeStep);
234         }
235     
236         updateCamera();
237         
238         if (eventManager.mouseButtonPressed[SDL_BUTTON_LEFT] && selectedEntity)
239         {
240             Vector3f dragPosition = cameraMousePoint(dragDistance);
241             selectedEntity.rbody.position = dragPosition;
242         }
243         
244         fpsText.setText(format("FPS: %s", eventManager.fps));
245     }
246     
247     override void onRedraw()
248     {       
249         double dt = eventManager.deltaTime;
250         
251         aspectRatio = cast(float)eventManager.windowWidth / cast(float)eventManager.windowHeight;
252         
253         glMatrixMode(GL_PROJECTION);
254         glLoadIdentity();
255         gluPerspective(fovY, aspectRatio, 0.1, 1000.0);
256         glMatrixMode(GL_MODELVIEW);
257         
258         glLoadIdentity();
259         glPushMatrix();
260         camera.bind(dt);
261         
262         grid.draw(dt);
263         glDisable(GL_DEPTH_TEST);
264         axes.draw(dt);
265         glEnable(GL_DEPTH_TEST);
266         
267         glEnable(GL_LIGHTING);
268         glEnable(GL_LIGHT0);
269         glLightfv(GL_LIGHT0, GL_POSITION, lightPosition.arrayof.ptr);
270         
271         foreach(d; drawables.data)
272             d.draw(dt);
273         if (selectedEntity)
274         {
275             selectedEntity.useMaterial = false;
276             drawWireframe(selectedEntity, dt);
277             selectedEntity.useMaterial = true;
278         }
279         glDisable(GL_LIGHTING);
280         
281         camera.unbind();
282         glPopMatrix();
283         
284         glMatrixMode(GL_PROJECTION);
285         glLoadIdentity();
286         glOrtho(0, eventManager.windowWidth, 0, eventManager.windowHeight, 0, 1);
287         glMatrixMode(GL_MODELVIEW);
288         
289         fpsText.draw(dt);
290     }
291     
292     void updateCamera()
293     {
294         if (eventManager.mouseButtonPressed[SDL_BUTTON_MIDDLE] && eventManager.keyPressed[SDLK_LSHIFT])
295         {
296             float shift_x = (eventManager.mouseX - prevMouseX) * 0.1f;
297             float shift_y = (eventManager.mouseY - prevMouseY) * 0.1f;
298             Vector3f trans = camera.getUpVector * shift_y + camera.getRightVector * shift_x;
299             camera.translateTarget(trans);
300         }
301         else if (eventManager.mouseButtonPressed[SDL_BUTTON_MIDDLE] && eventManager.keyPressed[SDLK_LCTRL])
302         {
303             float shift_x = (eventManager.mouseX - prevMouseX);
304             float shift_y = (eventManager.mouseY - prevMouseY);
305             camera.zoom((shift_x + shift_y) * 0.1f);
306         }
307         else if (eventManager.mouseButtonPressed[SDL_BUTTON_MIDDLE])
308         {                
309             float turn_m = (eventManager.mouseX - prevMouseX);
310             float pitch_m = -(eventManager.mouseY - prevMouseY);
311             camera.pitch(pitch_m);
312             camera.turn(turn_m);
313         }
314 
315         prevMouseX = eventManager.mouseX;
316         prevMouseY = eventManager.mouseY;
317     }
318     
319     void drawWireframe(Drawable drw, double dt)
320     {
321         glDisable(GL_DEPTH_TEST);
322         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
323         glDisable(GL_LIGHTING);
324         glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
325         drw.draw(dt);
326         glEnable(GL_LIGHTING);
327         glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
328         glEnable(GL_DEPTH_TEST);
329     }
330     
331     Vector3f cameraPoint(float x, float y, float dist)
332     {        
333         return camera.getPosition() + cameraDir(x, y) * dist;
334     }
335         
336     Vector3f cameraMousePoint(float dist)
337     {
338         return cameraPoint(eventManager.mouseX, eventManager.windowHeight - eventManager.mouseY, dist);
339     }
340     
341     Vector3f cameraDir(float x, float y)
342     {
343         Vector3f camDir = -camera.getDirectionVector();
344 
345         float fovX = fovXfromY(fovY, aspectRatio);
346 
347         float tfov1 = tan(fovY*PI/360.0f);
348         float tfov2 = tan(fovX*PI/360.0f);
349         
350         Vector3f camUp = camera.getUpVector() * tfov1;
351         Vector3f camRight = -camera.getRightVector() * tfov2;
352 
353         float width  = 1.0f - 2.0f * x / eventManager.windowWidth;
354         float height = 1.0f - 2.0f * y / eventManager.windowHeight;
355         
356         Vector3f m = camDir + camUp * height + camRight * width;
357         Vector3f dir = m.normalized;
358         
359         return dir;
360     }
361     
362     Ray cameraRay(float x, float y)
363     {
364         Vector3f camPos = camera.getPosition();
365         Ray r = Ray(camPos, camPos + cameraDir(x, y) * maxPickDistance);
366         return r;
367     }
368     
369     Vector3f mouseDir()
370     {
371         return cameraDir(eventManager.mouseX, eventManager.windowHeight - eventManager.mouseY);
372     }
373     
374     Ray mouseRay()
375     {
376         return cameraRay(eventManager.mouseX, eventManager.windowHeight - eventManager.mouseY);
377     }
378     
379     Ray mouseRay(float x, float y)
380     {
381         return cameraRay(x, eventManager.windowHeight - y);
382     }
383 }
384 
385 void main(string[] args)
386 {
387     loadLibraries();
388     writefln("Allocated memory at start: %s", allocatedMemory);
389     auto app = New!TestApp();
390     app.run();
391     Delete(app);
392     writefln("Allocated memory at end: %s", allocatedMemory);
393 }