Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help  How To Increase Particle Elevation?
#1
I'm attempting to put together a simple script based off ObiParticlePicker that supports multiple touches. The multiple touches is working fine. The issue I'm having is that when I select a particle index, I do not seem to understand how to modify the particle's position.

I'm attempting to add a value to the y-level of a renderable position in order to increase its height. I know the axis in which I need to affect depends on the rotation and position of the cloth.

I have also done some debugging and confirmed that an index is in-fact being selected.

   

The reason for this is because I don't really want the effect that the particle dragger produces. When moving the finger around the screen it attempts to hold on to that particle even if I swipe to the other side of the screen whereas my desired functionality is the elevation and/or depression of particles under the finger. I know I could simply adapt the OnParticleDragged and treat all movement as a sequence of OnParticlePicked and then OnParticleReleased, but my goal is not just functioning but actually increasing my understanding of the system. I'll probably be using it a lot in the future. Directly effecting the elevation also seems less 'hackish' than hooking the two events in series.

Also if it would be helpful you can use the multi-touch for the "courtesy" code because that does actually work. I promise I'm not a terrible developer, Obi has just been hard for me to absorb.

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

public class SlimeSolverManager : MonoBehaviour
{
   [SerializeField]
   ObiSolver m_Solver;

   [SerializeField]
   float m_TouchRadius;
   [SerializeField]
   float m_TouchDistortionLevel = 1;

   private int[] m_PickedParticleIndices;
   private float pickedParticleDepth = 0;

   private void OnEnable()
   {
       Debug.Assert(m_Solver != null, "Slime Solver Manager Missing Obi Solver Component Reference!");
   }

   // Update is called once per frame
   void LateUpdate()
   {
       if (m_Solver)
       {
           if (Input.touchCount > 0)
               m_PickedParticleIndices = new int[Input.touchCount];

           foreach (Touch touch in Input.touches)
           {
               m_PickedParticleIndices[touch.fingerId] = -1;

               Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

               float closestMu = float.MaxValue;
               float closestDistance = float.MaxValue;

               // Find the closest particle hit by the ray:
               for (int i = 0; i < m_Solver.renderablePositions.count; ++i)
               {
                   float mu;
                   Vector3 projected = ObiUtils.ProjectPointLine
                       (m_Solver.renderablePositions[i], ray.origin, ray.origin + ray.direction, out mu, false);

                   float distanceToRay = Vector3.SqrMagnitude
                       ((Vector3)m_Solver.renderablePositions[i] - projected);

                   mu = Mathf.Max(0, mu);

                   float radius = m_Solver.principalRadii[i][0] * m_TouchRadius;

                   if (distanceToRay <= radius * radius && distanceToRay < closestDistance && mu < closestMu)
                   {
                       closestMu = mu;
                       closestDistance = distanceToRay;
                       m_PickedParticleIndices[touch.fingerId] = i;
                   }
               }

               if (m_PickedParticleIndices[touch.fingerId] >= 0)
               {
                   pickedParticleDepth = Camera.main.transform.InverseTransformVector((Vector3)m_Solver.renderablePositions
                       [m_PickedParticleIndices[touch.fingerId]] - Camera.main.transform.position).z;

                   Vector3 worldPosition = Camera.main.ScreenToWorldPoint
                           (new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));

                   float x = m_Solver.renderablePositions[m_PickedParticleIndices[touch.fingerId]].x;
                   float y = m_Solver.renderablePositions[m_PickedParticleIndices[touch.fingerId]].y;
                   float z = m_Solver.renderablePositions[m_PickedParticleIndices[touch.fingerId]].z;
                   float w = m_Solver.renderablePositions[m_PickedParticleIndices[touch.fingerId]].w;

                   m_Solver.renderablePositions
                       [m_PickedParticleIndices[touch.fingerId]] =
                           new Vector4(x, y + m_TouchDistortionLevel, z, w);
               }

           }

       }
   }
}

I know the declaration of four floats is a little lazy but at this point I'm simply trying to get results. I'll opt later.
Reply
#2
(17-02-2020, 07:36 AM)GrimCaplan Wrote: I'm attempting to put together a simple script based off ObiParticlePicker that supports multiple touches. The multiple touches is working fine. The issue I'm having is that when I select a particle index, I do not seem to understand how to modify the particle's position.

I'm attempting to add a value to the y-level of a renderable position in order to increase its height. I know the axis in which I need to affect depends on the rotation and position of the cloth.

I have also done some debugging and confirmed that an index is in-fact being selected.



The reason for this is because I don't really want the effect that the particle dragger produces. When moving the finger around the screen it attempts to hold on to that particle even if I swipe to the other side of the screen whereas my desired functionality is the elevation and/or depression of particles under the finger. I know I could simply adapt the OnParticleDragged and treat all movement as a sequence of OnParticlePicked and then OnParticleReleased, but my goal is not just functioning but actually increasing my understanding of the system. I'll probably be using it a lot in the future. Directly effecting the elevation also seems less 'hackish' than hooking the two events in series.

Also if it would be helpful you can use the multi-touch for the "courtesy" code because that does actually work. I promise I'm not a terrible developer, Obi has just been hard for me to absorb.

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

public class SlimeSolverManager : MonoBehaviour
{
   [SerializeField]
   ObiSolver m_Solver;

   [SerializeField]
   float m_TouchRadius;
   [SerializeField]
   float m_TouchDistortionLevel = 1;

