quack/addons/cyclops_level_builder/math/general_mesh.gd
2023-05-24 00:27:34 +03:00

448 lines
13 KiB
GDScript

# 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())