@tool extends SpaceEntity ## Classic 2.5D sector beauty. ## class_name SpaceRegion # TODO: Make evelation and height be against the parent. @export var elevation: float = 0.0 @export var height: float = 100.0 @export var wall_texture: Texture2D @export var floor_texture: Texture2D @export var ceiling_texture: Texture2D @export var top_texture: Texture2D var _is_convex_cache: bool func generate_geometry(space: Space) -> Node3D: return _generate_geometry(space, true, null) func _process(delta): if Engine.is_editor_hint(): self.texture = top_texture if top_texture else floor_texture func _generate_geometry(space: Space, looked_from_inside: bool, parent_region: SpaceRegion ) -> Node3D: var geometry := MeshInstance3D.new() geometry.name = name geometry.position = Vector3( position.x * space.unit_scale, 0, position.y * space.unit_scale, ) var mesh := ArrayMesh.new() var surface_count: int = 0 mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, _generate_wall_arrays(space, looked_from_inside)) surface_count += 1 var wall_material := StandardMaterial3D.new() wall_material.albedo_texture = wall_texture mesh.surface_set_material(surface_count - 1, wall_material) if (looked_from_inside): mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, _generate_floor_arrays(space)) surface_count += 1 var floor_material := StandardMaterial3D.new() floor_material.albedo_texture = floor_texture mesh.surface_set_material(surface_count - 1, floor_material) mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, _generate_ceiling_arrays(space)) surface_count += 1 var ceiling_material := StandardMaterial3D.new() ceiling_material.albedo_texture = ceiling_texture ceiling_material.cull_mode = BaseMaterial3D.CULL_FRONT # todo: Don't require state change mesh.surface_set_material(surface_count - 1, ceiling_material) if parent_region and not parent_region._is_convex_cache: if parent_region.height > height: mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, _generate_ceiling_arrays(space)) surface_count += 1 var top_material := StandardMaterial3D.new() top_material.albedo_texture = top_texture mesh.surface_set_material(surface_count - 1, top_material) geometry.mesh = mesh _is_convex_cache = is_convex() for child in get_children(): if child is SpaceRegion: geometry.add_child(child._generate_geometry(space, not looked_from_inside, self)) geometry.create_trimesh_collision() return geometry func _generate_wall_arrays(space: Space, looked_from_inside: bool) -> Array: var polygon = self.polygon var polygon_pairs = Array() for i in range(-1, polygon.size() - 1): polygon_pairs.append([polygon[i], polygon[i+1]]) var vertices = PackedVector3Array() var uvs = PackedVector2Array() for line in polygon_pairs: _push_line(space, line, vertices, uvs, looked_from_inside) var arrays := Array() arrays.resize(Mesh.ARRAY_MAX) arrays[Mesh.ARRAY_VERTEX] = vertices arrays[Mesh.ARRAY_TEX_UV] = uvs return arrays func _push_line(space: Space, line: Array, vertices: PackedVector3Array, uvs: PackedVector2Array, looked_from_inside: bool ): var v0 := Vector3( line[0][0] * space.unit_scale, elevation * space.unit_scale, line[0][1] * space.unit_scale) var v1 := Vector3( line[1][0] * space.unit_scale, (elevation + height) * space.unit_scale, line[1][1] * space.unit_scale) var v2 := Vector3( line[1][0] * space.unit_scale, elevation * space.unit_scale, line[1][1] * space.unit_scale) var v3 := Vector3( line[0][0] * space.unit_scale, (elevation + height) * space.unit_scale, line[0][1] * space.unit_scale) if looked_from_inside: vertices.append_array([v0, v1, v2, v1, v0, v3]) else: vertices.append_array([v0, v2, v1, v1, v3, v0]) uvs.append(Vector2(0, 1)) uvs.append(Vector2(1, 0)) uvs.append(Vector2(1, 1)) uvs.append(Vector2(1, 0)) uvs.append(Vector2(0, 1)) uvs.append(Vector2(0, 0)) func _generate_floor_arrays(space: Space) -> Array: var triangulation := Geometry2D.triangulate_polygon(self.polygon) var vertices := PackedVector3Array() var uvs := PackedVector2Array() vertices.resize(triangulation.size()) uvs.resize(triangulation.size()) for i in range(triangulation.size()): vertices[i] = Vector3( self.polygon[triangulation[i]].x * space.unit_scale, elevation * space.unit_scale, self.polygon[triangulation[i]].y * space.unit_scale, ) uvs[i] = self.polygon[triangulation[i]] * space.unit_scale var arrays := Array() arrays.resize(Mesh.ARRAY_MAX) arrays[Mesh.ARRAY_VERTEX] = vertices arrays[Mesh.ARRAY_TEX_UV] = uvs return arrays # todo: Reuse triangulation with the floor func _generate_ceiling_arrays(space: Space) -> Array: var triangulation := Geometry2D.triangulate_polygon(self.polygon) var vertices := PackedVector3Array() var uvs := PackedVector2Array() vertices.resize(triangulation.size()) uvs.resize(triangulation.size()) for i in range(triangulation.size()): vertices[i] = Vector3( self.polygon[triangulation[i]].x * space.unit_scale, (elevation + height) * space.unit_scale, self.polygon[triangulation[i]].y * space.unit_scale, ) uvs[i] = self.polygon[triangulation[i]] * space.unit_scale var arrays := Array() arrays.resize(Mesh.ARRAY_MAX) arrays[Mesh.ARRAY_VERTEX] = vertices arrays[Mesh.ARRAY_TEX_UV] = uvs return arrays ## Check whether region polygon forms a convex geometry. ## ## Based on: https://math.stackexchange.com/a/1745427 func is_convex() -> bool: if self.polygon.size() < 3: return false var wSign: float = 0 # First nonzero orientation (positive or negative) var xSign: float = 0 var xFirstSign: float = 0 # Sign of first nonzero edge vector x var xFlips: float = 0 # Number of sign changes in x var ySign: float = 0 var yFirstSign: float = 0 # Sign of first nonzero edge vector y var yFlips: float = 0 # Number of sign changes in y var curr: Vector2 = self.polygon[-2] # Second-to-last vertex var next: Vector2 = self.polygon[-1] # Last vertex for v in self.polygon: # Each vertex, in order var prev: Vector2 = curr # Previous vertex curr = next # Current vertex next = v # Next vertex # Previous edge vector ("before"): var bx: float = curr.x - prev.x var by: float = curr.y - prev.y # Next edge vector ("after"): var ax: float = next.x - curr.x var ay: float = next.y - curr.y # Calculate sign flips using the next edge vector ("after"), # recording the first sign. if ax > 0: if xSign == 0: xFirstSign = +1 elif xSign < 0: xFlips = xFlips + 1 xSign = +1 elif ax < 0: if xSign == 0: xFirstSign = -1 elif xSign > 0: xFlips = xFlips + 1 xSign = -1 if xFlips > 2: return false if ay > 0: if ySign == 0: yFirstSign = +1 elif ySign < 0: yFlips = yFlips + 1 ySign = +1 elif ay < 0: if ySign == 0: yFirstSign = -1 elif ySign > 0: yFlips = yFlips + 1 ySign = -1 if yFlips > 2: return false # Find out the orientation of this pair of edges, # and ensure it does not differ from previous ones. var w := bx * ay - ax * by if (wSign == 0) and (w != 0): wSign = w elif (wSign > 0) and (w < 0): return false elif (wSign < 0) and (w > 0): return false # Final/wraparound sign flips: if (xSign != 0) and (xFirstSign != 0) and (xSign != xFirstSign): xFlips = xFlips + 1 if (ySign != 0) and (yFirstSign != 0) and (ySign != yFirstSign): yFlips = yFlips + 1 # Concave polygons have two sign flips along each axis. if (xFlips != 2) or (yFlips != 2): return false # This is a convex polygon. return true