Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Tutorial?
#11
Quote:josemendez

For a softbody to collide with another softbody (this is called intercollision) their particles just need to have different phase values. This is explained in the manual. No need for colliders or rigidbodies to be involved.




What manual this? 
http://obi.virtualmethodstudio.com/tutor...sions.html

Sorry but Im still confused. The "Phase Value" is on the "Obi Collider". You just said not to use a collider and a softbody at the same time?

I previously read the bit about collisions requiring different phase values, which is why I added an Obi Collider to the same gameobject that has my softbody. Its the only way I can see a field for a "phase" value.

https://i.imgur.com/HTQg4HR.png

I've gone hunting for other locs for a phase value, I tried editing the blueprint im using, but saw nothing in there. Still don't understand this section at all. I feel like more indepth video tutorials would really help Obi
Reply
#12
(19-09-2020, 04:24 PM)Havie Wrote: Sorry but Im still confused. The "Phase Value" is on the "Obi Collider". You just said not to use a collider and a softbody at the same time?

I previously read the bit about collisions requiring different phase values, which is why I added an Obi Collider to the same gameobject that has my softbody. Its the only way I can see a field for a "phase" value.

Quote from the collisions page in the manual/docs, in bold the important bits:
http://obi.virtualmethodstudio.com/tutor...sions.html

Quote:Sometimes you want certain actors, or even only some specific particles to ignore a particular collider. You can use phases for this. Each ObiCollider has a "Collision phase" property, and particles have a "Phase" channel that you can set using the blueprint editor. Only colliders and particles of different phases will collide. Colliders and particles of the same phase will ignore each other. By default, ObiColliders are in phase 0 and particles in phase 1, so they will collide right away.

Rephrasing this: both colliders and particles have a phase value. The phase for a collider is set in the ObiCollider component. The phase for a particle is set in the blueprint editor. Its one of the per-particle channels that you can set using the particle selection tool, or paint using the property painting tool.

(19-09-2020, 04:24 PM)Havie Wrote: I've gone hunting for other locs for a phase value, I tried editing the blueprint im using, but saw nothing in there. Still don't understand this section at all. I feel like more indepth video tutorials would really help Obi

In the blueprint editor, there's a "properties" section (both in the selection and painting tools) with a dropdown that lets you select which per-particle property you want to edit. Among these properties there's "Phase".

[Image: efuvquc.png]

In the page for softbody blueprints there's a rundown of all properties:

http://obi.virtualmethodstudio.com/tutor...setup.html

Quote:You can get/set any property of the currently selected particles:

Mass:
Particle mass. Determines how the particle behaves when involved in any constraint (collision, distance, bending...) with another particle or a rigidbody.
Radius:
Particle radius. Mainly used for collision detection.
Phase:
Particle phase, used to determine when collisions with rigidbodies and other particles should be ignored. For an in-depth explanation, see collisions.
Color:
Particle color. Useful to store additional info, or to be used in custom rendering.

Now, note that you don't need to have different blueprints if all you want is to have multiple actors using the same blueprint, with a different phase for each one. You can set the phase values for the particles at runtime too, check out the included BallPool sample scene.

(19-09-2020, 04:24 PM)Havie Wrote: I feel like more indepth video tutorials would really help Obi

You're totally right. I know there should be more video tutorials, but writing, recording and editing a video tutorial takes a lot of time, much more than generating written documentation. I'm a one-person team, so I have to carefully choose where to spend the time I devote to Obi to make the most out of it: maintaining the code, developing it further, supporting users, writing docs, maintaining the forums/webpage, etc. In the last update (5.6) I included one more sample scene for each asset, a more complex/complete one (kind of a mini-game) because that's what users demanded for a long time. The other most popular demand by far is "make more video tutorials", so you're backed up by lots of people. Rest assured that I will. Sonrisa
Reply
#13
Ahh thanks for your response. I am glad to hear you will be doing some video tutorials in the future.
The one on the assest store made it seem too easy.



So what Sample scene? Because I went looking for a sample scene on install to learn from, and didnt see one?
https://i.imgur.com/EBkH23k.png


Thanks for pointing out the drop down. I've painted Ball as phase 1 and Can as phase 2. And hit "done". Yet the changes do not seem to save.
Nothing collides and my trash can falls over immediately and self implodes for whatever reason.
Could you please take a look ?

