Skip to content

Instantly share code, notes, and snippets.

@Skaruts
Last active October 24, 2021 23:33
Show Gist options
  • Save Skaruts/83dc5995448a7c0ea2b66eceee6724eb to your computer and use it in GitHub Desktop.
Save Skaruts/83dc5995448a7c0ea2b66eceee6724eb to your computer and use it in GitHub Desktop.
[Godot] A tool that helps working with multiple ImmediateGeometry nodes
#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=
# 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