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