(24-06-2022, 01:43 AM)facaelectrica Wrote: Not sure how to phrase it, some images to explain what I'm running into.
I've created a relatively simple shape:
Created a blueprint:
But no matter what I do, even copying the values you've added to your objects when I run the game, it just falls flat (next to your example ball):
Why doesn't it stay more solid? What values should I be looking at?
How many substeps/iterations are you using? These largely determine the maximum stiffness the solver is able to reach while enforcing constraints. I'd start by cranking up the amount of substeps, that will make the softbody more solid.
The manual contains a very in-depth explanation of how the engine works internally and how substeps/iterations affect the outcome:
http://obi.virtualmethodstudio.com/manua...gence.html
Substeps can be found in the ObiFixedUpdater component, and shape matching iterations can be found in the ObiSolver's "Constraint settings" foldout menu.
(24-06-2022, 01:43 AM)facaelectrica Wrote: 1) I don't quite get the advantages behind doing smaller or bigger particles besides having a more precise shape and what I assume are more expensive calculations.
Basically the same as texture resolution, mesh resolution, etc:
higher resolution = larger memory cost = larger runtime cost = more detail.
lower resolution = less memory cost = less runtime cost = less detail.
(24-06-2022, 01:43 AM)facaelectrica Wrote: Do they affect the properties of the object? I read this paragraph but there aren't examples or anything precise about how it translates to practical examples:
Higher resolution leads to more particles and clusters being generated, making it more difficult to keep their shape. So given the same solver settings, high-resolution softbodies will be softer than low-resolution ones.
Yes, they do. This is commonplace in
all existing physics engines: the more objects you held together using constraints, the more work needs to be done to keep it all stiff. For instance if you create a stack of boxes in Unity, the more boxes you add to the stack the "springier" it will become.
The reason why lies at the core of how physics engines work: when multiple objects (in Obi's case, particles, but could also be cubes, capsules, whatever) are constrained/attached to each other in any way, their positions/orientations must be corrected so that they meet these constraints. Corrections are
propagated from each body to the surrounding ones, over multiple
iterations. So if you have few objects (few particles), corrections can be propagated very far in few iterations as they only have to traverse a few objects to get from object A to a different object B. Because of this, all constraints can be 100% enforced and look rigid enough. If you have many objects, corrections need more iterations to reach from A to B because they must travel trough more objects. So you would need more iterations for constraint corrections to be propagated trough all objects and things to look rigid.
As a result,
given the same solver settings, high-resolution softbodies will be softer than low-resolution ones (because corrections need to travel trough more particles). Hope this made sense.
(24-06-2022, 01:43 AM)facaelectrica Wrote: 2) What is the advantage between voxels or vertices when creating the blueprint? In my situation, I'm trying to get an object with an inner pocket to work as a softbody, so should I use vertices? I can still get some precision with voxels and increased resolution, is it preferable to go that way?
They're just different methods.
Voxel sampling ensures symmetric, more regular grid-like sampling. Does not guarantee particles to be placed exactly at vertex positions.
Vertex sampling guarantees particles will follow mesh topology, and be placed at vertex positions. Does not ensure symmetry though.
Btw, there's a known bug in vertex surface sampling. Replace the Initialize() method in Obi/Scripts/Softbody/Blueprints/ObiSoftbodySurfaceBlueprint.cs with this:
Code:
protected override IEnumerator Initialize()
{
if (inputMesh == null || !inputMesh.isReadable)
{
Debug.LogError("The input mesh is null, or not readable.");
yield break;
}
ClearParticleGroups();
// Prepare candidate particle arrays:
List<Vector3> particlePositions = new List<Vector3>();
List<Vector3> particleNormals = new List<Vector3>();
// Prepare constraint arrays:
particleIndices = new List<int>();
constraintIndices = new List<int>();
// Transform mesh data:
blueprintTransform = Matrix4x4.TRS(Vector3.zero, rotation, scale);
Vector3[] vertices = inputMesh.vertices;
Vector3[] normals = inputMesh.normals;
int[] tris = inputMesh.triangles;
Bounds transformedBounds = new Bounds();
for (int i = 0; i < vertices.Length; ++i)
{
vertices[i] = blueprintTransform.MultiplyPoint3x4(vertices[i]);
transformedBounds.Encapsulate(vertices[i]);
}
for (int i = 0; i < normals.Length; ++i)
normals[i] = Vector3.Normalize(blueprintTransform.MultiplyVector(normals[i]));
// initialize arrays:
particleType = new List<ParticleType>();
//voxelize for cluster placement:
var voxelize = VoxelizeForShapeAnalysis(transformedBounds.size);
while (voxelize.MoveNext()) yield return voxelize.Current;
//voxelize for surface:
voxelize = VoxelizeForSurfaceSampling(transformedBounds.size);
while (voxelize.MoveNext()) yield return voxelize.Current;
if (surfaceSamplingMode == SurfaceSamplingMode.Voxels)
{
var surface = VoxelSampling(surfaceVoxelizer, particlePositions, particleNormals, MeshVoxelizer.Voxel.Boundary, ParticleType.Surface);
while (surface.MoveNext()) yield return surface.Current;
IEnumerator ip = InsertParticlesIntoVoxels(surfaceVoxelizer, particlePositions);
while (ip.MoveNext()) yield return ip.Current;
IEnumerator vc = CreateClustersFromVoxels(surfaceVoxelizer, particlePositions, VoxelConnectivity.Faces | VoxelConnectivity.Edges, new List<ParticleType>(){ParticleType.Surface});
while (vc.MoveNext()) yield return vc.Current;
for (int i = 0; i < particlePositions.Count; ++i)
particlePositions[i] = ProjectOnMesh(particlePositions[i], vertices, tris);
}
else if (surfaceSamplingMode == SurfaceSamplingMode.Vertices)
{
var sv = VertexSampling(vertices, particlePositions);
while (sv.MoveNext()) yield return sv.Current;
// map vertices to particles:
var mp = MapVerticesToParticles(vertices, particlePositions);
while (mp.MoveNext()) yield return mp.Current;
var ss = SurfaceMeshShapeMatchingConstraints(particlePositions,tris);
while (ss.MoveNext()) yield return ss.Current;
}
if (volumeSamplingMode == VolumeSamplingMode.Voxels)
{
// sample the volume of the mesh:
voxelize = VoxelizeForVolumeSampling(transformedBounds.size);
while (voxelize.MoveNext()) yield return voxelize.Current;
var volume = VoxelSampling(volumeVoxelizer, particlePositions, particleNormals, MeshVoxelizer.Voxel.Inside, ParticleType.Volume);
while (volume.MoveNext()) yield return volume.Current;
var ip = InsertParticlesIntoVoxels(volumeVoxelizer, particlePositions);
while (ip.MoveNext()) yield return ip.Current;
var vc = CreateClustersFromVoxels(volumeVoxelizer, particlePositions, VoxelConnectivity.Faces, new List<ParticleType>() { ParticleType.Volume });
while (vc.MoveNext()) yield return vc.Current;
vc = CreateClustersFromVoxels(volumeVoxelizer, particlePositions, VoxelConnectivity.All, new List<ParticleType>() { ParticleType.Volume | ParticleType.Surface });
while (vc.MoveNext()) yield return vc.Current;
// map vertices to particles:
var mp = MapVerticesToParticles(vertices, particlePositions);
while (mp.MoveNext()) yield return mp.Current;
}
// sample skeleton:
var sk = SkeletonSampling(transformedBounds.size, particlePositions, particleNormals);
while (sk.MoveNext()) yield return sk.Current;
// create skeleton clusters:
if (skeleton != null)
{
var sc = CreateClustersFromSkeleton(particlePositions);
while (sc.MoveNext()) yield return sc.Current;
}
// generate particles:
var generate = GenerateParticles(normals, particlePositions, particleNormals);
while (generate.MoveNext()) yield return generate.Current;
// generate shape matching constraints:
IEnumerator bc = CreateShapeMatchingConstraints(particlePositions);
while (bc.MoveNext()) yield return bc.Current;
generatedMesh = inputMesh;
}
(24-06-2022, 01:43 AM)facaelectrica Wrote: 3) From what I understood, once the particles are generated, they will get connected (blue lines) between each other. Is there a way to control that level of connection?
No, that's fully automatic since it depends on particle distribution/surface analysis.
(24-06-2022, 01:43 AM)facaelectrica Wrote: The ObiSoftBodySkinner has a "Skinning Max Distance", is that what it does? And if so, how can I visualize it?
ObiSoftbodySkinner skins a mesh to the softbody. By the time you skin the mesh, the softbody has already been created so skinning it has no impact on shape matching constraints (the blue lines connecting particles). It simply "glues" vertices to particles.
The "skinning max distance" is the maximum distance between a vertex and a particle for the vertex to be skinned to the particle. This is run-of-the-mill
linear blend skinning, works and behaves the same way as character skinning in any 3D animation program.
(24-06-2022, 01:43 AM)facaelectrica Wrote: 4)Is it possible to visualize how the blueprint is being skinned in relation to the object(s) in the viewport?
Not really, since Unity's SkinnedMeshRenderer does not have any built-in way to visualize skin weights.