indexing

	description: "[

		A frustum is like an infinite pyramid cutoff by two planes: the near and the far plane.
		It can be used for various applications like representing a visible area or projection area.

	]"
	date: "$Date$"
	revision: "$Revision$"

class EM3D_FRUSTUM

inherit

	EM3D_MOVABLE
		redefine
			make
		end

	EM3D_CONSTANTS
		export {NONE} all
			{ANY} em3d_ortho, em3d_perspective
		undefine copy, is_equal end
	EMGL_VIEW
		export {NONE} all
		undefine copy, is_equal end
	EMGLU_VIEW
		export {NONE} all
		undefine copy, is_equal end
	EMGL_SETTINGS
		export {NONE} all
		undefine copy, is_equal end
create
	make

feature -- Initialisation

	make( a_scene_manager: EM3D_SCENE_MANAGER ) is
			-- Init
		local
			p: EM_VECTOR3D
		do
			Precursor (a_scene_manager)
			projection_type := em3d_perspective
			fovy := 45
			near_dist := 0.01
			far_dist := 10000
			aspect := 1.3333

			update_frustum
		end

feature -- Status report
	projection_type: INTEGER
	fovy: DOUBLE
		-- Specifies the field of view angle, in degrees, in the y direction.
	near_dist, far_dist: DOUBLE
		-- Specifies the distance from the viewer to the near/far clipping plane (always positive).
	left, right, bottom, top: DOUBLE
		-- Specify the coordinates for the left, right, bottom, top horizontal clipping planes.
		-- Used for 'em3d_ortho' projection
	aspect: DOUBLE
		-- Specifies  the  aspect ratio that determines the field of view in the x direction.
		-- The aspect ratio is the ratio of x (width) to y (height).
	frustum_planes: like frustum_planes_internal is
			-- Frustum planes specify the box surrounding the frustum
		do
			if has_changed then
				update_frustum
			end
			result := frustum_planes_internal
		end

feature -- Status setting
	set_left (a_left: DOUBLE) is
				-- Set `left' to `a_left'.
		do
			left := a_left
			has_changed_internal := true
		ensure
			definition_of_set_left: left = a_left
		end
	set_right (a_right: DOUBLE) is
				-- Set `right' to `a_right'.
		do
			right := a_right
			has_changed_internal := true
		ensure
			definition_of_set_right: right = a_right
		end
	set_top ( a_top: DOUBLE) is
				-- Set `top' to `a_top'.
		do
			top := a_top
			has_changed_internal := true
		ensure
			definition_of_set_top: top = a_top
		end
	set_bottom (a_bottom: DOUBLE) is
				-- Set `bottom' to `a_bottom'.
		do
			bottom := a_bottom
			has_changed_internal := true
		ensure
			definition_of_set_bottom: bottom = a_bottom
		end
	set_projection_type (a_projection_type: INTEGER) is
				-- Set `projection' to `a_projection'.
		require
			a_projection_valid: a_projection_type = em3d_ortho or a_projection_type = em3d_perspective
		do
			projection_type := a_projection_type
			has_changed_internal := true
		ensure
			definition_of_set_projection: projection_type = a_projection_type
		end
	set_fovy (a_fov: DOUBLE) is
				-- Set `fov' to `a_fov'.
		require
			a_fov_valid: a_fov > 0 and a_fov <= 180
		do
			fovy := a_fov
			has_changed_internal := true
		ensure
			definition_of_set_fov: fovy = a_fov
		end
	set_near_dist (a_near_dist: DOUBLE) is
				-- Set `near_dist' to `a_near_dist'.
		require
			a_near_dist_positive: a_near_dist > 0
			a_near_dist_smaller_far_dist: a_near_dist < far_dist
		do
			near_dist := a_near_dist
			has_changed_internal := true
		ensure
			definition_of_set_near_dist: near_dist = a_near_dist
		end
	set_far_dist (a_far_dist: DOUBLE) is
				-- Set `far_dist' to `a_far_dist'.
		require
			near_dist_smaller_a_far_dist: a_far_dist > near_dist
		do
			far_dist := a_far_dist
			has_changed_internal := true
		ensure
			definition_of_set_far_dist: far_dist = a_far_dist
		end
	set_aspect (a_aspect: DOUBLE) is
				-- Set `aspect' to `a_aspect'.
		require
			a_aspect_valid: a_aspect > 0
		do
			aspect := a_aspect
			has_changed_internal := true
		ensure
			definition_of_set_aspect: aspect = a_aspect
		end

