class CAD_POLYGON

(source code)

description

Polygon coordinates in wet and dry CAD model

note
	description: "Polygon coordinates in wet and dry CAD model"

	author: "Finnian Reilly"
	copyright: "Copyright (c) 2001-2022 Finnian Reilly"
	contact: "finnian at eiffel hyphen loop dot com"

	license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
	date: "2023-11-08 17:27:48 GMT (Wednesday 8th November 2023)"
	revision: "13"

class
	CAD_POLYGON

inherit
	EL_ARRAYED_LIST [COORDINATE_VECTOR]
		rename
			make as make_sized,
			item as point
		redefine
			out
		end

	DEBUG_OUTPUT undefine copy, is_equal, out end

create
	make, make_sized

feature {NONE} -- Initialization

	make (vertice_indices: CHAIN [INTEGER]; coordinate_array: SPECIAL [SPECIAL [DOUBLE]])
		require
			valid_indices: across vertice_indices as v_index all coordinate_array.valid_index (v_index.item) end
		do
			make_sized (vertice_indices.count)
			compare_objects
			across vertice_indices as v_index loop
				extend (create {COORDINATE_VECTOR}.make_from_area (coordinate_array [v_index.item]))
			end
		end

feature -- Access

	debug_output: STRING
		do
			Result := out
		end

	dry_part: like Current
		require
			has_wet_part: there_exists (agent is_wet_point)
		do
			Result := new_partial (agent is_wet_point)
		end

	out: STRING
		do
			create Result.make_filled ('D', count)
			across Current as coord loop
				if is_wet_point (coord.item) then
					Result [coord.cursor_index] := 'W'
				elseif is_surface_point (coord.item) then
					Result [coord.cursor_index] := 'B'
				end
			end
		end

	wet_part: like Current
		require
			has_dry_part: there_exists (agent is_dry_point)
		do
			Result := new_partial (agent is_dry_point)
		end

feature -- Status query

	is_dry: BOOLEAN
		do
			Result := across Current as coord all coord.item.z >= 0 end
		end

	is_wet: BOOLEAN
		do
			Result := across Current as coord all coord.item.z <= 0 end
		end

	is_wet_and_dry: BOOLEAN
		do
			Result := there_exists (agent is_dry_point) and then there_exists (agent is_wet_point)
		end

feature -- Comparison

	is_approximately_equal (other: like Current; precision: DOUBLE ): BOOLEAN
		do
			if count = other.count then
				Result := across Current as coord all
					coord.item.is_approximately_equal (other [coord.cursor_index], precision)
				end
			end
		end

feature -- Basic operations

	print_to (lio: EL_LOGGABLE)
		local
			str: STRING; buffer: EL_STRING_8_BUFFER_ROUTINES
		do
			lio.put_labeled_string ("Code", out)
			lio.put_new_line
			across Current as coord loop
				str := buffer.empty
				coord.item.append_to_string (str)
				lio.put_labeled_substitution ("Coord", "[%S] = [%S]", [coord.cursor_index, str])
				lio.put_new_line
			end
		end

feature -- Contract Support

	is_surface_point (p: COORDINATE_VECTOR): BOOLEAN
		-- `True' if point is on water surface
		do
			Result := p.z = zero
		end

	is_dry_point (p: COORDINATE_VECTOR): BOOLEAN
		-- `True' if point is above water
		do
			Result := p.z > zero
		end

	is_wet_point (p: COORDINATE_VECTOR): BOOLEAN
		-- `True' if point is under water
		do
			Result := p.z < zero
		end

feature {NONE} -- Implementation

	new_partial (excluded: PREDICATE [COORDINATE_VECTOR]): like Current
		-- polygon that is cut above or below waterplane according to `excluded' predicate
		local
			first_included, last_included, first_excluded, last_excluded: COORDINATE_VECTOR
			offset, i, included_count: INTEGER;
		do
			included_count := count - count_of (excluded)
			create Result.make_sized (included_count + 2)

			-- Pythonic circular zero based indexing
			-- use `offset' to effect a rotation such that `not excluded (point)' is first and `exluded (point)' is last
			from offset := 0 until not excluded (circular_i_th (offset)) and excluded (circular_i_th (offset + count - 1)) loop
				offset := offset + 1
			end

			-- Add included points
			from i := 0 until i = included_count loop
				Result.extend (circular_i_th (i + offset))
				i := i + 1
			end
			-- Add intersection of first and last included points with adjacent point on opposite side of plane
			last_included := circular_i_th (offset + included_count - 1)
			first_excluded := circular_i_th (offset + included_count)
			if not is_surface_point (last_included) then
				Result.extend (Surface_plane.intersection_point (last_included, first_excluded))
			end
			first_included := circular_i_th (offset); last_excluded := circular_i_th (offset - 1)
			if not is_surface_point (first_included) then
				Result.extend (Surface_plane.intersection_point (first_included, last_excluded))
			end
		end

feature {NONE} -- Constants

	Zero: DOUBLE = 0.0

	Surface_plane: PLANE_VECTOR
		once
			create Result.make (0, 0, 1, 0)
		end
end