Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Feedback Obi 8: what's coming up
#1
Hi all!

I've been working on Obi 8 for a few weeks now, and wanted to talk about what we're doing with it and get some feedback if possible.

The main driving force behind Obi 8 is making it more flexible. Currently, the only way to customize particle behavior is to add external forces and/or modify their positions outside the main solver loop. This is fine in many cases, but will fail miserably in others, specially when tight coupling with existing constraints is required and doing stuff every substep/iteration is a necessity. In these cases you'd be forced to crack open the engine and mess with internals, which isn't an appealing prospect.

So the idea is to rewrite a large part of the engine and expose an API that allows you to write entirely custom constraints. These get solved along with all built-in constraints, in fact, we're rewriting all built-in constraints using this new API. This is how it looks:

Constraints are split into 3 classes: a data container, a burst implementation (optional) and a compute implementation (optional). For instance, bend constraints would consist of:
  • ObiBendConstraintData (derived from ObiConstraintData)
  • ObiBurstBendConstraints (derived from ObiBurstConstraints)
  • ObiComputeBendConstraints (derived from ObiComputeConstraints)

To register a new constraint type in a solver, there's a generic RegisterConstraintType method that takes the name used to display constraint parameters in the solver and the parameters that should be shown in the ObiSolver inspector:

Code:
solver.RegisterConstrainType<T>(string name, ConstraintParameters params) where T : ObiConstraintData

The ability to pass custom constraint parameters allows your constraints to have their own mini-inspector in the ObiSolver component, to have all related parameters neatly presented along built-in ones. This has allowed us to move what were solver-global parameters (like ambient wind direction which is used by aerodynamic constraints, or collision margin which is used by collisions) to the constraints that actually depend on them, which is less confusing:

[Image: oY3y1Ee.png]

for example, bend constraints register themselves like so:

Code:
solver.RegisterConstrainType<ObiBendConstraintData>("Bend", new BendConstraintParameters());

Once you've registered a new type of constraints, you can add implementations for the Burst and Compute backends. These are optional, eg. if you don't provide a Compute implementation then these constraints won't do anything when using the Compute backend. I don't recommend not adding any implementation though! Guiño

Code:
solver.RegisterBurstConstraintsImplementation<T, U>() where T : ObiConstraintData where U : ObiBurstConstraints;
solver.RegisterComputeConstraintsImplementation<T, U>() where T : ObiConstraintData where U : ObiComputeConstraints

The constraint data class is very simple: you just declare data buffers (ObiNativeLists) to hold data for each constraint, and provide implementations to add constraints, resize the buffers, clear and dispose them. For instance bend constraint data looks like this:

Code:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;

namespace Obi
{
    [Serializable]
    public class ObiBendConstraintData : ObiConstraintData
    {
        /// <summary>
        /// one float per constraint: the rest bend distance.
        /// </summary>
        [HideInInspector] public ObiNativeFloatList restBends = new ObiNativeFloatList();               

        /// <summary>
        /// two floats per constraint: max bending and compliance.
        /// </summary>
        [HideInInspector] public ObiNativeVector2List bendingStiffnesses = new ObiNativeVector2List();

        /// <summary>
        /// two floats per constraint: plastic yield and creep.
        /// </summary>
        [HideInInspector] public ObiNativeVector2List plasticity = new ObiNativeVector2List();

        public override int priority => 100;
        public override int workItemSize => 4;

        public int AddConstraint(int batchIndex, Vector3Int indices, float restBend)
        {
            int constraintIndex = RegisterConstraint(batchIndex, indices.x, indices.y, indices.z);

            restBends[constraintIndex] = restBend;
            bendingStiffnesses[constraintIndex] = Vector2.zero;
            plasticity[constraintIndex] = Vector2.zero;

            return constraintIndex;
        }

        protected override void ResizeDataBuffers(int constraintCount)
        {
            restBends.ResizeUninitialized(constraintCount);
            bendingStiffnesses.ResizeUninitialized(constraintCount);
            plasticity.ResizeUninitialized(constraintCount);
        }

        protected override void CopyConstraintData(ObiActor actor, ObiConstraints source, int srcIndex, int dstIndex, int count)
        {
            var batch = source as ObiBendConstraintData;
            var user = actor as IBendConstraintsUser;

            if (batch != null && user != null)
            {
                restBends.CopyFrom(batch.restBends, srcIndex, dstIndex, count);
                bendingStiffnesses.CopyReplicate(new Vector2(user.maxBending, user.bendCompliance), dstIndex, count);
                plasticity.CopyReplicate(new Vector2(user.plasticYield, user.plasticCreep), dstIndex, count);
            }
        }