feature -- Miscellaneous
	projection_matrix: EM_MATRIX44 is
			-- The projection matrix of the current frustum
		do
			emgl_matrix_mode( em_gl_modelview )
			emgl_push_matrix
			emgl_load_identity

			if projection_type = em3d_perspective then
				emglu_perspective( fovy, aspect, near_dist, far_dist )
			else
				emgl_ortho( left, right, bottom, top, near_dist, far_dist )
			end
			result := emgl_get_modelview_matrix

			emgl_pop_matrix
		end
	modelview_matrix: EM_MATRIX44 is
			-- The projection matrix of the current frustum
		local
			direction: EM_VECTOR3D
		do
			emgl_matrix_mode( em_gl_modelview )
			emgl_push_matrix
			emgl_load_identity

			direction := orientation.axis_of_rotation
			emgl_rotated( orientation.angle_of_rotation_degree, direction.x, direction.y, direction.z )
			emgl_translated( position.x, position.y, position.z )
			result := emgl_get_modelview_matrix

			emgl_pop_matrix
		end
	frustum_matrix: EM_MATRIX44 is
			-- The projection matrix of the current frustum
		local
			direction: EM_VECTOR3D
		do
			emgl_matrix_mode( em_gl_modelview )
			emgl_push_matrix
			emgl_load_identity

			if projection_type = em3d_perspective then
				emglu_perspective( fovy, aspect, near_dist, far_dist )
			else
				emgl_ortho( left, right, bottom, top, near_dist, far_dist )
			end
			direction := orientation.axis_of_rotation
			emgl_rotated( orientation.angle_of_rotation_degree, direction.x, direction.y, direction.z )
			emgl_translated( position.x, position.y, position.z )
			result := emgl_get_modelview_matrix

			emgl_pop_matrix

		end
	update_frustum is
			-- Update the frustum planes
		local
			i: INTEGER
			fm: EM_MATRIX44
		do
			has_changed_internal := false
			old_position := position
			old_orientation := orientation
			if frustum_planes_internal=void or else
				(frustum_planes_internal.lower<0 and frustum_planes_internal.lower>5) then
				create frustum_planes_internal.make(0,5)
--				from i:=frustum_planes_internal.lower until i>frustum_planes_internal.upper loop
--					frustum_planes_internal[i] := [ 1,1,0,0 ]
--					i := i + 1
--				end
			end
			fm := frustum_matrix

			frustum_planes_internal.item(em3d_frustum_plane_right ).set( fm[1,4]-fm[1,1], fm[2,4]-fm[2,1], fm[3,4]-fm[3,1], fm[4,4]-fm[4,1] )
			frustum_planes_internal.item(em3d_frustum_plane_left  ).set( fm[1,4]+fm[1,1], fm[2,4]+fm[2,1], fm[3,4]+fm[3,1], fm[4,4]+fm[4,1] )
			frustum_planes_internal.item(em3d_frustum_plane_top   ).set( fm[1,4]-fm[1,2], fm[2,4]-fm[2,2], fm[3,4]-fm[3,2], fm[4,4]-fm[4,2] )
			frustum_planes_internal.item(em3d_frustum_plane_bottom).set( fm[1,4]+fm[1,2], fm[2,4]+fm[2,2], fm[3,4]+fm[3,2], fm[4,4]+fm[4,2] )
			frustum_planes_internal.item(em3d_frustum_plane_far   ).set( fm[1,4]-fm[1,3], fm[2,4]-fm[2,3], fm[3,4]-fm[3,3], fm[4,4]-fm[4,3] )
			frustum_planes_internal.item(em3d_frustum_plane_near  ).set( fm[1,4]+fm[1,3], fm[2,4]+fm[2,3], fm[3,4]+fm[3,3], fm[4,4]+fm[4,3] )
		end



feature {EM3D_SCENE_NODE} -- Basic operations

	render_opengl is
			-- Nothing
		do
