271 lines
7.5 KiB
GDScript
271 lines
7.5 KiB
GDScript
@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
|