X-Git-Url: http://nitlanguage.org diff --git a/lib/gamnit/cameras.nit b/lib/gamnit/cameras.nit index 43aae77..042896a 100644 --- a/lib/gamnit/cameras.nit +++ b/lib/gamnit/cameras.nit @@ -38,7 +38,7 @@ abstract class Camera fun mvp_matrix: Matrix is abstract end -# Simple camera with perspective oriented with Euler angles (`pitch`, `yaw`, `roll`) +# Simple camera with perspective oriented with Euler angles (`pitch, yaw, roll`) class EulerCamera super Camera @@ -63,8 +63,8 @@ class EulerCamera # Clipping wall the farthest of the camera, in world dimensions # - # Default at `100.0` but this one should be adapted to each context. - var far = 100.0 is writable + # Default at `10000.0` but this one should be adapted to each context. + var far = 10000.0 is writable # Look around sensitivity, used by `turn` var sensitivity = 0.005 is writable @@ -98,6 +98,17 @@ class EulerCamera position.y += dy end + # Aim the camera at `x, y, z` + fun look_at(x, y, z: Float) + do + var dx = position.x + var dy = position.y + var dz = position.z + + yaw = atan2(dx, dz) + pitch = atan2(-dy, dz) + end + # Rotation matrix produced by the current rotation of the camera protected fun rotation_matrix: Matrix do @@ -116,17 +127,70 @@ class EulerCamera var view = new Matrix.identity(4) # Translate the world away from the camera - view.translate(-position.x/2.0, -position.y/2.0, -position.z/2.0) + view.translate(-position.x, -position.y, -position.z) # Rotate the camera, first by looking left or right, then up or down view = view * rotation_matrix # Use a projection matrix with a depth - var projection = new Matrix.perspective(pi*field_of_view_y/2.0, + var projection = new Matrix.perspective(field_of_view_y, display.aspect_ratio, near, far) return view * projection end + + # Reset the camera position so that `height` world units are visible on the y axis at z=0 + # + # By default, `height` is set to `display.height`. + # + # After the reset, the camera sits on the Z axis and rotation values are reset to 0. + # The X axis is horizontal on the screen and the Y axis is vertical. + # Higher values on the Z axis are closer to the camera. + fun reset_height(height: nullable Float) + do + if height == null then height = display.height.to_f + + var opp = height / 2.0 + var angle = field_of_view_y / 2.0 + var adj = opp / angle.tan + + position.x = 0.0 + position.y = 0.0 + position.z = adj + + pitch = 0.0 + yaw = 0.0 + roll = 0.0 + end + + # Convert the position `x, y` on screen, to world coordinates on the plane at `target_z` + # + # `target_z` defaults to `0.0` and specifies the Z coordinates of the plane + # on which to project the screen position `x, y`. + # + # This method assumes that the camera is looking along the Z axis towards higher values. + # Using it in a different orientation can be useful, but won't result in valid + # world coordinates. + fun camera_to_world(x, y: Numeric, target_z: nullable Float): Point[Float] + do + # TODO, this method could be tweaked to support projecting the 2D point, + # on the near plane (x,y) onto a given distance no matter to orientation + # of the camera. + + target_z = target_z or else 0.0 + + # Convert from pixel units / window resolution to + # units on the near clipping wall to + # units on the target wall at Z = 0 + var near_height = (field_of_view_y/2.0).tan * near + var cross_screen_to_near = near_height / (display.height.to_f/2.0) + var cross_near_to_target = (position.z - target_z) / near + var mod = cross_screen_to_near * cross_near_to_target + + var wx = position.x + (x.to_f-display.width.to_f/2.0) * mod + var wy = position.y - (y.to_f-display.height.to_f/2.0) * mod + return new Point[Float](wx, wy) + end end # Orthogonal camera to draw UI objects with services to work with screens of different sizes @@ -146,13 +210,13 @@ class UICamera # Width in world units, defaults to the width in pixels of the screen var width: Float = display.width.to_f is lazy - # Height in world units, defaults to the height in pixels of the screen - var height: Float = display.height.to_f is lazy + # Height in world units, defaults to 1080.0 + # + # Set this value using `reset_height`. + var height = 1080.0 # Reset the camera position so that `height` world units are visible on the Y axis # - # By default, `height` is set to `display.height`. - # # This can be used to set standardized UI units independently from the screen resolution. fun reset_height(height: nullable Float) do @@ -170,29 +234,44 @@ class UICamera var wx = x.to_f * width / display.width.to_f - position.x var wy = y.to_f * height / display.height.to_f - position.y - return new Point[Float](wx, wy) + return new Point[Float](wx, -wy) end - # Anchor in the top left corner of the screen, at z = 0 + # Center of the screen, from the point of view of the camera, at z = 0 + fun center: Point3d[Float] do return new Point3d[Float](position.x + width / 2.0, position.y - height / 2.0, 0.0) + + # Center of the top of the screen, at z = 0 + fun top: Point3d[Float] do return new Point3d[Float](position.x + width / 2.0, position.y, 0.0) + + # Center of the bottom of the screen, at z = 0 + fun bottom: Point3d[Float] do return new Point3d[Float](position.x + width / 2.0, position.y - height, 0.0) + + # Center of the left border of the screen, at z = 0 + fun left: Point3d[Float] do return new Point3d[Float](position.x, position.y - height / 2.0, 0.0) + + # Center of the right border of the screen, at z = 0 + fun right: Point3d[Float] do return new Point3d[Float](position.x + width, position.y - height / 2.0, 0.0) + + # Top left corner of the screen, at z = 0 fun top_left: Point3d[Float] do return new Point3d[Float](position.x, position.y, 0.0) - # Anchor in the top right corner of the screen, at z = 0 + # Top right corner of the screen, at z = 0 fun top_right: Point3d[Float] do return new Point3d[Float](position.x + width, position.y, 0.0) - # Anchor in the bottom left corner of the screen, at z = 0 - fun bottom_left: Point3d[Float] do return new Point3d[Float](position.x, position.y + height, 0.0) + # Bottom left corner of the screen, at z = 0 + fun bottom_left: Point3d[Float] do return new Point3d[Float](position.x, position.y - height, 0.0) - # Anchor in the bottom right corner of the screen, at z = 0 - fun bottom_right: Point3d[Float] do return new Point3d[Float](position.x + width, position.y + height, 0.0) + # Bottom right corner of the screen, at z = 0 + fun bottom_right: Point3d[Float] do return new Point3d[Float](position.x + width, position.y - height, 0.0) - # TODO cache the anchors and the matrix + # TODO cache the anchors redef fun mvp_matrix do var view = new Matrix.identity(4) # Translate the world away from the camera - view.translate(-position.x/2.0, -position.y/2.0, -position.z/2.0) + view.translate(-position.x, -position.y, -position.z) # Use a projection matrix with a depth var projection = new Matrix.orthogonal(0.0, width, -height, 0.0, near, far)