1 /**
2  * Copyright:       Copyright (C) 2018 Gabriel Gheorghe, All Rights Reserved
3  * Authors:         $(Gabriel Gheorghe)
4  * License:         $(LINK2 https://www.gnu.org/licenses/gpl-3.0.txt, GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007)
5  * Source:          $(LINK2 https://github.com/GabyForceQ/LibertyEngine/blob/master/source/liberty/input/mouse/picker.d)
6  * Documentation:
7  * Coverage:
8 **/
9 module liberty.input.mouse.picker;
10 
11 import liberty.input.impl;
12 import liberty.math.transform;
13 import liberty.math.vector;
14 import liberty.math.matrix;
15 import liberty.camera.impl;
16 import liberty.framework.terrain;
17 import liberty.core.platform;
18 import liberty.core.window;
19 
20 /**
21  *
22 **/
23 final class MousePicker {
24   private {
25     static const int recursionCount = 20;
26     static const float rayRange = 60;
27 
28     Camera camera;
29     Terrain terrain;
30     Vector3F currentRay;
31     Vector3F currentTerrainPoint;
32   }
33   
34   /**
35    *
36   **/
37   Vector3F getCurrentRay()   {
38     return currentRay;
39   }
40 
41   /**
42    *
43   **/
44   Vector3F getCurrentTerrainPoint()   {
45     return currentTerrainPoint;
46   }
47 
48   /**
49    *
50   **/
51   void update(Camera camera, Terrain terrain) {
52     this.camera = camera;
53     this.terrain = terrain;
54 
55     currentRay = computeMouseRay();
56 
57     // (ISSUE #35)
58     //if (intersectionInRange(0, rayRange, currentRay))
59       currentTerrainPoint = binarySearch(0, 0, rayRange, currentRay);
60     //else
61     //  currentTerrainPoint = Vector3F(float.nan, float.nan, float.nan);
62   }
63 
64   private Vector3F computeMouseRay() {
65     Vector2F normalizedCoords = Input.getNormalizedDeviceCoords(Input.getMouse().getPostion());
66     Vector4F clipCoords = Vector4F(normalizedCoords.x , normalizedCoords.y, -1.0f, 1.0f);
67     Vector4F eyeCoords = toEyeCoords(clipCoords);
68     Vector3F worldRay = toWorldCoords(eyeCoords);
69 
70     /*static int oo = 0;
71     if (oo == 40) {
72       import liberty.core.engine;
73       Logger.exception(worldRay.toString());
74       oo = 0;
75     }
76     oo++;*/
77 
78     return worldRay;
79   }
80 
81   private Vector3F toWorldCoords(Vector4F eyeCoords) {
82     Matrix4F invView = camera.viewMatrix;
83     Vector4F rayWorld = Matrix4F.transformation(invView, eyeCoords);
84     Vector3F mouseRay = Vector3F(rayWorld.x, rayWorld.y, rayWorld.z);
85     mouseRay.normalize;
86 
87     return mouseRay;
88   }
89 
90   private Vector4F toEyeCoords(Vector4F clipCoords) {
91     Matrix4F invProjection = camera.projectionMatrix.inverse;
92     Vector4F eyeCoords = Matrix4F.transformation(invProjection, clipCoords);
93 
94     return Vector4F(eyeCoords.x, eyeCoords.y, -1.0f, 0.0f);
95   }
96 
97   private Vector3F getPointOnRay(Vector3F ray, float distance) {
98     return camera.component!Transform.getLocation + ray * distance;
99   }
100 
101   private Vector3F binarySearch(int count, float start, float finish, Vector3F ray) {
102     const float half = start + ((finish - start) / 2.0f);
103 
104     if (count >= recursionCount)
105       return getPointOnRay(ray, half);
106 
107     if (intersectionInRange(start, half, ray))
108       return binarySearch(count + 1, start, half, ray);
109     else
110       return binarySearch(count + 1, half, finish, ray);
111   }
112 
113   private bool intersectionInRange(float start, float finish, Vector3F ray) {
114     Vector3F startPoint = getPointOnRay(ray, start);
115     Vector3F endPoint = getPointOnRay(ray, finish);
116 
117     return !isUnderGround(startPoint) && isUnderGround(endPoint);
118   }
119 
120   private bool isUnderGround(Vector3F testPoint) {
121     float height = 0;
122 
123     height = terrain.getHeight(testPoint.x, testPoint.z);
124 
125     return testPoint.y < height;
126   }
127 }