929 lines
27 KiB
GDScript3
929 lines
27 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 ConvexVolume
|
||
|
|
||
|
|
||
|
class VertexInfo extends RefCounted:
|
||
|
var mesh:ConvexVolume
|
||
|
#var index:int
|
||
|
var point:Vector3
|
||
|
var normal:Vector3
|
||
|
var edge_indices:Array[int] = []
|
||
|
var selected:bool
|
||
|
#var active: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 mesh:ConvexVolume
|
||
|
var start_index:int
|
||
|
var end_index:int
|
||
|
var face_indices:Array[int] = []
|
||
|
var selected:bool
|
||
|
#var active:bool
|
||
|
|
||
|
func _init(mesh:ConvexVolume, start:int = 0, end:int = 0):
|
||
|
self.mesh = mesh
|
||
|
start_index = start
|
||
|
end_index = end
|
||
|
|
||
|
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 mesh:ConvexVolume
|
||
|
var id:int
|
||
|
var normal:Vector3 #Face normal points in direction of interior
|
||
|
var material_id:int
|
||
|
var uv_transform:Transform2D
|
||
|
var selected:bool
|
||
|
#var active:bool
|
||
|
var vertex_indices:Array[int]
|
||
|
var triangulation_indices:Array[int]
|
||
|
var lightmap_uvs:PackedVector2Array
|
||
|
|
||
|
func _init(mesh:ConvexVolume, id:int, normal:Vector3, uv_transform:Transform2D = Transform2D.IDENTITY, material_id:int = 0, 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
|
||
|
|
||
|
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
|
||
|
|
||
|
# func get_triangulation()->Array[int]:
|
||
|
# if triangulation_indices.is_empty():
|
||
|
# var points:PackedVector3Array
|
||
|
# var indices:Array[int]
|
||
|
# for v_idx in vertex_indices:
|
||
|
# points.append(mesh.vertices[v_idx].point)
|
||
|
# indices.append(v_idx)
|
||
|
#
|
||
|
## print("start points %s" % points)
|
||
|
#
|
||
|
# var normal:Vector3 = MathUtil.face_area_x2(points).normalized()
|
||
|
## print("normal %s" % normal)
|
||
|
# triangulation_indices = MathUtil.trianglate_face_indices(points, indices, normal)
|
||
|
## print("triangulation %s" % str(triangulation_indices))
|
||
|
#
|
||
|
# return triangulation_indices
|
||
|
|
||
|
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()
|
||
|
|
||
|
#class FaceVertexInfo extends RefCounted:
|
||
|
# var vert_idx:int
|
||
|
# var face_idx:int
|
||
|
|
||
|
|
||
|
|
||
|
var vertices:Array[VertexInfo] = []
|
||
|
var edges:Array[EdgeInfo] = []
|
||
|
var faces:Array[FaceInfo] = []
|
||
|
var bounds:AABB
|
||
|
|
||
|
var lightmap_uvs_dirty = true
|
||
|
|
||
|
var active_vertex:int = -1
|
||
|
var active_edge:int = -1
|
||
|
var active_face: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):
|
||
|
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)
|
||
|
|
||
|
|
||
|
func init_prism(base_points:Array[Vector3], extrude_dir:Vector3, uv_transform:Transform2D = Transform2D.IDENTITY, material_id:int = -1):
|
||
|
vertices = []
|
||
|
edges = []
|
||
|
faces = []
|
||
|
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)
|
||
|
vertices.append(v)
|
||
|
for p in base_points:
|
||
|
var v:VertexInfo = VertexInfo.new(self, p + extrude_dir)
|
||
|
vertices.append(v)
|
||
|
|
||
|
var f0:FaceInfo = FaceInfo.new(self, faces.size(), base_normal, uv_transform, material_id)
|
||
|
f0.vertex_indices = []
|
||
|
f0.vertex_indices.append_array(range(base_points.size()))
|
||
|
faces.append(f0)
|
||
|
var f1:FaceInfo = FaceInfo.new(self, faces.size(), -base_normal, uv_transform, material_id)
|
||
|
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, faces.size(), normal, uv_transform, material_id)
|
||
|
f.vertex_indices = [p_idx1, p_idx0, p_idx0 + base_points.size(), p_idx1 + base_points.size()]
|
||
|
faces.append(f)
|
||
|
|
||
|
build_edges()
|
||
|
calc_vertex_normals()
|
||
|
|
||
|
bounds = calc_bounds()
|
||
|
calc_lightmap_uvs()
|
||
|
|
||
|
func init_from_convex_block_data(data:ConvexBlockData):
|
||
|
vertices = []
|
||
|
edges = []
|
||
|
faces = []
|
||
|
|
||
|
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])
|
||
|
vertices.append(v)
|
||
|
v.selected = data.vertex_selected[i]
|
||
|
#v.active = data.vertex_active[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])
|
||
|
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]
|
||
|
|
||
|
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)
|
||
|
# var v_idx:int = data.face_vertex_indices[count]
|
||
|
face_vertex_count += 1
|
||
|
|
||
|
var normal = MathUtil.face_area_x2(vert_points).normalized()
|
||
|
var f:FaceInfo = FaceInfo.new(self, data.face_ids[face_idx], normal, data.face_uv_transform[face_idx], data.face_material_indices[face_idx])
|
||
|
f.selected = data.face_selected[face_idx]
|
||
|
#f.active = data.face_active[face_idx]
|
||
|
f.vertex_indices = vert_indices
|
||
|
|
||
|
faces.append(f)
|
||
|
|
||
|
|
||
|
calc_vertex_normals()
|
||
|
|
||
|
bounds = calc_bounds()
|
||
|
calc_lightmap_uvs()
|
||
|
#print("init_from_convex_block_data %s" % format_faces_string())
|
||
|
|
||
|
|
||
|
#Calc convex hull bouding points
|
||
|
func init_from_points(points:PackedVector3Array, uv_transform:Transform2D = Transform2D.IDENTITY, material_id:int = -1):
|
||
|
vertices = []
|
||
|
edges = []
|
||
|
faces = []
|
||
|
|
||
|
#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:
|
||
|
vertices.append(VertexInfo.new(self, p))
|
||
|
|
||
|
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, faces.size(), plane.normal, uv_transform, material_id)
|
||
|
f.vertex_indices = vert_indices
|
||
|
faces.append(f)
|
||
|
|
||
|
|
||
|
build_edges()
|
||
|
calc_vertex_normals()
|
||
|
|
||
|
bounds = calc_bounds()
|
||
|
calc_lightmap_uvs()
|
||
|
|
||
|
func calc_vertex_normals():
|
||
|
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()
|
||
|
|
||
|
|
||
|
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 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)
|
||
|
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)
|
||
|
|
||
|
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_ids(selected_only:bool = false)->PackedInt32Array:
|
||
|
var result:PackedInt32Array
|
||
|
for f in faces:
|
||
|
if !selected_only || f.selected:
|
||
|
result.append(f.id)
|
||
|
return result
|
||
|
|
||
|
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 copy_face_attributes(ref_vol:ConvexVolume):
|
||
|
for fl in faces:
|
||
|
var ref_face:FaceInfo = ref_vol.get_face_most_similar_to_plane(fl.get_plane())
|
||
|
|
||
|
fl.material_id = ref_face.material_id
|
||
|
fl.uv_transform = ref_face.uv_transform
|
||
|
fl.selected = ref_face.selected
|
||
|
|
||
|
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
|
||
|
|
||
|
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)
|
||
|
|
||
|
return result
|
||
|
|
||
|
func get_face(face_id:int)->FaceInfo:
|
||
|
for face in faces:
|
||
|
if face.id == face_id:
|
||
|
return face
|
||
|
return null
|
||
|
|
||
|
# 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_id: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.id == face_id:
|
||
|
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 translate(offset:Vector3, lock_uvs:bool = false):
|
||
|
transform(Transform3D(Basis.IDENTITY, offset), lock_uvs)
|
||
|
|
||
|
|
||
|
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:
|
||
|
# var xform_inv:Transform3D = xform.affine_inverse()
|
||
|
#var xform_inv:Transform3D = xform
|
||
|
#print("--xform %s" % xform)
|
||
|
|
||
|
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()
|
||
|
|
||
|
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 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)
|
||
|
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)->ArrayMesh:
|
||
|
# if Engine.is_editor_hint():
|
||
|
# return
|
||
|
# print("num faces %s" % faces.size())
|
||
|
# print("-creating mesh")
|
||
|
|
||
|
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
|
||
|
|
||
|
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 uv1s:PackedVector2Array
|
||
|
var uv2s:PackedVector2Array
|
||
|
|
||
|
var material = 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]
|
||
|
|
||
|
|
||
|
var axis:MathUtil.Axis = MathUtil.get_longest_axis(face.normal)
|
||
|
|
||
|
var fv_trianglation:Array[int] = face.get_triangulation()
|
||
|
|
||
|
for fv_idx in fv_trianglation:
|
||
|
|
||
|
var v_idx:int = face.vertex_indices[fv_idx]
|
||
|
# var p:Vector3 = triangles[i]
|
||
|
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[fv_idx])
|
||
|
|
||
|
normals.append(face.normal)
|
||
|
|
||
|
points.append(p)
|
||
|
|
||
|
var arrays:Array = []
|
||
|
arrays.resize(Mesh.ARRAY_MAX)
|
||
|
arrays[Mesh.ARRAY_VERTEX] = points
|
||
|
arrays[Mesh.ARRAY_NORMAL] = normals
|
||
|
arrays[Mesh.ARRAY_TEX_UV] = uv1s
|
||
|
arrays[Mesh.ARRAY_TEX_UV2] = 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 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_dir:Vector3 = viewport_camera.global_transform.basis.z
|
||
|
var cam_orig:Vector3 = viewport_camera.global_transform.origin
|
||
|
|
||
|
# print("append_mesh_outline %s" % cam_dir)
|
||
|
#points along Z
|
||
|
# var cylinder:GeometryMesh = MathGeometry.unit_cylinder(4, thickness, thickness, 0, -1)
|
||
|
|
||
|
var segments:PackedVector2Array
|
||
|
|
||
|
for edge in edges:
|
||
|
var has_front:bool = false
|
||
|
var has_back:bool = false
|
||
|
|
||
|
for f_idx in edge.face_indices:
|
||
|
var face = faces[f_idx]
|
||
|
#print("face norm %s" % face.normal)
|
||
|
var point_on_plane:Vector3 = vertices[face.vertex_indices[0]].point
|
||
|
var to_plane:Vector3 = cam_orig - point_on_plane
|
||
|
|
||
|
if face.normal.dot(to_plane) > 0:
|
||
|
has_front = true
|
||
|
elif face.normal.dot(to_plane) < 0:
|
||
|
has_back = true
|
||
|
|
||
|
#print("front %s back %s" % [has_front, has_back])
|
||
|
|
||
|
if has_front && has_back:
|
||
|
#print("drawing edge %s %s" % [edge.start_index, edge.end_index])
|
||
|
#Draw edge
|
||
|
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 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)
|
||
|
|
||
|
var loops:Array[PackedVector2Array] = MathUtil.get_loops_from_segments_2d(segments)
|
||
|
for loop_points in loops:
|
||
|
var out_dirs:PackedVector2Array
|
||
|
|
||
|
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:
|
||
|
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 face in faces:
|
||
|
# 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.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
|
||
|
|
||
|
|