Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help  Scripting rod forces
#1
Hi, I have got several quesions about scripting rods.
  1. Is there valid way to calculate rod tension? For example I need to know if rod is pressed against the wall and it's being compressed. I tried to compare compressed distances, but with chain constraint it does not work.
  2. Is there way to calculate pressure against softbody or get it from softbody directly?
  3. Is it possible to make different parts of rod have different elasticity? There is rotational mass, but it's hard to balance and there can't be huge difference in mass (1:10). Sometimes even if difference is not huge, the rod becomes wiggly and can't stop.
  4. Is there way to damp rod without touching solver main parameter (damping)? I guess I could write script just for rod to reduce velocities every step, but it also affects other forces.
  5. What is difference between changing particle velocity and externalForces?
  6. Is it possible to change rod default (initial) shape during runtime? There are various properties like startPositions, endPositions, restPositions, any many other, but some of them has no effect and others are not what would be expected. I would expect particle delta to show something, but I think they are reset during simulation.
  7. Is there way to "pin" my burst job to main job queue? I wish I could just add my job handle to be executed with all other stuff without need to complete job instantly.
Reply
#2
(08-08-2025, 06:33 PM)Qriva0 Wrote: Hi, I have got several quesions about scripting rods.
  1. Is there valid way to calculate rod tension? For example I need to know if rod is pressed against the wall and it's being compressed. I tried to compare compressed distances, but with chain constraint it does not work.
  2. Is there way to calculate pressure against softbody or get it from softbody directly?
  3. Is it possible to make different parts of rod have different elasticity? There is rotational mass, but it's hard to balance and there can't be huge difference in mass (1:10). Sometimes even if difference is not huge, the rod becomes wiggly and can't stop.
  4. Is there way to damp rod without touching solver main parameter (damping)? I guess I could write script just for rod to reduce velocities every step, but it also affects other forces.
  5. What is difference between changing particle velocity and externalForces?
  6. Is it possible to change rod default (initial) shape during runtime? There are various properties like startPositions, endPositions, restPositions, any many other, but some of them has no effect and others are not what would be expected. I would expect particle delta to show something, but I think they are reset during simulation.
  7. Is there way to "pin" my burst job to main job queue? I wish I could just add my job handle to be executed with all other stuff without need to complete job instantly.
1. You can refer to the tear method of the rope. It shows how to read the "force" value of the rope/rod. But please note the value you read is from Obi engine, which is different from unity physics engine. As an example, if a rod is connected a cube with a unity joint, the force read from joint is most likely different from the value you read from Obi constraints. The difference depends on  the amount of constraints , the mass ratios between objects , and the frequency ratio 
2. I think yes but as I have no experience in softbody so leave this question to Obi dev.
3. Adjust rotational mass should be the way to go. Why it's hard to balance? There probably be something else wrongly configured when you see weird behavior.
4. No but you can reduce particle's velocity by yourself to get a customized damper effect.
5. Same as unity addForce (Velocity v.s Force)
6. Rest shape is defined by blueprint I don't think you can do that. If you mean change the shape as soon as rod/rope is initialized I think it's doable. Or you may create new blueprint every time do do that.
7. What's your "burst job" actually do? The solver has some event handlers in its lifecycle that you may hook so maybe you don't need that?
Reply
#3
Qriva0 Wrote:Hi, I have got several quesions about scripting rods.
Is there valid way to calculate rod tension? For example I need to know if rod is pressed against the wall and it's being compressed. I tried to compare compressed distances, but with chain constraint it does not work.

Hi!

Most constraint types have a "lambda" value associated to each of them. These are lagrange multipliers, outputted by the simulation, that can be converted to forces dividing by the timestep squared. These lambda values are stored in an array, in each constraint batch. In ObiRope.cs you have an example of this in the ApplyTear() method: it iterates trough all lagrange multipliers for distance constraints, then breaks those constraints whose force exceeds a threshold.

