var display: GamnitDisplay
# Position of this camera in world space
- var position = new Point3d[Float](0.0, 0.0, 0.0) is writable
+ var position = new Point3d[Float](0.0, 0.0, 0.0)
# The Model-View-Projection matrix created by this camera
#
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
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
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
# Clipping wall the farthest of the camera, defaults to -100.0
var far: Float = -100.0 is writable
- # Width in world units, defaults to the width in pixels of the screen
- var width: Float = display.width.to_f is lazy
+ # Width in world units, calculated from `height` and the screen aspect ratio
+ fun width: Float do return height * display.aspect_ratio
- # 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
if height == null then height = display.height.to_f
-
self.height = height
- self.width = height * display.aspect_ratio
end
# Convert the position `x, y` on screen, to UI coordinates
- fun camera_to_ui(x, y: Numeric): Point[Float]
+ fun camera_to_ui(x, y: Numeric): Point3d[Float]
do
# FIXME this kind of method should use something like a canvas
# instead of being hard coded on the display.
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 Point3d[Float](wx, -wy, 0.0)
end
- # Anchor in the 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)
+ # Center of the screen, from the point of view of the camera, at z = 0
+ var center: IPoint3d[Float] = new CameraAnchor(self, 0.5, -0.5)
+
+ # Center of the top of the screen, at z = 0
+ var top: IPoint3d[Float] = new CameraAnchor(self, 0.5, 0.0)
+
+ # Center of the bottom of the screen, at z = 0
+ var bottom: IPoint3d[Float] = new CameraAnchor(self, 0.5, -1.0)
+
+ # Center of the left border of the screen, at z = 0
+ var left: IPoint3d[Float] = new CameraAnchor(self, 0.0, -0.5)
+
+ # Center of the right border of the screen, at z = 0
+ var right: IPoint3d[Float] = new CameraAnchor(self, 1.0, -0.5)
- # Anchor in the 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)
+ # Top left corner of the screen, at z = 0
+ var top_left: IPoint3d[Float] = new CameraAnchor(self, 0.0, 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)
+ # Top right corner of the screen, at z = 0
+ var top_right: IPoint3d[Float] = new CameraAnchor(self, 1.0, 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 left corner of the screen, at z = 0
+ var bottom_left: IPoint3d[Float] = new CameraAnchor(self, 0.0, -1.0)
- # TODO cache the anchors and the matrix
+ # Bottom right corner of the screen, at z = 0
+ var bottom_right: IPoint3d[Float] = new CameraAnchor(self, 1.0, -1.0)
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)
return view * projection
end
end
+
+# Immutable relative anchors for reference points on `camera`
+private class CameraAnchor
+ super IPoint3d[Float]
+
+ # Reference camera
+ var camera: UICamera
+
+ # Reference position, the top left of the screen
+ var ref: Point3d[Float] = camera.position is lazy
+
+ # X position as proportion of the screen width
+ var relative_x: Float
+
+ # Y position as proportion of the screen height
+ var relative_y: Float
+
+ redef fun x do return ref.x + relative_x*camera.width
+ redef fun y do return ref.y + relative_y*camera.height
+ redef fun z do return ref.z
+
+ redef fun offset(x, y, z) do return new OffsetPoint3d(self, x.to_f, y.to_f, z.to_f)
+end
+
+# Position relative to another point or usually a `CameraAnchor`
+private class OffsetPoint3d
+ super Point3d[Float]
+
+ autoinit ref, offset_x, offset_y, offset_z
+
+ # Reference point to which the offsets are applied
+ var ref: IPoint3d[Float]
+
+ # Difference on the X axis
+ var offset_x: Float
+
+ # Difference on the X axis
+ var offset_y: Float
+
+ # Difference on the X axis
+ var offset_z: Float
+
+ redef fun x do return ref.x + offset_x
+ redef fun y do return ref.y + offset_y
+ redef fun z do return ref.z + offset_z
+
+ redef fun x=(value) do if value != null then offset_x += value - x
+ redef fun y=(value) do if value != null then offset_y += value - y
+ redef fun z=(value) do if value != null then offset_z += value - z
+
+ redef fun offset(x, y, z) do return new OffsetPoint3d(self, x.to_f, y.to_f, z.to_f)
+end