https://youtu.be/fd7GmCl3rOs
Reply
#14
(19-09-2020, 10:41 PM)Havie Wrote: So what Sample scene? Because I went looking for a sample scene on install to learn from, and didnt see one?
https://i.imgur.com/EBkH23k.png

Look in that Softbody folder. Whole path from Assets: Obi/Samples/Softbody. Anyway, you could just use the search bar to filter and show all scenes, no need to look for them yourself.

(19-09-2020, 10:41 PM)Havie Wrote: Thanks for pointing out the drop down. I've painted Ball as phase 1 and Can as phase 2. And hit "done". Yet the changes do not seem to save.
Nothing collides and my trash can falls over immediately and self implodes for whatever reason.
Could you please take a look ?

https://youtu.be/fd7GmCl3rOs

Seems to be the wrong video? Nothing related to Obi in there...
Reply
#15
omgosh so sorry, 

https://youtu.be/eEW56osPuaM


Looking into that scene now

Ok  I've given up on my scene and just basically cloned your barrel scene since its pretty much what I needed.

I cant tell what you have different than mine, but this is working a lot better now. The Barrel doesnt self implode and the ball can collide.

However, now
its possible for the ball to get stuck inside the barrel then when firing it again, it bugs out and goes below the ground.

Thoughts?

https://youtu.be/UoyVYGCBIno


I am also having trouble figuring out how to do an "OnCollisionEnter" call. From what I've read I understand its the Solvers Job to handle collisions.
"solvers will generate collision constraints"

However the only items I see on the solver related to Collisons are :

collisionConstraintParameters (field)
collisionMaterials (field)
OnCollision (event)
OnParticleCollision (event)
particleCollisionConstraintParameters (field)


I am not finding any info about these under "Solver" in API
https://i.imgur.com/OToZBlg.png


Best thing I can find is on the collisionConstraintParameter :
EvaluationOrder evaluationOrder
int iterations
float SORFactor
bool enabled

But theres really no info here on how to get any info out of it? like what object is being collider with?

I've Tried subscribing to this event
https://i.imgur.com/iSHPTxy.png

But as far as I can tell theres nothing of value in an OniContact for telling me which GameObject was hit ?

EDIT:
I found this under the scripting section:
http://obi.virtualmethodstudio.com/tutor...sions.html
Would be really nice if there was a link to this under the Collision section as its relevant and i totally missed it
Reply
#16
https://i.imgur.com/MlYQhok.png

Why is this line erroring?

Your guide tells us how to get the collider for the OTHER item involved in a collision, but I first need to verify that the initial item in the collision belongs to the gameobject this script is on.

For some reason 

ObiColliderBase self = handles[contact.particle].owner;

results in ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index


I cant seem to get a gameobject from solver.particleToActor[contact.particle]; either
Reply
#17
(20-09-2020, 04:18 PM)Havie Wrote: https://i.imgur.com/MlYQhok.png

Why is this line erroring?

Your guide tells us how to get the collider for the OTHER item involved in a collision, but I first need to verify that the initial item in the collision belongs to the gameobject this script is on.

For some reason 

ObiColliderBase self = handles[contact.particle].owner;

results in ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index

You’re indexing an array of collider handles using the index of a particle. This makes no sense, and usually will result in index out of bounds errors since there’s more particles than colliders involved in a typical scene.

handles[contact.other] is the correct way to do it. Remember that:

Contact.particle: the index of a particle. Can be used to retrieve data from any of the per-particle data arrays in the solver.

Contact.other: the index of a collider handle (in case of the OnCollision event) or another particle (in case of the OnParticleCollision event).

(20-09-2020, 04:18 PM)Havie Wrote: I cant seem to get a gameobject from  solver.particleToActor[contact.particle]; either

Its pretty straightforward:

var particleInActor = solver.particleToActor[contact.particle];
GameObject go = particleInActor.actor.gameObject;

The manual specifies that the ParticleInActor struct contains both a reference to the actor, and the index of the particle in that actor. Once you have a reference to the actor (which is a component) you can access the gameobject as you would from any component in Unity.
Reply
#18
(20-09-2020, 07:20 PM)josemendez Wrote: Its pretty straightforward:

var particleInActor = solver.particleToActor[contact.particle];
GameObject go = particleInActor.actor.gameObject;
Okay i got this :
https://i.imgur.com/fLyaind.png

