Last active
October 24, 2021 23:33
-
-
Save Skaruts/83dc5995448a7c0ea2b66eceee6724eb to your computer and use it in GitHub Desktop.
[Godot] A tool that helps working with multiple ImmediateGeometry nodes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#= | |
# Immediate Geometry Tool (0.7) | |
# | |
# experimental WIP | |
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#= | |
extends Spatial | |
class_name IGTool | |
enum {A,B,C,D,E,F,G,H} # cube vertex indices | |
enum { | |
WIRE = 1, | |
FACES = 2, | |
BOTH = 3 | |
} | |
var _igs:Dictionary = {} | |
func _init(parent:Node, _name = null) -> void: | |
if _name: name = _name | |
parent.add_child(self) | |
func _ready() -> void: | |
# timer for debugging purposes | |
if 0: | |
var timer = Timer.new() | |
add_child(timer) | |
timer.connect("timeout", self, "_on_Timer_timeout") | |
timer.set_wait_time(1.0) | |
timer.set_timer_process_mode(Timer.TIMER_PROCESS_IDLE) | |
timer.start() | |
func _on_Timer_timeout() -> void: | |
print("igs: ", _igs.size()) | |
# not sure if this is needed. If errors related to IGs ever ocurr | |
# then this may solve them. But so far it seems fine without this. | |
#func queue_free(): | |
# delete_all_igs() | |
# .queue_free() | |
func _is_valid_ig(ig_name:String) -> bool: | |
return _igs.has(ig_name) | |
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#= | |
# Public General Interface | |
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#= | |
func clear_igs() -> void: | |
for ig in _igs.values(): | |
ig.clear() | |
func new_ig(ig_name:String, mat:SpatialMaterial) -> ImmediateGeometry: | |
if _is_valid_ig(ig_name): # prevent creating the same ig twice | |
push_warning("(IGTool for %s): ig '%s' already exists." % [self, ig_name]) | |
return _igs[ig_name] | |
var ig := ImmediateGeometry.new() | |
ig.set_name(ig_name) | |
add_child(ig) | |
ig.material_override = mat | |
ig.cast_shadow = GeometryInstance.SHADOW_CASTING_SETTING_OFF | |
_igs[ig_name] = ig | |
return ig | |
func add_ig(ig_name:String, ig:ImmediateGeometry) -> void: | |
if not _is_valid_ig(ig_name): # prevent creating the same ig twice | |
_igs[ig_name] = ig | |
func delete_ig(ig_name:String) -> void: | |
# Doesn't seem to need to be cleared | |
if not _is_valid_ig(ig_name): | |
push_warning("(IGTool for %s): trying to clear an ig that doesn't exist" % [self, ig_name]) | |
return | |
_igs[ig_name].queue_free() | |
_igs.erase(ig_name) | |
func delete_all_igs() -> void: | |
for ig_name in _igs.keys(): | |
_igs[ig_name].queue_free() | |
_igs.erase(ig_name) | |
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#= | |
# Public IG Interface | |
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#= | |
func ig_set_material(ig_name:String, mat:SpatialMaterial) -> void: | |
_igs[ig_name].set_material_override( mat ) | |
func ig_clear(ig_name:String) -> void: | |
if _igs.has(ig_name): | |
_igs[ig_name].clear() | |
func ig_set_hidden(ig_name:String, hidden:bool) -> void: | |
_igs[ig_name].set_hidden(hidden) | |
func ig_set_pos(ig_name:String, pos:Vector3) -> void: | |
_igs[ig_name].translation = pos | |
func ig_scale(ig_name:String, scale:Vector3) -> void: | |
_igs[ig_name].scale = scale | |
func ig_begin(ig_name:String, primitive:="line") -> void: | |
match primitive: | |
"line": _igs[ig_name].begin(Mesh.PRIMITIVE_LINES, null) | |
"strip": _igs[ig_name].begin(Mesh.PRIMITIVE_LINE_STRIP, null) | |
"tris": _igs[ig_name].begin(Mesh.PRIMITIVE_TRIANGLES, null) | |
"fan": _igs[ig_name].begin(Mesh.PRIMITIVE_TRIANGLE_FAN, null) | |
_: assert(false, "(IGTool %s): invalid primitive type '%s'." % [name, primitive]) | |
func ig_end(ig_name:String) -> void: | |
_igs[ig_name].end() | |
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#= | |
# Public API | |
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#= | |
# Return an array with all 'verts' modified by a scaling factor. | |
# There must be 8 'verts' that form a cube. | |
# Scales the vertex positions to 'scale_to' percentage of the original cube. | |
# if 'scale_to' < 1, the cube is shrinked | |
# if 'scale_to' > 1, the cube is grown | |
# if 'centered', the cube shrinks/grows from all sides by half the difference | |
# between the new scale and the old scale. So if 'scale_to' is 0.9, then | |
# every side will move inward by 0.05. | |
# | |
# For example: | |
# A 'scale_to' of 0.9 will: | |
# if 'centered' | |
# - subtract '0.1/2' (0.05) from all sides of the cube | |
# else | |
# - subtract '0.1' from the sides that face toward | |
# positive axes. | |
# | |
# A 'scale_to' of '1.1' will add '0.1/2' or '0.1', respectively. | |
# ----------------------------------------------------------- | |
func scale_cube_verts(verts:Array, scale_to:float, centered:=false) -> Array: | |
if centered: | |
var s:float = (1-(1*scale_to))/2.0 | |
return [ | |
verts[A] + Vector3( s, -s, s ), | |
verts[B] + Vector3( -s, -s, s ), | |
verts[C] + Vector3( -s, s, s ), | |
verts[D] + Vector3( s, s, s ), | |
verts[E] + Vector3( s, -s, -s ), | |
verts[F] + Vector3( -s, -s, -s ), | |
verts[G] + Vector3( -s, s, -s ), | |
verts[H] + Vector3( s, s, -s ) | |
] | |
var s:float = (1*scale_to)-1 | |
return [ | |
verts[A] + Vector3( 0, s, 0 ), | |
verts[B] + Vector3( s, s, 0 ), | |
verts[C] + Vector3( s, 0, 0 ), | |
verts[D] + Vector3( 0, 0, 0 ), | |
verts[E] + Vector3( 0, s, s ), | |
verts[F] + Vector3( s, s, s ), | |
verts[G] + Vector3( s, 0, s ), | |
verts[H] + Vector3( 0, 0, s ) | |
] | |
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#= | |
# Drawing API | |
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#= | |
# Draw a line, or a set of lines in a single array. | |
# ex: | |
# # for a segment [a, b] | |
# draw_line("ig", [a,b]) | |
# | |
# # for a square [a, b, c, d] | |
# draw_line("ig", [a,b, b,c, c,d, d,a]) | |
# ----------------------------------------------------------- | |
func draw_line(ig_name:String, verts:Array) -> void: | |
var ig = _igs[ig_name] | |
ig.begin(Mesh.PRIMITIVE_LINES, null) | |
for v in verts: | |
ig.add_vertex(v) | |
ig.end() | |
func draw_raycast(ig_name:String, a:Vector3, b:Vector3) -> void: | |
var ig = _igs[ig_name] | |
ig.begin(Mesh.PRIMITIVE_LINES, null) | |
ig.set_color(Color.white) | |
ig.add_vertex(a) | |
ig.set_color(Color.black) | |
ig.add_vertex(b) | |
ig.end() | |
# Draw multiple sets of lines at once. | |
# ex: | |
# batch_draw_lines("ig", [[a,b], [c,d], [e,f]]) | |
# ----------------------------------------------------------- | |
func batch_draw_lines(ig_name:String, lines:Array) -> void: | |
if lines.size() > 0: | |
var ig = _igs[ig_name] | |
ig.begin(Mesh.PRIMITIVE_LINES, null) | |
for verts in lines: | |
for v in verts: | |
ig.add_vertex(v) | |
ig.end() | |
# Draw a line strip. | |
# | |
# ex: for the square (a,b,c,d) | |
# draw_linestrip("ig", [a,b,c,d,a]) | |
# ----------------------------------------------------------- | |
func draw_linestrip(ig_name:String, verts:Array) -> void: | |
var ig = _igs[ig_name] | |
ig.begin(Mesh.PRIMITIVE_LINE_STRIP, null) | |
for v in verts: | |
ig.add_vertex(v) | |
ig.end() | |
# Draw multiple line strips at once. | |
# ex: | |
# draw_linestrip("ig", [[a,b,c,d,a], [e,f,g,h,e]]) | |
# ----------------------------------------------------------- | |
func batch_draw_strip(ig_name:String, lines:Array) -> void: | |
if lines.size() > 0: | |
var ig = _igs[ig_name] | |
for verts in lines: | |
ig.begin(Mesh.PRIMITIVE_LINE_STRIP, null) | |
for v in verts: | |
ig.add_vertex(v) | |
ig.end() | |
# Draw a sphere. | |
# | |
# NOTE: this function uses 'ig.add_sphere'. It's not | |
# yet made to support drawing wireframe and faces at | |
# the same time. | |
# ----------------------------------------------------------- | |
func draw_sphere(ig_name:String, radius:float, wireframe:=false, lats:=8, lons:=8) -> void: | |
var ig = _igs[ig_name] | |
if wireframe: ig.begin(Mesh.PRIMITIVE_LINES, null) | |
else: ig.begin(Mesh.PRIMITIVE_TRIANGLES, null) | |
ig.add_sphere(lats, lons, radius, false) | |
ig.end() | |
# Draw an icosphere. | |
# | |
# NOTE: the radius is not really a radius. It's the | |
# width of the rectangles that connect the triangles. | |
# I still need to figure out how to calculate this | |
# from an actual radius. | |
# | |
# TODO: this seems pretty bad | |
# ----------------------------------------------------------- | |
func draw_icosphere(ig_name:String, position:Vector3, radius:float, flags:=BOTH) -> void: | |
var p = position | |
var r = radius | |
var t = ((1.0 + sqrt(5.0)) / 2.0) * r | |
var a = Vector3( p.x - r, p.y + t, p.z ) # Plane 1 | |
var b = Vector3( p.x + r, p.y + t, p.z ) | |
var c = Vector3( p.x + r, p.y - t, p.z ) | |
var d = Vector3( p.x - r, p.y - t, p.z ) | |
var e = Vector3( p.x, p.y + r, p.z - t ) # Plane 2 | |
var f = Vector3( p.x, p.y + r, p.z + t ) | |
var g = Vector3( p.x, p.y - r, p.z + t ) | |
var h = Vector3( p.x, p.y - r, p.z - t ) | |
var i = Vector3( p.x - t, p.y, p.z - r ) # Plane 3 | |
var j = Vector3( p.x + t, p.y, p.z - r ) | |
var k = Vector3( p.x + t, p.y, p.z + r ) | |
var l = Vector3( p.x - t, p.y, p.z + r ) | |
if flags & WIRE: | |
batch_draw_strip(ig_name, | |
[ | |
[b,f,l,i,e,b], | |
[a,e,j,k,f,a], | |
[d,g,k,j,h,d], | |
[c,h,i,l,g,c], | |
[h,j,b,a,i,h], | |
[g,l,a,b,k,g], | |
[f,k,c,d,l,f], | |
[e,i,d,c,j,e], | |
[l,d,h,e,a,l], | |
[k,b,e,h,c,k], | |
[j,c,g,f,b,j], | |
[i,a,f,g,d,i] | |
]) | |
if flags & FACES: | |
__batch_draw_triangle_fan(ig_name, | |
[ | |
[a, b,f,l,i,e,b], # first vertex is the center of each fan | |
[b, a,e,j,k,f,a], | |
[c, d,g,k,j,h,d], | |
[d, c,h,i,l,g,c], | |
[e, h,j,b,a,i,h], | |
[f, g,l,a,b,k,g], | |
[g, f,k,c,d,l,f], | |
[h, e,i,d,c,j,e], | |
[i, l,d,h,e,a,l], | |
[j, k,b,e,h,c,k], | |
[k, j,c,g,f,b,j], | |
[l, i,a,f,g,d,i] | |
]) | |
# Draw a square, with a wireframe, a face, or both. | |
# The face can be double sided. | |
# ----------------------------------------------------------- | |
func draw_square(ig_name:String, verts:Array, flags:=BOTH) -> void: | |
if flags & WIRE: draw_linestrip( ig_name, verts + [verts[0]] ) | |
if flags & FACES: __draw_face( ig_name, verts, flags ) | |
# Draw a polygon, with a wireframe, a face, or both. | |
# The face can be double sided. | |
# ----------------------------------------------------------- | |
func draw_polygon(ig_name:String, verts:Array, flags:=BOTH) -> void: | |
if flags & WIRE: draw_linestrip( ig_name, verts + [verts[0]] ) | |
if flags & FACES: | |
var ig = _igs[ig_name] | |
ig.begin(Mesh.PRIMITIVE_TRIANGLES, null) | |
for v in verts: | |
ig.add_vertex(v) # TODO: this only works for tris | |
ig.end() | |
# Draw a cube from a set of 8 vertices. Can be a wireframe | |
# and/or have faces. | |
# ----------------------------------------------------------- | |
func draw_cube(ig_name:String, verts:Array, flags:=BOTH) -> void: | |
__draw_cube(ig_name, verts, flags) | |
# draw a cube's wireframe made of triangles | |
func draw_cube_wire_tris(ig_name:String, verts:Array) -> void: | |
var a:Vector3 = verts[A] | |
var b:Vector3 = verts[B] | |
var c:Vector3 = verts[C] | |
var d:Vector3 = verts[D] | |
var e:Vector3 = verts[E] | |
var f:Vector3 = verts[F] | |
var g:Vector3 = verts[G] | |
var h:Vector3 = verts[H] | |
batch_draw_strip( ig_name, [ | |
[e,h,a,d], # West | |
[g,f,c,b], # East | |
[a,d,b,c], # North | |
[h,e,g,f], # South | |
[h,g,c,d,h,c], # Bottom | |
[a,b,f,e,a,f], # Top | |
] ) # top & bottom squares | |
# Draw a cube at ´point´ location. Can be a wireframe and/or | |
# have faces. If 'offset' is true, the cube will be offset | |
# by half its size (useful to make it fit a grid). | |
# | |
# (not working properly if size isn't 1) | |
# ----------------------------------------------------------- | |
func draw_point_cube(ig_name:String, point:Vector3, size:float, offset:bool, flags:=BOTH) -> void: | |
var p := point | |
var hs := size/2.0 # half size | |
var ofs := hs if offset else 0.0 | |
__draw_cube(ig_name, | |
[ | |
Vector3( p.x - hs + ofs, p.y + hs + ofs, p.z - hs + ofs ), | |
Vector3( p.x + hs + ofs, p.y + hs + ofs, p.z - hs + ofs ), | |
Vector3( p.x + hs + ofs, p.y - hs + ofs, p.z - hs + ofs ), | |
Vector3( p.x - hs + ofs, p.y - hs + ofs, p.z - hs + ofs ), | |
Vector3( p.x - hs + ofs, p.y + hs + ofs, p.z + hs + ofs ), | |
Vector3( p.x + hs + ofs, p.y + hs + ofs, p.z + hs + ofs ), | |
Vector3( p.x + hs + ofs, p.y - hs + ofs, p.z + hs + ofs ), | |
Vector3( p.x - hs + ofs, p.y - hs + ofs, p.z + hs + ofs ) | |
], flags) | |
# ----------------------------------------------------------- | |
# EXPERIMENTAL | |
# ----------------------------------------------------------- | |
# Draw a cube with faces scaled by their own centers | |
# ----------------------------------------------------------- | |
func draw_cube_scaled_faces(ig_name:String, verts:Array, scale:float, flags:int) -> void: | |
var s := scale | |
var a:Vector3 = verts[A] | |
var b:Vector3 = verts[B] | |
var c:Vector3 = verts[C] | |
var d:Vector3 = verts[D] | |
var e:Vector3 = verts[E] | |
var f:Vector3 = verts[F] | |
var g:Vector3 = verts[G] | |
var h:Vector3 = verts[H] | |
var faces := [ | |
[ b+Vector3(-s,-s, 0 ) ,a+Vector3( s,-s, 0 ) ,d+Vector3( s, s, 0 ) ,c+Vector3(-s, s, 0 ) ], | |
[ e+Vector3( s,-s, 0 ), f+Vector3(-s,-s, 0 ), g+Vector3(-s, s, 0 ), h+Vector3( s, s, 0 ) ], | |
[ a+Vector3( 0,-s, s ), e+Vector3( 0,-s,-s ), h+Vector3( 0, s,-s ), d+Vector3( 0, s, s ) ], | |
[ f+Vector3( 0,-s,-s ), b+Vector3( 0,-s, s ), c+Vector3( 0, s, s ), g+Vector3( 0, s,-s ) ], | |
[ a+Vector3( s, 0, s ), b+Vector3(-s, 0, s ), f+Vector3(-s, 0,-s ), e+Vector3( s, 0,-s ) ], | |
[ h+Vector3( s, 0,-s ), g+Vector3(-s, 0,-s ), c+Vector3(-s, 0, s ), d+Vector3( s, 0, s ) ], | |
] | |
for i in 6: | |
draw_linestrip( ig_name, faces[i] + [faces[i][0]] ) | |
if not flags & WIRE: # TODO: check that this works | |
# if not wireframe: | |
for i in 6: | |
__draw_face( ig_name, faces[i], flags ) | |
# Draw a face at 'point' location | |
# | |
# TODO: Consider using Planes | |
# ----------------------------------------------------------- | |
enum { X_AXIS, Y_AXIS, Z_AXIS } | |
func draw_point_face(ig_name:String, point:Vector3, size:float, facing:int) -> void: | |
var s := size | |
var p := point | |
var verts:Array | |
if facing == X_AXIS: | |
verts = [ Vector3( p.x - s, p.y - s, p.z ), | |
Vector3( p.x + s, p.y - s, p.z ), | |
Vector3( p.x + s, p.y + s, p.z ), | |
Vector3( p.x - s, p.y + s, p.z ), | |
Vector3( p.x - s, p.y - s, p.z ) ] | |
elif facing == Y_AXIS: | |
verts = [ Vector3( p.x - s, p.y, p.z - s ), | |
Vector3( p.x + s, p.y, p.z - s ), | |
Vector3( p.x + s, p.y, p.z + s ), | |
Vector3( p.x - s, p.y, p.z + s ), | |
Vector3( p.x - s, p.y, p.z - s ) ] | |
elif facing == Z_AXIS: | |
verts = [ Vector3( p.x, p.y - s, p.z + s ), | |
Vector3( p.x, p.y - s, p.z - s ), | |
Vector3( p.x, p.y + s, p.z - s ), | |
Vector3( p.x, p.y + s, p.z + s ), | |
Vector3( p.x, p.y - s, p.z + s ) ] | |
var ig = _igs[ig_name] | |
ig.begin(Mesh.PRIMITIVE_LINE_STRIP, null) | |
for v in verts: | |
ig.add_vertex(v) | |
ig.end() | |
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#= | |
# Private Drawing API | |
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#= | |
func __draw_face(ig_name:String, verts:Array, flags:int) -> void: | |
var ig = _igs[ig_name] | |
ig.begin(Mesh.PRIMITIVE_TRIANGLES, null) | |
ig.add_vertex(verts[A]) | |
ig.add_vertex(verts[C]) | |
ig.add_vertex(verts[D]) | |
ig.add_vertex(verts[A]) | |
ig.add_vertex(verts[B]) | |
ig.add_vertex(verts[C]) | |
ig.end() | |
func __batch_draw_faces(ig_name:String, faces:Array, flags:int) -> void: | |
var ig = _igs[ig_name] | |
ig.begin(Mesh.PRIMITIVE_TRIANGLES, null) | |
if faces.size(): | |
for verts in faces: | |
ig.add_vertex(verts[A]) | |
ig.add_vertex(verts[C]) | |
ig.add_vertex(verts[D]) | |
ig.add_vertex(verts[A]) | |
ig.add_vertex(verts[B]) | |
ig.add_vertex(verts[C]) | |
ig.end() | |
func __draw_cube(ig_name:String, verts:Array, flags:=BOTH) -> void: | |
# TODO: consider making a cube out of 2 faces + 1 triangle fan. Might be slightly faster. | |
var a:Vector3 = verts[A] | |
var b:Vector3 = verts[B] | |
var c:Vector3 = verts[C] | |
var d:Vector3 = verts[D] | |
var e:Vector3 = verts[E] | |
var f:Vector3 = verts[F] | |
var g:Vector3 = verts[G] | |
var h:Vector3 = verts[H] | |
if flags & WIRE: | |
batch_draw_strip( ig_name, [ [a, b, f, e, a], [d, c, g, h, d] ] ) # top & bottom squares | |
draw_line( ig_name, [d,a, c,b, h,e, g,f] ) # side edges | |
if flags & FACES: | |
__batch_draw_faces(ig_name, | |
[ | |
[b,a,d,c], # West | |
[e,f,g,h], # East | |
[a,e,h,d], # North | |
[f,b,c,g], # South | |
[h,g,c,d], # Bottom | |
[a,b,f,e], # Top | |
], flags) | |
func __draw_triangle_fan(ig_name:String, verts:Array) -> void: | |
var ig = _igs[ig_name] | |
ig.begin(Mesh.PRIMITIVE_TRIANGLE_FAN, null) | |
for v in verts: | |
ig.add_vertex(v) | |
ig.end() | |
func __batch_draw_triangle_fan(ig_name:String, fans:Array) -> void: | |
if fans.size() > 0: | |
var ig = _igs[ig_name] | |
for verts in fans: | |
ig.begin(Mesh.PRIMITIVE_TRIANGLE_FAN, null) | |
for v in verts: | |
ig.add_vertex(v) | |
ig.end() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment