a1eed2f3c5991ef6142df71fdf831948d5e827e9
[nit.git] / lib / gamnit / cameras.nit
1 # This file is part of NIT (http://www.nitlanguage.org).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Camera services producing Model-View-Projection matrices
16 module cameras
17
18 import geometry
19 import matrix::projection
20
21 import display
22
23 # A camera with a point of view on the world
24 abstract class Camera
25
26 # TODO make this a physical object in the world
27
28 # The host `GamnitDisplay`
29 var display: GamnitDisplay
30
31 # Position of this camera in world space
32 var position = new Point3d[Float](0.0, 0.0, 0.0) is writable
33
34 # The Model-View-Projection matrix created by this camera
35 #
36 # This method should only be called by the display at the moment
37 # of drawing to the screen.
38 fun mvp_matrix: Matrix is abstract
39 end
40
41 # Simple camera with perspective oriented with Euler angles (`pitch, yaw, roll`)
42 class EulerCamera
43 super Camera
44
45 # Rotation around the X axis (looking down or up)
46 var pitch = 0.0 is writable
47
48 # Rotation around the Y axis (looking left or right)
49 var yaw = 0.0 is writable
50
51 # Rotation around the Z axis
52 var roll = 0.0 is writable
53
54 # Field of view in radians on the vertical axis of the screen
55 #
56 # Default at `0.8`
57 var field_of_view_y = 0.8 is writable
58
59 # Clipping wall near the camera, in world dimensions
60 #
61 # Default at `0.01`.
62 var near = 0.01 is writable
63
64 # Clipping wall the farthest of the camera, in world dimensions
65 #
66 # Default at `10000.0` but this one should be adapted to each context.
67 var far = 10000.0 is writable
68
69 # Look around sensitivity, used by `turn`
70 var sensitivity = 0.005 is writable
71
72 # Apply a mouse movement (or similar) to the camera
73 #
74 # `dx` and `dy` are relative mouse movements in pixels.
75 fun turn(dx, dy: Float)
76 do
77 # Moving on x, turn around the y axis
78 yaw -= dx*sensitivity
79 pitch -= dy*sensitivity
80
81 # Protect rotation around then x axis for not falling on your back
82 pitch = pitch.min(pi/2.0)
83 pitch = pitch.max(-pi/2.0)
84 end
85
86 # Move the camera considering the current orientation
87 fun move(dx, dy, dz: Float)
88 do
89 # +dz move forward
90 position.x -= yaw.sin*dz
91 position.z -= yaw.cos*dz
92
93 # +dx strafe to the right
94 position.x += yaw.cos*dx
95 position.z -= yaw.sin*dx
96
97 # +dz move towards the sky
98 position.y += dy
99 end
100
101 # Aim the camera at `x, y, z`
102 fun look_at(x, y, z: Float)
103 do
104 var dx = position.x
105 var dy = position.y
106 var dz = position.z
107
108 yaw = atan2(dx, dz)
109 pitch = atan2(-dy, dz)
110 end
111
112 # Rotation matrix produced by the current rotation of the camera
113 protected fun rotation_matrix: Matrix
114 do
115 var view = new Matrix.identity(4)
116
117 # Rotate the camera, first by looking left or right, then up or down
118 view.rotate(yaw, 0.0, 1.0, 0.0)
119 view.rotate(pitch, 1.0, 0.0, 0.0)
120 view.rotate(roll, 0.0, 0.0, 1.0)
121
122 return view
123 end
124
125 redef fun mvp_matrix
126 do
127 var view = new Matrix.identity(4)
128
129 # Translate the world away from the camera
130 view.translate(-position.x, -position.y, -position.z)
131
132 # Rotate the camera, first by looking left or right, then up or down
133 view = view * rotation_matrix
134
135 # Use a projection matrix with a depth
136 var projection = new Matrix.perspective(pi*field_of_view_y/2.0,
137 display.aspect_ratio, near, far)
138
139 return view * projection
140 end
141
142 # Reset the camera position so that `height` world units are visible on the y axis at z=0
143 #
144 # By default, `height` is set to `display.height`.
145 #
146 # After the reset, the camera sits on the Z axis and rotation values are reset to 0.
147 # The X axis is horizontal on the screen and the Y axis is vertical.
148 # Higher values on the Z axis are closer to the camera.
149 fun reset_height(height: nullable Float)
150 do
151 if height == null then height = display.height.to_f
152
153 var opp = height / 2.0
154 var angle = field_of_view_y / 2.0
155 var adj = opp / angle.tan
156
157 position.x = 0.0
158 position.y = 0.0
159 position.z = adj
160
161 pitch = 0.0
162 yaw = 0.0
163 roll = 0.0
164 end
165 end
166
167 # Orthogonal camera to draw UI objects with services to work with screens of different sizes
168 #
169 # X axis: left to right of the screen, from `position.x` to `position.x + width`
170 # Y axis: top to bottom of the screen, from `position.y` to `position.y + height`
171 # Z axis: far to near the camera (usually when values are higher), from `far` to `near`
172 class UICamera
173 super Camera
174
175 # Clipping wall near the camera, defaults to 100.0
176 var near = 100.0 is writable
177
178 # Clipping wall the farthest of the camera, defaults to -100.0
179 var far: Float = -100.0 is writable
180
181 # Width in world units, defaults to the width in pixels of the screen
182 var width: Float = display.width.to_f is lazy
183
184 # Height in world units, defaults to the height in pixels of the screen
185 var height: Float = display.height.to_f is lazy
186
187 # Reset the camera position so that `height` world units are visible on the Y axis
188 #
189 # By default, `height` is set to `display.height`.
190 #
191 # This can be used to set standardized UI units independently from the screen resolution.
192 fun reset_height(height: nullable Float)
193 do
194 if height == null then height = display.height.to_f
195
196 self.height = height
197 self.width = height * display.aspect_ratio
198 end
199
200 # Convert the position `x, y` on screen, to UI coordinates
201 fun camera_to_ui(x, y: Numeric): Point[Float]
202 do
203 # FIXME this kind of method should use something like a canvas
204 # instead of being hard coded on the display.
205
206 var wx = x.to_f * width / display.width.to_f - position.x
207 var wy = y.to_f * height / display.height.to_f - position.y
208 return new Point[Float](wx, wy)
209 end
210
211 # Center of the screen, from the point of view of the camera, at z = 0
212 fun center: Point3d[Float] do return new Point3d[Float](position.x + width / 2.0, position.y - height / 2.0, 0.0)
213
214 # Anchor in the top left corner of the screen, at z = 0
215 fun top_left: Point3d[Float] do return new Point3d[Float](position.x, position.y, 0.0)
216
217 # Anchor in the top right corner of the screen, at z = 0
218 fun top_right: Point3d[Float] do return new Point3d[Float](position.x + width, position.y, 0.0)
219
220 # Anchor in the bottom left corner of the screen, at z = 0
221 fun bottom_left: Point3d[Float] do return new Point3d[Float](position.x, position.y - height, 0.0)
222
223 # Anchor in the bottom right corner of the screen, at z = 0
224 fun bottom_right: Point3d[Float] do return new Point3d[Float](position.x + width, position.y - height, 0.0)
225
226 # TODO cache the anchors and the matrix
227
228 redef fun mvp_matrix
229 do
230 var view = new Matrix.identity(4)
231
232 # Translate the world away from the camera
233 view.translate(-position.x, -position.y, -position.z)
234
235 # Use a projection matrix with a depth
236 var projection = new Matrix.orthogonal(0.0, width, -height, 0.0, near, far)
237
238 return view * projection
239 end
240 end