        public override bool EnabledForActor(ObiActor actor)
        {
            var user = actor as IBendConstraintsUser;
            return user != null && user.bendConstraintsEnabled;
        }
        protected override void OnClear()
        {
            restBends.Clear();
            bendingStiffnesses.Clear();
            plasticity.Clear();
        }

        protected override void OnDispose()
        {
            restBends.Dispose();
            bendingStiffnesses.Dispose();
            plasticity.Dispose();
        }
    }
}

Concrete implementations for the burst/compute backends need to implement a method to retrieve constraint data, and a method to actually enforce the constraints (that gets called once per solver iteration of that constraint type). Burst bend constraints look like this:

Code:
using UnityEngine;
using Unity.Jobs;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using Unity.Burst;
using System.Collections;

namespace Obi
{
    public class ObiBurstBendConstraints: ObiBurstConstraints
    {
        BendConstraintsBatchJob projectConstraints;

        public override void SetConstraintData()
        {
            ObiBendConstraintsData b = abstraction as ObiBendConstraintsData;

            this.lambdas = b.lambdas.AsNativeArray<float>();
            this.batches = b.batches.AsNativeArray<int4>();

            projectConstraints.workItems = b.workItems.AsNativeArray<int2>();
            projectConstraints.particleIndices = b.particleIndices.AsNativeArray<int>();
            projectConstraints.restBends = b.restBends.AsNativeArray<float>();
            projectConstraints.stiffnesses = b.bendingStiffnesses.AsNativeArray<float2>();
            projectConstraints.plasticity = b.plasticity.AsNativeArray<float2>();
            projectConstraints.lambdas = this.lambdas;
        }

        public override JobHandle SolvePositions(JobHandle inputDeps, float stepTime, float substepTime, int steps, float timeLeft)
        {
            projectConstraints.positions = solverImplementation.positions;
            projectConstraints.invMasses = solverImplementation.invMasses;
            projectConstraints.deltas = smooth ? solverImplementation.positionDeltas : solverImplementation.positions;
            projectConstraints.deltaTime = substepTime;
            projectConstraints.countConstraints = smooth ? 1 : 0;
            projectConstraints.workItemSize = workItemSize;

            for (int i = 0; i < batches.Length; ++i)
            {
                projectConstraints.offset = batches[i].x;
                inputDeps = projectConstraints.Schedule(batches[i].y, 8, inputDeps);
            }

            if (smooth)
                inputDeps = ApplyDeltas(inputDeps);

            return inputDeps;
        }

        [BurstCompile]
        public struct BendConstraintsBatchJob : IJobParallelFor
        {
            [ReadOnly] public NativeArray<int2> workItems;
            [ReadOnly] public NativeArray<int> particleIndices;

            [ReadOnly] public NativeArray<float4> positions;
            [ReadOnly] public NativeArray<float2> stiffnesses;
            [ReadOnly] public NativeArray<float2> plasticity; //plastic yield, creep
            [ReadOnly] public NativeArray<float> invMasses;

            [NativeDisableContainerSafetyRestriction] [NativeDisableParallelForRestriction] public NativeArray<float> restBends;
            [NativeDisableContainerSafetyRestriction] [NativeDisableParallelForRestriction] public NativeArray<float> lambdas;
            [NativeDisableContainerSafetyRestriction] [NativeDisableParallelForRestriction] public NativeArray<float4> deltas;

            [ReadOnly] public float deltaTime;
            [ReadOnly] public float sor;
            [ReadOnly] public int countConstraints;
            [ReadOnly] public uint workItemSize;
            [ReadOnly] public int offset;