Bend/Twist and Stretch/Shear constraints used in rods also have lambda values, that you can use to calculate deformation. Chain constraints however don't, since they're not really physically based - more of a geometrical constraint.

Qriva0 Wrote:Is there a way to calculate pressure against softbody or get it from softbody directly?

You can calculate softbody deformation gradient/magnitude using the current linear transform matrix for each shape matching constraint. Using its Frobenius norm usually works well if you just want to measure amount of deformation. See the "DeformationGradient" softbody sample scene as well as the "DeformationToColors.cs" sample script.

Note however that pressure is force per area unit, mostly decoupled from deformation (pressure may or may not lead to deformation). That's a lot more complex to calculate accurately and would require processing individual contacts.

Qriva0 Wrote:Is it possible to make different parts of rod have different elasticity? There is rotational mass, but it's hard to balance and there can't be huge difference in mass (1:10). Sometimes even if difference is not huge, the rod becomes wiggly and can't stop.

Not currently. We want to expose bend/twist/shear compliances in the path editor, as a per-control point property just like mass and rotational mass. This should arrive in Obi 7.2.

Qriva0 Wrote:Is there way to damp rod without touching solver main parameter (damping)? I guess I could write script just for rod to reduce velocities every step, but it also affects other forces.


Not currently. You can do this yourself for each separate actor, the formula used for an individual particle is simply: velocity *= pow(saturate(1-damping), deltaTime).

Note that damping affects particle velocities after all constraints have been applied, at the end of each simulation step. If you are hoping for a per-constraint damping parameter, the situation is complicated: XPBD has built-in damping, but only works for compliant constraints. Infinitely stiff constraints (compliance = 0) apply the maximum positional correction possible, and this means the simulation will gain energy if the correction is too large.

(08-08-2025, 06:33 PM)Qriva0 Wrote: What is difference between changing particle velocity and externalForces?

Force and Velocity are not the same thing. Force results in a mass-dependent acceleration (F=ma), so the same force results in smaller acceleration (less change in velocity) when applied to a heavier object.

(08-08-2025, 06:33 PM)Qriva0 Wrote: Is it possible to change rod default (initial) shape during runtime?


Yes, by taking it out of the solver, modifying its blueprint and then bringing it back in. But this of course means re-loading the rod. What's your use case for this?


(08-08-2025, 06:33 PM)Qriva0 Wrote: There are various properties like startPositions, endPositions, restPositions, any many other, but some of them has no effect and others are not what would be expected.

"startPositions" and "endPositions" are particle positions at the start and end of the whole simulation step, respectively.
"prevPositions" and "positions" are particle positions at the start and end of the current substep, respectively.
"restPositions" are the positions at rest (used to determine whether particles self-intersect at rest, and deactivate self collisions for those that do). "renderablePositions" are the values actually rendered to the screen (which may be interpolated or extrapolated from start/end positions depending on your solver interpolation settings).

Usually, you'd work with positions and renderable positions. These are conceptually equivalent to rigidbody.positon and transform.position in Unity.

(08-08-2025, 06:33 PM)Qriva0 Wrote: I would expect particle delta to show something, but I think they are reset during simulation.


position/orientation deltas store the change in position/orientation applied during each constraint projection. These are reset to zero after each constraint type has been projected (so that the next constraint type has a clean slate), they aren't useful unless you're writing custom constraints.



(08-08-2025, 06:33 PM)Qriva0 Wrote: Is there way to "pin" my burst job to main job queue? I wish I could just add my job handle to be executed with all other stuff without need to complete job instantly.

