Property definitions

gamnit $ EulerCamera :: defaultinit
# Simple camera with perspective oriented with Euler angles (`pitch, yaw, roll`)
class EulerCamera
	super Camera

	# Rotation around the X axis (looking down or up)
	var pitch = 0.0 is writable

	# Rotation around the Y axis (looking left or right)
	var yaw = 0.0 is writable

	# Rotation around the Z axis
	var roll = 0.0 is writable

	# Field of view in radians on the vertical axis of the screen
	#
	# Default at `0.8`
	var field_of_view_y = 0.8 is writable

	# Clipping wall near the camera, in world dimensions
	#
	# Default at `0.01`.
	var near = 0.01 is writable

	# Clipping wall the farthest of the camera, in world dimensions
	#
	# 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

	# Apply a mouse movement (or similar) to the camera
	#
	# `dx` and `dy` are relative mouse movements in pixels.
	fun turn(dx, dy: Float)
	do
		# Moving on x, turn around the y axis
		yaw -= dx*sensitivity
		pitch -= dy*sensitivity

		# Protect rotation around then x axis for not falling on your back
		pitch = pitch.min(pi/2.0)
		pitch = pitch.max(-pi/2.0)
	end

	# Move the camera considering the current orientation
	fun move(dx, dy, dz: Float)
	do
		# +dz move forward
		position.x -= yaw.sin*dz
		position.z -= yaw.cos*dz

		# +dx strafe to the right
		position.x += yaw.cos*dx
		position.z -= yaw.sin*dx

		# +dz move towards the sky
		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)

		# Rotate the camera, first by looking left or right, then up or down
		view.rotate(yaw,   0.0, 1.0, 0.0)
		view.rotate(pitch, 1.0, 0.0, 0.0)
		view.rotate(roll,  0.0, 0.0, 1.0)

		return view
	end

	redef fun mvp_matrix
	do
		var view = new Matrix.identity(4)

		# Translate the world away from the camera
		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(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
lib/gamnit/cameras.nit:41,1--194,3