            public void Execute(int w)
            {
                var wi = workItems[offset + w];

                for (int i = wi.x, j = 0; j < workItemSize; ++i, ++j)
                {
                    if ((wi.y & (1 << j)) == 0) continue;

                    int p1 = particleIndices[i * 3];
                    int p2 = particleIndices[i * 3 + 1];
                    int p3 = particleIndices[i * 3 + 2];

                    float w1 = invMasses[p1];
                    float w2 = invMasses[p2];
                    float w3 = invMasses[p3];

                    float wsum = w1 + w2 + 2 * w3;

                    float4 bendVector = positions[p3] - (positions[p1] + positions[p2] + positions[p3]) / 3.0f;
                    float bend = math.length(bendVector);

                    float constraint = bend - restBends[i];

                    constraint = math.max(0, constraint - stiffnesses[i].x) +
                                 math.min(0, constraint + stiffnesses[i].x);

                    // plasticity:
                    if (math.abs(constraint) > plasticity[i].x)
                        restBends[i] += constraint * plasticity[i].y * deltaTime;

                    // calculate time adjusted compliance
                    float compliance = stiffnesses[i].y / (deltaTime * deltaTime);

                    // since the third particle moves twice the amount of the other 2, the modulus of its gradient is 2:
                    float dlambda = (-constraint - compliance * lambdas[i]) / (wsum + compliance + BurstMath.epsilon);
                    float3 correction = 2 * dlambda * bendVector.xyz / (bend + BurstMath.epsilon);

                    lambdas[i] += dlambda;

                    deltas[p1] += new float4(-correction * w1, countConstraints);
                    deltas[p2] += new float4(-correction * w2, countConstraints);
                    deltas[p3] += new float4(correction * 2 * w3, countConstraints);
                }
            }
        }
    }
}

The Compute equivalent is very similar, but instead of a Job it uses a compute shader to so the same work.

Some constraints are not just static relationships between particles, but require colliders or particle neighbor information that changes over time. We're making sure to expose collider and particle neighbor data (gathered during collision detection) as well so that it can be used in custom constraints. You'll be able to, for example, dynamically create constraints between particles that come in the vicinity of each other, and destroy the constraints once they're under too much stress.

Along with this, we're rewritting constraint coloring/batching. Instead of batching individual constraints, we group them into tiny chunks of 2-8 constraints called work items, and batch the work items instead. The coloring algorithm used in blueprint generation has been also upgraded from greedy coloring to recursive largest-first. Both changes result in considerably less batches, and a speedup of around 10-30% depending on the scene.

So my hope is for Obi 8 to be simpler, faster and a lot more flexible all at once.

I'm also tinkering with the idea of allowing custom particle attributes (so that your particles can have position, mass, political affiliation and social security number if you so choose) and custom colliders. This is in very early stages so the API hasn't come together yet.

Let me know what you think, what you'd like to see in the future or ask me if a specific use case will be possible. This way the thing that ends up being shipped will meet your needs better! Any feedback is welcome.

kind regards
Reply
#2
Hi!
This sounds like greate feature, exactly what I wanted Gran sonrisa 
In my opinion providing ways to customize obi is great step and anything that allows to customize particles, forces constraints is really great tool.

I have got questions about managing constraints and additional helpers related to that.
1. Is it possible to show inspector in obi actors? So for example in obi softbody next to shape constraint to have some options for particles?
2. I guess by default those constraints are added from code, so dedicated component could be created to manage adding batches, something like stich constraint?
3. Would it be possible to create custom actor? For example custom, different type of softbody that uses different rules, structure, constraints?
4. What about order of execution? Can I control when custom constraint is executed? By this I mean obviously substeps, to make my constraint to execute as last or before other constraint.

Great work!
Reply
#3
(26-11-2025, 12:26 PM)Qriva0 Wrote: Hi!
This sounds like greate feature, exactly what I wanted Gran sonrisa 
In my opinion providing ways to customize obi is great step and anything that allows to customize particles, forces constraints is really great tool.

I have got questions about managing constraints and additional helpers related to that.
1. Is it possible to show inspector in obi actors? So for example in obi softbody next to shape constraint to have some options for particles?

Actors can implement a IConstraintUser interface (name might change) that exposes parameters related to that constraint in the inspector. This is similar to how we currently do it, but want to expose this cleanly in the new API. Which options about particles would you like to see exposed in the inspector?

(26-11-2025, 12:26 PM)Qriva0 Wrote: 2. I guess by default those constraints are added from code, so dedicated component could be created to manage adding batches, something like stich constraint?

Constraints can come from:
A)  a blueprint
B)  a custom component (like ObiParticleAttachment or ObiStitcher)
C)  at runtime from particle-particle or particle-collider neighboring information (like contact constraints)

There will be helper APIs for common tasks like graph coloring (to generate batches from a list of constraints), segmented prefix sums for binning constraints, stream compaction, etc. But yes, we're focusing on providing tools to do this from code. If you wanted a component to add specific constraints at runtime, you can write that (would be option B) in the above list)

(26-11-2025, 12:26 PM)Qriva0 Wrote: 3. Would it be possible to create custom actor? For example custom, different type of softbody that uses different rules, structure, constraints?