--			check false end
		end

feature -- testing functions

	print_orientation is
			-- print out orientation
		do
			io.new_line
			io.put_string("orientation: rotation: " + orientation.angle_of_rotation_degree.out + " axis: x: " + orientation.axis_of_rotation.x.out + " y: " + orientation.axis_of_rotation.y.out + " z: " + orientation.axis_of_rotation.z.out)
			io.new_line
		end



	print_position is
			-- print out position of the camera
		do
			io.new_line
			io.put_string ("position: x " + position.x.out + ", y: " + position.y.out + ", z: " + position.z.out)
			io.new_line
		end


	print_fm is
			-- print out the matrix
		local
			fm: EM_MATRIX44
			i, j: INTEGER
		do
			io.new_line
			io.put_string ("frustum matrix:")
			io.new_line
			fm := frustum_matrix
			from
				i := 1
			until
				i > 4
			loop
				from
					j := 1
				until
					j > 4
				loop
					io.put_string (fm[i,j].out + " ")
					j := j + 1
				end
				io.new_line
				i := i + 1
			end
			io.new_line
		end

	print_planes is
			-- print out the planes
		local
			i : INTEGER
		do
			io.new_line
			io.put_string ("internal_planes:")
			io.new_line
			from i := frustum_planes_internal.lower
			until i > frustum_planes_internal.upper
			loop
				io.put_string ("plane " + i.out + ": a: " + frustum_planes_internal.item(i).a.out + ", b: " + frustum_planes_internal.item(i).b.out + ", c: " + frustum_planes_internal.item(i).c.out + ", d: " + frustum_planes_internal.item(i).d.out)
				io.new_line
				i := i + 1
			end
			io.new_line
		end




feature -- inside Tests

	insideTest_Point(p: EM_VECTOR3D):INTEGER is
			-- calculate if p is outside the frustum or not
		require
			p /= void
		local
			i:INTEGER
		do
			create Result

			Result := em3d_frustum_inside

			from i := frustum_planes_internal.lower
			until i > frustum_planes_internal.upper
			loop
				if (frustum_planes_internal[i].distance_to_point(p) < 0) then
					Result := em3d_frustum_outside
				end

				i := i + 1
			end
		end


	insideTest_Sphere(m: EM_VECTOR3D; r: DOUBLE):INTEGER is
			-- calculate if the sphere with m and radius r is outside frustum or inside or intersects
		require
			m /= void
			r /= void
		local
			distance: DOUBLE
			stop: BOOLEAN
			i: INTEGER
		do
			create Result
			Result := em3d_frustum_inside
			stop := false

			from i := frustum_planes_internal.lower
			until i > frustum_planes_internal.upper or stop
			loop
				distance := frustum_planes_internal[i].distance_to_point(m)
				--io.put_string ("distance to " + i.out + " " + distance.out)
				--io.new_line
				if (distance < -r) then
					Result := em3d_frustum_outside
					stop := true
				elseif (distance < r) then
					Result := em3d_frustum_intersection
					stop := true
				end
				i := i + 1
			end
		end




--	FRUSTUM_INSIDE: INTEGER is 1
--	FRUSTUM_INTERSECTION: INTEGER is 2
--	FRUSTUM_OUTSIDE: INTEGER is 0


feature {EM3D_SCENE_NODE} -- Access
	position : EM_VECTOR3D is
			-- The position of the frustum
		do
			result := world_position
		end
	orientation : EM_QUATERNION is
			-- The orientation of the frustum
		do
			result := world_orientation
		end
feature {NONE} --Access
	has_changed_internal: BOOLEAN
	frustum_planes_internal: ARRAY[ EM_PLANE ]
	old_position: like position
	old_orientation: like orientation
	has_changed: BOOLEAN is
			-- Type your comment here
		do
			result := has_changed_internal or (old_position /= position)
				or (old_orientation /= orientation)
		end

invariant
	fovy_valid: fovy > 0 and fovy <= 180
	near_dist_smaller_far_dist: near_dist < far_dist
	near_dist_positive: near_dist > 0
	projection_valid: projection_type = em3d_ortho or projection_type = em3d_perspective
	aspect_valid: aspect > 0

end