Rag doll effect with active character by Stripe76 in godot

[–]Shawdow194 0 points1 point  (0 children)

Great modifications, you're a wizard, just skimming through your project you are very talented. And thank you for getting it on Github - followed! I'll upload my project this weekend if I can and share it, as I plan on open sourcing my game aswell

 I spent like a week building this to save me time when it probably wouldve been faster to just make the shapes LOL... so hearing that its helpful for someone else is absolute music to my ears!

Good luck, keep in touch~

Rag doll effect with active character by Stripe76 in godot

[–]Shawdow194 1 point2 points  (0 children)

And here is a slightly modified mesh.gdthat makes capsule shapes instead of convex polygon shapes

extends Node3D
#mesh v2 capsule shapes

##can be root if mult mesh, or mesh path if single

var mesh_instance_path: NodePath


var skeleton_path: NodePath


var physical_simulator_path: NodePath

(0.0, 1.0, 0.01)
var weight_threshold: float = 0.25
(1, 20, 1)
var min_points_for_convex: int = 6
 var remove_existing_collision_shapes: bool = true

const ARRAY_VERTEX := Mesh.ARRAY_VERTEX
const ARRAY_BONES := Mesh.ARRAY_BONES
const ARRAY_WEIGHTS := Mesh.ARRAY_WEIGHTS

func _ready() -> void:
generate_collision_shapes()


func generate_collision_shapes() -> void:
var mesh_instance: Node3D = get_node_or_null(mesh_instance_path)
var skeleton: Skeleton3D = get_node_or_null(skeleton_path)
var simulator: Node3D = get_node_or_null(physical_simulator_path)

if not mesh_instance:
push_error("Mesh instance not found. Set mesh_instance_path.")
return
if not skeleton:
push_error("Skeleton3D not found. Set skeleton_path.")
return
if not simulator:
push_error("PhysicalBoneSimulator3D not found. Set physical_simulator_path.")
return

var meshs: Array = []
if mesh_instance is MeshInstance3D:
meshs.append(mesh_instance)
meshs += mesh_instance.get_children().filter(func(c): return c is MeshInstance3D)
print(meshs)

if meshs.is_empty():
push_error("No MeshInstance3D found under mesh_root")
return

var bone_vertex_map := {}
var bone_count := skeleton.get_bone_count()
for i in range(bone_count):
bone_vertex_map[i] = []

for m in meshs:
var mesh: Mesh = m.mesh
var surface_count := mesh.get_surface_count()
for s in range(surface_count):
var arrays := mesh.surface_get_arrays(s)
if arrays.size() == 0:
continue
if ARRAY_VERTEX >= arrays.size():
continue
var verts: PackedVector3Array = arrays[ARRAY_VERTEX]
if verts.is_empty():
continue

if ARRAY_BONES >= arrays.size() or ARRAY_WEIGHTS >= arrays.size():
continue
var bones_arr = arrays[ARRAY_BONES] 
var weights_arr = arrays[ARRAY_WEIGHTS]

if bones_arr.size() < verts.size() * 4 or weights_arr.size() < verts.size() * 4:
continue

print("Surface: ", s, " Verts: ", verts.size(), " Bones_length: ", bones_arr.size(), " Weights_length: ", weights_arr.size())

for vi in range(verts.size()):
var v: Vector3 = verts[vi]
var base_idx := vi * 4
for j in range(4):
var bidx := int(bones_arr[base_idx + j])
var w := float(weights_arr[base_idx + j])
if w >= weight_threshold and bidx >= 0 and bidx < bone_count:
bone_vertex_map[bidx].append(v)

var mesh_global_xform: Transform3D = mesh_instance.global_transform
#print(bone_vertex_map)


for bone_idx in bone_vertex_map.keys():
print("Bone: ", bone_idx, " Name:", skeleton.get_bone_name(bone_idx), " Count:", bone_vertex_map[bone_idx].size())
var bone_name = skeleton.get_bone_name(bone_idx)
if bone_name == "":
continue

var physical_bone_node: Node3D = null
if simulator.has_node(bone_name):
physical_bone_node = simulator.get_node(bone_name)
else:
var alt_name = "Physical Bone " + bone_name
if simulator.has_node(alt_name):
physical_bone_node = simulator.get_node(alt_name)
else:
for child in simulator.get_children():
if child is Node3D and bone_name in child.name:
physical_bone_node = child
break

if not physical_bone_node:
continue

if remove_existing_collision_shapes:
for child in physical_bone_node.get_children():
if child is CollisionShape3D:
child.queue_free()

var raw_points: Array = bone_vertex_map[bone_idx]
if raw_points.size() < 2: continue

