Repository: cloudofoz/godot-deformablemesh Branch: main Commit: ee6f41486b87 Files: 21 Total size: 39.8 KB Directory structure: gitextract_20c1fllx/ ├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md └── addons/ └── deformablemesh/ ├── dm_debug_sphere_material.tres ├── dm_debug_sphere_mesh.tres ├── dm_deformable_mesh.gd ├── dm_deformable_mesh.gd.uid ├── dm_deformer.gd ├── dm_deformer.gd.uid ├── dm_drag_deformer.gd ├── dm_drag_deformer.gd.uid ├── dm_spherical_deformer.gd ├── dm_spherical_deformer.gd.uid ├── dm_std_deformer.gd ├── dm_std_deformer.gd.uid ├── dm_surface_data.gd ├── dm_surface_data.gd.uid ├── plugin.cfg ├── plugin.gd └── plugin.gd.uid ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Normalize EOL for all files that Git considers text files. * text=auto eol=lf # Only include the addons folder when downloading from the Asset Library. /** export-ignore /addons !export-ignore /addons/** !export-ignore ================================================ FILE: .gitignore ================================================ # Godot 4+ specific ignores .godot/ ================================================ FILE: LICENSE.md ================================================ MIT License Copyright (C) 2023 Claudio Z. (cloudofoz) 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. ================================================ FILE: README.md ================================================ ![Version](https://img.shields.io/badge/Godot-v4.5-informational) ![License](https://img.shields.io/github/license/cloudofoz/godot-deformablemesh) ## godot-deformablemesh **This addon allows to deform 3D meshes using a stack of customizable deformers at run-time**

## Main Features Use the default deformers: - `SphericalDeformer` - `StandardDeformer` (Bend, Twist, and Taper) - `DragDeformer` (In **Rest Pose Mode**, position the deformer, toggle it off, and deform the mesh by moving the node.)
Or **easily create your own** by extending the base class and overriding just a couple of methods in `dm_deformer.gd`. ## Getting Started 1. Download the [repository](https://github.com/cloudofoz/godot-deformablemesh/archive/refs/heads/main.zip) or download the *previous version* of the addon from the AssetLib in Godot ([link](https://godotengine.org/asset-library/asset/1794)). 2. Import the **addons** folder into your project. 3. Activate `DeformableMesh` under *Project > Project Settings > Plugins.*

4. Add a *deformer* node to the scene.

5. Add a `DeformableMeshInstance3D` node to the scene.

6. Set the *mesh resource* you want to deform in the **Original Mesh** property.

7. Link the *deformer node* you created before to the list of **Deformers** that will affect this mesh in the property panel.

7. Tweak the *deformer* properties to achieve the desired result.