   private int[] m_PickedParticleIndices;
   private float pickedParticleDepth = 0;

   private void OnEnable()
   {
       Debug.Assert(m_Solver != null, "Slime Solver Manager Missing Obi Solver Component Reference!");
   }

   // Update is called once per frame
   void LateUpdate()
   {
       if (m_Solver)
       {
           if (Input.touchCount > 0)
               m_PickedParticleIndices = new int[Input.touchCount];

           foreach (Touch touch in Input.touches)
           {
               m_PickedParticleIndices[touch.fingerId] = -1;

               Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

               float closestMu = float.MaxValue;
               float closestDistance = float.MaxValue;

               // Find the closest particle hit by the ray:
               for (int i = 0; i < m_Solver.renderablePositions.count; ++i)
               {
                   float mu;
                   Vector3 projected = ObiUtils.ProjectPointLine
                       (m_Solver.renderablePositions[i], ray.origin, ray.origin + ray.direction, out mu, false);

                   float distanceToRay = Vector3.SqrMagnitude
                       ((Vector3)m_Solver.renderablePositions[i] - projected);

                   mu = Mathf.Max(0, mu);

                   float radius = m_Solver.principalRadii[i][0] * m_TouchRadius;

                   if (distanceToRay <= radius * radius && distanceToRay < closestDistance && mu < closestMu)
                   {
                       closestMu = mu;
                       closestDistance = distanceToRay;
                       m_PickedParticleIndices[touch.fingerId] = i;
                   }
               }

               if (m_PickedParticleIndices[touch.fingerId] >= 0)
               {
                   pickedParticleDepth = Camera.main.transform.InverseTransformVector((Vector3)m_Solver.renderablePositions
                       [m_PickedParticleIndices[touch.fingerId]] - Camera.main.transform.position).z;

                   Vector3 worldPosition = Camera.main.ScreenToWorldPoint
                           (new Vector3(Input.mousePosition.x, Input.mousePosition.y, pickedParticleDepth));

                   float x = m_Solver.renderablePositions[m_PickedParticleIndices[touch.fingerId]].x;
                   float y = m_Solver.renderablePositions[m_PickedParticleIndices[touch.fingerId]].y;
                   float z = m_Solver.renderablePositions[m_PickedParticleIndices[touch.fingerId]].z;
                   float w = m_Solver.renderablePositions[m_PickedParticleIndices[touch.fingerId]].w;

                   m_Solver.renderablePositions
                       [m_PickedParticleIndices[touch.fingerId]] =
                           new Vector4(x, y + m_TouchDistortionLevel, z, w);
               }

           }

       }
   }
}

I know the declaration of four floats is a little lazy but at this point I'm simply trying to get results. I'll opt later.

Renderable positions, as the name implies, are only to be used for rendering. They are generated from scratch at the end of every simulation step, but are not part of the simulation. So modifying them does not have any effect on physics at all, unlike regular positions. From the manual:

Quote:If the solver's interpolation mode is set to interpolate, solver.renderablePositions will contain temporally smoothed-out positions that won´t coincide with the actual particle positions at the end of the simulation step. That's why they are called "renderable": they are only used for smooth rendering, but they aren't needed by the solver and thus not calculated unless requested.

I assume you're going for a "squeeze-release" type of effect: particles should raise under a finger, and slowly fall back to y = 0 after the finger has moved away. Is this correct? if so, easiest way to achieve it would be using external forces, similar to what the default picker does:

- Calculate a spring force that strives to keep each particle at y = 0. This is what will cause particles to fall back to their position when a touch ceases.
- When there's a touch present, calculate a force opposite to the previous one, that strives to keep the particle at y = some positive value.
Reply
#3
Could you actually provide some code snippets on how to accomplish what you're describing? I'd appreciate it.

For instance:
Quote:- Calculate a spring force that strives to keep each particle at y = 0. This is what will cause particles to fall back to their position when a touch ceases.

- When there's a touch present, calculate a force opposite to the previous one, that strives to keep the particle at y = some positive value.
Reply
#4
(17-02-2020, 01:35 PM)GrimCaplan Wrote: Could you actually provide some code snippets on how to accomplish what you're describing? I'd appreciate it.

For instance:

Hi,

Spring forces are calculated like this, using Hooke's law:

Code:
F = -kx

where F is the force, k the spring stiffness, and x the displacement. If you also want to throw some damping in, simply remove some of the velocity:

Code:
F = -kx - vel * damp

Optionally, multiply by mass (that is, divide by inverse mass) if you want to make the spring stiffness mass-independent.
In your case, say we want a force that tries to keep the y coordinate at zero:

Code:
Vector4 targetPosition = solver.positions[particleIndex];
targetPosition.y = 0;
solver.externalForces[particleIndex] = ((targetPosition - solver.positions[particleIndex]) * springStiffness - velocity * springDamping) / solver.invMasses[particleIndex];

(Here, targetPosition - solver.positions[particleIndex] is the spring displacement, "x". springStiffness is k.)

That's it. When you detect a touch, add another force that fights this one, trying to make the particle rise to a given height:

Code:
Vector4 targetPosition = solver.positions[particleIndex];
targetPosition.y = <your height>;
solver.externalForces[particleIndex] += ((targetPosition - solver.positions[particleIndex]) * springStiffness - velocity * springDamping) / solver.invMasses[particleIndex];
Reply
#5
Thanks for clearing up my misunderstanding! My apologies for so many questions. It's not an easy system to learn!
Reply