Yes, you can technically do this right now but we want to clean up the API, put safety fences and thoroughly document it so that the correct workflow is easy to understand and follow.

(26-11-2025, 12:26 PM)Qriva0 Wrote: 4. What about order of execution? Can I control when custom constraint is executed? By this I mean obviously substeps, to make my constraint to execute as last or before other constraint.

Yes, each constraint type has a "priority" (an integer number) that determines its relative update order. You define its value when deriving from ObiConstraintData, in the above examples see the ObiBendConstraintData class:

Code:
public override int priority => 100;

Ideally we want to allow constraints in the ObiSolver inspector to be drag/dropped to be reordered too (like in Unity's Script Execution Order window), so that you can modify the default priority on a per-solver basis for specific scenarios.

cheers!
Reply
#4
(26-11-2025, 12:38 PM)josemendez Wrote: Actors can implement a IConstraintUser interface (name might change) that exposes parameters related to that constraint in the inspector. This is similar to how we currently do it, but want to expose this cleanly in the new API. Which options about particles would you like to see exposed in the inspector?
Lets assume I made custom distance constraint (it just tries to keep distance between particles not less than some number, nothing more).
Consider two cases:
1. I want to use it on some particles of two bodies - softbody and cloth and I need parameter to "minDistance" to control how close those particles can be.
In this case I would assume there would be custom component, because two actors are affected.
2. I want to have additional constraint between particles for specific actor, in this example softbody, so what I want is to display "Min Distance Constraint" section in softbody inspector with option "minDistance" and have that option used by constraint.

(26-11-2025, 12:38 PM)josemendez Wrote: Yes, you can technically do this right now but we want to clean up the API, put safety fences and thoroughly document it so that the correct workflow is easy to understand and follow.
This is great! Good documentation and examples are really important.

(26-11-2025, 12:38 PM)josemendez Wrote: Yes, each constraint type has a "priority" (an integer number) that determines its relative update order. You define its value when deriving from ObiConstraintData, in the above examples see the ObiBendConstraintData class:
Oh I see, very good there is option for that.

Thanks for answers, cheers!
Reply
#5
(01-12-2025, 10:57 AM)Qriva0 Wrote: Lets assume I made custom distance constraint (it just tries to keep distance between particles not less than some number, nothing more).
Consider two cases:
1. I want to use it on some particles of two bodies - softbody and cloth and I need parameter to "minDistance" to control how close those particles can be.
In this case I would assume there would be custom component, because two actors are affected.

Yes, that would be case B) in my list above.

(01-12-2025, 10:57 AM)Qriva0 Wrote: 2. I want to have additional constraint between particles for specific actor, in this example softbody, so what I want is to display "Min Distance Constraint" section in softbody inspector with option "minDistance" and have that option used by constraint.

That would either be case A) or case C) depending on whether you want these constraints to be created during blueprint generation or entirely at runtime. One of my test custom constraints is basically "persistent" adhesion, when two particles get closer than a specified threshold a distance constraint is created between them, when the strain of that constraint exceeds a threshold the constraint is removed. I think this is quite close to what you describe here.

cheers,
Reply
#6
This is fantastic news, didn't dare to wish for it but here it is! Sonrisa

Sounds smart to make it more customisable for future proofing also.

Can I request to also include possibilities to extend colliders to add more collider handlers?
Maybe a nicer interface, but that would allow me to have unmodified code!
https://obi.virtualmethodstudio.com/foru...l#pid17412
Reply
#7
Is Obi 8 backword compatible? Do I need to to change my existing Obi 7.1 code to upgrade?
Reply
#8
(12-12-2025, 03:05 AM)chenji Wrote: Is Obi 8 backword compatible? Do I need to to change my existing Obi 7.1 code to upgrade?

Major versions (6.X to 7.X, 7.X to 8.X) are not backwards compatible. In this case, the entire API is being redesigned to accommodate the above changes, so you'd certainly need to rewrite your 7.1 code.
Reply
#9
(02-12-2025, 04:42 PM)goosejordan Wrote: This is fantastic news, didn't dare to wish for it but here it is! Sonrisa

Sounds smart to make it more customisable for future proofing also.

Can I request to also include possibilities to extend colliders to add more collider handlers?
Maybe a nicer interface, but that would allow me to have unmodified code!
https://obi.virtualmethodstudio.com/foru...l#pid17412

Yes, this is something we have on our roadmap as well. It would indeed be nice if you could write your own colliders and narrow phase collision detection code.
Reply