building destruction particles
This commit is contained in:
parent
dba09165e8
commit
afc0aab441
127 changed files with 27285 additions and 240 deletions
211
addons/voronoishatter/tools/voronoishatter.gd
Normal file
211
addons/voronoishatter/tools/voronoishatter.gd
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
## The editor node that allows you to shatter a MeshInstance3D.
|
||||
@tool
|
||||
@icon("res://addons/voronoishatter/tools/voronoishatter.svg")
|
||||
extends Node3D
|
||||
|
||||
class_name VoronoiShatter
|
||||
|
||||
@export_tool_button("Generate Fracture Meshes", "MeshInstance3D") var execute_action = execute
|
||||
|
||||
@export_category("Materials")
|
||||
## Randomly assign color to each resulting fracture mesh (ignoring any other materials). Good for previewing the fractures.
|
||||
@export var random_color: bool = true:
|
||||
set(value):
|
||||
random_color = value
|
||||
notify_property_list_changed()
|
||||
|
||||
var _inherit_outer_material := false
|
||||
## Inherit the target mesh's surface materials
|
||||
@export var inherit_outer_material: bool:
|
||||
get: return _inherit_outer_material
|
||||
set(value):
|
||||
_inherit_outer_material = value
|
||||
notify_property_list_changed()
|
||||
## This material is applied to all clipped/original surfaces
|
||||
@export var outer_material: StandardMaterial3D
|
||||
## This material applies to the inner surfaces within the volume
|
||||
@export var inner_material: StandardMaterial3D
|
||||
|
||||
@export_category("Structure")
|
||||
## The number of samples used to determine the fracture points. Lower samples = bigger pieces (and faster generation). However, a < ~6 sample-size may lead to missing pieces.
|
||||
@export_range(1, 1024, 1, "hide_slider") var samples: int = 32:
|
||||
set(value):
|
||||
samples = value
|
||||
refresh_view()
|
||||
|
||||
## The random seed used to generate the samples.
|
||||
@export var seed: int = 0:
|
||||
set(value):
|
||||
seed = value
|
||||
refresh_view()
|
||||
|
||||
## Cell scale - change the size of the shape that is actually clipped
|
||||
@export
|
||||
var cell_scale: float = 1.0
|
||||
|
||||
## An optional 3D texture that influences where the samples points are generated.
|
||||
@export var sample_texture: Texture3D:
|
||||
set(value):
|
||||
if sample_texture:
|
||||
sample_texture.changed.disconnect(refresh_view)
|
||||
|
||||
sample_texture = value
|
||||
if sample_texture:
|
||||
value.changed.connect(refresh_view)
|
||||
|
||||
refresh_view()
|
||||
|
||||
## Randomize the random seed.
|
||||
@export_tool_button("Randomize seed", "RandomNumberGenerator") var randomize_seed_action = randomize_seed
|
||||
@export_category("Original Mesh")
|
||||
## Hide the target mesh after generating the fracture mesh.
|
||||
@export var hide_original: bool = true
|
||||
## Delete any fracture mesh children of this node before generation.
|
||||
@export var delete_existing_fractures: bool = true
|
||||
|
||||
const NO_MESH_CHILD = "VoronoiShatter must have a MeshInstance3D as a child."
|
||||
func randomize_seed():
|
||||
seed = randi()
|
||||
|
||||
var editable_owner: Node
|
||||
var voronoi_generator: VoronoiGenerator
|
||||
|
||||
func _enter_tree():
|
||||
# Plugin initialization
|
||||
if Engine.is_editor_hint():
|
||||
EditorInterface.get_selection().connect("selection_changed", refresh_view)
|
||||
editable_owner = get_tree().get_edited_scene_root()
|
||||
voronoi_generator = Engine.get_singleton("EditorVoronoiGenerator") as VoronoiGenerator
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if Engine.is_editor_hint():
|
||||
EditorInterface.get_selection().disconnect("selection_changed", refresh_view)
|
||||
|
||||
func get_target_mesh() -> MeshInstance3D:
|
||||
for child in get_children():
|
||||
if is_instance_of(child, MeshInstance3D):
|
||||
return child
|
||||
|
||||
return null
|
||||
|
||||
func execute():
|
||||
started = Time.get_ticks_usec()
|
||||
await get_tree().process_frame
|
||||
|
||||
if delete_existing_fractures:
|
||||
for child in get_children():
|
||||
if is_instance_of(child, VoronoiCollection):
|
||||
child.queue_free()
|
||||
|
||||
generate_fracture_meshes(get_config())
|
||||
|
||||
var current_collection
|
||||
var started = null
|
||||
|
||||
func generate_fracture_meshes(config: VoronoiGeneratorConfig):
|
||||
var target = get_target_mesh()
|
||||
if not target:
|
||||
VoronoiLog.err(NO_MESH_CHILD)
|
||||
return
|
||||
|
||||
VoronoiLog.log("Creating Voronoi geometry for %s..." % target.name)
|
||||
|
||||
# Create the parent node collection
|
||||
current_collection = VoronoiCollection.new()
|
||||
current_collection.set_block_signals(true)
|
||||
current_collection.name = "Fractured_" + target.name + "_" + str(Time.get_ticks_msec())
|
||||
add_child(current_collection)
|
||||
current_collection.set_owner(owner)
|
||||
|
||||
if hide_original:
|
||||
target.visible = false
|
||||
|
||||
var results := voronoi_generator.create_from_mesh(target, config)
|
||||
for result in results:
|
||||
create_from_voronoi_mesh(result)
|
||||
VoronoiLog.log("Completed in " + str((Time.get_ticks_usec() - started) / 10e5) + " seconds")
|
||||
current_collection.set_block_signals(false)
|
||||
|
||||
func create_from_voronoi_mesh(voronoi_mesh: VoronoiMesh):
|
||||
if voronoi_mesh == null:
|
||||
VoronoiLog.err("Skipping creation of null mesh")
|
||||
|
||||
var mesh_instance = MeshInstance3D.new()
|
||||
mesh_instance.set_block_signals(true)
|
||||
var target = get_target_mesh()
|
||||
var mesh = voronoi_mesh.mesh
|
||||
mesh_instance.scale = target.scale
|
||||
mesh_instance.name = "FracturedPiece_" + str(mesh.get_rid())
|
||||
mesh_instance.mesh = mesh
|
||||
mesh_instance.position -= voronoi_mesh.position * target.scale
|
||||
mesh_instance.scale = Vector3.ONE * cell_scale
|
||||
|
||||
var has_outside_faces = mesh_instance.mesh.get_surface_count() > 1
|
||||
|
||||
if random_color:
|
||||
var random_color: Color = Color(randf(), randf(), randf())
|
||||
for surface in range(mesh_instance.mesh.get_surface_count()):
|
||||
var material = StandardMaterial3D.new()
|
||||
material.albedo_color = random_color
|
||||
|
||||
mesh_instance.mesh.surface_set_material(surface, material)
|
||||
else:
|
||||
if has_outside_faces:
|
||||
if not inherit_outer_material:
|
||||
for surface_id in range(1, mesh_instance.mesh.get_surface_count()):
|
||||
mesh_instance.mesh.surface_set_material(surface_id, outer_material)
|
||||
|
||||
if inner_material:
|
||||
mesh_instance.mesh.surface_set_material(0, inner_material)
|
||||
|
||||
call_deferred("_deferred_add_child", mesh_instance)
|
||||
|
||||
func _deferred_add_child(mesh_instance: MeshInstance3D):
|
||||
current_collection.add_child(mesh_instance)
|
||||
mesh_instance.set_owner(owner)
|
||||
mesh_instance.set_block_signals(false)
|
||||
|
||||
func get_config() -> VoronoiGeneratorConfig:
|
||||
var config = VoronoiGeneratorConfig.new()
|
||||
config.num_samples = samples
|
||||
config.random_seed = seed
|
||||
config.texture = sample_texture
|
||||
return config
|
||||
|
||||
# INTERNAL FUNCTIONS - for showing things in the editor, e.g.
|
||||
var sample_visualizers: Array[CSGSphere3D] = []
|
||||
|
||||
func refresh_view():
|
||||
if not Engine.is_editor_hint() or not is_instance_valid(voronoi_generator):
|
||||
return
|
||||
|
||||
for visualizer in sample_visualizers:
|
||||
visualizer.queue_free()
|
||||
|
||||
sample_visualizers = []
|
||||
|
||||
var target = get_target_mesh()
|
||||
if not EditorInterface.get_selection().get_selected_nodes().has(self) or not target:
|
||||
return
|
||||
|
||||
var config = get_config()
|
||||
var material = StandardMaterial3D.new()
|
||||
material.albedo_color = Color.RED
|
||||
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
material.disable_receive_shadows = true
|
||||
|
||||
for sample in voronoi_generator.sample_points(target.mesh, config):
|
||||
var sphere = CSGSphere3D.new()
|
||||
sphere.set_block_signals(true)
|
||||
sphere.material = material
|
||||
sphere.radius = 0.02
|
||||
sphere.rings = 4
|
||||
sphere.radial_segments = 4
|
||||
sphere.position = sample + target.position
|
||||
sphere.cast_shadow = false
|
||||
sample_visualizers += [sphere]
|
||||
add_child(sphere)
|
||||
|
||||
func _get_configuration_warnings():
|
||||
if not get_target_mesh():
|
||||
return [NO_MESH_CHILD]
|
||||
Loading…
Add table
Add a link
Reference in a new issue