(12-07-2024, 08:50 AM)josemendez Wrote: Notice how the order in which particles appear in the rope is different to the order in which they are stored, and how there's a cut in the rope (as particles C and B aren't connected by any element). There's 5 particles but only 3 elements. You need to reflect this in the data you store for your ropes, otherwise particles won't be properly connected. This can only be done by storing both particles and elements, altering the order in which you store the particles is not enough.
Hello, it's been a long time. I apologize that I have been too busy catching up on progress and developing new features to reply to the post.
Your suggestions to me were enlightening. I realized that my previous approach was too fixated on the internal logic of the rope, when in fact the simplest method is to describe the specific shape of the rope with a series of coordinates and rotations, and that's enough.
The day after reading your response, I successfully implemented the function to restore the specific shape of the rope from JSON data. In fact, stepping away from the work environment and thinking about the problem in a completely new environment or with a fresh mindset is more likely to lead to new discoveries.
Here are the specific steps for storing and restoring the shape of the rope using JSON, which I hope will be convenient for any developers in need and can help you all
- Save the restLength of the obi rope. This helps to describe the specific length the rope needs when restoring, which will also affect the space required for the position to take effect in the solver.
- Save the positions of the controlPoints. In my case, controlPoints mean some twist points that the rope must pass through in addition to the starting and ending points. For example, if you want to make the rope into an M shape, then there are a total of 5 controlPoints.
- Obtain the particle indices in the rope.elements, and then get the position and rotation of the particle in the solver, and it is very important to save in order, because this means a complete and coherent rope path.
- Generate a new instance of the obi rope in any scene, use controlPoints to define the shape of the rope, and generate enough particles.
- After generating a new rope instance, start the work of restoring data from JSON.
- Restore the restLength and call ChangeLength. This will ensure that the rope is adjusted to have enough length and space to accommodate the total number of particles needed to restore the shape of the rope.
- Restore the previously saved positions and rotations of the particles, and it is very important to read in order.
(17-08-2024, 09:02 AM)tapLucas Wrote: Here is the detailed code:Save to Json:
Code:
public JSONObject ToJson()
{
var restLength = gameObject.GetComponent<ObiRope>().restLength;
jsonObject.AddField("restLength", restLength);
jsonObject.AddField("particles", GetRopeParticleData());
jsonObject.AddField("controlPoints", GetRopeControlPoints());
return jsonObject;
}
JSONObject GetRopeControlPoints()
{
var rope = gameObject.GetComponent<ObiRope>();
var pointsArray = JSONObject.emptyArray;
foreach (var obiWingedPoint in rope.ropeBlueprint.path.m_Points.data)
{
pointsArray.Add((JSONNode)obiWingedPoint.position);
}
var jsonObject = JSONObject.emptyObject;
jsonObject.AddField("points", pointsArray);
return jsonObject;
}
JSONObject GetRopeParticleData()
{
var rope = gameObject.GetComponent<ObiRope>();
var solver = rope.solver;
var elementsArray = JSONObject.emptyArray;
foreach (var element in rope.elements)
{
var json = JSONObject.emptyObject;
json.AddField("particle1", (JSONNode)solver.positions[element.particle1]);
json.AddField("particle2", (JSONNode)solver.positions[element.particle2]);
json.AddField("restLength", element.restLength);
elementsArray.Add(json);
}
var ropeObject = JSONObject.emptyObject;
ropeObject.AddField("elements", elementsArray);
return ropeObject;
}
Instantiate rope by controlPoints:
Code:
private List<ObiWingedPoint> GetControlPoints(JSONObject ropeJsonObject)
{
var controlPoints = ropeJsonObject["controlPoints"];
var pointsArray = controlPoints["points"];
List<ObiWingedPoint> Points = new List<ObiWingedPoint>();
if (pointsArray != null)
{
for (var i = 0; i < pointsArray.list.Count; i++)
{
var position = JSONNode.ToVector3_Short(pointsArray[i]);
Points.Add(new ObiWingedPoint(Vector3.zero, position, Vector3.zero));
}
}
return Points;
}
private GameObject InstantiateRope(GameObject objectToAttach1, GameObject objectToAttach2, List<ObiWingedPoint> controlPoints)
{
var(rpObject, isNewCreate) = ObjectPool.Instance.GetRopeFromPool();
ObiRope rope = rpObject.GetComponent<ObiRope>();
Transform transformA = objectToAttach1.transform;
Transform transformB = objectToAttach2.transform;
Vector3 positionA = transformA.position;
Vector3 positionB = transformB.position;
Vector3 objectScale = transformA.localScale;
Vector3 offset = new Vector3(0, 0.05f, 0);
Vector3 startPositionLS = transform.InverseTransformPoint(positionA + offset);
Vector3 endPositionLS = transform.InverseTransformPoint(positionB + offset);
//Vector3 tangentLS = (endPositionLS - startPositionLS).normalized;
Vector3 tangentLS = Vector3.zero;
ObiRopeBlueprint blueprint = ScriptableObject.CreateInstance<ObiRopeBlueprint>();
blueprint.path.Clear();
blueprint.pooledParticles = 50;
blueprint.resolution = blueprintResolutionDefault;
// Build the rope path:
int filter = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 0);
int posIndex = 0;
foreach (var obiWingedPoint in controlPoints)
{
string pointName = $"pos_{posIndex}";
if (posIndex == 0)
{
pointName = "start";
}
else if (posIndex == controlPoints.Count - 1)
{
pointName = "end";
}
blueprint.path.AddControlPoint(obiWingedPoint.position, obiWingedPoint.inTangent, obiWingedPoint.outTangent,
Vector3.up, ropeMass, 0.1f, ropeThickness, filter, Color.yellow, pointName);
posIndex++;
}
blueprint.path.FlushEvents();
// Generate particles/constraints:
//blueprint.GenerateImmediate();
if (!isNewCreate)
{
var attachComps = rpObject.GetComponents<ObiParticleAttachment>();
foreach (var comp in attachComps)
{
Destroy(comp);
}
}
ObiParticleAttachment attachment1 = rpObject.AddComponent<ObiParticleAttachment>();
ObiParticleAttachment attachment2 = rpObject.AddComponent<ObiParticleAttachment>();
// Set the blueprint:
rope.ropeBlueprint = blueprint;
// Attach both ends:
attachment1.target = transformA;
attachment2.target = transformB;
attachment1.particleGroup = blueprint.groups[0];
attachment2.particleGroup = blueprint.groups.Last();
// Parent the actor under a solver to start the simulation:
rpObject.transform.SetParent(obiSolverObject.transform);
return rpObject;
}
After instantiation, call FromJson:
Code:
private JSONObject particleDataToRestore;
private float ropeLength;
public void FromJson(JSONObject jsonObject)
{
ropeLength = jsonObject["restLength"].floatValue;
particleDataToRestore = jsonObject["particles"];
StartCoroutine(PostFromJson());
}
IEnumerator PostFromJson()
{
yield return new WaitForEndOfFrame(); // may not necessary
gameObject.GetComponent<ObiRopeCursor>().ChangeLength(RopeLength);
yield return new WaitForEndOfFrame(); // may not necessary
SetElementsPosition(gameObject.GetComponent<ObiRope>());
}
void SetElementsPosition(ObiActor actor)
{
var solver = actor.solver;
var rope = actor as ObiRope;
var elementsArray = particleDataToRestore["elements"];
if (elementsArray != null && elementsArray.type == JSONObject.Type.Array)
{
int elementCount = elementsArray.list.Count;
for (var i = 0; i < elementCount; i++)
{
var element = elementsArray[i];
if (i >= rope.elements.Count)
{
Debug.LogWarning("Out of Range Exception ! ");
continue;
}
var restLength = float.Parse(element["restLength"].stringValue);
rope.elements[i].restLength = restLength;
rope.elements[i].constraintForce = 0;
rope.elements[i].tearResistance = 1;
var particle1 = JSONNode.ToVector3_Short(element["particle1"]);
var particle2 = JSONNode.ToVector3_Short(element["particle2"]);
solver.positions[rope.elements[i].particle1] = particle1;
solver.positions[rope.elements[i].particle2] = particle2;
solver.prevPositions[rope.elements[i].particle1] = particle1;
solver.prevPositions[rope.elements[i].particle2] = particle2;
solver.velocities[rope.elements[i].particle1] = Vector4.zero;
solver.velocities[rope.elements[i].particle2] = Vector4.zero;
solver.angularVelocities[rope.elements[i].particle1] = Vector4.zero;
solver.angularVelocities[rope.elements[i].particle2] = Vector4.zero;
}
}
}
Unfortunately, at present, the primary target platform for my project is WEBGL. After I packaged the test project, I found that the running efficiency was extremely poor and the frame rate was very low. During the first project survey, after implementing the obi rope plugin, I had packaged a test project once, and at that time there were only 2 ropes in the scene, and I mistakenly thought it was due to the data and jobs not being well configured. After several days of continuous attempts and data collection, I determined that the obi rope is not suitable for WEBGL, and I have to start over with the rope code. (At least as of today, August 17, 2024.)