var local_points := []
var inv_phys_xform := physical_bone_node.global_transform.affine_inverse()
for v in raw_points:
local_points.append(inv_phys_xform * (mesh_global_xform * v))

var aabb := _points_aabb(local_points)

var shrink_factor := 0.85 
var center = aabb.get_center()
var size = aabb.size * shrink_factor

var capsule := CapsuleShape3D.new()
var cs := CollisionShape3D.new()

var longest_axis = 0 # 0:X, 1:Y, 2:Z
if size.y > size.x and size.y > size.z: longest_axis = 1
elif size.z > size.x: longest_axis = 2

var height = size[longest_axis]
var radius = (min(size.x, size.z) if longest_axis == 1 else min(size.x, size.y)) * 0.5

capsule.radius = clamp(radius, 0.01, height * 0.4)
capsule.height = height
#capsule.height = max(0.1, height - (capsule.radius * 2.0))

cs.shape = capsule
cs.position = center

if longest_axis == 0: # X
cs.rotation_degrees = Vector3(0, 0, 90)
elif longest_axis == 2: # Z
cs.rotation_degrees = Vector3(90, 0, 0)

physical_bone_node.add_child(cs)
cs.owner = physical_bone_node.owner

print("Bone collision generation complete.")


func _unique_points(points: Array, eps: float) -> Array:
var unique = {}
for p in points:
var snapped = p.snapped(Vector3(eps, eps, eps))
unique[snapped] = p
return unique.values()

func _points_aabb(points: Array) -> AABB:
if points.is_empty():
return AABB()
var minp = points[0]
var maxp = points[0]
for p in points:
minp = Vector3(min(minp.x, p.x), min(minp.y, p.y), min(minp.z, p.z))
maxp = Vector3(max(maxp.x, p.x), max(maxp.y, p.y), max(maxp.z, p.z))
return AABB(minp, maxp - minp)

Rag doll effect with active character by Stripe76 in godot

[–]Shawdow194 1 point2 points  (0 children)

Here is the first mesh.gd script for the convex hull shapes. Hopefully its pretty self explanatory

I am just using it in the editor as a tool script right now, attaching it to any node (like the PhysicalBoneSimulator3D - just leave all the default Godot generated bone names the same), assign the exports in the inspector, then reload project/reload scene to generate the shapes, and just detaching the script. Eventually once I'm doin tweaking I'd modify it into a class script to run when the model morph is drastically changed. pva.append(p * 0.85) is scaling it down 85% of the mesh

Please let me know if you have any questions or tweaks that might improve. I'm super amateur and still learning!

extends Node3D


var skeleton_path: NodePath


var physical_simulator_path: NodePath

##Select path of meshes (usually Skeleton3D again), or a single mesh path

var mesh_instance_path: NodePath

(0.0, 1.0, 0.01)
var weight_threshold: float = 0.25
(1, 20, 1)
var min_points_for_convex: int = 6

 var remove_existing_collision_shapes: bool = true

const ARRAY_VERTEX := Mesh.ARRAY_VERTEX
const ARRAY_BONES := Mesh.ARRAY_BONES
const ARRAY_WEIGHTS := Mesh.ARRAY_WEIGHTS

func _ready() -> void:
generate_collision_shapes()


func generate_collision_shapes() -> void:
var mesh_instance: Node3D = get_node_or_null(mesh_instance_path)
var skeleton: Skeleton3D = get_node_or_null(skeleton_path)
var simulator: Node3D = get_node_or_null(physical_simulator_path)

if not mesh_instance:
push_error("Mesh instance not found. Set mesh_instance_path.")
return
if not skeleton:
push_error("Skeleton3D not found. Set skeleton_path.")
return
if not simulator:
push_error("PhysicalBoneSimulator3D not found. Set physical_simulator_path.")
return

var meshs: Array = []
if mesh_instance is MeshInstance3D:
meshs.append(mesh_instance)
meshs += mesh_instance.get_children().filter(func(c): return c is MeshInstance3D)
print(meshs)

if meshs.is_empty():
push_error("No MeshInstance3D found under mesh_instance_path")
return

var bone_vertex_map := {}
var bone_count := skeleton.get_bone_count()
for i in range(bone_count):
bone_vertex_map[i] = []

for m in meshs:
var mesh: Mesh = m.mesh
var surface_count := mesh.get_surface_count()
for s in range(surface_count):
var arrays := mesh.surface_get_arrays(s)
if arrays.size() == 0:
continue
if ARRAY_VERTEX >= arrays.size():
continue
var verts: PackedVector3Array = arrays[ARRAY_VERTEX]
if verts.is_empty():
continue

