@tool class_name BushMesh extends PrimitiveMesh @export_range(0.001, 1., 0.001) var span_size : float = 0.08 @export_range(0.001, 1., 0.001) var initial_diameter : float = 0.01 @export_range(0.001, 1., 0.001) var initial_length : float = 0.435 @export_range(0.001, 1., 0.001) var length_decay : float = 0.6 @export_range(-2.* PI, 2. * PI) var deviation_angle : float = 0.369 : set(value) : deviation_angle = value; request_update() # L-System describing the bush @export var system : LSystem = null : set(value) : if system != null: system.changed.disconnect(request_update) elif value != null: value.changed.connect(request_update) system = value request_update() const CENTER_QUAD : Array[Vector3] = [ Vector3(-0.5, 0., -0.5), Vector3( 0.5, 0., -0.5), Vector3(-0.5, 0., 0.5), Vector3( 0.5, 0., 0.5) ] const BOX_INDICES : Array[int] = [ 2, 6, 7, 2, 7, 3, 3, 7, 5, 3, 5, 1, 1, 5, 4, 1, 4, 0, 0, 4, 6, 0, 6, 2 ] # Alphabet : 3D Turtle. # + -> Turn left by angle δ, using rotation matrix RU(δ). # − -> Turn right by angle δ, using rotation matrix RU(−δ). # & -> Pitch down by angle δ, using rotation matrix RL(δ). # ∧ -> Pitch up by angle δ, using rotation matrix RL(−δ). # \ -> Roll left by angle δ, using rotation matrix RH(δ). # / -> Roll right by angle δ, using rotation matrix RH(−δ). # | -> Turn around, using rotation matrix RU(180) # [ -> Push the current state of the turtle in the stack # ] -> Pop the last state of the turtle from the stack # ! -> Decrement diameter of the branch # ' -> Increment color index # { } -> Enclose triangle span # f -> Add current position to triangle span and move forward by span_size # . -> Create a point within a triangle span # F -> Move Forward class TurtleState: var transform : Transform3D : set(value) : transform = value normal = Vector3(0., 1., 0.) * transform var global_position : Vector3 var length : float var normal : Vector3 var diameter : float func duplicate() -> TurtleState: var n : TurtleState = TurtleState.new() n.transform = Transform3D(transform) n.global_position = Vector3(global_position) n.length = length n.diameter = diameter n.normal = Vector3(normal) return n func rotate(axis : Vector3, angle : float) -> void: transform = transform.rotated(axis, angle) normal = Vector3(0., 1., 0.) * transform func forward(decay : float) -> void: global_position = global_position + length * normal length *= decay func decode_turtle(orders : String) -> Array: var vertices : Array[Vector3] = [] var colors : Array[Color] = [] var initial_state : TurtleState = TurtleState.new() initial_state.global_position = Vector3.ZERO initial_state.length = initial_length initial_state.transform = Transform3D.IDENTITY initial_state.diameter = initial_diameter var span : Array[Vector3] var stack : Array[TurtleState] = [] var current_state : TurtleState = initial_state for o in orders: match o: "+" : current_state.rotate(Vector3(1., 0., 0.), deviation_angle) "-" : current_state.rotate(Vector3(1., 0., 0.), -deviation_angle) "&" : current_state.rotate(Vector3(0., 0., 1.), deviation_angle) "^" : current_state.rotate(Vector3(0., 0., 1.), -deviation_angle) "/" : current_state.rotate(Vector3(0., 1., 0.), deviation_angle) "\\" : current_state.rotate(Vector3(0., 1., 0.), -deviation_angle) "|" : current_state.rotate(Vector3(1., 0., 0.), PI) "[" : stack.push_front(current_state) current_state = current_state.duplicate() "]" : current_state = stack.pop_front() "!" : current_state.diameter /= 2.0 "F" : var start_position : Vector3 = current_state.global_position current_state.forward(length_decay) var end_position : Vector3 = current_state.global_position var quad : Array[Vector3] = [] for v in CENTER_QUAD: quad.append(v * current_state.diameter * current_state.transform) var ref_vertices : Array[Vector3] = [] for v in quad: ref_vertices.append(v + start_position) for v in quad: ref_vertices.append(v + end_position) for i in BOX_INDICES: vertices.append(ref_vertices[i]) colors.append(Color.SADDLE_BROWN) "{" : # Start a triangle span. stack.push_front(current_state) current_state = current_state.duplicate() span = [ current_state.global_position ] "f" : # Add a vertices to the span var pos : Vector3 = current_state.global_position + current_state.normal * span_size; span.append(pos) current_state.global_position = pos "}" : # End the triangle span. current_state = stack.pop_front() var span_count : int = span.size() if span_count > 2: var center : Vector3 = Vector3.ZERO for s in span: center += s center /= float(span_count) for i in range(span_count - 1): vertices.append(span[i]) vertices.append(span[i+1]) vertices.append(center) colors.append_array([ Color.FOREST_GREEN, Color.SEA_GREEN, Color.LIGHT_GREEN] ) _: pass return [ vertices, colors ] # Primitive Mesh entry point func _create_mesh_array() -> Array: var vertices : Array[Vector3] = [ Vector3(0., 0., 0.), Vector3(1., 0., 0.), Vector3(1., 1., 0.) ] var normals : Array[Vector3] = [ Vector3(0., 0., 1.), Vector3(0., 0., 1.), Vector3(0., 0., 1.) ] var colors : Array[Color] = [ Color.WHITE, Color.WHITE, Color.WHITE ] if system != null: var data : Array = decode_turtle(system.get_result()) vertices = data[0] colors = data[1] var vertices_count : int = vertices.size() normals.resize(vertices_count) @warning_ignore("integer_division") var face_count : int = vertices_count / 3 for i in range(face_count): var j : int = i * 3 var n : Vector3 = -(vertices[j + 1] - vertices[j]).cross(vertices[j + 2] - vertices[j]).normalized() normals[j] = n; normals[j + 1] = n; normals[j + 2] = n var info : Array = [] info.resize(Mesh.ARRAY_MAX) info[Mesh.ARRAY_VERTEX] = PackedVector3Array(vertices) info[Mesh.ARRAY_NORMAL] = PackedVector3Array(normals) info[Mesh.ARRAY_COLOR] = PackedColorArray(colors) return info
or share this direct link: