1447 lines
42 KiB
GDScript
1447 lines
42 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 ConvexVolume
|
|
|
|
|
|
class VertexInfo extends RefCounted:
|
|
var index:int
|
|
var mesh:ConvexVolume
|
|
var point:Vector3
|
|
var normal:Vector3
|
|
var edge_indices:Array[int] = []
|
|
var selected:bool
|
|
|
|
func _init(mesh:ConvexVolume, point:Vector3 = Vector3.ZERO):
|
|
self.mesh = mesh
|
|
self.point = point
|
|
|
|
func _to_string():
|
|
var s:String = "%s [" % [point]
|
|
for i in edge_indices:
|
|
s += "%s " % i
|
|
s += "]"
|
|
|
|
return s
|
|
|
|
class EdgeInfo extends RefCounted:
|
|
var index:int
|
|
var mesh:ConvexVolume
|
|
var start_index:int
|
|
var end_index:int
|
|
var face_indices:Array[int] = []
|
|
var selected:bool
|
|
|
|
func _init(mesh:ConvexVolume, start:int = 0, end:int = 0):
|
|
self.mesh = mesh
|
|
start_index = start
|
|
end_index = end
|
|
|
|
func get_midpoint()->Vector3:
|
|
var p0:Vector3 = mesh.vertices[start_index].point
|
|
var p1:Vector3 = mesh.vertices[end_index].point
|
|
return (p0 + p1) / 2
|
|
|
|
func _to_string():
|
|
var s:String = "%s %s [" % [start_index, end_index]
|
|
for i in face_indices:
|
|
s += "%s " % i
|
|
s += "]"
|
|
return s
|
|
|
|
|
|
class FaceInfo extends RefCounted:
|
|
var index:int
|
|
var mesh:ConvexVolume
|
|
#@deprecated
|
|
#var id:int
|
|
var normal:Vector3 #Face normal points in direction of interior
|
|
var material_id:int
|
|
var uv_transform:Transform2D
|
|
var color:Color
|
|
var visible:bool
|
|
var selected:bool
|
|
var vertex_indices:Array[int]
|
|
var face_vertex_indices:Array[int]
|
|
var triangulation_indices:Array[int]
|
|
var lightmap_uvs:PackedVector2Array
|
|
|
|
func _init(mesh:ConvexVolume, normal:Vector3, uv_transform:Transform2D = Transform2D.IDENTITY, material_id:int = 0, visible:bool = true, color:Color = Color.WHITE, selected:bool = false):
|
|
self.mesh = mesh
|
|
#self.id = id
|
|
self.normal = normal
|
|
self.material_id = material_id
|
|
self.uv_transform = uv_transform
|
|
self.selected = selected
|
|
self.visible = visible
|
|
self.color = color
|
|
|
|
func get_plane()->Plane:
|
|
return Plane(normal, mesh.vertices[vertex_indices[0]].point)
|
|
|
|
func get_points()->PackedVector3Array:
|
|
var result:PackedVector3Array
|
|
for i in vertex_indices:
|
|
result.append(mesh.vertices[i].point)
|
|
return result
|
|
|
|
func get_centroid()->Vector3:
|
|
var points:PackedVector3Array = get_points()
|
|
var center:Vector3
|
|
for p in points:
|
|
center += p
|
|
center /= points.size()
|
|
return center
|
|
|
|
##Returns vector with magnitude equal to twice the area of the face and
|
|
## pointing along the face normal
|
|
func get_area_vector_x2()->Vector3:
|
|
var points:PackedVector3Array = get_points()
|
|
return MathUtil.face_area_x2(points)
|
|
|
|
func get_triangulation()->Array[int]:
|
|
if triangulation_indices.is_empty():
|
|
var points:PackedVector3Array
|
|
for v_idx in vertex_indices:
|
|
points.append(mesh.vertices[v_idx].point)
|
|
|
|
# print("start points %s" % points)
|
|
|
|
var normal:Vector3 = MathUtil.face_area_x2(points).normalized()
|
|
# print("normal %s" % normal)
|
|
triangulation_indices = MathUtil.trianglate_face_vertex_indices(points, normal)
|
|
# print("triangulation %s" % str(triangulation_indices))
|
|
|
|
return triangulation_indices
|
|
|
|
func get_trianges()->PackedVector3Array:
|
|
var indices:Array[int] = get_triangulation()
|
|
var result:PackedVector3Array
|
|
|
|
for fv_idx in indices:
|
|
var v_idx:int = vertex_indices[fv_idx]
|
|
result.append(mesh.vertices[v_idx].point)
|
|
|
|
# print("triangules %s" % result)
|
|
|
|
return result
|
|
|
|
func reverse():
|
|
normal = -normal
|
|
vertex_indices.reverse()
|
|
triangulation_indices.clear()
|
|
|
|
#Vertex on face closest to given point
|
|
func get_closest_vertex(point:Vector3)->int:
|
|
var best_dist:float = -1
|
|
var best_idx:int = -1
|
|
for v_idx in vertex_indices:
|
|
var v:VertexInfo = mesh.vertices[v_idx]
|
|
var dist:float = v.point.distance_to(point)
|
|
if best_idx == -1 || dist < best_dist:
|
|
best_idx = v_idx
|
|
best_dist = dist
|
|
|
|
return best_idx
|
|
|
|
|
|
|
|
class FaceVertexInfo extends RefCounted:
|
|
var index:int
|
|
var mesh:ConvexVolume
|
|
var face_index:int
|
|
var vertex_index:int
|
|
var vertex_local_index:int #Position of vertex within this face loop - eg, if this face has 5 verts, the local vert numbers are in order [0, 1, 2, 3, 4]
|
|
var color:Color = Color.WHITE
|
|
var normal:Vector3
|
|
|
|
|
|
var vertices:Array[VertexInfo] = []
|
|
var edges:Array[EdgeInfo] = []
|
|
var faces:Array[FaceInfo] = []
|
|
var face_vertices:Array[FaceVertexInfo] = []
|
|
var face_vertex_coord_map:Dictionary
|
|
|
|
var bounds:AABB
|
|
|
|
var lightmap_uvs_dirty = true
|
|
|
|
var active_vertex:int = -1
|
|
var active_edge:int = -1
|
|
var active_face:int = -1
|
|
var active_face_vertex:int = -1
|
|
|
|
func _to_string()->String:
|
|
var result:String = ""
|
|
for v in vertices:
|
|
result += str(v.point) + ", "
|
|
return result
|
|
|
|
|
|
func init_block(block_bounds:AABB, uv_transform:Transform2D = Transform2D.IDENTITY, material_id:int = -1, visible:bool = true, color:Color = Color.WHITE):
|
|
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, uv_transform, material_id, visible, color)
|
|
|
|
|
|
func init_prism(base_points:Array[Vector3], extrude_dir:Vector3, uv_transform:Transform2D = Transform2D.IDENTITY, material_id:int = -1, visible:bool = true, color:Color = Color.WHITE):
|
|
vertices = []
|
|
edges = []
|
|
faces = []
|
|
face_vertices = []
|
|
face_vertex_coord_map.clear()
|
|
|
|
var base_normal = -extrude_dir.normalized()
|
|
|
|
var face_area_x2:Vector3 = MathUtil.face_area_x2(base_points)
|
|
if face_area_x2.dot(extrude_dir) > 0:
|
|
base_points.reverse()
|
|
|
|
for p in base_points:
|
|
var v:VertexInfo = VertexInfo.new(self, p)
|
|
v.index = vertices.size()
|
|
vertices.append(v)
|
|
for p in base_points:
|
|
var v:VertexInfo = VertexInfo.new(self, p + extrude_dir)
|
|
v.index = vertices.size()
|
|
vertices.append(v)
|
|
|
|
var f0:FaceInfo = FaceInfo.new(self, base_normal, uv_transform, material_id, visible, color)
|
|
f0.index = faces.size()
|
|
f0.vertex_indices = []
|
|
f0.vertex_indices.append_array(range(base_points.size()))
|
|
faces.append(f0)
|
|
var f1:FaceInfo = FaceInfo.new(self, -base_normal, uv_transform, material_id, visible, color)
|
|
f1.index = faces.size()
|
|
f1.vertex_indices = []
|
|
f1.vertex_indices.append_array(range(base_points.size(), base_points.size() * 2))
|
|
f1.vertex_indices.reverse()
|
|
faces.append(f1)
|
|
|
|
|
|
for i in base_points.size():
|
|
var p_idx0:int = i
|
|
var p_idx1:int = wrap(i + 1, 0, base_points.size())
|
|
|
|
var v0:VertexInfo = vertices[p_idx0]
|
|
var v1:VertexInfo = vertices[p_idx1]
|
|
|
|
var normal = base_normal.cross(v1.point - v0.point).normalized()
|
|
var f:FaceInfo = FaceInfo.new(self, normal, uv_transform, material_id, visible, color)
|
|
f.index = faces.size()
|
|
f.vertex_indices = [p_idx1, p_idx0, p_idx0 + base_points.size(), p_idx1 + base_points.size()]
|
|
faces.append(f)
|
|
|
|
build_edges()
|
|
build_face_vertices()
|
|
calc_vertex_normals()
|
|
|
|
bounds = calc_bounds()
|
|
calc_lightmap_uvs()
|
|
|
|
func init_from_convex_block_data(data:ConvexBlockData):
|
|
#print("init_from_convex_block_data")
|
|
#print(var_to_str(data))
|
|
|
|
vertices = []
|
|
edges = []
|
|
faces = []
|
|
face_vertices = []
|
|
face_vertex_coord_map.clear()
|
|
|
|
if !data:
|
|
return
|
|
#data.validate_arrays()
|
|
|
|
active_vertex = data.active_vertex
|
|
active_edge = data.active_edge
|
|
active_face = data.active_face
|
|
|
|
for i in data.vertex_points.size():
|
|
var v:VertexInfo = VertexInfo.new(self, data.vertex_points[i])
|
|
v.index = vertices.size()
|
|
vertices.append(v)
|
|
v.selected = data.vertex_selected[i]
|
|
|
|
var num_edges:int = data.edge_vertex_indices.size() / 2
|
|
for i in num_edges:
|
|
var edge:EdgeInfo = EdgeInfo.new(self, data.edge_vertex_indices[i * 2], data.edge_vertex_indices[i * 2 + 1])
|
|
edge.index = edges.size()
|
|
edges.append(edge)
|
|
edge.face_indices.append(data.edge_face_indices[i * 2])
|
|
edge.face_indices.append(data.edge_face_indices[i * 2 + 1])
|
|
edge.selected = data.edge_selected[i]
|
|
#edge.active = data.edge_active[i]
|
|
|
|
#print("data.face_vertex_count ", data.face_vertex_count)
|
|
var face_vertex_count:int = 0
|
|
for face_idx in data.face_vertex_count.size():
|
|
var num_verts:int = data.face_vertex_count[face_idx]
|
|
var vert_indices:Array[int]
|
|
var vert_points:PackedVector3Array
|
|
for i in num_verts:
|
|
var vert_idx:int = data.face_vertex_indices[face_vertex_count]
|
|
vert_indices.append(vert_idx)
|
|
vert_points.append(vertices[vert_idx].point)
|
|
face_vertex_count += 1
|
|
|
|
var normal = MathUtil.face_area_x2(vert_points).normalized()
|
|
|
|
var face_uv_transform:Transform2D = data.face_uv_transform[face_idx]
|
|
var face_mat_index:int = data.face_material_indices[face_idx]
|
|
var face_visible:int = data.face_visible[face_idx]
|
|
var face_color:Color = data.face_color[face_idx]
|
|
var f:FaceInfo = FaceInfo.new(self, normal, face_uv_transform, face_mat_index, face_visible, face_color)
|
|
f.index = faces.size()
|
|
f.selected = data.face_selected[face_idx]
|
|
#f.active = data.face_active[face_idx]
|
|
f.vertex_indices = vert_indices
|
|
|
|
faces.append(f)
|
|
|
|
#print("faces buit ", faces.size())
|
|
|
|
bounds = calc_bounds()
|
|
calc_lightmap_uvs()
|
|
|
|
#Rebuild face verticies if input data is erronious
|
|
var all_zero:bool = true
|
|
for f_idx in data.face_vertex_face_index:
|
|
if f_idx != 0:
|
|
all_zero = false
|
|
break
|
|
|
|
if data.face_vertex_face_index.size() == 0 || all_zero:
|
|
#print("<<0>>")
|
|
#Face vertices not initialized - generate new ones
|
|
build_face_vertices()
|
|
else:
|
|
#print("<<1>>")
|
|
for fv_idx in data.face_vertex_face_index.size():
|
|
var f_idx:int = data.face_vertex_face_index[fv_idx]
|
|
var v_idx:int = data.face_vertex_vertex_index[fv_idx]
|
|
var fv:FaceVertexInfo = FaceVertexInfo.new()
|
|
face_vertices.append(fv)
|
|
#faces[f_idx].face_vertex_indices.append(fv_idx)
|
|
|
|
fv.face_index = f_idx
|
|
fv.vertex_index = v_idx
|
|
var coord:Vector2i = Vector2i(f_idx, v_idx)
|
|
face_vertex_coord_map[coord] = fv
|
|
|
|
var f:FaceInfo = faces[f_idx]
|
|
fv.normal = data.face_vertex_normal[fv_idx] if data.face_vertex_normal.size() > fv_idx else f.normal
|
|
fv.color = data.face_vertex_color[fv_idx] if data.face_vertex_color.size() > fv_idx else Color(1, 1, 1, 1)
|
|
#print("init_from_convex_block_data face_vertex_coord_map ", face_vertex_coord_map)
|
|
for f_idx in faces.size():
|
|
var face:FaceInfo = faces[f_idx]
|
|
for v_idx in face.vertex_indices:
|
|
face.face_vertex_indices.append(face_vertex_coord_map[Vector2i(f_idx, v_idx)].index)
|
|
|
|
|
|
calc_vertex_normals()
|
|
|
|
#print("init_from_convex_block_data %s" % format_faces_string())
|
|
|
|
func init_from_mesh_vector_data(mvd:MeshVectorData):
|
|
#print("init_from_mesh_vector_data")
|
|
var block_data:ConvexBlockData = ConvexBlockData.new()
|
|
block_data.init_from_mesh_vector_data(mvd)
|
|
init_from_convex_block_data(block_data)
|
|
|
|
|
|
#Calc convex hull bouding points
|
|
func init_from_points(points:PackedVector3Array, uv_transform:Transform2D = Transform2D.IDENTITY, material_id:int = -1, visible:bool = true, color:Color = Color.WHITE):
|
|
vertices = []
|
|
edges = []
|
|
faces = []
|
|
face_vertices = []
|
|
face_vertex_coord_map.clear()
|
|
|
|
#print("init_from_points %s" % points)
|
|
var hull:QuickHull.Hull = QuickHull.quickhull(points)
|
|
#print("hull %s" % hull.format_points())
|
|
|
|
var hull_points:Array[Vector3] = hull.get_points()
|
|
|
|
for p in hull_points:
|
|
var v:VertexInfo = VertexInfo.new(self, p)
|
|
v.index = vertices.size()
|
|
vertices.append(v)
|
|
|
|
for facet in hull.facets:
|
|
var plane:Plane = facet.plane
|
|
var vert_indices:Array[int] = []
|
|
|
|
for p in facet.points:
|
|
var vert_idx:int = hull_points.find(p)
|
|
vert_indices.append(vert_idx)
|
|
|
|
var f:FaceInfo = FaceInfo.new(self, plane.normal, uv_transform, material_id, visible, color)
|
|
f.index = faces.size()
|
|
f.vertex_indices = vert_indices
|
|
faces.append(f)
|
|
|
|
|
|
build_edges()
|
|
build_face_vertices()
|
|
calc_vertex_normals()
|
|
|
|
bounds = calc_bounds()
|
|
calc_lightmap_uvs()
|
|
|
|
func calc_vertex_normals(smooth:bool = false):
|
|
#print("calc_vertex_normals ", _to_string())
|
|
#print("calc_vertex_normals face_vertex_coord_map ", face_vertex_coord_map)
|
|
|
|
for v_idx in vertices.size():
|
|
var v:VertexInfo = vertices[v_idx]
|
|
var weighted_normal:Vector3
|
|
|
|
for face in faces:
|
|
if face.vertex_indices.has(v_idx):
|
|
weighted_normal += MathUtil.face_area_x2(face.get_points())
|
|
|
|
v.normal = weighted_normal.normalized()
|
|
|
|
#Calc face vertices
|
|
for f_idx in faces.size():
|
|
var face:FaceInfo = faces[f_idx]
|
|
if face.vertex_indices.has(v_idx):
|
|
var fv:FaceVertexInfo = face_vertex_coord_map[Vector2i(f_idx, v_idx)]
|
|
fv.normal = v.normal if smooth else face.normal
|
|
|
|
func get_vertices_in_sphere(center:Vector3, radius:float)->Array[VertexInfo]:
|
|
var result:Array[VertexInfo]
|
|
for v in vertices:
|
|
var dist2 = v.point.distance_squared_to(center)
|
|
if dist2 <= radius * radius:
|
|
result.append(v)
|
|
|
|
return result
|
|
|
|
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 get_face_vertex(face_idx:int, vertex_idx:int)->FaceVertexInfo:
|
|
var coord:Vector2i = Vector2i(face_idx, vertex_idx)
|
|
return face_vertex_coord_map[coord]
|
|
|
|
func build_face_vertices():
|
|
#print("build_face_vertices")
|
|
for f_idx in faces.size():
|
|
var face:FaceInfo = faces[f_idx]
|
|
for v_local_idx in face.vertex_indices.size():
|
|
var v_idx = face.vertex_indices[v_local_idx]
|
|
var vert:VertexInfo = vertices[v_idx]
|
|
|
|
var fv:FaceVertexInfo = FaceVertexInfo.new()
|
|
var fv_idx:int = face_vertices.size()
|
|
face_vertices.append(fv)
|
|
var coord:Vector2i = Vector2i(f_idx, v_idx)
|
|
#print("Storing fv ", coord)
|
|
face_vertex_coord_map[coord] = fv
|
|
fv.index = fv_idx
|
|
fv.mesh = self
|
|
fv.face_index = f_idx
|
|
fv.vertex_index = v_idx
|
|
fv.vertex_local_index = v_local_idx
|
|
fv.color = face.color
|
|
|
|
face.face_vertex_indices.append(fv_idx)
|
|
|
|
func build_edges():
|
|
|
|
#Calculate edges
|
|
for face in faces:
|
|
var num_corners = face.vertex_indices.size()
|
|
for i0 in num_corners:
|
|
var i1:int = wrap(i0 + 1, 0, num_corners)
|
|
var v0_idx:int = face.vertex_indices[i0]
|
|
var v1_idx:int = face.vertex_indices[i1]
|
|
|
|
var edge:EdgeInfo = get_edge(v0_idx, v1_idx)
|
|
if !edge:
|
|
var edge_idx = edges.size()
|
|
edge = EdgeInfo.new(self, v0_idx, v1_idx)
|
|
edge.index = edges.size()
|
|
edges.append(edge)
|
|
|
|
var v0:VertexInfo = vertices[v0_idx]
|
|
v0.edge_indices.append(edge_idx)
|
|
|
|
var v1:VertexInfo = vertices[v1_idx]
|
|
v1.edge_indices.append(edge_idx)
|
|
|
|
# edge.face_indices.append(face.id)
|
|
edge.face_indices.append(face.index)
|
|
|
|
func get_face_coincident_with_plane(plane:Plane)->FaceInfo:
|
|
for f in faces:
|
|
var p:Plane = f.get_plane()
|
|
if p.is_equal_approx(plane):
|
|
return f
|
|
return null
|
|
|
|
func get_face_indices(selected_only:bool = false)->PackedInt32Array:
|
|
var result:PackedInt32Array
|
|
for f_idx in faces.size():
|
|
var f:FaceInfo = faces[f_idx]
|
|
if !selected_only || f.selected:
|
|
result.append(f_idx)
|
|
return result
|
|
|
|
func get_trimesh_indices()->PackedInt32Array:
|
|
var result:PackedInt32Array
|
|
|
|
for f in faces:
|
|
for fv_idx in f.get_triangulation():
|
|
var v_idx:int = f.vertex_indices[fv_idx]
|
|
result.append(v_idx)
|
|
|
|
return result
|
|
|
|
func get_face_most_similar_to_plane(plane:Plane)->FaceInfo:
|
|
var best_dot:float = -1
|
|
var best_face:FaceInfo
|
|
|
|
for f in faces:
|
|
var p:Plane = f.get_plane()
|
|
var dot = p.normal.dot(plane.normal)
|
|
if dot >= best_dot:
|
|
best_dot = dot
|
|
best_face = f
|
|
return best_face
|
|
|
|
func get_vertex_at_position(point:Vector3)->VertexInfo:
|
|
for v in vertices:
|
|
if v.point.is_equal_approx(point):
|
|
return v
|
|
return null
|
|
|
|
func get_edge_at_position(point:Vector3)->EdgeInfo:
|
|
for e in edges:
|
|
if e.get_midpoint().is_equal_approx(point):
|
|
return e
|
|
return null
|
|
|
|
func get_face_at_position(point:Vector3)->FaceInfo:
|
|
for f in faces:
|
|
if f.get_centroid().is_equal_approx(point):
|
|
return f
|
|
return null
|
|
|
|
func copy_vertex_attributes(ref_vol:ConvexVolume):
|
|
for v_idx in vertices.size():
|
|
var v:VertexInfo = vertices[v_idx]
|
|
var ref_v:VertexInfo = ref_vol.get_vertex_at_position(v.point)
|
|
if ref_v:
|
|
v.selected = ref_v.selected
|
|
|
|
func copy_face_attributes(ref_vol:ConvexVolume):
|
|
for f_idx in faces.size():
|
|
var f:FaceInfo = faces[f_idx]
|
|
var ref_face:FaceInfo = ref_vol.get_face_most_similar_to_plane(f.get_plane())
|
|
|
|
f.material_id = ref_face.material_id
|
|
f.uv_transform = ref_face.uv_transform
|
|
f.visible = ref_face.visible
|
|
f.color = ref_face.color
|
|
f.selected = ref_face.selected
|
|
|
|
#Copy face vertex values
|
|
for v_local_idx in f.vertex_indices.size():
|
|
var v_idx:int = f.vertex_indices[v_local_idx]
|
|
var v:VertexInfo = vertices[v_idx]
|
|
var fv:FaceVertexInfo = face_vertex_coord_map[Vector2i(f_idx, v_idx)]
|
|
|
|
var v_idx_ref:int = ref_face.get_closest_vertex(v.point)
|
|
|
|
var fv_ref:FaceVertexInfo = ref_vol.face_vertex_coord_map[Vector2i(ref_face.index, v_idx_ref)]
|
|
|
|
fv.normal = fv_ref.normal
|
|
fv.color = fv_ref.color
|
|
|
|
func to_convex_block_data()->ConvexBlockData:
|
|
var result:ConvexBlockData = ConvexBlockData.new()
|
|
|
|
result.active_vertex = active_vertex
|
|
result.active_edge = active_edge
|
|
result.active_face = active_face
|
|
result.active_face_vertex = active_face_vertex
|
|
|
|
for v in vertices:
|
|
result.vertex_points.append(v.point)
|
|
result.vertex_selected.append(v.selected)
|
|
#result.vertex_active.append(v.active)
|
|
|
|
for e in edges:
|
|
result.edge_vertex_indices.append_array([e.start_index, e.end_index])
|
|
result.edge_face_indices.append_array([e.face_indices[0], e.face_indices[1]])
|
|
result.edge_selected.append(e.selected)
|
|
#result.edge_active.append(e.active)
|
|
|
|
for face in faces:
|
|
var num_verts:int = face.vertex_indices.size()
|
|
result.face_vertex_count.append(num_verts)
|
|
result.face_vertex_indices.append_array(face.vertex_indices)
|
|
#result.face_ids.append(face.id)
|
|
result.face_selected.append(face.selected)
|
|
#result.face_active.append(face.active)
|
|
result.face_material_indices.append(face.material_id)
|
|
result.face_uv_transform.append(face.uv_transform)
|
|
result.face_visible.append(face.visible)
|
|
result.face_color.append(face.color)
|
|
|
|
for fv_idx in face_vertices.size():
|
|
var fv:FaceVertexInfo = face_vertices[fv_idx]
|
|
#print("to_convex_block_data fv ", fv.face_index, " ", fv.vertex_index)
|
|
result.face_vertex_face_index.append(fv.face_index)
|
|
result.face_vertex_vertex_index.append(fv.vertex_index)
|
|
result.face_vertex_normal.append(fv.normal)
|
|
result.face_vertex_color.append(fv.color)
|
|
|
|
return result
|
|
|
|
func to_mesh_vector_data()->MeshVectorData:
|
|
var mvd:MeshVectorData = MeshVectorData.new()
|
|
var block_data:ConvexBlockData = to_convex_block_data()
|
|
mvd.create_from_convex_block(block_data)
|
|
return mvd
|
|
|
|
func get_face(face_index:int)->FaceInfo:
|
|
return faces[face_index]
|
|
|
|
func get_centroid()->Vector3:
|
|
var points:PackedVector3Array = get_points()
|
|
var sum:Vector3
|
|
for p in points:
|
|
sum += p
|
|
return sum / points.size()
|
|
|
|
# Creates a new volume that is equal to the portion of this volume on the top
|
|
# side of the passed plane. Does not modify the geometry of this volume.
|
|
func cut_with_plane(plane:Plane, uv_transform:Transform2D = Transform2D.IDENTITY, material_id:int = 0)->ConvexVolume:
|
|
#
|
|
var planes:Array[Plane]
|
|
for f in faces:
|
|
#Top of planr should point toward interior
|
|
planes.append(MathUtil.flip_plane(f.get_plane()))
|
|
planes.append(plane)
|
|
|
|
#print("planes %s" % GeneralUtil.format_planes_string(planes))
|
|
|
|
var hull_points:Array[Vector3] = MathUtil.get_convex_hull_points_from_planes(planes)
|
|
if hull_points.is_empty():
|
|
return null
|
|
|
|
var new_vol:ConvexVolume = ConvexVolume.new()
|
|
new_vol.init_from_points(hull_points)
|
|
|
|
new_vol.copy_face_attributes(self)
|
|
|
|
for f in new_vol.faces:
|
|
var f_plane:Plane = MathUtil.flip_plane(f.get_plane())
|
|
if f_plane.is_equal_approx(plane):
|
|
f.uv_transform = uv_transform
|
|
f.material_id = material_id
|
|
break
|
|
|
|
return new_vol
|
|
|
|
func is_empty():
|
|
return bounds.size.is_zero_approx()
|
|
|
|
# Returns a new ConvexVolume equal to this volume after the plane of the
|
|
# indicated face has been translated the given offset. Does not modify the
|
|
# geometry of this volume.
|
|
func translate_face_plane(face_index:int, offset:Vector3, lock_uvs:bool = false)->ConvexVolume:
|
|
var xform:Transform3D = Transform3D(Basis.IDENTITY, -offset)
|
|
|
|
var source_face:FaceInfo
|
|
var transformed_plane:Plane
|
|
|
|
var planes:Array[Plane] = []
|
|
for f in faces:
|
|
if f.index == face_index:
|
|
transformed_plane = MathUtil.flip_plane(f.get_plane()) * xform
|
|
planes.append(transformed_plane)
|
|
source_face = f
|
|
else:
|
|
planes.append(MathUtil.flip_plane(f.get_plane()))
|
|
|
|
#print("planes %s" % str(planes))
|
|
var hull_points:Array[Vector3] = MathUtil.get_convex_hull_points_from_planes(planes)
|
|
if hull_points.is_empty():
|
|
return null
|
|
|
|
var new_vol:ConvexVolume = ConvexVolume.new()
|
|
new_vol.init_from_points(hull_points)
|
|
new_vol.copy_face_attributes(self)
|
|
|
|
return new_vol
|
|
|
|
func translated(offset:Vector3, lock_uvs:bool = false)->ConvexVolume:
|
|
return transformed(Transform3D(Basis.IDENTITY, offset), lock_uvs)
|
|
|
|
func translate(offset:Vector3, lock_uvs:bool = false):
|
|
transform(Transform3D(Basis.IDENTITY, offset), lock_uvs)
|
|
|
|
func transformed(xform:Transform3D, lock_uvs:bool = false)->ConvexVolume:
|
|
var new_vol:ConvexVolume = ConvexVolume.new()
|
|
new_vol.init_from_convex_block_data(to_convex_block_data())
|
|
new_vol.transform(xform)
|
|
return new_vol
|
|
|
|
|
|
func transform_uvs(xform:Transform3D):
|
|
# var xform:Transform3D = obj_xform.affine_inverse()
|
|
|
|
for f in faces:
|
|
var axis:MathUtil.Axis = MathUtil.get_longest_axis(f.normal)
|
|
|
|
match axis:
|
|
MathUtil.Axis.X:
|
|
var orig_p:Vector3 = xform.origin
|
|
var u_p:Vector3 = xform * Vector3(0, 0, 1) - orig_p
|
|
var v_p:Vector3 = xform * Vector3(0, 1, 0) - orig_p
|
|
var move_xform:Transform2D = Transform2D(Vector2(u_p.z, u_p.y), \
|
|
Vector2(v_p.z, v_p.y), \
|
|
Vector2(orig_p.z, orig_p.y))
|
|
|
|
f.uv_transform = f.uv_transform * move_xform
|
|
|
|
MathUtil.Axis.Y:
|
|
var orig_p:Vector3 = xform.origin
|
|
var u_p:Vector3 = xform * Vector3(1, 0, 0) - orig_p
|
|
var v_p:Vector3 = xform * Vector3(0, 0, 1) - orig_p
|
|
var move_xform:Transform2D = Transform2D(Vector2(u_p.x, u_p.z), \
|
|
Vector2(v_p.x, v_p.z), \
|
|
Vector2(orig_p.x, orig_p.z))
|
|
|
|
f.uv_transform = f.uv_transform * move_xform
|
|
|
|
MathUtil.Axis.Z:
|
|
#var xform_inv = xform.affine_inverse()
|
|
var orig_p:Vector3 = xform.origin
|
|
var u_p:Vector3 = xform * Vector3(1, 0, 0) - orig_p
|
|
var v_p:Vector3 = xform * Vector3(0, 1, 0) - orig_p
|
|
var move_xform:Transform2D = Transform2D(Vector2(u_p.x, u_p.y), \
|
|
Vector2(v_p.x, v_p.y), \
|
|
Vector2(orig_p.x, orig_p.y))
|
|
|
|
f.uv_transform = f.uv_transform * move_xform
|
|
|
|
|
|
func transform(xform:Transform3D, lock_uvs:bool = false):
|
|
for v in vertices:
|
|
v.point = xform * v.point
|
|
|
|
if xform.basis.determinant() < 0:
|
|
for f in faces:
|
|
f.reverse()
|
|
|
|
if lock_uvs:
|
|
|
|
for f in faces:
|
|
var axis:MathUtil.Axis = MathUtil.get_longest_axis(f.normal)
|
|
|
|
match axis:
|
|
MathUtil.Axis.X:
|
|
var orig_p:Vector3 = xform.origin
|
|
var u_p:Vector3 = xform * Vector3(0, 0, 1) - orig_p
|
|
var v_p:Vector3 = xform * Vector3(0, 1, 0) - orig_p
|
|
var move_xform:Transform2D = Transform2D(Vector2(u_p.z, u_p.y), \
|
|
Vector2(v_p.z, v_p.y), \
|
|
Vector2(orig_p.z, orig_p.y))
|
|
|
|
f.uv_transform = f.uv_transform * move_xform
|
|
|
|
MathUtil.Axis.Y:
|
|
var orig_p:Vector3 = xform.origin
|
|
var u_p:Vector3 = xform * Vector3(1, 0, 0) - orig_p
|
|
var v_p:Vector3 = xform * Vector3(0, 0, 1) - orig_p
|
|
var move_xform:Transform2D = Transform2D(Vector2(u_p.x, u_p.z), \
|
|
Vector2(v_p.x, v_p.z), \
|
|
Vector2(orig_p.x, orig_p.z))
|
|
|
|
f.uv_transform = f.uv_transform * move_xform
|
|
|
|
MathUtil.Axis.Z:
|
|
#var xform_inv = xform.affine_inverse()
|
|
var orig_p:Vector3 = xform.origin
|
|
var u_p:Vector3 = xform * Vector3(1, 0, 0) - orig_p
|
|
var v_p:Vector3 = xform * Vector3(0, 1, 0) - orig_p
|
|
var move_xform:Transform2D = Transform2D(Vector2(u_p.x, u_p.y), \
|
|
Vector2(v_p.x, v_p.y), \
|
|
Vector2(orig_p.x, orig_p.y))
|
|
|
|
f.uv_transform = f.uv_transform * move_xform
|
|
|
|
#calc_lightmap_uvs()
|
|
|
|
#@deprecated
|
|
#func unused_face_id()->int:
|
|
#var idx = 0
|
|
#for p in faces:
|
|
#idx = max(idx, p.id)
|
|
#return idx + 1
|
|
|
|
func contains_point(point:Vector3)->bool:
|
|
for f in faces:
|
|
var plane:Plane = f.get_plane()
|
|
if !plane.has_point(point) && !plane.is_point_over(point):
|
|
return false
|
|
return true
|
|
|
|
|
|
func get_points()->PackedVector3Array:
|
|
var points:PackedVector3Array
|
|
|
|
for v in vertices:
|
|
points.append(v.point)
|
|
|
|
return points
|
|
|
|
func calc_bounds()->AABB:
|
|
if vertices.is_empty():
|
|
return AABB()
|
|
|
|
var result:AABB = AABB(vertices[0].point, Vector3.ZERO)
|
|
|
|
for v_idx in range(1, vertices.size()):
|
|
result = result.expand(vertices[v_idx].point)
|
|
|
|
return result
|
|
|
|
func calc_bounds_xform(xform:Transform3D)->AABB:
|
|
if vertices.is_empty():
|
|
return AABB()
|
|
|
|
var result:AABB = AABB(xform * vertices[0].point, Vector3.ZERO)
|
|
|
|
for v_idx in range(1, vertices.size()):
|
|
result = result.expand(xform * vertices[v_idx].point)
|
|
|
|
return result
|
|
|
|
|
|
func tristrip_vertex_range(num_verts:int)->PackedInt32Array:
|
|
var result:PackedInt32Array
|
|
|
|
result.append(0)
|
|
result.append(1)
|
|
for i in range(2, num_verts):
|
|
if (i & 1) == 0:
|
|
result.append(num_verts - (i >> 1))
|
|
else:
|
|
result.append((i >> 1) + 1)
|
|
|
|
return result
|
|
|
|
func tristrip_vertex_range_reverse(num_verts:int)->PackedInt32Array:
|
|
var result:PackedInt32Array
|
|
|
|
result.append(1)
|
|
result.append(0)
|
|
for i in range(2, num_verts):
|
|
if (i & 1) == 0:
|
|
result.append((i >> 1) + 1)
|
|
else:
|
|
result.append(num_verts - (i >> 1))
|
|
|
|
return result
|
|
|
|
func calc_lightmap_uvs():
|
|
var packer:FacePacker = FacePacker.new()
|
|
var max_dim:float = max(bounds.size.x, bounds.size.y, bounds.size.z)
|
|
var tree:FacePacker.FaceTree = packer.build_faces(self, max_dim * .1)
|
|
|
|
var xform:Transform2D = Transform2D.IDENTITY
|
|
xform = xform.scaled(tree.bounds.size)
|
|
if is_zero_approx(xform.determinant()):
|
|
return
|
|
var xform_inv = xform.affine_inverse()
|
|
|
|
for ft in tree.face_list:
|
|
var face:FaceInfo = faces[ft.face_index]
|
|
face.lightmap_uvs = xform_inv * ft.points
|
|
|
|
func create_mesh(material_list:Array[Material], default_material:Material, override_with_default_material:bool = false)->ArrayMesh:
|
|
|
|
var mesh:ArrayMesh = ArrayMesh.new()
|
|
mesh.blend_shape_mode = Mesh.BLEND_SHAPE_MODE_NORMALIZED
|
|
mesh.lightmap_size_hint = Vector2(1000, 1000)
|
|
|
|
var shadow_mesh:ArrayMesh = ArrayMesh.new()
|
|
shadow_mesh.blend_shape_mode = Mesh.BLEND_SHAPE_MODE_NORMALIZED
|
|
|
|
#print("create_mesh")
|
|
#print("faces.size() ", faces.size())
|
|
|
|
var face_dict:Dictionary = {}
|
|
for f_idx in faces.size():
|
|
# print("check F_idx %s" % f_idx)
|
|
var face:FaceInfo = faces[f_idx]
|
|
if face_dict.has(face.material_id):
|
|
var arr = face_dict[face.material_id]
|
|
arr.append(f_idx)
|
|
# print("arr %s" % [arr])
|
|
face_dict[face.material_id] = arr
|
|
# print("append %s to %s" % [f_idx, face.material_id])
|
|
else:
|
|
face_dict[face.material_id] = [f_idx]
|
|
# print("starting %s to %s" % [f_idx, face.material_id])
|
|
|
|
var surface_idx:int = 0
|
|
for mat_id in face_dict.keys():
|
|
# print("surface mat grp %s" % mat_id)
|
|
|
|
var points:PackedVector3Array
|
|
var normals:PackedVector3Array
|
|
var tangents:PackedFloat32Array
|
|
var colors:PackedColorArray
|
|
var uv1s:PackedVector2Array
|
|
var uv2s:PackedVector2Array
|
|
|
|
var material = default_material
|
|
if !override_with_default_material:
|
|
if mat_id >= 0 && mat_id < material_list.size():
|
|
material = material_list[mat_id]
|
|
|
|
for f_idx in face_dict[mat_id]:
|
|
# print("f_idx %s" % f_idx)
|
|
|
|
var face:FaceInfo = faces[f_idx]
|
|
if !face.visible:
|
|
continue
|
|
|
|
var axis:MathUtil.Axis = MathUtil.get_longest_axis(face.normal)
|
|
|
|
var fv_trianglation:Array[int] = face.get_triangulation()
|
|
|
|
for v_local_idx in fv_trianglation:
|
|
|
|
var v_idx:int = face.vertex_indices[v_local_idx]
|
|
# var fv_idx:int = face.face_vertex_indices[v_local_idx]
|
|
# var fv_idx:int = face_vertex_coord_map[Vector2i(f_idx, v_idx)].index
|
|
var fv:FaceVertexInfo = face_vertex_coord_map[Vector2i(f_idx, v_idx)]
|
|
#var fv:ConvexVolume.FaceVertexInfo = face
|
|
#vol.get_face_vertex(f_idx, v_idx)
|
|
|
|
var p:Vector3 = vertices[v_idx].point
|
|
|
|
var uv:Vector2
|
|
if axis == MathUtil.Axis.X:
|
|
uv = Vector2(-p.z, -p.y)
|
|
elif axis == MathUtil.Axis.Y:
|
|
uv = Vector2(-p.x, -p.z)
|
|
elif axis == MathUtil.Axis.Z:
|
|
uv = Vector2(-p.x, -p.y)
|
|
|
|
uv = face.uv_transform * uv
|
|
uv1s.append(uv)
|
|
uv2s.append(face.lightmap_uvs[v_local_idx])
|
|
|
|
# normals.append(face.normal)
|
|
normals.append(fv.normal)
|
|
# colors.append(face.color)
|
|
# colors.append(face_vertices[fv_idx].color)
|
|
colors.append(fv.color)
|
|
|
|
points.append(p)
|
|
|
|
#Calculate tangents
|
|
#http://foundationsofgameenginedev.com/FGED2-sample.pdf
|
|
for i in range(0, points.size(), 3):
|
|
var p0:Vector3 = points[i]
|
|
var p1:Vector3 = points[i + 1]
|
|
var p2:Vector3 = points[i + 2]
|
|
|
|
var uv0:Vector2 = uv1s[i]
|
|
var uv1:Vector2 = uv1s[i + 1]
|
|
var uv2:Vector2 = uv1s[i + 2]
|
|
|
|
var n:Vector3 = normals[i]
|
|
|
|
var e1:Vector3 = p1 - p0
|
|
var e2:Vector3 = p2 - p0
|
|
|
|
var duv1:Vector2 = uv1 - uv0
|
|
var duv2:Vector2 = uv2 - uv0
|
|
|
|
var r:float = 1.0 / (duv1.x * duv2.y - duv2.x * duv1.y)
|
|
var t:Vector3 = (e1 * duv2.y - e2 * duv1.y) * r
|
|
var b:Vector3 = (e2 * duv1.x - e1 * duv2.x) * r
|
|
|
|
t = t.normalized()
|
|
|
|
for j in 3:
|
|
tangents.append(t.x)
|
|
tangents.append(t.y)
|
|
tangents.append(t.z)
|
|
tangents.append(-1.0 if t.cross(b).dot(n) > 0 else 1.0)
|
|
|
|
var arrays:Array = create_indexed_vertex_array(points, normals, tangents, colors, uv1s, uv2s)
|
|
|
|
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
|
mesh.surface_set_material(surface_idx, material)
|
|
|
|
var shadow_arrays:Array = []
|
|
shadow_arrays.resize(Mesh.ARRAY_MAX)
|
|
shadow_arrays[Mesh.ARRAY_VERTEX] = points
|
|
|
|
shadow_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, shadow_arrays)
|
|
shadow_mesh.surface_set_material(surface_idx, material)
|
|
|
|
surface_idx += 1
|
|
|
|
mesh.shadow_mesh = shadow_mesh
|
|
# var err = mesh.lightmap_unwrap(Transform3D.IDENTITY, 10)
|
|
# print("Lightmap unwrap Error: %s" % err)
|
|
return mesh
|
|
|
|
|
|
|
|
func create_indexed_vertex_array(points:PackedVector3Array, normals:PackedVector3Array, tangents:PackedFloat32Array, colors:PackedColorArray, uv1s:PackedVector2Array, uv2s:PackedVector2Array)->Array:
|
|
var vert_idx_map:Dictionary
|
|
var indices:PackedInt32Array
|
|
var points_indexed:PackedVector3Array
|
|
var normals_indexed:PackedVector3Array
|
|
var tangents_indexed:PackedFloat32Array
|
|
var colors_indexed:PackedColorArray
|
|
var uv1s_indexed:PackedVector2Array
|
|
var uv2s_indexed:PackedVector2Array
|
|
|
|
for v_idx in points.size():
|
|
var vertex:PackedFloat32Array
|
|
vertex.append(points[v_idx].x)
|
|
vertex.append(points[v_idx].y)
|
|
vertex.append(points[v_idx].z)
|
|
|
|
vertex.append(normals[v_idx].x)
|
|
vertex.append(normals[v_idx].y)
|
|
vertex.append(normals[v_idx].z)
|
|
|
|
vertex.append(tangents[v_idx * 4])
|
|
vertex.append(tangents[v_idx * 4 + 1])
|
|
vertex.append(tangents[v_idx * 4 + 2])
|
|
vertex.append(tangents[v_idx * 4 + 3])
|
|
|
|
vertex.append(colors[v_idx].r)
|
|
vertex.append(colors[v_idx].g)
|
|
vertex.append(colors[v_idx].b)
|
|
vertex.append(colors[v_idx].a)
|
|
|
|
vertex.append(uv1s[v_idx].x)
|
|
vertex.append(uv1s[v_idx].y)
|
|
|
|
vertex.append(uv2s[v_idx].x)
|
|
vertex.append(uv2s[v_idx].y)
|
|
|
|
var new_index:int
|
|
if !vert_idx_map.has(vertex):
|
|
#print("alloc vtx ", vertex)
|
|
|
|
new_index = vert_idx_map.size()
|
|
vert_idx_map[vertex] = new_index
|
|
points_indexed.append(points[v_idx])
|
|
normals_indexed.append(normals[v_idx])
|
|
tangents_indexed.append(tangents[v_idx * 4])
|
|
tangents_indexed.append(tangents[v_idx * 4 + 1])
|
|
tangents_indexed.append(tangents[v_idx * 4 + 2])
|
|
tangents_indexed.append(tangents[v_idx * 4 + 3])
|
|
colors_indexed.append(colors[v_idx])
|
|
uv1s_indexed.append(uv1s[v_idx])
|
|
uv2s_indexed.append(uv2s[v_idx])
|
|
else:
|
|
new_index = vert_idx_map[vertex]
|
|
|
|
#print("index ", new_index)
|
|
indices.append(new_index)
|
|
|
|
#print("indices ", indices)
|
|
|
|
var arrays:Array = []
|
|
arrays.resize(Mesh.ARRAY_MAX)
|
|
arrays[Mesh.ARRAY_VERTEX] = points_indexed
|
|
arrays[Mesh.ARRAY_NORMAL] = normals_indexed
|
|
arrays[Mesh.ARRAY_TANGENT] = tangents_indexed
|
|
arrays[Mesh.ARRAY_TEX_UV] = uv1s_indexed
|
|
arrays[Mesh.ARRAY_TEX_UV2] = uv2s_indexed
|
|
arrays[Mesh.ARRAY_COLOR] = colors_indexed
|
|
arrays[Mesh.ARRAY_INDEX] = indices
|
|
|
|
return arrays
|
|
|
|
|
|
func append_mesh_backfacing(mesh:ImmediateMesh, material:Material, offset:float = .2):
|
|
# if Engine.is_editor_hint():
|
|
# return
|
|
|
|
for face in faces:
|
|
|
|
mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP, material)
|
|
# print("face %s" % face.index)
|
|
|
|
mesh.surface_set_normal(face.normal)
|
|
|
|
# for i in tristrip_vertex_range_reverse(face.vertex_indices.size()):
|
|
for i in tristrip_vertex_range_reverse(face.vertex_indices.size()):
|
|
var v_idx:int = face.vertex_indices[i]
|
|
var v:VertexInfo = vertices[v_idx]
|
|
var p:Vector3 = v.point + v.normal * offset
|
|
#var p:Vector3 = v.point + Vector3(.1, .1, .1)
|
|
|
|
mesh.surface_add_vertex(p)
|
|
|
|
mesh.surface_end()
|
|
|
|
func append_mesh_outline(mesh:ImmediateMesh, viewport_camera:Camera3D, local_to_world:Transform3D, material:Material, thickness:float = 4):
|
|
var cam_orig:Vector3 = viewport_camera.global_transform.origin
|
|
|
|
var segments:PackedVector2Array
|
|
|
|
#print("--append_mesh_outline")
|
|
#var view_plane:Plane = Plane(-viewport_camera.global_basis.z,
|
|
#viewport_camera.global_position
|
|
#- viewport_camera.global_basis.z * viewport_camera.near * 1)
|
|
|
|
var frustum:Array[Plane] = viewport_camera.get_frustum()
|
|
|
|
for edge in edges:
|
|
var v0:VertexInfo = vertices[edge.start_index]
|
|
var v1:VertexInfo = vertices[edge.end_index]
|
|
var p0_world:Vector3 = local_to_world * v0.point
|
|
var p1_world:Vector3 = local_to_world * v1.point
|
|
|
|
var frustum_culled:bool = false
|
|
for p in frustum:
|
|
var p_flip:Plane = MathUtil.flip_plane(p)
|
|
var result:PackedVector3Array = MathUtil.clip_segment_to_plane_3d(p_flip, p0_world, p1_world)
|
|
if result.is_empty():
|
|
frustum_culled = true
|
|
break
|
|
p0_world = result[0]
|
|
p1_world = result[1]
|
|
|
|
if frustum_culled:
|
|
continue
|
|
|
|
var has_front:bool = false
|
|
var has_back:bool = false
|
|
|
|
for f_idx in edge.face_indices:
|
|
var face = faces[f_idx]
|
|
|
|
var plane = face.get_plane()
|
|
plane = local_to_world * plane
|
|
|
|
if plane.is_point_over(cam_orig):
|
|
has_front = true
|
|
else:
|
|
has_back = true
|
|
|
|
#print("front %s back %s" % [has_front, has_back])
|
|
|
|
if has_front && has_back:
|
|
#Draw edge
|
|
var p0_screen:Vector2 = viewport_camera.unproject_position(p0_world)
|
|
var p1_screen:Vector2 = viewport_camera.unproject_position(p1_world)
|
|
segments.append(p0_screen)
|
|
segments.append(p1_screen)
|
|
#print("seg %s %s" % [p0_screen, p1_screen])
|
|
|
|
#print("segments ", segments)
|
|
|
|
var loops:Array[Loop2D] = MathUtil.get_loops_from_segments_2d(segments)
|
|
for loop in loops:
|
|
var out_dirs:PackedVector2Array
|
|
|
|
#print("loop ", loop)
|
|
for v_idx in loop.points.size():
|
|
var p0_screen:Vector2 = loop.points[wrap(v_idx - 1, 0, loop.points.size())]
|
|
var p1_screen:Vector2 = loop.points[v_idx]
|
|
var p2_screen:Vector2 = loop.points[wrap(v_idx + + 1, 0, loop.points.size())]
|
|
#var span:Vector2 = p2_screen - p1_screen
|
|
|
|
var norm01:Vector2 = (p1_screen - p0_screen).normalized()
|
|
var norm12:Vector2 = (p2_screen - p1_screen).normalized()
|
|
|
|
var out_dir1:Vector2 = (-norm01 + norm12).normalized()
|
|
var perp:Vector2 = out_dir1 - out_dir1.project(norm12)
|
|
#Check winding
|
|
if perp.x * norm12.y - perp.y * norm12.x < 0:
|
|
out_dir1 = -out_dir1
|
|
|
|
out_dirs.append(out_dir1)
|
|
|
|
|
|
mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP, material)
|
|
for v_idx in loop.points.size() + (1 if loop.closed else 0):
|
|
var p_screen:Vector2 = loop.points[wrap(v_idx, 0, loop.points.size())]
|
|
var p_out_dir:Vector2 = out_dirs[wrap(v_idx, 0, loop.points.size())]
|
|
|
|
var z_pos:float = (viewport_camera.near + viewport_camera.far) / 2
|
|
var p0:Vector3 = viewport_camera.project_position(p_screen, z_pos)
|
|
var p1:Vector3 = viewport_camera.project_position(p_screen + p_out_dir * thickness, z_pos)
|
|
|
|
mesh.surface_add_vertex(p0)
|
|
mesh.surface_add_vertex(p1)
|
|
|
|
mesh.surface_end()
|
|
|
|
|
|
|
|
func create_mesh_wire(material:Material)->ImmediateMesh:
|
|
# if Engine.is_editor_hint():
|
|
# return
|
|
var mesh:ImmediateMesh = ImmediateMesh.new()
|
|
|
|
mesh.surface_begin(Mesh.PRIMITIVE_LINES, material)
|
|
|
|
for e in edges:
|
|
var v0:VertexInfo = vertices[e.start_index]
|
|
var v1:VertexInfo = vertices[e.end_index]
|
|
|
|
mesh.surface_add_vertex(v0.point)
|
|
mesh.surface_add_vertex(v1.point)
|
|
|
|
mesh.surface_end()
|
|
|
|
return mesh
|
|
|
|
|
|
func intersect_ray_closest(origin:Vector3, dir:Vector3)->IntersectResults:
|
|
if bounds.intersects_ray(origin, dir) == null:
|
|
return null
|
|
|
|
var best_result:IntersectResults
|
|
|
|
for f_idx in faces.size():
|
|
var face:FaceInfo = faces[f_idx]
|
|
# var tris:PackedVector3Array = MathUtil.trianglate_face(face.get_points(), face.normal)
|
|
var tris:PackedVector3Array = face.get_trianges()
|
|
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_id = face.id
|
|
result.face_index = f_idx
|
|
result.normal = face.normal
|
|
result.position = p_hit
|
|
result.distance_squared = dist_sq
|
|
|
|
best_result = result
|
|
|
|
return best_result
|
|
|
|
func format_faces_string()->String:
|
|
var s:String = ""
|
|
for f in faces:
|
|
s = s + "["
|
|
for v_idx in f.vertex_indices:
|
|
s += "%s, " % vertices[v_idx].point
|
|
s = s + "],\n"
|
|
return s
|
|
|
|
func update_edge_and_face_selection_from_vertices():
|
|
for e in edges:
|
|
e.selected = vertices[e.start_index].selected && vertices[e.end_index].selected
|
|
|
|
for f in faces:
|
|
var all_sel:bool = true
|
|
for v_idx in f.vertex_indices:
|
|
if !vertices[v_idx].selected:
|
|
all_sel = false
|
|
break
|
|
f.selected = all_sel
|
|
|
|
|
|
func intersects_plane(plane:Plane)->bool:
|
|
|
|
var is_over:bool = false
|
|
var is_under:bool = false
|
|
|
|
for v in vertices:
|
|
var p:Vector3 = v.point
|
|
|
|
if plane.has_point(p):
|
|
continue
|
|
|
|
if plane.is_point_over(p):
|
|
is_over = true
|
|
else:
|
|
is_under = true
|
|
|
|
if is_over && is_under:
|
|
return true
|
|
|
|
return false
|
|
|
|
func subtract(subtrahend:ConvexVolume)->Array[ConvexVolume]:
|
|
var result_list:Array[ConvexVolume]
|
|
|
|
var split_vol:ConvexVolume = self
|
|
|
|
for face in subtrahend.faces:
|
|
var p:Plane = face.get_plane()
|
|
|
|
if !split_vol.intersects_plane(p):
|
|
continue
|
|
|
|
var vol_over:ConvexVolume = split_vol.cut_with_plane(p)
|
|
var vol_under:ConvexVolume = split_vol.cut_with_plane(MathUtil.flip_plane(p))
|
|
|
|
result_list.append(vol_over)
|
|
split_vol = vol_under
|
|
|
|
# result_list.append(split_vol)
|
|
|
|
return result_list
|
|
|
|
|
|
func intersect(subtrahend:ConvexVolume)->ConvexVolume:
|
|
var result_list:Array[ConvexVolume]
|
|
|
|
var split_vol:ConvexVolume = self
|
|
|
|
for face in subtrahend.faces:
|
|
var p:Plane = face.get_plane()
|
|
|
|
if !split_vol.intersects_plane(p):
|
|
continue
|
|
|
|
var vol_over:ConvexVolume = split_vol.cut_with_plane(p)
|
|
var vol_under:ConvexVolume = split_vol.cut_with_plane(MathUtil.flip_plane(p))
|
|
|
|
result_list.append(vol_over)
|
|
split_vol = vol_under
|
|
|
|
return split_vol
|
|
|
|
|
|
func is_over_or_on_plane(plane:Plane)->bool:
|
|
for v in vertices:
|
|
if !plane.is_point_over(v.point) && !plane.has_point(v.point):
|
|
return false
|
|
|
|
return true
|
|
|
|
func intersects_convex_volume(vol:ConvexVolume)->bool:
|
|
#Look for plane of separtion between two volumes
|
|
for f in vol.faces:
|
|
var p:Plane = f.get_plane()
|
|
if is_over_or_on_plane(p):
|
|
return false
|
|
|
|
return true
|
|
|
|
|
|
func intersects_frustum(frustum:Array[Plane])->bool:
|
|
|
|
for face in faces:
|
|
var points:PackedVector3Array = face.get_points()
|
|
if MathUtil.polygon_intersects_frustum(points, frustum):
|
|
return true
|
|
|
|
return false
|
|
|
|
func make_convex():
|
|
var selected_points:PackedVector3Array
|
|
var new_points:PackedVector3Array
|
|
|
|
for v in vertices:
|
|
new_points.append(v.point)
|
|
|
|
var new_vol:ConvexVolume = ConvexVolume.new()
|
|
new_vol.init_from_points(new_points)
|
|
|
|
new_vol.copy_vertex_attributes(self)
|
|
new_vol.copy_face_attributes(self)
|
|
|
|
if active_vertex != -1:
|
|
var v:VertexInfo = vertices[active_vertex]
|
|
var new_v:VertexInfo = new_vol.get_vertex_at_position(v.point)
|
|
if new_v:
|
|
new_vol.active_vertex = new_v.index
|
|
|
|
if active_edge != -1:
|
|
var e:EdgeInfo = edges[active_edge]
|
|
var mp:Vector3 = e.get_midpoint()
|
|
var new_e:EdgeInfo = new_vol.get_edge_at_position(mp)
|
|
if new_e:
|
|
new_vol.active_edge = new_e.index
|
|
|
|
if active_face != -1:
|
|
var f:FaceInfo = faces[active_face]
|
|
var centroid:Vector3 = f.get_centroid()
|
|
var new_f:FaceInfo = new_vol.get_face_at_position(centroid)
|
|
if new_f:
|
|
new_vol.active_face = new_f.index
|
|
|
|
|
|
#for v_idx in new_vol.vertices.size():
|
|
#var v:ConvexVolume.VertexInfo = new_vol.vertices[v_idx]
|
|
## print ("vol point %s " % v.point)
|
|
#if selected_points.has(v.point):
|
|
## print("set sel")
|
|
#v.selected = true
|
|
#
|
|
#block.mesh_vector_data = new_vol.to_mesh_vector_data()
|
|
|
|
|