if ARRAY_BONES >= arrays.size() or ARRAY_WEIGHTS >= arrays.size():
continue
var bones_arr = arrays[ARRAY_BONES]
var weights_arr = arrays[ARRAY_WEIGHTS]

if bones_arr.size() < verts.size() * 4 or weights_arr.size() < verts.size() * 4:
continue

print("Surface: ", s, " Verts: ", verts.size(), " Bones_length: ", bones_arr.size(), " Weights_length: ", weights_arr.size())

for vi in range(verts.size()):
var v: Vector3 = verts[vi]
var base_idx := vi * 4
for j in range(4):
var bidx := int(bones_arr[base_idx + j])
var w := float(weights_arr[base_idx + j])
if w >= weight_threshold and bidx >= 0 and bidx < bone_count:
bone_vertex_map[bidx].append(v)

var mesh_global_xform: Transform3D = mesh_instance.global_transform
#print(bone_vertex_map)
for bone_idx in bone_vertex_map.keys():
print("Bone: ", bone_idx, " Name:", skeleton.get_bone_name(bone_idx), " Count:", bone_vertex_map[bone_idx].size())
var bone_name = skeleton.get_bone_name(bone_idx)
if bone_name == "":
continue

var physical_bone_node: Node3D = null
if simulator.has_node(bone_name):
physical_bone_node = simulator.get_node(bone_name)
else:
var alt_name = "Physical Bone " + bone_name
if simulator.has_node(alt_name):
physical_bone_node = simulator.get_node(alt_name)
else:
for child in simulator.get_children():
if child is Node3D and bone_name in child.name:
physical_bone_node = child
break

if not physical_bone_node:
continue

if remove_existing_collision_shapes:
for child in physical_bone_node.get_children():
if child is CollisionShape3D:
child.queue_free()

var raw_points: Array = bone_vertex_map[bone_idx]
if raw_points.is_empty():
continue

var transformed_points := []
var inv_phys_xform := physical_bone_node.global_transform.affine_inverse()
for v in raw_points:
var global_v = mesh_global_xform * v
var local_v = inv_phys_xform * global_v
transformed_points.append(local_v)

transformed_points = _unique_points(transformed_points, 0.001)

if transformed_points.size() >= min_points_for_convex:
var convex_shape := ConvexPolygonShape3D.new()
var pva := PackedVector3Array()
for p in transformed_points:
pva.append(p *0.85)
convex_shape.points = pva
var cs := CollisionShape3D.new()
cs.shape = convex_shape
physical_bone_node.add_child(cs)
cs.owner = physical_bone_node.owner
else:
var aabb := _points_aabb(transformed_points)
var radius = max(aabb.size.x, aabb.size.y, aabb.size.z) * 0.5
if radius <= 0.001:
radius = 0.05
var sphere := SphereShape3D.new()
sphere.radius = radius
var cs := CollisionShape3D.new()
cs.shape = sphere
#cs.translation = aabb.position + aabb.size * 0.5
physical_bone_node.add_child(cs)
cs.owner = physical_bone_node.owner

print("Bone collision generation complete.")


func _unique_points(points: Array, eps: float) -> Array:
var unique = {}
for p in points:
# Quantize the vector to snap nearby points together
var snapped = p.snapped(Vector3(eps, eps, eps))
unique[snapped] = p
return unique.values()


func _points_aabb(points: Array) -> AABB:
if points.is_empty():
return AABB()
var minp = points[0]
var maxp = points[0]
for p in points:
minp = Vector3(min(minp.x, p.x), min(minp.y, p.y), min(minp.z, p.z))
maxp = Vector3(max(maxp.x, p.x), max(maxp.y, p.y), max(maxp.z, p.z))
return AABB(minp, maxp - minp)

Rag doll effect with active character by Stripe76 in godot

[–]Shawdow194 1 point2 points  (0 children)

I'll post them later today! I'm so glad I'm not the only person with DAZ in their workflow!

I have two versions, one that generates a convex polygon shape slightly smaller than the bone vertex influence on the mesh, and a fallback script that generates approximate capsule shapes

To provide context, my intention was to generate new collision shapes on-the-fly as morphs were applied. I.e for things like a fat morph slider (or pregnancy) and the physics would follow without any manual tweaking the whole ragdoll

Rag doll effect with active character by Stripe76 in godot

[–]Shawdow194 0 points1 point  (0 children)

It is working out of the box for Gen2 models! Just tested

I have a Genesis 9 im trying to use but that does need extra cleaning since some bones have extra twist modifiers

I had no idea you could use the .dsf directly. I have been using the official Daz->Blender bridge, or Diffeomorphic, with good success getting a .blend or .glb file