## Example Project 1. [**You can download here**](media/dm_example_scene.zip) an example project that shows the basic functionalities of `DeformableMesh`. 2. Unzip the file 3. Import the project with Godot Engine 4+ 4. Open the scene `dm_example_scene_v030.tscn` (if it's not already opened) You can now try tweaking the deformer parameters. Some effects are also controlled by the positions and the rotations of the deformer nodes. `DeformableMesh` can apply multiple deformers like in a stack, so the order is important to achieve the correct effect. You need also to specify the correct deformation axis (for some effects like bending, but it's not important with spherical deformers). ## Known Limitations - This addon is designed with simplicity and versatility as primary goals, making it well-suited for simple, standard use cases. However, it is not optimized for specialized use cases, such as higher-density meshes (and, in some cases, multiple surfaces, which may also impact performance) in performance-critical applications. Users are encouraged to thoroughly test the addon to ensure it meets their specific requirements. - While other deformers support deforming multiple meshes at once, a `DragDeformer` can only be tied to a single mesh at a time. ## Credits I wish to thank the community for any contribution or feedback. Special thanks to: - **Kevin Loustau**, for sparking the idea behind the `DragDeformer` feature. ## Changelog **v0.40** - **Add**: `DragDeformer` node. - **Add**: `_on_end_update()` overridable method for the deformer base class. - **Fix**: Removing a deformer now correctly unregisters the linked event listeners. - **Fix**: `_on_begin_update()` is now called only once, even with multiple surfaces. [**v0.30**](https://github.com/cloudofoz/godot-deformablemesh/releases/tag/v0.30) - **Add**: `StandardDeformer` (Bend, Twist, and Taper). - **Remove**: `BendDeformer` (now included as part of `StandardDeformer`). - **Improve**: Deformer selection list. [**v0.20**](https://github.com/cloudofoz/godot-deformablemesh/releases/tag/v0.20) - **Add**: Bend deformers. - **Add**: Base class to easily create custom deformers. - **Refactor**: Codebase and minor improvements. [**v0.10**](https://github.com/cloudofoz/godot-deformablemesh/tree/v0.1) - **First release** ## License [MIT License](/LICENSE.md) ================================================ FILE: addons/deformablemesh/dm_debug_sphere_material.tres ================================================ [gd_resource type="StandardMaterial3D" format=3 uid="uid://dwqr7ia23sngt"] [resource] depth_draw_mode = 2 no_depth_test = true shading_mode = 0 diffuse_mode = 3 disable_ambient_light = true albedo_color = Color(0.14902, 1, 0.713726, 1) ================================================ FILE: addons/deformablemesh/dm_debug_sphere_mesh.tres ================================================ [gd_resource type="ArrayMesh" load_steps=2 format=4 uid="uid://b3o4hmiksh4it"] [ext_resource type="Material" uid="uid://dwqr7ia23sngt" path="res://addons/deformablemesh/dm_debug_sphere_material.tres" id="1_d8wbp"] [resource] _surfaces = [{ "aabb": AABB(-0.990686, 0, -0.997669, 1.9907, 1e-05, 1.99534), "format": 34359738369, "material": ExtResource("1_d8wbp"), "primitive": 2, "uv_scale": Vector4(0, 0, 0, 0), "vertex_count": 24, "vertex_data": PackedByteArray("AACAPwAAAAAAAAAAv4F2PwAAAADNIoo+O7taPwAAAAB0AwU/zrsuPwAAAAARGDs/pY3rPgAAAAByTGM/xlZQPgAAAABBpXo/rsKLvQAAAAA4Z38/V3WrvgAAAAADOHE/UqETvwAAAADwJFE/BJVGvwAAAAD7jiE/XM5qvwAAAAA6+8s+mJ19vwAAAABFbws+mJ19vwAAAABFbwu+XM5qvwAAAAA6+8u+BJVGvwAAAAD7jiG/UqETvwAAAADwJFG/V3WrvgAAAAADOHG/rsKLvQAAAAA4Z3+/xlZQPgAAAABBpXq/pY3rPgAAAAByTGO/zrsuPwAAAAARGDu/O7taPwAAAAB0AwW/v4F2PwAAAADNIoq+AACAPwAAAAAAMI2l") }, { "aabb": AABB(-4.37114e-08, -0.990686, -0.997669, 1e-05, 1.9907, 1.99534), "format": 34359738369, "material": ExtResource("1_d8wbp"), "primitive": 2, "uv_scale": Vector4(0, 0, 0, 0), "vertex_count": 24, "vertex_data": PackedByteArray("Lr07swAAgD8AAAAA8cY0s7+Bdj/NIoo+aGggszu7Wj90AwU/UCQAs867Lj8RGDs/ib6ssqWN6z5yTGM/WMkYssZWUD5BpXo/+vxMMa7Ci704Z38/1np7Mld1q74DOHE/34fYMlKhE7/wJFE/mqERMwSVRr/7jiE/QjIsM1zOar86+8s+if05M5idfb9Fbws+if05M5idfb9Fbwu+QjIsM1zOar86+8u+mqERMwSVRr/7jiG/34fYMlKhE7/wJFG/1np7Mld1q74DOHG/+vxMMa7Ci704Z3+/WMkYssZWUD5BpXq/ib6ssqWN6z5yTGO/UCQAs867Lj8RGDu/aGggszu7Wj90AwW/8cY0s7+Bdj/NIoq+Lr07swAAgD8AMI2l") }, { "aabb": AABB(-0.990686, -0.997669, -4.36095e-08, 1.9907, 1.99534, 1.00436e-05), "format": 34359738369, "material": ExtResource("1_d8wbp"), "primitive": 2, "uv_scale": Vector4(0, 0, 0, 0), "vertex_count": 24, "vertex_data": PackedByteArray("AACAPwAAAAAAAAAAv4F2P80iij4Bm0qyO7taP3QDBT+iF8OyzrsuPxEYOz/XNAmzpY3rPnJMYz/QsCazxlZQPkGlej/wzzezrsKLvThnfz8jTTuzV3WrvgM4cT8s5jCzUqETv/AkUT+JYBmzBJVGv/uOIT+q9eyyXM5qvzr7yz5Al5WymJ19v0VvCz6jgsyxmJ19v0VvC76jgswxXM5qvzr7y75Al5UyBJVGv/uOIb+q9ewyUqETv/AkUb+JYBkzV3WrvgM4cb8s5jAzrsKLvThnf78jTTszxlZQPkGler/wzzczpY3rPnJMY7/QsCYzzrsuPxEYO7/XNAkzO7taP3QDBb+iF8Myv4F2P80iir4Bm0oyAACAPwAwjaXMFE8Z") }] ================================================ FILE: addons/deformablemesh/dm_deformable_mesh.gd ================================================ # Copyright (C) 2023-2024 Claudio Z. (cloudofoz) # # 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 MeshInstance3D #--------------------------------------------------------------------------------------------------- # CONSTANTS #--------------------------------------------------------------------------------------------------- const SurfaceData = preload("dm_surface_data.gd") const Deformer = preload("dm_deformer.gd") #--------------------------------------------------------------------------------------------------- # PUBLIC VARIABLES #--------------------------------------------------------------------------------------------------- @export_category("Deformable Mesh") ## Original mesh resource to be deformed @export var original_mesh: Mesh = null: set(value): if(original_mesh): original_mesh.changed.disconnect(dm_init_surfaces) original_mesh = value if(!original_mesh): return original_mesh.changed.connect(dm_init_surfaces) dm_init_surfaces() ## Array of deformer node paths that affects this mesh. @onready @export var deformers: Array[Deformer]: set(value): deformers = value dm_find_deformers() #--------------------------------------------------------------------------------------------------- # PRIVATE VARIABLES #--------------------------------------------------------------------------------------------------- var dm_surfaces = [SurfaceData]; var dm_need_update: bool = false var dm_deformers: Array[Deformer] #--------------------------------------------------------------------------------------------------- # VIRTUAL METHODS #--------------------------------------------------------------------------------------------------- func _init(): set_notify_transform(true) dm_clean_deformers() func _ready(): dm_find_deformers() func _process(_delta): if(dm_need_update): dm_update() func _notification(what): match what: NOTIFICATION_TRANSFORM_CHANGED: dm_need_update = true NOTIFICATION_ENTER_WORLD: dm_find_deformers() #--------------------------------------------------------------------------------------------------- # PRIVATE METHODS #--------------------------------------------------------------------------------------------------- func dm_init_surfaces(): if(!original_mesh): return mesh = ArrayMesh.new() var surface_count = original_mesh.get_surface_count() dm_surfaces.clear() for i in range(surface_count): var s = SurfaceData.new() s.create_from_surface(original_mesh, i) dm_surfaces.push_back(s) dm_update() func dm_update(): if(dm_surfaces.size() < 1): return if(!mesh): return for deformer in dm_deformers: if(!deformer.visible): continue deformer._on_begin_update(self) mesh.clear_surfaces() for sidx in range(dm_surfaces.size()): var s = dm_surfaces[sidx] if(!s): continue s.update_surface(dm_deformers, self) s.commit_to_surface(mesh) mesh.surface_set_material(sidx, original_mesh.surface_get_material(sidx)) dm_need_update = false for deformer in dm_deformers: if(!deformer.visible): continue deformer._on_end_update() func dm_clean_deformers(): #dm_deformers.clear() while not dm_deformers.is_empty(): var deformer = dm_deformers.back() deformer.on_deformer_updated.disconnect(_on_deformer_updated) deformer.on_deformer_removed.disconnect(_on_deformer_removed) dm_deformers.pop_back() func dm_add_deformer(deformer: Deformer) -> void: dm_deformers.push_back(deformer) if(!deformer.on_deformer_updated.is_connected(_on_deformer_updated)): deformer.on_deformer_updated.connect(_on_deformer_updated) if(!deformer.on_deformer_removed.is_connected(_on_deformer_removed)): deformer.on_deformer_removed.connect(_on_deformer_removed) func dm_rem_deformer(deformer: Deformer) -> void: var didx = dm_deformers.find(deformer) if(didx > -1): dm_deformers.remove_at(didx) if(deformer.on_deformer_updated.is_connected(_on_deformer_updated)): deformer.on_deformer_updated.disconnect(_on_deformer_updated) if(deformer.on_deformer_removed.is_connected(_on_deformer_removed)): deformer.on_deformer_removed.disconnect(_on_deformer_removed) func dm_find_deformers(): if(!is_inside_tree()): return dm_clean_deformers() for d in deformers: if(d && !dm_deformers.has(d)): dm_add_deformer(d) else: var didx = deformers.find(d) deformers[didx] = null notify_property_list_changed() dm_need_update = true #--------------------------------------------------------------------------------------------------- # CALLBACKS #--------------------------------------------------------------------------------------------------- func _on_deformer_updated(deformer: Deformer): var i = dm_deformers.find(deformer) if( i == -1 ): dm_add_deformer(deformer) dm_need_update = true func _on_deformer_removed(deformer: Deformer): dm_rem_deformer(deformer) dm_need_update = true #--------------------------------------------------------------------------------------------------- # KNOWN BUGS / LIMITATIONS #--------------------------------------------------------------------------------------------------- #BUG: Error message when deleting node referenced through NodePath property or metadata #75168 # https://github.com/godotengine/godot/issues/75168 #LIMITATION: A deformer is currently only selectable from the scene tree #LIMITATION: A deformable mesh is currently limited by one UV set ================================================ FILE: addons/deformablemesh/dm_deformable_mesh.gd.uid ================================================ uid://iakvtj364g6q ================================================ FILE: addons/deformablemesh/dm_deformer.gd ================================================ # Copyright (C) 2023-2024 Claudio Z. (cloudofoz) # # 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 MeshInstance3D ## This base class is useful to create your own deformers for the deformable meshes. ## ## Basic usage: extend this class and override these two methods: ## ## ## This method is called once before updating vertices. ## ## It's useful if you want to capture some information from the mesh that is going to ## ## to be modified. (for ex. the deformer position local to that istance) ## func _on_begin_update(DeformableMeshInstance3D) -> void ## ## ## This method is called for every vertex of the mesh. It takes the current vertex ## ## position and returns the new vertex position ## func _on_update_vertex(Vector3) -> Vector3 class_name DM_Deformer #--------------------------------------------------------------------------------------------------- # CONSTANTS #--------------------------------------------------------------------------------------------------- const DeformableMeshInstance3D = preload("dm_deformable_mesh.gd") const DebugSphereMesh = preload("dm_debug_sphere_mesh.tres") #--------------------------------------------------------------------------------------------------- # SIGNALS #--------------------------------------------------------------------------------------------------- signal on_deformer_updated(deformer) signal on_deformer_removed(deformer) #--------------------------------------------------------------------------------------------------- # PUBLIC VARIABLES #--------------------------------------------------------------------------------------------------- @export_category("Deformer") ## A debug mesh to show in the editor with this node @export var debug_mesh: Mesh = DebugSphereMesh ## Draws the deformer mesh (only visible in editor mode) @export var show_debug_mesh: bool = true: set(value): show_debug_mesh = value mesh = debug_mesh if value else null #--------------------------------------------------------------------------------------------------- # CALLBACKS #--------------------------------------------------------------------------------------------------- func _on_user_changed_mesh(): if(show_debug_mesh && mesh != debug_mesh): mesh = debug_mesh #--------------------------------------------------------------------------------------------------- # VIRTUAL METHODS #--------------------------------------------------------------------------------------------------- ## Override this method to set up initial parameters in the deformer. ## Called once before every deformable mesh update. func _on_begin_update(deformable: DeformableMeshInstance3D) -> void: pass ## This is the main method to override for every type of deformer. ## The default behaviour will leave the vertex unchanged. ## It's called for every vertex of the deformable mesh. func _on_update_vertex(mesh_vertex: Vector3) -> Vector3: return mesh_vertex ## Override this method to perform final operations in the deformer. ## Called once after every deformable mesh update is finished. func _on_end_update() -> void: pass func _init(): set_notify_transform(true) if(!self.visibility_changed.is_connected(dm_update_deformables)): self.visibility_changed.connect(dm_update_deformables) if(Engine.is_editor_hint()): if(!property_list_changed.is_connected(_on_user_changed_mesh)): property_list_changed.connect(_on_user_changed_mesh) func _ready(): if(Engine.is_editor_hint()): if(show_debug_mesh): mesh = debug_mesh # scale = Vector3(radius, radius, radius) #TODO: Fix else: show_debug_mesh = false dm_update_deformables() func _notification(what): match what: NOTIFICATION_TRANSFORM_CHANGED: dm_update_deformables() func _exit_tree(): on_deformer_removed.emit(self) #--------------------------------------------------------------------------------------------------- # PRIVATE METHODS #--------------------------------------------------------------------------------------------------- func dm_update_deformables(): if(!is_inside_tree()): return on_deformer_updated.emit(self) ================================================ FILE: addons/deformablemesh/dm_deformer.gd.uid ================================================ uid://bi1bdmhkk4kui ================================================ FILE: addons/deformablemesh/dm_drag_deformer.gd ================================================ # Copyright (C) 2024 Claudio Z. (cloudofoz) # # 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 "dm_deformer.gd" #--------------------------------------------------------------------------------------------------- # PUBLIC VARIABLES #--------------------------------------------------------------------------------------------------- @export_category("Drag Deformer") ## Radius of the sphere affecting the mesh deformation. @export var radius: float = 1.5: set(value): radius = value dm_update_deformables() self.scale = Vector3(radius, radius, radius) ## Falloff of the deformation effect, decreasing with distance from the center. @export_exp_easing("attenuation") var attenuation: float = 1.0: set(value): attenuation = value dm_update_deformables() ## Strength of the deformation effect. @export_range(0, 2.0, 0.1, "or_greater") var strength: float = 1: set(value): strength = value dm_update_deformables() ## Toggle the Rest Pose Mode. ## Enables setting the deformer position at which there is no deformation. ## When disabled, deformation is calculated from the rest pose. @export var rest_pose: bool = true: set(value): if value == rest_pose: return rest_pose = value if not rest_pose: dm_compute_rest_distances = true dm_update_deformables() # Move the deformer to its rest position elif is_instance_valid(dm_active_deformable): self.global_position = dm_active_deformable.to_global(dm_rest_pos) get: return rest_pose #--------------------------------------------------------------------------------------------------- # PRIVATE VARIABLES #--------------------------------------------------------------------------------------------------- var dm_delta_translation: Vector3 var dm_rest_pos: Vector3 var dm_rest_distances: PackedFloat32Array var dm_active_deformable: DeformableMeshInstance3D var dm_active_mesh: Mesh var dm_compute_rest_distances: bool = false var dm_vertex_index: int #--------------------------------------------------------------------------------------------------- # PRIVATE METHODS #--------------------------------------------------------------------------------------------------- func dm_needs_computation(d: DeformableMeshInstance3D) -> bool: # Check if the active deformable is different from the current one. if dm_active_deformable != d: # If the current deformer is associated with a different mesh, remove it. if dm_active_deformable != null and dm_active_deformable.deformers.find(self) != -1: d.deformers.remove_at(d.deformers.find(self)) d.dm_find_deformers() push_warning("A DragDeformer can support only one mesh at a time.") return true # Check if the active mesh has changed. if dm_active_mesh != d.original_mesh: return true # Check if rest distances need initialization. if dm_rest_distances.is_empty(): return true # No recomputation is needed. return false #--------------------------------------------------------------------------------------------------- # VIRTUAL METHODS #--------------------------------------------------------------------------------------------------- func _ready(): dm_rest_pos = self.get_meta("dm_rest_pos", Vector3(0,0,0)) super._ready() func _on_end_update() -> void: # If the rest distances were computed during this frame, force a refresh. # This ensures the meshes are correctly deformed immediately after loading # a scene, avoiding the need to move the deformer to trigger the update. if dm_compute_rest_distances: dm_compute_rest_distances = false dm_update_deformables() func _on_begin_update(d: DeformableMeshInstance3D) -> void: # When rest pose is active, only the rest position is stored, # and no deformation occurs. if rest_pose: dm_rest_pos = d.to_local(self.global_position) self.set_meta("dm_rest_pos", dm_rest_pos) return # Clear previous data if recomputation of distances is needed. if dm_compute_rest_distances or dm_needs_computation(d): dm_active_deformable = d dm_active_mesh = d.original_mesh dm_rest_distances.clear() dm_compute_rest_distances = true return # Otherwise, compute the deformation translation vector. dm_vertex_index = 0 dm_delta_translation = d.to_local(self.global_position) - dm_rest_pos func _on_update_vertex(v: Vector3) -> Vector3: # If in rest pose mode, return the vertex unchanged. if rest_pose: return v # Compute and store rest distances if needed, without deforming the vertex. if dm_compute_rest_distances: var d = dm_rest_pos.distance_to(v) dm_rest_distances.push_back(d) return v # Retrieve the rest distance for the current vertex. var rest_distance = dm_rest_distances[dm_vertex_index] dm_vertex_index += 1 var delta_radius = radius - rest_distance if delta_radius > 0.0: # Apply deformation using an eased interpolation factor. var t = strength * ease(delta_radius / radius, attenuation) v += dm_delta_translation * t return v ================================================ FILE: addons/deformablemesh/dm_drag_deformer.gd.uid ================================================ uid://btbnin5d1s6y0 ================================================ FILE: addons/deformablemesh/dm_spherical_deformer.gd ================================================ # Copyright (C) 2023-2024 Claudio Z. (cloudofoz) # # 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 "dm_deformer.gd" #--------------------------------------------------------------------------------------------------- # PUBLIC VARIABLES #--------------------------------------------------------------------------------------------------- @export_category("Spherical Deformer") ## Radius of the deformation sphere. @export var radius: float = 1.5: set(value): radius = value dm_update_deformables() self.scale = Vector3(radius, radius, radius) ## Strength of the deformation. @export var strength: float = 1.5: set(value): strength = value dm_update_deformables() ## Falloff of the deformation, based on distance from the center. @export_exp_easing("attenuation") var attenuation: float = 1.0: set(value): attenuation = value dm_update_deformables() #--------------------------------------------------------------------------------------------------- # PRIVATE VARIABLES #--------------------------------------------------------------------------------------------------- var dm_deformable_local_pos: Vector3 #--------------------------------------------------------------------------------------------------- # VIRTUAL METHODS #--------------------------------------------------------------------------------------------------- func _on_begin_update(d: DeformableMeshInstance3D) -> void: dm_deformable_local_pos = d.to_local(self.global_position) func _on_update_vertex(v: Vector3) -> Vector3: var d = dm_deformable_local_pos.distance_to(v) var delta = radius - d if(delta <= 0): return v var k = strength * ease(delta / radius, attenuation) var n = (dm_deformable_local_pos - v).normalized() v -= n * k return v ================================================ FILE: addons/deformablemesh/dm_spherical_deformer.gd.uid ================================================ uid://y1tsa0v2pepl ================================================ FILE: addons/deformablemesh/dm_std_deformer.gd ================================================ # Copyright (C) 2023-2024 Claudio Z. (cloudofoz) # # 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 "dm_deformer.gd" #--------------------------------------------------------------------------------------------------- # CONSTANTS #--------------------------------------------------------------------------------------------------- const dm_eps = 0.0001 const dm_ref_axis = [Vector3.MODEL_LEFT, Vector3.MODEL_TOP, Vector3.MODEL_FRONT] #--------------------------------------------------------------------------------------------------- # PUBLIC VARIABLES #--------------------------------------------------------------------------------------------------- @export_category("Standard Deformer") ## Deformer Type @export_enum("Bend:0", "Twist:1", "Taper:2") var type = 0: set(value): type = value dm_update_deformables() ## Main deformation axis. @export_enum("X:0", "Y:1", "Z:2") var main_axis = 1: set(value): main_axis = value if value == second_axis: if is_node_ready(): push_warning("Main axis has to be different from the secondary axis") dm_axis_internal_update = true second_axis = ( value + 1 ) % 3 dm_axis_internal_update = false dm_third_axis = dm_find_third_axis(main_axis, second_axis) dm_update_deformables() # Secondary axis. @export_enum("X:0", "Y:1", "Z:2") var second_axis = 0: set(value): if value != main_axis: second_axis = value else: if is_node_ready(): push_warning("Secondary axis has to be different from the main axis") second_axis = ( value + 1 ) % 3 if !dm_axis_internal_update: dm_third_axis = dm_find_third_axis(main_axis, second_axis) dm_update_deformables() @export_subgroup("Blend - Twist", "bending_") ## Bending / Twisting angle @export_range(-360, +360, 5, "degrees", "or_greater", "or_less") var bending_angle: int = 35: set(value): bending_angle = value dm_bending_angle = deg_to_rad(value) if abs(value) >= dm_eps else dm_eps dm_update_deformables() @export_subgroup("Taper", "taper_") # Taper Factor @export_range(-2.0, 2.0, 0.25, "or_greater", "or_less") var taper_factor: float = 0: set(value): taper_factor = value dm_update_deformables() #--------------------------------------------------------------------------------------------------- # PRIVATE VARIABLES #--------------------------------------------------------------------------------------------------- var dm_axis_internal_update: bool = false var dm_third_axis: float = 2 var dm_bending_angle: float = deg_to_rad(bending_angle) var dm_radius: float var dm_local_pos: Vector3 var dm_length: float #--------------------------------------------------------------------------------------------------- # STATIC METHODS #--------------------------------------------------------------------------------------------------- static func dm_vsub(v1: Vector3, v2: Vector3, i: int) -> float: return abs(v1[i] - v2[i]) static func dm_find_third_axis(a: int, b: int) -> int: for c in range(3): if c != a && c != b: return c return -1 #--------------------------------------------------------------------------------------------------- # VIRTUAL METHODS #--------------------------------------------------------------------------------------------------- func _on_begin_update(d: DeformableMeshInstance3D) -> void: var aabb = d.original_mesh.get_aabb() dm_local_pos = d.to_local(self.global_position) dm_length = aabb.size[main_axis] dm_radius = dm_length / dm_bending_angle func _on_update_vertex(v: Vector3) -> Vector3: match(type): 0: return dm_bend_deform(v) 1: return dm_twist_deform(v) 2: return dm_taper_deform(v) return v #--------------------------------------------------------------------------------------------------- # PRIVATE METHODS #--------------------------------------------------------------------------------------------------- func dm_bend_deform(v: Vector3) -> Vector3: var p = v.rotated(dm_ref_axis[main_axis], self.rotation[main_axis]) var alpha = dm_bending_angle * ( p[main_axis] - dm_local_pos[main_axis] ) / dm_length var r = dm_radius + p[second_axis] var out: Vector3 out[main_axis] = r * sin(alpha) + dm_local_pos[main_axis] out[second_axis] = r * cos(alpha) - dm_radius out[dm_third_axis] = p[dm_third_axis] return out func dm_twist_deform(v: Vector3) -> Vector3: var alpha = dm_bending_angle * ( v[main_axis] - dm_local_pos[main_axis] ) / dm_length var p = v.rotated(dm_ref_axis[main_axis], alpha) return p func dm_taper_deform(v: Vector3) -> Vector3: var h = dm_length - dm_local_pos[main_axis] if v[main_axis] >= dm_local_pos[main_axis] else dm_local_pos[main_axis] var f = taper_factor * (v[main_axis]- dm_local_pos[main_axis]) / h var sec_axis_pos = v[second_axis] + v[second_axis] * f if sign(sec_axis_pos) != sign(v[second_axis]): sec_axis_pos = 0 var trd_axis_pos = v[dm_third_axis] + v[dm_third_axis] * f if sign(trd_axis_pos) != sign(v[dm_third_axis]): trd_axis_pos = 0 v[second_axis] = sec_axis_pos v[dm_third_axis] = trd_axis_pos return v ================================================ FILE: addons/deformablemesh/dm_std_deformer.gd.uid ================================================ uid://c0qauxqxaunnv ================================================ FILE: addons/deformablemesh/dm_surface_data.gd ================================================ # Copyright (C) 2023-2024 Claudio Z. (cloudofoz) # # 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 Object #--------------------------------------------------------------------------------------------------- # CONSTANTS #--------------------------------------------------------------------------------------------------- const Deformer = preload("dm_deformer.gd") const DeformableMeshInstance3D = preload("dm_deformable_mesh.gd") #--------------------------------------------------------------------------------------------------- # PRIVATE VARIABLES #--------------------------------------------------------------------------------------------------- var dm_vpos: PackedVector3Array var dm_uvcoords: PackedVector2Array var dm_indices: PackedInt32Array var dm_st: SurfaceTool = null var dm_has_indices: bool = true var dm_has_uv: bool = true #--------------------------------------------------------------------------------------------------- # PRIVATE METHODS #--------------------------------------------------------------------------------------------------- #--------------------------------------------------------------------------------------------------- # PUBLIC METHODS #--------------------------------------------------------------------------------------------------- ## Uses specified surface of given mesh to pupulate data of SurfaceData func create_from_surface(mesh: Mesh, surface_index: int): assert(mesh && surface_index >= 0) if(!self.dm_st): self.dm_st = SurfaceTool.new() else: self.dm_mesh_data.clear() var dm_arrays = mesh.surface_get_arrays(surface_index) dm_vpos = dm_arrays[Mesh.ARRAY_VERTEX] if dm_arrays[Mesh.ARRAY_INDEX]: dm_indices = dm_arrays[Mesh.ARRAY_INDEX] else: dm_has_indices = false if dm_arrays[Mesh.ARRAY_TEX_UV]: dm_uvcoords = dm_arrays[Mesh.ARRAY_TEX_UV] else: dm_has_uv = false ## Generate a deformed mesh surface from an array of deformers func update_surface(deformers: Array[Deformer], deformable: DeformableMeshInstance3D) -> void: assert(dm_st) var vcount = dm_vpos.size() var icount = dm_indices.size() dm_st.clear() dm_st.begin(Mesh.PRIMITIVE_TRIANGLES) for vidx in range(vcount): var v = dm_vpos[vidx] for deformer in deformers: if(!deformer.visible): continue v = deformer._on_update_vertex(v) if dm_has_uv: dm_st.set_uv(dm_uvcoords[vidx]) dm_st.add_vertex(v) if dm_has_indices: for idx in range(icount): dm_st.add_index(dm_indices[idx]) ## Adds a new surface to a specified mesh with edited data func commit_to_surface(mesh: ArrayMesh): assert(mesh && dm_st) dm_st.generate_normals() mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, dm_st.commit_to_arrays()) ================================================ FILE: addons/deformablemesh/dm_surface_data.gd.uid ================================================ uid://dlrcubdpvkf5o ================================================ FILE: addons/deformablemesh/plugin.cfg ================================================ [plugin] name="DeformableMesh" description="This addon enables real-time deformation of 3D meshes using customizable deformers. This version includes the following deformer nodes: SphericalDeformer, SimpleDeformer (bend, twist, taper) and DragDeformer." author="cloudofoz" version="0.40" script="plugin.gd" ================================================ FILE: addons/deformablemesh/plugin.gd ================================================ # Copyright (C) 2023-2024 Claudio Z. (cloudofoz) # # 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 EditorPlugin func _enter_tree() -> void: add_custom_type("DragDeformer", "MeshInstance3D", preload("dm_drag_deformer.gd"), preload("dm_icon_drag_deformer.svg")) add_custom_type("SphericalDeformer", "MeshInstance3D", preload("dm_spherical_deformer.gd"), preload("dm_icon_spherical_deformer.svg")) add_custom_type("StandardDeformer", "MeshInstance3D", preload("dm_std_deformer.gd"), preload("dm_icon_std_deformer.svg")) add_custom_type("DeformableMeshInstance3D", "MeshInstance3D", preload("dm_deformable_mesh.gd"), preload("dm_icon_deformable_mesh.svg")) func _exit_tree() -> void: remove_custom_type("DragDeformer") remove_custom_type("SphericalDeformer") remove_custom_type("StandardDeformer") remove_custom_type("DeformableMeshInstance3D") ================================================ FILE: addons/deformablemesh/plugin.gd.uid ================================================ uid://coabw0coyhvo6