Also this is being called constantly even when nothing is happening in the scene because its touching the terrain(floor). I can't imagine this being performative when this script is on multiple objects all wanting to know when they've been collided with. Surely theres some other solution you have to recreate the collision system such as OnCollisionEnter() ? Or is this not a big performance hit as Unitys collision system already did this?
Reply
#19
(21-09-2020, 07:02 PM)Havie Wrote: Okay i got this :
https://i.imgur.com/fLyaind.png

Also this is being called constantly even when nothing is happening in the scene because its touching the terrain(floor). I can't imagine this being performative when this script is on multiple objects all wanting to know when they've been collided with. Surely theres some other solution you have to recreate the collision system such as OnCollisionEnter() ? Or is this not a big performance hit as Unitys collision system already did this?

Internally, all physics engines have to deal with a list of contacts every frame, as contacts need to be solved in order for the simulation to work. In fact, the contacts list is iterated several times per frame to solve the system. Unity does this, and Obi does this, all physics engines in existence do this: they build a list of contacts, then solve the contact equation system by iterating trough all contacts at least once.

So iterating trough all contacts one last time to filter or post process the contacts is no big deal, if done correctly.

Internally, Unity performs boolean list operations to determine when to call OnCollisionEnter/Exit/Stay for different objects. In Obi, this is up to you. Of course, you could iterate over the list of contacts in parallel using jobs instead of a naive for loop in plain C#, and that would be much faster.

Quote:I can't imagine this being performative when this script is on multiple objects all wanting to know when they've been collided with.

