109 lines
4.7 KiB
GDScript
109 lines
4.7 KiB
GDScript
class_name UrbanMeshBatch
|
|
extends RefCounted
|
|
|
|
# Static city detail is merged into one mesh per material, rather than one node
|
|
# per bolt, brick or frame. All dimensions are metres. No physics is added here.
|
|
var _surfaces: Dictionary = {}
|
|
var _materials: Dictionary = {}
|
|
|
|
func _surface(kind: String, tint: Color) -> SurfaceTool:
|
|
var key := kind + tint.to_html()
|
|
if not _surfaces.has(key):
|
|
var tool := SurfaceTool.new()
|
|
tool.begin(Mesh.PRIMITIVE_TRIANGLES)
|
|
_surfaces[key] = tool
|
|
_materials[key] = UrbanMaterials.get_surface(kind, tint)
|
|
return _surfaces[key]
|
|
|
|
func mesh(source: Mesh, position: Vector3, kind: String, tint: Color = Color.WHITE, angles: Vector3 = Vector3.ZERO, scale_value: Vector3 = Vector3.ONE) -> void:
|
|
var basis := Basis.from_euler(angles).scaled(scale_value)
|
|
var normalized := SurfaceTool.new()
|
|
normalized.create_from(source, 0)
|
|
normalized.deindex()
|
|
_surface(kind, tint).append_from(normalized.commit(), 0, Transform3D(basis, position))
|
|
|
|
func box(position: Vector3, size: Vector3, kind: String, tint: Color = Color.WHITE, angles: Vector3 = Vector3.ZERO) -> void:
|
|
var shape := BoxMesh.new()
|
|
shape.size = size
|
|
mesh(shape, position, kind, tint, angles)
|
|
|
|
func cylinder(position: Vector3, radius: float, height: float, kind: String, tint: Color = Color.WHITE, angles: Vector3 = Vector3.ZERO, top_radius: float = -1.0) -> void:
|
|
var shape := CylinderMesh.new()
|
|
shape.bottom_radius = radius
|
|
shape.top_radius = radius if top_radius < 0 else top_radius
|
|
shape.height = height
|
|
shape.radial_segments = 16
|
|
shape.rings = 1
|
|
mesh(shape, position, kind, tint, angles)
|
|
|
|
func sphere(position: Vector3, size: Vector3, kind: String, tint: Color = Color.WHITE) -> void:
|
|
var shape := SphereMesh.new()
|
|
shape.radius = 0.5
|
|
shape.height = 1.0
|
|
shape.radial_segments = 16
|
|
shape.rings = 8
|
|
mesh(shape, position, kind, tint, Vector3.ZERO, size)
|
|
|
|
func _triangle(tool: SurfaceTool, a: Vector3, b: Vector3, c: Vector3) -> void:
|
|
var normal := (c - a).cross(b - a).normalized()
|
|
for vertex in [a, b, c]:
|
|
tool.set_normal(normal)
|
|
tool.set_uv(Vector2(vertex.x + vertex.z, vertex.y))
|
|
tool.add_vertex(vertex)
|
|
|
|
func quad(a: Vector3, b: Vector3, c: Vector3, d: Vector3, kind: String, tint: Color = Color.WHITE, double_sided: bool = false) -> void:
|
|
var tool := _surface(kind, tint)
|
|
_triangle(tool, a, b, c)
|
|
_triangle(tool, a, c, d)
|
|
if double_sided:
|
|
_triangle(tool, c, b, a)
|
|
_triangle(tool, d, c, a)
|
|
|
|
# Profile points are (Z,Y). Bevel rings give sloping bodywork and softened
|
|
# concrete edges without a heavyweight external asset dependency.
|
|
func profile(points: PackedVector2Array, width: float, position: Vector3, kind: String, tint: Color = Color.WHITE, bevel: float = 0.06) -> void:
|
|
var center := Vector2.ZERO
|
|
for point in points:
|
|
center += point
|
|
center /= points.size()
|
|
var rings: Array[PackedVector3Array] = []
|
|
var safe_bevel := minf(bevel, width * 0.2)
|
|
for ring_index in 4:
|
|
var ring := PackedVector3Array()
|
|
var x: float = [-width * 0.5, -width * 0.5 + safe_bevel, width * 0.5 - safe_bevel, width * 0.5][ring_index]
|
|
for point in points:
|
|
var offset := (center - point).normalized() * safe_bevel if ring_index in [0, 3] else Vector2.ZERO
|
|
ring.append(position + Vector3(x, point.y + offset.y, point.x + offset.x))
|
|
rings.append(ring)
|
|
var surface := _surface(kind, tint)
|
|
for ring_index in 3:
|
|
for i in points.size():
|
|
var j := (i + 1) % points.size()
|
|
_triangle(surface, rings[ring_index][i], rings[ring_index + 1][j], rings[ring_index + 1][i])
|
|
_triangle(surface, rings[ring_index][i], rings[ring_index][j], rings[ring_index + 1][j])
|
|
var indices := Geometry2D.triangulate_polygon(points)
|
|
for i in range(0, indices.size(), 3):
|
|
_triangle(surface, rings[0][indices[i]], rings[0][indices[i + 2]], rings[0][indices[i + 1]])
|
|
_triangle(surface, rings[3][indices[i + 2]], rings[3][indices[i]], rings[3][indices[i + 1]])
|
|
|
|
func bevel_box(position: Vector3, size: Vector3, kind: String, tint: Color = Color.WHITE, bevel: float = 0.06) -> void:
|
|
var half_z := size.z * 0.5
|
|
var half_y := size.y * 0.5
|
|
var cut := minf(bevel, minf(half_y, half_z) * 0.35)
|
|
profile(PackedVector2Array([Vector2(-half_z + cut, -half_y), Vector2(half_z - cut, -half_y),
|
|
Vector2(half_z, -half_y + cut), Vector2(half_z, half_y - cut),
|
|
Vector2(half_z - cut, half_y), Vector2(-half_z + cut, half_y),
|
|
Vector2(-half_z, half_y - cut), Vector2(-half_z, -half_y + cut)]), size.x, position, kind, tint, cut)
|
|
|
|
func finish(parent: Node3D, name_prefix: String = "Static") -> void:
|
|
for key in _surfaces:
|
|
var tool: SurfaceTool = _surfaces[key]
|
|
tool.index()
|
|
var result := MeshInstance3D.new()
|
|
result.name = name_prefix + "_" + str(key)
|
|
result.mesh = tool.commit()
|
|
result.material_override = _materials[key]
|
|
parent.add_child(result)
|
|
_surfaces.clear()
|
|
_materials.clear()
|