1 module minermain;
2 
3 import dlangui;
4 import dlangui.graphics.scene.scene3d;
5 import dlangui.graphics.scene.camera;
6 import dlangui.graphics.scene.mesh;
7 import dlangui.graphics.scene.material;
8 import dlangui.graphics.scene.effect;
9 import dlangui.graphics.scene.model;
10 import dlangui.graphics.scene.node;
11 import dlangui.graphics.scene.light;
12 import dlangui.graphics.scene.drawableobject;
13 import dlangui.graphics.scene.skybox;
14 import dlangui.graphics.scene.effect;
15 import dlangui.graphics.glsupport;
16 import dlangui.graphics.gldrawbuf;
17 
18 /*
19 version (Android) {
20     //enum SUPPORT_LEGACY_OPENGL = false;
21     public import EGL.eglplatform : EGLint;
22     public import EGL.egl;
23     //public import GLES2.gl2;
24     public import GLES3.gl3;
25 } else {
26     //enum SUPPORT_LEGACY_OPENGL = true;
27     import derelict.opengl3.gl3;
28     import derelict.opengl3.gl;
29 }
30 */
31 
32 import dminer.core.minetypes;
33 import dminer.core.blocks;
34 import dminer.core.world;
35 import dminer.core.generators;
36 import dminer.core.chunk;
37 
38 mixin APP_ENTRY_POINT;
39 
40 /// entry point for dlangui based application
41 extern (C) int UIAppMain(string[] args) {
42     // embed resources listed in views/resources.list into executable
43     embeddedResourceList.addResources(embedResourcesFromList!("resources.list")());
44     //embeddedResourceList.dumpEmbeddedResources();
45 
46     debug { 
47         testPlanes();
48     }
49 
50     // create window
51     Window window = Platform.instance.createWindow("DlangUI Voxel RPG", null, WindowFlag.Resizable, 600, 500);
52     window.mainWidget = new UiWidget();
53 
54     //MeshPart part = new MeshPart();
55 
56     // show window
57     window.show();
58 
59     // run message loop
60     return Platform.instance.enterMessageLoop();
61 }
62 
63 class ChunkVisitCounter : ChunkVisitor {
64     int count;
65     void visit(World world, SmallChunk * chunk) {
66         count++;
67     }
68 }
69 
70 class MinerDrawable : MaterialDrawableObject, ChunkVisitor {
71 
72     import dlangui.graphics.scene.node;
73     private World _world;
74     private ChunkDiamondVisitor _chunkVisitor;
75     private VisibilityCheckIterator _chunkIterator;
76     private Vector3d _pos;
77     private Node3d _node;
78     private Camera _cam;
79     private vec3 _camPosition;
80     private vec3 _camForwardVector;
81 
82     this(World world, Material material, Camera cam) {
83         super(material);
84         _world = world;
85         _cam = cam;
86     }
87     int _skippedCount;
88     int _drawnCount;
89     override void draw(Node3d node, bool wireframe) {
90         /// override it
91         _node = node;
92         //Log.d("drawing Miner scene");
93         //_chunkVisitor.init(_world, MAX_VIEW_DISTANCE, this);
94         _pos = _world.camPosition.pos;
95         _camPosition = _cam.translation;
96         _camForwardVector = _cam.forwardVectorWorld;
97         _camPosition -= _camForwardVector * 8;
98         _skippedCount = _drawnCount = 0;
99         long ts = currentTimeMillis();
100         //_chunkVisitor.visitChunks(_pos);
101         Vector3d camVector;
102         camVector.x = cast(int)(_camForwardVector.x * 256);
103         camVector.y = cast(int)(_camForwardVector.y * 256);
104         camVector.z = cast(int)(_camForwardVector.z * 256);
105         ChunkVisitCounter countVisitor = new ChunkVisitCounter();
106         _chunkIterator.start(_world, _world.camPosition.pos, MAX_VIEW_DISTANCE);
107         _chunkIterator.visitVisibleChunks(countVisitor, camVector);
108         long durationNoDraw = currentTimeMillis() - ts;
109         _chunkIterator.start(_world, _world.camPosition.pos, MAX_VIEW_DISTANCE);
110         _chunkIterator.visitVisibleChunks(this, camVector);
111         long duration = currentTimeMillis() - ts;
112         Log.d("drawing of Miner scene finished in ", duration, " ms  skipped:", _skippedCount, " drawn:", _drawnCount, " duration(noDraw)=", durationNoDraw);
113     }
114     void visit(World world, SmallChunk * chunk) {
115         if (chunk) {
116             Vector3d p = chunk.position;
117             vec3 chunkPos = vec3(p.x + 4, p.y + 4, p.z + 4);
118             vec3 chunkDirection = (chunkPos - _camPosition).normalized;
119             float dot = _camForwardVector.dot(chunkDirection);
120             //Log.d("visit() chunkPos ", chunkPos, " chunkDir ", chunkDirection, " camDir ");
121             if (dot < 0.7) { // cos(45)
122                 _skippedCount++;
123                 return;
124             }
125             Mesh mesh = chunk.getMesh(world);
126             if (mesh) {
127                 _material.bind(_node, mesh, lights(_node));
128                 _material.drawMesh(mesh);
129                 _material.unbind();
130                 _drawnCount++;
131             }
132         }
133     }
134 }
135 
136 class UiWidget : VerticalLayout { //, CellVisitor
137     this() {
138         super("OpenGLView");
139         layoutWidth = FILL_PARENT;
140         layoutHeight = FILL_PARENT;
141         alignment = Align.Center;
142         try {
143             parseML(q{
144                 {
145                   margins: 0
146                   padding: 0
147                   //backgroundImageId: "tx_fabric.tiled"
148                   backgroundColor: 0x000000;
149                   layoutWidth: fill
150                   layoutHeight: fill
151 
152                   VerticalLayout {
153                     id: glView
154                     margins: 0
155                     padding: 0
156                     layoutWidth: fill
157                     layoutHeight: fill
158                     TextWidget { text: "MinerD example"; textColor: "red"; fontSize: 150%; fontWeight: 800; fontFace: "Arial" }
159                     VSpacer { layoutWeight: 30 }
160                     TextWidget { id: lblPosition; text: ""; backgroundColor: 0x80202020; textColor: 0xFFE0E0 }
161                   }
162                 }
163             }, "", this);
164         } catch (Exception e) {
165             Log.e("Failed to parse dml", e);
166         }
167         // assign OpenGL drawable to child widget background
168         childById("glView").backgroundDrawable = DrawableRef(new OpenGLDrawable(&doDraw));
169 
170         _scene = new Scene3d();
171 
172         _cam = new Camera();
173         _cam.translate(vec3(0, 14, -7));
174 
175         _scene.activeCamera = _cam;
176 
177         static if (true) {
178             _scene.skyBox.setFaceTexture(SkyBox.Face.Right, "skybox_night_right1");
179             _scene.skyBox.setFaceTexture(SkyBox.Face.Left, "skybox_night_left2");
180             _scene.skyBox.setFaceTexture(SkyBox.Face.Top, "skybox_night_top3");
181             _scene.skyBox.setFaceTexture(SkyBox.Face.Bottom, "skybox_night_bottom4");
182             _scene.skyBox.setFaceTexture(SkyBox.Face.Front, "skybox_night_front5");
183             _scene.skyBox.setFaceTexture(SkyBox.Face.Back, "skybox_night_back6");
184         } else {
185             _scene.skyBox.setFaceTexture(SkyBox.Face.Right, "debug_right");
186             _scene.skyBox.setFaceTexture(SkyBox.Face.Left, "debug_left");
187             _scene.skyBox.setFaceTexture(SkyBox.Face.Top, "debug_top");
188             _scene.skyBox.setFaceTexture(SkyBox.Face.Bottom, "debug_bottom");
189             _scene.skyBox.setFaceTexture(SkyBox.Face.Front, "debug_front");
190             _scene.skyBox.setFaceTexture(SkyBox.Face.Back, "debug_back");
191         }
192 
193         dirLightNode = new Node3d();
194         dirLightNode.rotateY(-15);
195         dirLightNode.translateX(2);
196         dirLightNode.translateY(3);
197         dirLightNode.translateZ(0);
198         dirLightNode.light = Light.createPoint(vec3(1.0, 1.0, 1.0), 55); //Light.createDirectional(vec3(1, 0.5, 0.5));
199         //dirLightNode.light = Light.createDirectional(vec3(1, 0.5, 0.5));
200         dirLightNode.light.enabled = true;
201         _scene.addChild(dirLightNode);
202 
203 
204         int x0 = 0;
205         int y0 = 0;
206         int z0 = 0;
207 
208 
209         _minerMesh = new Mesh(VertexFormat(VertexElementType.POSITION, VertexElementType.NORMAL, VertexElementType.COLOR, VertexElementType.TEXCOORD0));
210         _world = new World();
211 
212         initWorldTerrain(_world);
213 
214         int cy0 = 3;
215         for (int y = CHUNK_DY - 1; y > 0; y--)
216             if (!_world.canPass(Vector3d(0, y, 0))) {
217                 cy0 = y;
218                 break;
219             }
220         _world.camPosition = Position(Vector3d(0, cy0, 0), Vector3d(0, 0, 1));
221 
222         _world.setCell(5, cy0 + 5, 7, BlockId.face_test);
223         _world.setCell(-5, cy0 + 5, 7, BlockId.face_test);
224         _world.setCell(5, cy0 + 5, -7, BlockId.face_test);
225         _world.setCell(3, cy0 + 5, 13, BlockId.face_test);
226 
227 
228         //_world.makeCastleWall(Vector3d(25, cy0 - 5, 12), Vector3d(1, 0, 0), 12, 30, 4, BlockId.brick);
229         _world.makeCastle(Vector3d(0, cy0, 60), 30, 12);
230 
231         updateCamPosition(false);
232         //updateMinerMesh();
233 
234         Material minerMaterial = new Material(EffectId("textured.vert", "textured.frag", null), "blocks");
235         minerMaterial.ambientColor = vec3(0.05,0.05,0.05);
236         minerMaterial.textureLinear = false;
237         minerMaterial.fogParams = new FogParams(vec4(0.01, 0.01, 0.01, 1), 12, 80);
238         //minerMaterial.specular = 10;
239         _minerDrawable = new MinerDrawable(_world, minerMaterial, _cam);
240         //_minerDrawable.autobindLights = false;
241         //Model minerDrawable = new Model(minerMaterial, _minerMesh);
242         Node3d minerNode = new Node3d("miner", _minerDrawable);
243         _scene.addChild(minerNode);
244 
245 
246         focusable = true;
247     }
248 
249     MinerDrawable _minerDrawable;
250 
251     int lastMouseX;
252     int lastMouseY;
253     /// process key event, return true if event is processed.
254     override bool onMouseEvent(MouseEvent event) {
255         if (event.action == MouseAction.ButtonDown) {
256             lastMouseX = event.x;
257             lastMouseY = event.y;
258             if (event.button == MouseButton.Left && false) {
259                 int x = event.x;
260                 int y = event.y;
261                 int xindex = 0;
262                 if (x > width * 2 / 3)
263                     xindex = 2;
264                 else if (x > width * 1 / 3)
265                     xindex = 1;
266                 int yindex = 0;
267                 if (y > height * 2 / 3)
268                     yindex = 2;
269                 else if (y > height * 1 / 3)
270                     yindex = 1;
271                 int index = yindex * 3 + xindex;
272                 /*
273                    index:
274                      0  1  2
275                      3  4  5
276                      6  7  8
277                 */
278                 switch(index) {
279                     default:
280                     case 1:
281                     case 4:
282                         //_world.camPosition.forward(1);
283                         //updateCamPosition();
284                         startMoveAnimation(_world.camPosition.direction.forward);
285                         break;
286                     case 0:
287                     case 3:
288                         _world.camPosition.turnLeft();
289                         updateCamPosition();
290                         break;
291                     case 2:
292                     case 5:
293                         _world.camPosition.turnRight();
294                         updateCamPosition();
295                         break;
296                     case 7:
297                         //_world.camPosition.backward(1);
298                         //updateCamPosition();
299                         startMoveAnimation(-_world.camPosition.direction.forward);
300                         break;
301                     case 6:
302                         //_world.camPosition.moveLeft();
303                         //updateCamPosition();
304                         startMoveAnimation(_world.camPosition.direction.left);
305                         break;
306                     case 8:
307                         //_world.camPosition.moveRight();
308                         //updateCamPosition();
309                         startMoveAnimation(_world.camPosition.direction.right);
310                         break;
311                 }
312             }
313         } else if (event.action == MouseAction.Move) {
314             if (event.lbutton.isDown) {
315                 int deltaX = event.x - lastMouseX;
316                 int deltaY = event.y - lastMouseY;
317                 int maxshift = width > 100 ? width : 100;
318                 float deltaAngleX = deltaX * 45.0f / maxshift;
319                 float deltaAngleY = deltaY * 45.0f / maxshift;
320                 lastMouseX = event.x;
321                 lastMouseY = event.y;
322                 float newAngle = _angle + deltaAngleX;
323                 if (newAngle < -180)
324                     newAngle += 360;
325                 else if (newAngle > 180)
326                     newAngle -= 360;
327                 setAngle(newAngle, true);
328                 float newAngleY = _yAngle + deltaAngleY;
329                 if (newAngleY < -65)
330                     newAngleY = -65;
331                 else if (newAngleY > 65)
332                     newAngleY = 65;
333                 setYAngle(newAngleY, true);
334             }
335         } else if (event.action == MouseAction.ButtonUp || event.action == MouseAction.Cancel) {
336             stopMoveAnimation();
337         }
338         return true;
339     }
340 
341     /// process key event, return true if event is processed.
342     override bool onKeyEvent(KeyEvent event) {
343         if (event.action == KeyAction.KeyDown) {
344             switch(event.keyCode) with(KeyCode) {
345                 case KEY_W:
346                 case UP:
347                     _world.camPosition.forward(1);
348                     updateCamPosition();
349                     return true;
350                 case DOWN:
351                 case KEY_S:
352                     _world.camPosition.backward(1);
353                     updateCamPosition();
354                     return true;
355                 case KEY_A:
356                 case LEFT:
357                     _world.camPosition.turnLeft();
358                     updateCamPosition();
359                     return true;
360                 case KEY_D:
361                 case RIGHT:
362                     _world.camPosition.turnRight();
363                     updateCamPosition();
364                     return true;
365                 case HOME:
366                 case KEY_E:
367                     _world.camPosition.moveUp();
368                     updateCamPosition();
369                     return true;
370                 case END:
371                 case KEY_Q:
372                     _world.camPosition.moveDown();
373                     updateCamPosition();
374                     return true;
375                 case KEY_Z:
376                     _world.camPosition.moveLeft();
377                     updateCamPosition();
378                     return true;
379                 case KEY_C:
380                     _world.camPosition.moveRight();
381                     updateCamPosition();
382                     return true;
383                 case KEY_F:
384                     flying = !flying;
385                     if (!flying)
386                         _world.camPosition.pos.y = CHUNK_DY - 3;
387                     updateCamPosition();
388                     return true;
389                 case KEY_U:
390                     enableMeshUpdate = !enableMeshUpdate;
391                     updateCamPosition();
392                     return true;
393                 default:
394                     return false;
395             }
396         }
397         return false;
398     }
399 
400     Node3d dirLightNode;
401 
402     //void visit(World world, ref Position camPosition, Vector3d pos, cell_t cell, int visibleFaces) {
403     //    BlockDef def = BLOCK_DEFS[cell];
404     //    def.createFaces(world, world.camPosition, pos, visibleFaces, _minerMesh);
405     //}
406     
407     bool flying = false;
408     bool enableMeshUpdate = true;
409     Vector3d _moveAnimationDirection;
410 
411     void animateMoving() {
412         if (_moveAnimationDirection != Vector3d(0,0,0)) {
413             Vector3d animPos = _world.camPosition.pos + _moveAnimationDirection;
414             vec3 p = vec3(animPos.x + 0.5f, animPos.y + 0.5f, animPos.z + 0.5f);
415             if ((_animatingPosition - p).length < 2) {
416                 _world.camPosition.pos += _moveAnimationDirection;
417                 updateCamPosition(true);
418             }
419         }
420     }
421 
422     void updateCamPosition(bool animateIt = true) {
423         import std.string;
424         import std.conv : to;
425         import std.utf : toUTF32;
426         import std.format;
427 
428         if (!flying) {
429             animateMoving();
430             while(_world.canPass(_world.camPosition.pos + Vector3d(0, -1, 0)))
431                 _world.camPosition.pos += Vector3d(0, -1, 0);
432             if(!_world.canPass(_world.camPosition.pos + Vector3d(0, -1, 0))) {
433                 if (_world.canPass(_world.camPosition.pos + Vector3d(0, 1, 0)))
434                     _world.camPosition.pos += Vector3d(0, 1, 0);
435                 else if (_world.canPass(_world.camPosition.pos + Vector3d(1, 0, 0)))
436                     _world.camPosition.pos += Vector3d(1, 0, 0);
437                 else if (_world.canPass(_world.camPosition.pos + Vector3d(-1, 0, 0)))
438                     _world.camPosition.pos += Vector3d(-1, 0, 0);
439                 else if (_world.canPass(_world.camPosition.pos + Vector3d(0, 0, 1)))
440                     _world.camPosition.pos += Vector3d(0, 0, 1);
441                 else if (_world.canPass(_world.camPosition.pos + Vector3d(0, 0, -1)))
442                     _world.camPosition.pos += Vector3d(0, 0, -1);
443                 while(_world.canPass(_world.camPosition.pos + Vector3d(0, -1, 0)))
444                     _world.camPosition.pos += Vector3d(0, -1, 0);
445             }
446         }
447 
448         setPos(vec3(_world.camPosition.pos.x + 0.5f, _world.camPosition.pos.y + 0.5f, _world.camPosition.pos.z + 0.5f), animateIt);
449         setAngle(_world.camPosition.direction.angle, animateIt);
450 
451         updatePositionMessage();
452     }
453 
454     void updatePositionMessage() {
455         import std.string : format;
456         Widget w = childById("lblPosition");
457         string dir = _world.camPosition.direction.dir.to!string;
458         dstring s = format("pos(%d,%d) h=%d fps:%d %s    [F]lying: %s   [U]pdateMesh: %s", _world.camPosition.pos.x, _world.camPosition.pos.z, _world.camPosition.pos.y, 
459                            _fps,
460                            dir,
461                            flying, 
462                            enableMeshUpdate).toUTF32;
463         w.text = s;
464     }
465 
466     int _fps = 0;
467 
468     void startMoveAnimation(Vector3d direction) {
469         _moveAnimationDirection = direction;
470         updateCamPosition();
471     }
472 
473     void stopMoveAnimation() {
474         _moveAnimationDirection = Vector3d(0, 0, 0);
475         updateCamPosition();
476     }
477 
478     //void updateMinerMesh() {
479     //    _minerMesh.reset();
480     //    long ts = currentTimeMillis;
481     //    _world.visitVisibleCells(_world.camPosition, this);
482     //    long duration = currentTimeMillis - ts;
483     //    Log.d("DiamondVisitor finished in ", duration, " ms  ", "Vertex count: ", _minerMesh.vertexCount);
484     //
485     //    invalidate();
486     //    //for (int i = 0; i < 20; i++)
487     //    //    Log.d("vertex: ", _minerMesh.vertex(i));
488     //}
489 
490     World _world;
491     vec3 _position;
492     float _directionAngle = 0;
493     float _yAngle = -15;
494     float _angle;
495     vec3 _animatingPosition;
496     float _animatingAngle;
497     float _animatingYAngle;
498 
499     void setPos(vec3 newPos, bool animateIt = false) {
500         if (animateIt) {
501             _position = newPos;
502         } else {
503             _animatingPosition = newPos;
504             _position = newPos;
505         }
506     }
507 
508     void setAngle(float newAngle, bool animateIt = false) {
509         if (animateIt) {
510             _angle = newAngle;
511         } else {
512             _animatingAngle = newAngle;
513             _angle = newAngle;
514         }
515     }
516 
517     void setYAngle(float newAngle, bool animateIt = false) {
518         if (animateIt) {
519             _yAngle = newAngle;
520         } else {
521             _animatingYAngle = newAngle;
522             _yAngle = newAngle;
523         }
524     }
525 
526     /// returns true is widget is being animated - need to call animate() and redraw
527     @property override bool animating() { return true; }
528     /// animates window; interval is time left from previous draw, in hnsecs (1/10000000 of second)
529     override void animate(long interval) {
530         //Log.d("animating");
531         if (interval > 0) {
532             int newfps = cast(int)(10000000.0 / interval);
533             if (newfps < _fps - 3 || newfps > _fps + 3) {
534                 _fps = newfps;
535                 updatePositionMessage();
536             }
537         }
538         animateMoving();
539         if (_animatingAngle != _angle) {
540             float delta = _angle - _animatingAngle;
541             if (delta > 180)
542                 delta -= 360;
543             else if (delta < -180)
544                 delta += 360;
545             float dist = delta < 0 ? -delta : delta;
546             if (dist < 5) {
547                 _animatingAngle = _angle;
548             } else {
549                 float speed = 360 / 2;
550                 float step = speed * interval / 10000000.0f;
551                 //Log.d("Rotate animation delta=", delta, " dist=", dist, " elapsed=", interval, " step=", step);
552                 if (step > dist)
553                     step = dist;
554                 delta = delta * (step /dist);
555                 _animatingAngle += delta;
556             }
557         }
558         if (_animatingYAngle != _yAngle) {
559             float delta = _yAngle - _animatingYAngle;
560             if (delta > 180)
561                 delta -= 360;
562             else if (delta < -180)
563                 delta += 360;
564             float dist = delta < 0 ? -delta : delta;
565             if (dist < 5) {
566                 _animatingYAngle = _yAngle;
567             } else {
568                 float speed = 360 / 2;
569                 float step = speed * interval / 10000000.0f;
570                 //Log.d("Rotate animation delta=", delta, " dist=", dist, " elapsed=", interval, " step=", step);
571                 if (step > dist)
572                     step = dist;
573                 delta = delta * (step /dist);
574                 _animatingYAngle += delta;
575             }
576         }
577         if (_animatingPosition != _position) {
578             vec3 delta = _position - _animatingPosition;
579             float dist = delta.length;
580             if (dist < 0.01) {
581                 _animatingPosition = _position;
582                 // done
583             } else {
584                 float speed = 8;
585                 if (dist > 2)
586                     speed = (dist - 2) * 3 + speed;
587                 float step = speed * interval / 10000000.0f;
588                 //Log.d("Move animation delta=", delta, " dist=", dist, " elapsed=", interval, " step=", step);
589                 if (step > dist)
590                     step = dist;
591                 delta = delta * (step / dist);
592                 _animatingPosition += delta;
593             }
594         }
595         invalidate();
596     }
597     float angle = 0;
598 
599     Scene3d _scene;
600     Camera _cam;
601     Mesh _minerMesh;
602 
603 
604     /// this is OpenGLDrawableDelegate implementation
605     private void doDraw(Rect windowRect, Rect rc) {
606         _cam.setPerspective(rc.width, rc.height, 45.0f, 0.3, MAX_VIEW_DISTANCE);
607         _cam.setIdentity();
608         _cam.translate(_animatingPosition);
609         _cam.rotateY(_animatingAngle);
610         _cam.rotateX(_yAngle);
611         
612 
613         dirLightNode.setIdentity();
614         dirLightNode.translate(_animatingPosition);
615         dirLightNode.rotateY(_animatingAngle);
616 
617         checkgl!glEnable(GL_CULL_FACE);
618         //checkgl!glDisable(GL_CULL_FACE);
619         checkgl!glEnable(GL_DEPTH_TEST);
620         checkgl!glCullFace(GL_BACK);
621         
622         Log.d("Drawing position ", _animatingPosition, " angle ", _animatingAngle);
623 
624         _scene.drawScene(false);
625 
626         checkgl!glDisable(GL_DEPTH_TEST);
627         checkgl!glDisable(GL_CULL_FACE);
628     }
629 
630     ~this() {
631         destroy(_scene);
632         destroy(_world);
633     }
634 }