This is not something that's exposed, but you could modify ObiSolver's source code to give yourself access to the simulation job handle. Note that the lifetime of the job is rather complex and varies depending on the synchronization method used (see "Bolting Obi onto Unity's timestepping": https://obi.virtualmethodstudio.com/manu...eloop.html). It's very easy to break things if you're not intimately familiar with how and when physics should be updated.

kind regards,
Reply
#4
Thank you very much! This is a lot of useful informations!

(11-08-2025, 08:31 AM)josemendez Wrote: Yes, by taking it out of the solver, modifying its blueprint and then bringing it back in. But this of course means re-loading the rod. What's your use case for this?

I would like to kind of bend the rod, or change shape and make it permament (for runtime duration). That would be similar to deformation related stuff and because it's possible via forces, I thought it could be possible to change during runtime. Also another case is that I might want to turn rod into bendable tool, for example imagine tool used to explore pipes with camera on the tip, it might be possible to steer that cable, bend left/right to change direction, so in reality it changes it's default shape, however it still bends properly from that rest shape.

Is there way to access rod default shape?

(11-08-2025, 08:31 AM)josemendez Wrote: This is not something that's exposed, but you could modify ObiSolver's source code to give yourself access to the simulation job handle. Note that the lifetime of the job is rather complex and varies depending on the synchronization method used (see "Bolting Obi onto Unity's timestepping": https://obi.virtualmethodstudio.com/manu...eloop.html). It's very easy to break things if you're not intimately familiar with how and when physics should be updated.

Yeah, that is the reason why it would be nice if there was simple functionality to "attach" some job/jobs to chain, some method or event where job can be added and then that job would be for sure completed in obi OnSimulationEnd event. That would ensure that whatever runs it's executed in the same pipeline with Obi.
I am aware I can most likely hack the whole thing, but it's better to not touch the source code, so it would be nice thing to have.

In any case thank you for help, I will check and try things you mentioned Sonrisa
Reply
#5
(11-08-2025, 11:14 AM)Qriva0 Wrote: Thank you very much! This is a lot of useful informations!

I would like to kind of bend the rod, or change shape and make it permament (for runtime duration). That would be similar to deformation related stuff and because it's possible via forces, I thought it could be possible to change during runtime. Also another case is that I might want to turn rod into bendable tool, for example imagine tool used to explore pipes with camera on the tip, it might be possible to steer that cable, bend left/right to change direction, so in reality it changes it's default shape, however it still bends properly from that rest shape.

That cannot be done by altering the blueprint, since changing the blueprint requires re-creating the rope from scratch and reloading the entire thing anew - current velocities, positions, etc will be lost. Visually, it would look like the rod loses all momentum and "pops" into its new shape.

What you want to do is called plastic deformation. Elastic materials return to their previous pose after external forces cease to be applied, elastoplastic materials however retain part of the deformation induced by stress (could require little or a lot of stress, depending on the material's plastic yield), even after external forces are gone. Some constraints in Obi support plastic deformation (eg. bend/twist,  shape matching constraints). If you just want the rod to be able to bend and keep its shape, adjusting plastic yield and creep in the rod's bend/twist constraints will do this.

Note that stretch/shear constraints do not support plasticity. To fully support this, the implementation of stretch/shear constraints must be heavily extended. Will add this to our roadmap as a feature request.

(11-08-2025, 11:14 AM)Qriva0 Wrote: Yeah, that is the reason why it would be nice if there was simple functionality to "attach" some job/jobs to chain, some method or event where job can be added and then that job would be for sure completed in obi OnSimulationEnd event. That would ensure that whatever runs it's executed in the same pipeline with Obi.
I am aware I can most likely hack the whole thing, but it's better to not touch the source code, so it would be nice thing to have.

There's not a single point in the code where the main simulation job ("simulationHandle", in ObiSolver.cs) is completed(). Will see if it's possible to expose an event that gets called before completing the job, passing the handle to the user so that you can insert custom jobs at the end of the simulation pipeline without the need for an intermediate Complete().

kind regards,
Reply
#6
(11-08-2025, 11:26 AM)josemendez Wrote: What you want to do is called plastic deformation. Elastic materials return to their previous pose after external forces cease to be applied, elastoplastic materials however retain part of the deformation induced by stress (could require little or a lot of stress, depending on the material's plastic yield), even after external forces are gone. Some constraints in Obi support plastic deformation (eg. bend/twist,  shape matching constraints). If you just want the rod to be able to bend and keep its shape, adjusting plastic yield and creep in the rod's bend/twist constraints will do this.

Well, I want it to bend, but from some defined shape and control the exact shape of the rod.
I think good example is medical endoscope - doctor can bend the tip with control handle, so default "rest" shape becomes different, however the whole thing is still a bit elastic. As you said, ignoring the simulation problem when blueprint is changed, that is what I want, to change initial shape, but not just swap, but "define" new or rather modify every frame.
Actually how rod keeps it's shape? Does it try to keep relative orientations always the same?
Reply
#7
(11-08-2025, 12:25 PM)Qriva0 Wrote: Well, I want it to bend, but from some defined shape and control the exact shape of the rod.
I think good example is medical endoscope - doctor can bend the tip with control handle, so default "rest" shape becomes different, however the whole thing is still a bit elastic.

Ok, so that's not plasticity after all - there's no external forces involved. You can manipulate the rest orientation of each bend/twist constraint yourself (by writing to their restDarbouxVectors array), or you can use a ObiBone which does exactly what you're looking for: it creates and drives a rod-like structure using a skeleton. This way you can just rotate the skeleton joints to the shape you want the rod to have, and it will try to follow it while performing the simulation.

(11-08-2025, 12:25 PM)Qriva0 Wrote: Actually how rod keeps it's shape? Does it try to keep relative orientations always the same?

For each constraint, it measures the current darboux vector and tries to keep it the same as the rest darboux vector. If you want the nitty-gritty details, it implements this article.

kind regards,
Reply
#8
(11-08-2025, 12:48 PM)josemendez Wrote: Ok, so that's not plasticity after all - there's no external forces involved. You can manipulate the rest orientation of each bend/twist constraint yourself (by writing to their restDarbouxVectors array), or you can use a ObiBone which does exactly what you're looking for: it creates and drives a rod-like structure using a skeleton. This way you can just rotate the skeleton joints to the shape you want the rod to have, and it will try to follow it while performing the simulation.


For each constraint, it measures the current darboux vector and tries to keep it the same as the rest darboux vector. If you want the nitty-gritty details, it implements this article.

kind regards,

Thanks for the hint! I am going to try it.
Reply
#9
Hi again, I have got several questions to things you mentioned before.

(11-08-2025, 08:31 AM)josemendez Wrote: Most constraint types have a "lambda" value associated to each of them. These are lagrange multipliers, outputted by the simulation, that can be converted to forces dividing by the timestep squared. These lambda values are stored in an array, in each constraint batch. In ObiRope.cs you have an example of this in the ApplyTear() method: it iterates trough all lagrange multipliers for distance constraints, then breaks those constraints whose force exceeds a threshold.
Bend/Twist and Stretch/Shear constraints used in rods also have lambda values, that you can use to calculate deformation. Chain constraints however don't, since they're not really physically based - more of a geometrical constraint.

You reffered to rope tearing as example, but there are things that confuse me.

Code:
var dc = GetConstraintsByType(Oni.ConstraintType.Distance) as ObiConstraints<ObiDistanceConstraintsBatch>;
var sc = this.solver.GetConstraintsByType(Oni.ConstraintType.Distance) as ObiConstraints<ObiDistanceConstraintsBatch>;

if (dc != null && sc != null)
{
    // iterate up to the amount of entries in solverBatchOffsets, insteaf of dc.batchCount. This ensures
    // the batches we access have been added to the solver, as solver.UpdateConstraints() could have not been called yet on a newly added actor.
    for (int j = 0; j < solverBatchOffsets[(int)Oni.ConstraintType.Distance].Count; ++j)
    {
        var batch = dc.GetBatch(j) as ObiDistanceConstraintsBatch;
        var solverBatch = sc.batches[j] as ObiDistanceConstraintsBatch;

        for (int i = 0; i < batch.activeConstraintCount; i++)
        {
            int elementIndex = j + 2 * i;

            // divide lambda by squared delta time to get force in newtons:
            int offset = solverBatchOffsets[(int)Oni.ConstraintType.Distance][j];
            float force = solverBatch.lambdas[offset + i] / sqrTime;

            elements[elementIndex].constraintForce = force;

            if (-force > tearResistanceMultiplier)
            {
                tornElements.Add(elements[elementIndex]);
            }
        }
    }
}

First of all why constraint is accessed twice in two ways? Are those constraint batches different?
Second, why first batch is accesed differently? Is there difference between GetBatch(j) and batches[j]?
Third, how to read lambda? In case of strech constraint lambda has 3 components, I guess one for each "axis", so do I need to multiply by 3?
float force = solverBatch.lambdas[offset + i * 3] / sqrTime;
Forth - why substep time is used here? And this leads to next point:

(11-08-2025, 08:31 AM)josemendez Wrote: "startPositions" and "endPositions" are particle positions at the start and end of the whole simulation step, respectively.
"prevPositions" and "positions" are particle positions at the start and end of the current substep, respectively.

When to use substepTime and simDelta? If I understand correctly start and end position are positions for single simulation step.
There are also substeps and those are run as group of jobs - there is no way to do anything between substeps, so is prevPositions and positions just data from the last substep? If that is the case, then in the code above is substepDelta used because we have access only to the last one? I think I understand it wrong, because lambda is accumulated, so can you clarify how that actually work?

I found example here about adding forces (Using Burst/Jobs to process particles) https://obi.virtualmethodstudio.com/manu...icles.html
It does not work, it uses undeclared variable stepTime, but I guess it would be substepTime. The problem is that if we have 5 substeps, then does that mean that we are applying only 1/5 of forces? For example I wanted to create script that uses onSimulationStart to move particles, velocity or position delta is 5 per second, so in such a case should I use timeToSimulate or substepTime as delta? 
In short where to use different delta times, including lambda example above from tearing?
Reply
#10
(Yesterday, 11:30 AM)Qriva0 Wrote: First of all why constraint is accessed twice in two ways? Are those constraint batches different?
Second, why first batch is accesed differently? Is there difference between GetBatch(j) and batches[j]?

Yes, there's solver and actor constraint batches. These are completely different. When you create an actor, its constraints are grouped into batches. When you add an actor to a solver (so it becomes part of the simulation), a copy of each constraint batch in the actor is created and merged with *all other existing constraints in the solver*, including those belonging to other actors to maximize parallelism.

As a result:
- Actor batches contain constraint data at rest for a specific actor.
- Solver batches contain current constraint data for all constraints in the simulation, at any point during simulation.
- The number of solver batches and the number of constraints in each solver batch is different than its actor counterparts, and are accessed differently.

See: https://obi.virtualmethodstudio.com/manu...aints.html

Quote:Very much like particle data, constraint data isn't laid out in the solver the same way it is in an actor. When an actor gets added to a solver, all its constraints are merged with the existing constraints in the solver to maximize performance. This means each constraint batch in the solver contains constraints belonging to different actors, so if we want to access data for a certain actor in particular, we need to know the offset of that actor's constraints inside the solver batch.

As the manual mentions, this mirrors how particle data is laid out: actor.positions and solver.positions are not the same thing, do not have the same amount of entries, and are not accessed the same way.


(Yesterday, 11:30 AM)Qriva0 Wrote: Third, how to read lambda? In case of strech constraint lambda has 3 components, I guess one for each "axis", so do I need to multiply by 3?
float force = solverBatch.lambdas[offset + i * 3] / sqrTime;

How to access lambdas depends on the specific constraint type. Some constraints have only 1 lambda, since they only have 1 gradient direction. Some have 2, 3, or more lambda values. Stretch/shear constraints have 3 lambdas: one for stretch, one for  the first shear axis, and another for the second shear axis. So you have to multiply the current index by 3 when iterating, and add an offset ranging from 0 to 2 depending on which lambda you want to access for each constraint:

Code:
float shearForce1 = solverBatch.lambdas[(offset + i) * 3] / sqrTime;
float shearForce2 = solverBatch.lambdas[(offset + i) * 3 + 1] / sqrTime;
float stretchForce = solverBatch.lambdas[(offset + i) * 3 + 2] / sqrTime;

Note this is just one way of doing it. You could also reinterpret the memory as Vector3/float3 and access it that way.

(Yesterday, 11:30 AM)Qriva0 Wrote: Forth - why substep time is used here? And this leads to next point:

Because lagrange multipliers (lambdas) in XPBD are positional deltas, calculated and applied during each substep, so to convert them to forces you divide by substep duration squared.

(Yesterday, 11:30 AM)Qriva0 Wrote: When to use substepTime and simDelta?

Entirely depends on what you're using them for. Just like in Unity you sometimes use deltaTime, but some other times you use fixedDeltaTime.

(Yesterday, 11:30 AM)Qriva0 Wrote: If I understand correctly start and end position are positions for single simulation step.

Correct. These are used to interpolate physics state between frames, since rendering happens between full steps (substeps are just chained back to back) they store data for full steps instead of susbteps.

(Yesterday, 11:30 AM)Qriva0 Wrote: There are also substeps and those are run as group of jobs - there is no way to do anything between substeps, so is prevPositions and positions just data from the last substep?

Correct. A single simulation step is comprised of multiple substeps, and each substep is comprised of multiple iterations. During solver callbacks (and Unity's events like Update(), LateUpdate(), etc), all information you get belongs to the last executed substep.

(Yesterday, 11:30 AM)Qriva0 Wrote: If that is the case, then in the code above is substepDelta used because we have access only to the last one? I think I understand it wrong, because lambda is accumulated, so can you clarify how that actually work?

Lambda values are set to zero at the start of each substep, and accumulated between iterations. However, while time advances from one substep to the next, it does not advance between iterations: iterations simply refine the solution for the current substep.

The rope tearing code uses the lambda values from the last substep, using the substep time delta to get a force value, and tears the rope if the force exceeds a threshold. It's possible for the constraint force to briefly go over the tear threshold during an intermediate substep, but these sudden "spikes" are ignored: we're only interested in forces that are sustained over at least one full simulation step.

(Yesterday, 11:30 AM)Qriva0 Wrote: I found example here about adding forces (Using Burst/Jobs to process particles) https://obi.virtualmethodstudio.com/manu...icles.html
It does not work, it uses undeclared variable stepTime, but I guess it would be substepTime.

That should be timeToSimulate, which is the total amount of time passed between steps (substeps * substepTime). I've corrected it in the example, thanks for reporting the issue!

(Yesterday, 11:30 AM)Qriva0 Wrote: The problem is that if we have 5 substeps, then does that mean that we are applying only 1/5 of forces?

We're applying the full force (since it's multiplied by substep*substepTime), but only changing its direction/magnitude every N substeps.

(Yesterday, 11:30 AM)Qriva0 Wrote: For example I wanted to create script that uses onSimulationStart to move particles, velocity or position delta is 5 per second, so in such a case should I use timeToSimulate or substepTime as delta? 

This happens over an entire step, so you should use timeToSimulate.

I'd recommend reading the "how it works" section of the manual, as it goes in-depth about timesteps, substeps and iterations:
https://obi.virtualmethodstudio.com/manu...gence.html

These articles are also useful for reference:
https://matthias-research.github.io/page...s/XPBD.pdf
https://mmacklin.com/smallsteps.pdf

let me know if I can be of further help,

kind regards
Reply