448 lines
13 KiB
GDScript3
448 lines
13 KiB
GDScript3
|
# MIT License
|
||
|
#
|
||
|
# Copyright (c) 2023 Mark McKay
|
||
|
# https://github.com/blackears/cyclopsLevelBuilder
|
||
|
#
|
||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
# of this software and associated documentation files (the "Software"), to deal
|
||
|
# in the Software without restriction, including without limitation the rights
|
||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
# copies of the Software, and to permit persons to whom the Software is
|
||
|
# furnished to do so, subject to the following conditions:
|
||
|
#
|
||
|
# The above copyright notice and this permission notice shall be included in all
|
||
|
# copies or substantial portions of the Software.
|
||
|
#
|
||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
|
# SOFTWARE.
|
||
|
|
||
|
@tool
|
||
|
extends RefCounted
|
||
|
class_name GeneralMesh
|
||
|
|
||
|
|
||
|
class VertexInfo extends RefCounted:
|
||
|
var index:int
|
||
|
var point:Vector3
|
||
|
var edge_indices:Array[int] = []
|
||
|
var selected:bool
|
||
|
|
||
|
func _init(_index:int, _point:Vector3 = Vector3.ZERO):
|
||
|
index = _index
|
||
|
point = _point
|
||
|
|
||
|
func _to_string():
|
||
|
var s:String = "%s %s [" % [index, point]
|
||
|
for i in edge_indices:
|
||
|
s += "%s " % i
|
||
|
s += "]"
|
||
|
|
||
|
return s
|
||
|
|
||
|
class EdgeInfo extends RefCounted:
|
||
|
var index:int
|
||
|
var start_index:int
|
||
|
var end_index:int
|
||
|
var face_indices:Array[int] = []
|
||
|
var selected:bool
|
||
|
|
||
|
func _init(_index:int, _start:int = 0, _end:int = 0):
|
||
|
index = _index
|
||
|
start_index = _start
|
||
|
end_index = _end
|
||
|
|
||
|
func _to_string():
|
||
|
var s:String = "%s %s %s [" % [index, start_index, end_index]
|
||
|
for i in face_indices:
|
||
|
s += "%s " % i
|
||
|
s += "]"
|
||
|
return s
|
||
|
|
||
|
class FaceInfo extends RefCounted:
|
||
|
var index:int
|
||
|
var normal:Vector3
|
||
|
# var vertex_indices:Array[int]
|
||
|
var face_corner_indices:Array[int]
|
||
|
var material_index:int
|
||
|
var selected:bool
|
||
|
|
||
|
func _init(_index:int, _face_corner_indices:Array[int] = [], _mat_index:int = 0):
|
||
|
index = _index
|
||
|
face_corner_indices = _face_corner_indices
|
||
|
material_index = _mat_index
|
||
|
|
||
|
func _to_string():
|
||
|
var s:String = "%s %s %s [" % [index, normal, material_index]
|
||
|
for i in face_corner_indices:
|
||
|
s += "%s " % i
|
||
|
s += "]"
|
||
|
return s
|
||
|
|
||
|
class FaceCornerInfo extends RefCounted:
|
||
|
var index:int
|
||
|
var uv:Vector2
|
||
|
var vertex_index:int
|
||
|
var face_index:int
|
||
|
var selected:bool
|
||
|
|
||
|
func _init(_index:int, _vertex_index:int, _face_index:int):
|
||
|
vertex_index = _vertex_index
|
||
|
face_index = _face_index
|
||
|
|
||
|
func _to_string():
|
||
|
var s:String = "%s %s %s %s" % [index, uv, vertex_index, face_index]
|
||
|
return s
|
||
|
|
||
|
|
||
|
|
||
|
var vertices:Array[VertexInfo] = []
|
||
|
var edges:Array[EdgeInfo] = []
|
||
|
var faces:Array[FaceInfo] = []
|
||
|
var face_corners:Array[FaceCornerInfo] = []
|
||
|
var bounds:AABB
|
||
|
|
||
|
#var points:PackedVector3Array
|
||
|
|
||
|
func _init():
|
||
|
# init_block(Vector3.ZERO, Vector3.LEFT + Vector3.FORWARD, Vector3.UP)
|
||
|
# dump()
|
||
|
pass
|
||
|
|
||
|
func get_face_indices()->PackedInt32Array:
|
||
|
var result:PackedInt32Array
|
||
|
for f in faces:
|
||
|
result.append(f.index)
|
||
|
return result
|
||
|
|
||
|
func clear_lists():
|
||
|
vertices = []
|
||
|
edges = []
|
||
|
faces = []
|
||
|
face_corners = []
|
||
|
bounds = AABB()
|
||
|
|
||
|
func init_block(block_bounds:AABB):
|
||
|
var p000:Vector3 = block_bounds.position
|
||
|
var p111:Vector3 = block_bounds.end
|
||
|
var p001:Vector3 = Vector3(p000.x, p000.y, p111.z)
|
||
|
var p010:Vector3 = Vector3(p000.x, p111.y, p000.z)
|
||
|
var p011:Vector3 = Vector3(p000.x, p111.y, p111.z)
|
||
|
var p100:Vector3 = Vector3(p111.x, p000.y, p000.z)
|
||
|
var p101:Vector3 = Vector3(p111.x, p000.y, p111.z)
|
||
|
var p110:Vector3 = Vector3(p111.x, p111.y, p000.z)
|
||
|
|
||
|
init_prism([p000, p001, p011, p010], p100 - p000)
|
||
|
|
||
|
|
||
|
func init_prism(base_points:Array[Vector3], extrude_dir:Vector3):
|
||
|
|
||
|
var verts:PackedVector3Array
|
||
|
for p in base_points:
|
||
|
verts.append(p)
|
||
|
for p in base_points:
|
||
|
verts.append(p + extrude_dir)
|
||
|
|
||
|
var index_list:PackedInt32Array
|
||
|
var face_len_list:PackedInt32Array
|
||
|
|
||
|
var num_points:int = base_points.size()
|
||
|
for i0 in num_points:
|
||
|
var i1:int = wrap(i0 + 1, 0, num_points)
|
||
|
|
||
|
index_list.append(i0)
|
||
|
index_list.append(i1)
|
||
|
index_list.append(i1 + num_points)
|
||
|
index_list.append(i0 + num_points)
|
||
|
face_len_list.append(4)
|
||
|
|
||
|
for i0 in num_points:
|
||
|
# index_list.append(i0)
|
||
|
index_list.append(num_points - i0 - 1)
|
||
|
face_len_list.append(num_points)
|
||
|
|
||
|
for i0 in num_points:
|
||
|
index_list.append(i0 + num_points)
|
||
|
# index_list.append(num_points * 2 - i0 - 1)
|
||
|
face_len_list.append(num_points)
|
||
|
|
||
|
init_from_face_lists(verts, index_list, face_len_list)
|
||
|
|
||
|
|
||
|
func init_from_face_lists(verts:PackedVector3Array, index_list:PackedInt32Array, face_len_list:PackedInt32Array):
|
||
|
clear_lists()
|
||
|
|
||
|
for i in verts.size():
|
||
|
var v:VertexInfo = VertexInfo.new(i, verts[i])
|
||
|
vertices.append(v)
|
||
|
|
||
|
if i == 0:
|
||
|
bounds = AABB(verts[0], Vector3.ZERO)
|
||
|
else:
|
||
|
bounds = bounds.expand(verts[i])
|
||
|
|
||
|
var vertex_index_offset:int = 0
|
||
|
for face_index in face_len_list.size():
|
||
|
var num_face_verts = face_len_list[face_index]
|
||
|
# if num_face_verts < 3:
|
||
|
# continue
|
||
|
|
||
|
var face_corners_local:Array[int] = []
|
||
|
for i in num_face_verts:
|
||
|
var face_corner_index:int = face_corners.size()
|
||
|
var face_corner:FaceCornerInfo = FaceCornerInfo.new(face_corner_index, index_list[vertex_index_offset], face_index)
|
||
|
face_corners.append(face_corner)
|
||
|
face_corners_local.append(face_corner_index)
|
||
|
vertex_index_offset += 1
|
||
|
|
||
|
var face:FaceInfo = FaceInfo.new(face_index, face_corners_local)
|
||
|
faces.append(face)
|
||
|
|
||
|
#Calc normal
|
||
|
var fc0:FaceCornerInfo = face_corners[face_corners_local[0]]
|
||
|
# var vidx0 = fc0.vertex_index
|
||
|
var p0:Vector3 = vertices[fc0.vertex_index].point
|
||
|
#
|
||
|
var weighted_normal:Vector3
|
||
|
for i in range(1, num_face_verts - 1):
|
||
|
var fc1:FaceCornerInfo = face_corners[face_corners_local[i]]
|
||
|
var fc2:FaceCornerInfo = face_corners[face_corners_local[i + 1]]
|
||
|
# var vidx1 = fc1.vertex_index
|
||
|
# var vidx2 = fc2.vertex_index
|
||
|
var p1:Vector3 = vertices[fc1.vertex_index].point
|
||
|
var p2:Vector3 = vertices[fc2.vertex_index].point
|
||
|
|
||
|
var v1:Vector3 = p1 - p0
|
||
|
var v2:Vector3 = p2 - p0
|
||
|
weighted_normal += v2.cross(v1)
|
||
|
|
||
|
face.normal = weighted_normal.normalized()
|
||
|
|
||
|
#Calculate edges
|
||
|
for face in faces:
|
||
|
var num_corners = face.face_corner_indices.size()
|
||
|
for i0 in num_corners:
|
||
|
var i1:int = wrap(i0 + 1, 0, num_corners)
|
||
|
var fc0:FaceCornerInfo = face_corners[face.face_corner_indices[i0]]
|
||
|
var fc1:FaceCornerInfo = face_corners[face.face_corner_indices[i1]]
|
||
|
|
||
|
var edge:EdgeInfo = get_edge(fc0.vertex_index, fc1.vertex_index)
|
||
|
if !edge:
|
||
|
var edge_idx = edges.size()
|
||
|
edge = EdgeInfo.new(edge_idx, fc0.vertex_index, fc1.vertex_index)
|
||
|
edges.append(edge)
|
||
|
|
||
|
var v0:VertexInfo = vertices[fc0.vertex_index]
|
||
|
v0.edge_indices.append(edge_idx)
|
||
|
|
||
|
var v1:VertexInfo = vertices[fc1.vertex_index]
|
||
|
v1.edge_indices.append(edge_idx)
|
||
|
|
||
|
edge.face_indices.append(face.index)
|
||
|
|
||
|
|
||
|
func get_edge(vert_idx0:int, vert_idx1:int)->EdgeInfo:
|
||
|
for e in edges:
|
||
|
if e.start_index == vert_idx0 && e.end_index == vert_idx1:
|
||
|
return e
|
||
|
if e.start_index == vert_idx1 && e.end_index == vert_idx0:
|
||
|
return e
|
||
|
return null
|
||
|
|
||
|
|
||
|
func init_block_data(block:BlockData):
|
||
|
clear_lists()
|
||
|
|
||
|
for i in block.points.size():
|
||
|
var v:VertexInfo = VertexInfo.new(i, block.points[i])
|
||
|
vertices.append(v)
|
||
|
|
||
|
if i == 0:
|
||
|
bounds = AABB(v.point, Vector3.ZERO)
|
||
|
else:
|
||
|
bounds = bounds.expand(v.point)
|
||
|
|
||
|
var corner_index_offset:int = 0
|
||
|
for face_index in block.face_vertex_count.size():
|
||
|
var num_face_verts = block.face_vertex_count[face_index]
|
||
|
|
||
|
var face_corners_local:Array[int] = []
|
||
|
for i in num_face_verts:
|
||
|
var vertex_index = block.face_vertex_indices[corner_index_offset]
|
||
|
|
||
|
var face_corner:FaceCornerInfo = FaceCornerInfo.new(corner_index_offset, vertex_index, face_index)
|
||
|
face_corner.uv = block.uvs[corner_index_offset]
|
||
|
face_corners.append(face_corner)
|
||
|
face_corners_local.append(corner_index_offset)
|
||
|
corner_index_offset += 1
|
||
|
|
||
|
var face:FaceInfo = FaceInfo.new(face_index, face_corners_local)
|
||
|
face.material_index = block.face_material_indices[face_index]
|
||
|
faces.append(face)
|
||
|
|
||
|
#Calc normal
|
||
|
var fc0:FaceCornerInfo = face_corners[face_corners_local[0]]
|
||
|
var p0:Vector3 = vertices[fc0.vertex_index].point
|
||
|
#
|
||
|
var weighted_normal:Vector3
|
||
|
for i in range(1, num_face_verts - 1):
|
||
|
var fc1:FaceCornerInfo = face_corners[face_corners_local[i]]
|
||
|
var fc2:FaceCornerInfo = face_corners[face_corners_local[i + 1]]
|
||
|
var p1:Vector3 = vertices[fc1.vertex_index].point
|
||
|
var p2:Vector3 = vertices[fc2.vertex_index].point
|
||
|
|
||
|
var v1:Vector3 = p1 - p0
|
||
|
var v2:Vector3 = p2 - p0
|
||
|
weighted_normal += v2.cross(v1)
|
||
|
|
||
|
face.normal = weighted_normal.normalized()
|
||
|
|
||
|
#Calculate edges
|
||
|
for face in faces:
|
||
|
var num_corners = face.face_corner_indices.size()
|
||
|
for i0 in num_corners:
|
||
|
var i1:int = wrap(i0 + 1, 0, num_corners)
|
||
|
var fc0:FaceCornerInfo = face_corners[face.face_corner_indices[i0]]
|
||
|
var fc1:FaceCornerInfo = face_corners[face.face_corner_indices[i1]]
|
||
|
|
||
|
var edge:EdgeInfo = get_edge(fc0.vertex_index, fc1.vertex_index)
|
||
|
if !edge:
|
||
|
var edge_idx = edges.size()
|
||
|
edge = EdgeInfo.new(edge_idx, fc0.vertex_index, fc1.vertex_index)
|
||
|
edges.append(edge)
|
||
|
|
||
|
var v0:VertexInfo = vertices[fc0.vertex_index]
|
||
|
v0.edge_indices.append(edge_idx)
|
||
|
|
||
|
var v1:VertexInfo = vertices[fc1.vertex_index]
|
||
|
v1.edge_indices.append(edge_idx)
|
||
|
|
||
|
edge.face_indices.append(face.index)
|
||
|
|
||
|
|
||
|
func to_block_data()->BlockData:
|
||
|
var block:BlockData = preload("res://addons/cyclops_level_builder/resources/block_data.gd").new()
|
||
|
# var block:BlockData = BlockData.new()
|
||
|
|
||
|
for v in vertices:
|
||
|
block.points.append(v.point)
|
||
|
|
||
|
for f in faces:
|
||
|
block.face_vertex_count.append(f.face_corner_indices.size())
|
||
|
block.face_material_indices.append(f.material_index)
|
||
|
|
||
|
for fc_idx in f.face_corner_indices:
|
||
|
var fc:FaceCornerInfo = face_corners[fc_idx]
|
||
|
block.face_vertex_indices.append(fc.vertex_index)
|
||
|
block.uvs.append(fc.uv)
|
||
|
|
||
|
return block
|
||
|
|
||
|
func append_mesh(mesh:ImmediateMesh, material:Material, color:Color = Color.WHITE):
|
||
|
|
||
|
for face in faces:
|
||
|
mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP, material)
|
||
|
# print("face %s" % face.index)
|
||
|
|
||
|
mesh.surface_set_normal(face.normal)
|
||
|
|
||
|
var num_corners:int = face.face_corner_indices.size()
|
||
|
for i in num_corners:
|
||
|
var idx = (i + 1) / 2 if i & 1 else wrap(num_corners - (i / 2), 0, num_corners)
|
||
|
var fc:FaceCornerInfo = face_corners[face.face_corner_indices[idx]]
|
||
|
|
||
|
mesh.surface_set_color(color)
|
||
|
mesh.surface_set_uv(fc.uv)
|
||
|
mesh.surface_add_vertex(vertices[fc.vertex_index].point)
|
||
|
# print ("%s %s %s" % [idx, fc.vertex_index, control_mesh.vertices[fc.vertex_index].point])
|
||
|
|
||
|
mesh.surface_end()
|
||
|
|
||
|
func triplanar_unwrap(scale:float = 1):
|
||
|
for fc in face_corners:
|
||
|
var v:VertexInfo = vertices[fc.vertex_index]
|
||
|
var f:FaceInfo = faces[fc.face_index]
|
||
|
|
||
|
if abs(f.normal.x) > abs(f.normal.y) && abs(f.normal.x) > abs(f.normal.z):
|
||
|
fc.uv = Vector2(v.point.y, v.point.z) * scale
|
||
|
elif abs(f.normal.y) > abs(f.normal.z):
|
||
|
fc.uv = Vector2(v.point.x, v.point.z) * scale
|
||
|
else:
|
||
|
fc.uv = Vector2(v.point.x, v.point.y) * scale
|
||
|
|
||
|
|
||
|
func get_face_points(face:FaceInfo)->PackedVector3Array:
|
||
|
var points:PackedVector3Array
|
||
|
for fc_idx in face.face_corner_indices:
|
||
|
var fc:FaceCornerInfo = face_corners[fc_idx]
|
||
|
points.append(vertices[fc.vertex_index].point)
|
||
|
return points
|
||
|
|
||
|
func triangulate_face(face:FaceInfo)->PackedVector3Array:
|
||
|
var points:PackedVector3Array = get_face_points(face)
|
||
|
return MathUtil.trianglate_face(points, face.normal)
|
||
|
|
||
|
|
||
|
func intersect_ray_closest(origin:Vector3, dir:Vector3)->IntersectResults:
|
||
|
if bounds.intersects_ray(origin, dir) == null:
|
||
|
return null
|
||
|
|
||
|
var best_result:IntersectResults
|
||
|
|
||
|
for f in faces:
|
||
|
var tris:PackedVector3Array = triangulate_face(f)
|
||
|
for i in range(0, tris.size(), 3):
|
||
|
var p0:Vector3 = tris[i]
|
||
|
var p1:Vector3 = tris[i + 1]
|
||
|
var p2:Vector3 = tris[i + 2]
|
||
|
|
||
|
#Godot uses clockwise winding
|
||
|
var tri_area_x2:Vector3 = MathUtil.triangle_area_x2(p0, p1, p2)
|
||
|
|
||
|
var p_hit:Vector3 = MathUtil.intersect_plane(origin, dir, p0, tri_area_x2)
|
||
|
if !p_hit.is_finite():
|
||
|
continue
|
||
|
|
||
|
if MathUtil.triangle_area_x2(p_hit, p0, p1).dot(tri_area_x2) < 0:
|
||
|
continue
|
||
|
if MathUtil.triangle_area_x2(p_hit, p1, p2).dot(tri_area_x2) < 0:
|
||
|
continue
|
||
|
if MathUtil.triangle_area_x2(p_hit, p2, p0).dot(tri_area_x2) < 0:
|
||
|
continue
|
||
|
|
||
|
#Intersection
|
||
|
var dist_sq:float = (origin - p_hit).length_squared()
|
||
|
if !best_result || best_result.distance_squared > dist_sq:
|
||
|
|
||
|
var result:IntersectResults = IntersectResults.new()
|
||
|
result.face_index = f.index
|
||
|
result.normal = f.normal
|
||
|
result.position = p_hit
|
||
|
result.distance_squared = dist_sq
|
||
|
|
||
|
best_result = result
|
||
|
|
||
|
return best_result
|
||
|
|
||
|
func translate(offset:Vector3):
|
||
|
for v in vertices:
|
||
|
v.point += offset
|
||
|
|
||
|
func dump():
|
||
|
print ("Verts")
|
||
|
for v in vertices:
|
||
|
print(v.to_string())
|
||
|
print ("Edges")
|
||
|
for e in edges:
|
||
|
print(e.to_string())
|
||
|
print ("Faces")
|
||
|
for f in faces:
|
||
|
print(f.to_string())
|
||
|
print ("Face Corners")
|
||
|
for f in face_corners:
|
||
|
print(f.to_string())
|