This would of course result in terrible performance. You should iterate trough all contacts just once, and extract the information you need for different objects. Iterating trough all contacts once per object is a really bad idea.
Reply
#20
Here's an example on how to filter out contacts and determine enter/exit/stay events fast, regardless of how many colliders are there in the scene. It's a component that you add to the solver, and it calls enter/exit/stay Unity events. The actual contact passed to each event is guaranteed to be between unique <actor, collider> pairs, but the particular  particle in the contact passed for the actor is undefined (in case there's more than one contact between the actor and the collider, it could pass a contact for any of the actor particles).

Not terribly fast (not multithreaded, non vectorized, vanilla C# only) but reasonably well optimized and useable right away:

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

namespace Obi
{
    [RequireComponent(typeof(ObiSolver))]
    public class ObiContactEventDispatcher : MonoBehaviour
    {
        private ObiSolver solver;
        private Oni.Contact[] prevData;
        private int prevCount;
        private ContactComparer comparer;

        private class ContactComparer : IComparer<Oni.Contact>
        {
            ObiSolver solver;

            public ContactComparer(ObiSolver solver)
            {
                this.solver = solver;
            }

            public int Compare(Oni.Contact x, Oni.Contact y)
            {
                return CompareByRef(ref x, ref y, solver);
            }
        }

        private static int CompareByRef(ref Oni.Contact a, ref Oni.Contact b, ObiSolver solver)
        {
            if (a.other == b.other)
            {
                int hashA = solver.particleToActor[a.particle].actor.GetInstanceID();
                int hashB = solver.particleToActor[b.particle].actor.GetInstanceID();
                return hashA.CompareTo(hashB);
            }
            return a.other.CompareTo(b.other);
        }

        [System.Serializable]
        public class ContactCallback : UnityEvent<ObiSolver, Oni.Contact> { }

        public float distanceThreshold = 0.01f;
        public ContactCallback onContactEnter = new ContactCallback();
        public ContactCallback onContactStay = new ContactCallback();
        public ContactCallback onContactExit = new ContactCallback();

        void Awake()
        {
            solver = GetComponent<ObiSolver>();
            comparer = new ContactComparer(solver);
            prevData = new Oni.Contact[0];
        }

        void OnEnable()
        {
            solver.OnCollision += Solver_OnCollision;
        }

        void OnDisable()
        {
            solver.OnCollision -= Solver_OnCollision;
        }

        private int FilterOutDistantContacts(Oni.Contact[] data, int count)
        {
            int filteredCount = count;

            // simply iterate trough all contacts,
            // moving the ones above the threshold to the end of the array:
            for (int i = count -1 ; i >= 0; --i)
                if (data[i].distance > distanceThreshold)
                    ObiUtils.Swap(ref data[i], ref data[--filteredCount]);

            return filteredCount;
        }

        private int RemoveDuplicates(Oni.Contact[] data, int count)
        {
            if (count == 0)
                return 0;

            // assuming the array is sorted, iterate trough the array
            // replacing duplicates by the first contact that's different:
            int i = 0, r = 0;
            while (++i != count)
                if (CompareByRef(ref data[i], ref data[r], solver) != 0 && ++r != i)
                    data[r] = data[i];

            return ++r;
        }

        private void InvokeCallbacks(Oni.Contact[] data, int count)
        {
            int a = 0, b = 0;
            int lengthA = count, lengthB = prevCount;

            // while we haven't reached the end of either array:
            while (a < lengthA && b < lengthB)
            {
                // compare both contacts:
                int compare = CompareByRef(ref data[a], ref prevData[b], solver);

                // call the appropiate event depending on the comparison result:
                if (compare < 0)
                    onContactEnter.Invoke(solver, data[a++]);
                else if (compare > 0)
                    onContactExit.Invoke(solver, prevData[b++]);
                else
                {
                    onContactStay.Invoke(solver, data[a++]); b++;
                }
            }

            // finish iterating trough both lists:
            while (a < lengthA)
                onContactEnter.Invoke(solver, data[a++]);

            while (b < lengthB)
                onContactExit.Invoke(solver, prevData[b++]);
        }

        void Solver_OnCollision(object sender, ObiSolver.ObiCollisionEventArgs args)
        {
            // here we access the internal backing array (Data) directly,
            // instead of using the accessor property. This slightly improves performance.
            // note: the backing array length is the lists' capacity, so we
            // need to use args.contacts.Count to get the actual number of contacts.

            // skip all contacts above the distance threshold by moving them to the end of the array:
            int filteredCount = FilterOutDistantContacts(args.contacts.Data, args.contacts.Count);

            // sort the remaining contacts by collider, then by actor:
            Array.Sort(args.contacts.Data, 0, filteredCount, comparer);

            // remove duplicates:
            filteredCount = RemoveDuplicates(args.contacts.Data, filteredCount);

            // zip trough the current and previous contact lists once, invoking events when appropiate.
            InvokeCallbacks(args.contacts.Data, filteredCount);

            // store current contact list/count for next frame.
            // could get better performance by double buffering instead of copying:

            if (filteredCount > prevData.Length)
                Array.Resize(ref prevData, filteredCount);
            Array.Copy(args.contacts.Data, prevData, filteredCount);

            prevCount = filteredCount;
        }

    }
}

It works like this:
  • First, it moves all contacts above a distance threshold to the end of the contacts array. This ensures only contacts within the threshold are actually considered.
  • Sorts the remaining contacts
  • Removes duplicate contacts (between the same <actor, collider> pair). Since contacts are sorted, this can be done in linear time.
  • Uses a simple zipper-like algorithm to iterate trough both the current contact list and the last frame's contact list. Again, since they're both sorted, comparing them in linear time lets us know if they exist only in the new list (enter event), only in the previous list (exit event) or in both (stay event).
  • Stores the current contact list as the previous list, for the next frame.

"n" being the amount of contacts this frame and "m" the amount of contacts in the previous frame, the algorithmic worst case cost of the whole thing is O(n*log(n) + m). So quite good compared to the naive quadratic approach. You could further optimize it by using parallel sorting, as sorting is the bottleneck.

Once you have this component, you can just subscribe to the events and do whatever you need. For instance, subscribing this sample component:

Code:
using UnityEngine;
using Obi;

public class ContactBlinker : MonoBehaviour
{
 
    public void OnEnter(ObiSolver solver, Oni.Contact c)
    {
        var col = ObiColliderWorld.GetInstance().colliderHandles[c.other].owner;

        var blinker = col.GetComponent<Blinker>();
        blinker.highlightColor = Color.green;
        blinker.Blink();
    }

    public void OnExit(ObiSolver solver, Oni.Contact c)
    {
        var col = ObiColliderWorld.GetInstance().colliderHandles[c.other].owner;

        var blinker = col.GetComponent<Blinker>();
        blinker.highlightColor = Color.red;
        blinker.Blink();
    }

}

Makes colliders blink green when the actor enters, and blink red when it exits. I'm not providing code for the Blinker component, as it simply changes the material's color when you call Blink(). Here's the result:

Reply