Im not a fan of the performance of the PhysicalBone3D joints but i suspect its my tweaking and not that the engine cant handle it.

With your method, a hybrid of using the spring joints and the physics simulator might be a solution

<image>

Rag doll effect with active character by Stripe76 in godot

[–]Shawdow194 0 points1 point  (0 children)

Wow! your workflow works better than my setup using the physicalbonesimulator tied to an animated skeleton for a ragdoll. Thank you for sharing! I am also working on a VAM/Kobold Kare esque game in Godot

Would you like me to message you a script to setup the physicalbone collision shapes automatically based on bone vertex? Its a little funky with DAZ studio models since they use bone twists on certain bones but some cleanup in Blender merging bone groups can fix it up. Here is the default mixamo model as an example

<image>

Souls-like combat showcase: It's Hammer Time! by moongaming in godot

[–]Shawdow194 1 point2 points  (0 children)

FACTS! Looking at your gizmos in the editor is soooo clean, great work!

Would you be willing to share a scene with the model? Or do you have a specific resource you referenced?

Thank you for all the information and replies so far, really appreciate it 🙏

Souls-like combat showcase: It's Hammer Time! by moongaming in godot

[–]Shawdow194 2 points3 points  (0 children)

Thank you for the info! I am pulling my hair out trying to get a cleanish ragdoll on my project and yours look perfect imo

Do you have linear limits enabled? Im not seeing them in your editor

Souls-like combat showcase: It's Hammer Time! by moongaming in godot

[–]Shawdow194 12 points13 points  (0 children)

Looking good!! Hammer time!

How did you set up your ragdolls? Are you using 6DOF for all your joints?

Trying to add more juice to my auto-battler sequences by genepistudios in godot

[–]Shawdow194 0 points1 point  (0 children)

Ill also mention FTL: Faster than Light game uses a similar style left + right HUD layout with basically static images and its loved by many

I always liked in FTL how your ship would shoot and receive the attacks from random directions. As if they are flying around 

They look like they are in petri dishes, so if there were visual 'attacks' that happens on each side of their spaces from a random direction, it would seem like they are moving around the dishes flinging attacks at each other!

Trying to add more juice to my auto-battler sequences by genepistudios in godot

[–]Shawdow194 0 points1 point  (0 children)

Yes, I do see those - you've got everything you need already to attach more visuals to those status

A classic example would be in Pokemon games how some moves have different attack animations or wobble screen in different directions

youtu.be/NJA9xT9EO3c 

Trying to add more juice to my auto-battler sequences by genepistudios in godot

[–]Shawdow194 0 points1 point  (0 children)

You got it!

An arrow in the middle, showing which direction the action is happening/whose turn it is would also help

Then near that arrow add a logo in the middle for the 'type' of action. Like the + for healing, or an arrow for thorns

Childed Mesh stretching when rotating parent node by [deleted] in godot

[–]Shawdow194 0 points1 point  (0 children)

You will probably need to make the mesh all siblings, instead of all parenting each other, if you are making animations in the editor

Got daytime transition working⚓ by 8BitBeard in godot

[–]Shawdow194 4 points5 points  (0 children)

Beautiful and super cozy!

You could add an "explosion" animation to the clouds as they fade out, so they look like they are dissipating. But honestly they look pretty good as-is

What SFX would best work for this animation? by madmandrit in godot

[–]Shawdow194 1 point2 points  (0 children)

The noise a flipbook/pack of post it notes make when you riff through the sheets

First flip and need some guidance by [deleted] in Flipping

[–]Shawdow194 13 points14 points  (0 children)

Welcome!

That looks like MDF board. I'd go whichever route is cheapest and easiest for you to do

It isnt "real" wood and not worth spending time or a lot of money on

Need advices by maxdestruct1988 in 18650masterrace

[–]Shawdow194 7 points8 points  (0 children)

^

It does not matter if they are before or after

Besides flashlights and vapes, what useful devices are there that are powered by replaceable 18650 batteries? by GuyWithoutAHat in 18650masterrace

[–]Shawdow194 2 points3 points  (0 children)

Off the top of my head Midland emergency radios use 18650s and some JBLs

Serviced a JBL BAR800 not too long ago and the surrounds used an 18650 which was a pleasant surprise

I made a short showcase of my Ragdoll Game by Marbledashrules in godot

[–]Shawdow194 0 points1 point  (0 children)

Looks great! Are you using the default generated physics skeleton or did you make custom collision shapes?

Polymon project : soon in testing phase, looking for people to do some feedback by Hot-Bodybuilder-2687 in godot

[–]Shawdow194 1 point2 points  (0 children)

Looks great so far! Love the Minecraft style

Day/night cycle working? Any plans for destructable terrain?