Posts: 5
Threads: 2
Joined: Jul 2024
Reputation:
0
Hi,
I want to achieve the goal shown in the video above. The first thing I want to do is strip the rope. Is it possible to do this with Obi Rope? If so, how can I achieve it?
Additionally, I want to make my rope behave like the cables in the video, but I need to be able to cut the ropes and bind them together.
Thanks for your help,
https://youtu.be/yo1vWmFQyIM?t=58
Posts: 11
Threads: 2
Joined: Jan 2024
Reputation:
0
Wrote this reply on another thread but I think it is relevant.
I made a script for my game that connects multiple targets to a defined spline as an attachment. The rope is able to feed through the attachment with a desired friction.
Here's the code where the magic happens, maybe that can help you. The lambda for the distance constraint can be interpreted as the force stretching the rope element if you divide by time squared. Instead of manipulating _positionInElement and _elementIndex, I guess you want to update the cursor script instead.
Code: float sqrTime = stepTime * stepTime;
float forwardForce = -SolverLambda(startElement, solverConstraints) / sqrTime;
float backwardForce = -SolverLambda(endElement, solverConstraints) / sqrTime;
float totalForce = forwardForce - backwardForce;
if (totalForce > ForwardFriction) ApplyForceToMovement(totalForce - ForwardFriction, sqrTime);
else if (totalForce < -BackwardFriction) ApplyForceToMovement(totalForce + BackwardFriction, sqrTime);
Code: private float SolverLambda(int elementIndex, ObiConstraints<ObiDistanceConstraintsBatch> solverConstraints)
{
int batchCount = solverConstraints.GetBatchCount();
int index = elementIndex / batchCount;
int batchIndex = elementIndex % batchCount;
int offset = _rope.solverBatchOffsets[(int)Oni.ConstraintType.Distance][batchIndex];
return solverConstraints.batches[batchIndex].lambdas[offset + index];
}
Code: private void ApplyForceToMovement(float force, float sqrTime)
{
float movement = (force) * sqrTime;
movement = Mathf.Lerp(movement, 0, _movemementDamping);
int startElement = Mathf.Clamp(_elementIndex - 1, 0, _rope.elements.Count - 1);
float movementInElement = movement / _rope.elements[startElement].restLength;
_positionInElement += movementInElement;
if (_positionInElement < 0)
{
_positionInElement += 1f;
_positionInElement *= _rope.elements[startElement].restLength;
_elementIndex--;
_positionInElement /= _rope.elements[startElement].restLength;
} else if (_positionInElement > 1f)
{
_positionInElement -= 1f;
_positionInElement *= _rope.elements[startElement].restLength;
_elementIndex++;
_positionInElement /= _rope.elements[startElement].restLength;
}
_position = _elementIndex + _positionInElement;
}
Posts: 5
Threads: 2
Joined: Jul 2024
Reputation:
0
(09-08-2024, 09:40 AM)goosejordan Wrote: Wrote this reply on another thread but I think it is relevant.
I made a script for my game that connects multiple targets to a defined spline as an attachment. The rope is able to feed through the attachment with a desired friction.
Here's the code where the magic happens, maybe that can help you. The lambda for the distance constraint can be interpreted as the force stretching the rope element if you divide by time squared. Instead of manipulating _positionInElement and _elementIndex, I guess you want to update the cursor script instead.
Code: float sqrTime = stepTime * stepTime;
float forwardForce = -SolverLambda(startElement, solverConstraints) / sqrTime;
float backwardForce = -SolverLambda(endElement, solverConstraints) / sqrTime;
float totalForce = forwardForce - backwardForce;
if (totalForce > ForwardFriction) ApplyForceToMovement(totalForce - ForwardFriction, sqrTime);
else if (totalForce < -BackwardFriction) ApplyForceToMovement(totalForce + BackwardFriction, sqrTime);
Code: private float SolverLambda(int elementIndex, ObiConstraints<ObiDistanceConstraintsBatch> solverConstraints)
{
int batchCount = solverConstraints.GetBatchCount();
int index = elementIndex / batchCount;
int batchIndex = elementIndex % batchCount;
int offset = _rope.solverBatchOffsets[(int)Oni.ConstraintType.Distance][batchIndex];
return solverConstraints.batches[batchIndex].lambdas[offset + index];
}
Code: private void ApplyForceToMovement(float force, float sqrTime)
{
float movement = (force) * sqrTime;
movement = Mathf.Lerp(movement, 0, _movemementDamping);
int startElement = Mathf.Clamp(_elementIndex - 1, 0, _rope.elements.Count - 1);
float movementInElement = movement / _rope.elements[startElement].restLength;
_positionInElement += movementInElement;
if (_positionInElement < 0)
{
_positionInElement += 1f;
_positionInElement *= _rope.elements[startElement].restLength;
_elementIndex--;
_positionInElement /= _rope.elements[startElement].restLength;
} else if (_positionInElement > 1f)
{
_positionInElement -= 1f;
_positionInElement *= _rope.elements[startElement].restLength;
_elementIndex++;
_positionInElement /= _rope.elements[startElement].restLength;
}
_position = _elementIndex + _positionInElement;
}
Thank you for your answer but Is it relevant to stripping the rope like in the video that I shared? If you have example video that what this code do I would be very glad.
Posts: 11
Threads: 2
Joined: Jan 2024
Reputation:
0
09-08-2024, 10:36 AM
(This post was last modified: 09-08-2024, 10:57 AM by goosejordan.)
(09-08-2024, 10:21 AM)Ferhat123 Wrote: Thank you for your answer but Is it relevant to stripping the rope like in the video that I shared? If you have example video that what this code do I would be very glad.
Here's where I use it.
This I think is a multi-stage rocket of a problem.
The code is for the interaction of clamping the tool on the cable and then pulling the cable through it.
Once that is in place you can store the position on the rope that was first attached to. Then pass that information to the rendering.
If you are talking about the rendering of a stripped rope I can't help you much right now. But I think you'd need a custom shader or modify the mesh extruder to achieve this.
Here's the full script, that should be a lot more helpful
Code: using System;
using System.Collections.Generic;
using Obi;
using UnityEngine;
using UnityEngine.Splines;
public class RopeCursorPin : MonoBehaviour
{
private ObiPinConstraintsBatch batch = new ObiPinConstraintsBatch();
[SerializeField] private ObiRopeBase _rope;
[SerializeField] private Vector3 _offset;
[SerializeField] private Vector3 _direction;
[SerializeField] private float _clampLength;
[SerializeField] private float _position;
[SerializeField] private float _pinCompress = 1f;
[SerializeField] private float _fadeToCompliance;
[SerializeField] private SplineContainer _spline;
[SerializeField] private float _frictionForce;
[SerializeField] private float _movemementDamping;
public float ForwardFriction;
public float BackwardFriction;
private int _elementIndex;
private float _positionInElement;
private ObiColliderBase _obiCollider;
private float _prevPosition = 0;
private float _currentForce = 0;
private Vector3 _forwardsPull;
private Vector3 _backwardsPull;
private bool _isBound = false;
private bool _isRegistered = false;
[NonSerialized] private int[] _solverIndices;
[NonSerialized] private Vector3[] _localDirections;
[NonSerialized] private Vector3[] _localOffsets;
[NonSerialized] private int attachedColliderHandleIndex;
public ObiRopeBase Rope
{
get { return _rope; }
}
public bool IsBound
{
get { return _isBound; }
}
private void Awake()
{
_obiCollider = GetComponent<ObiColliderBase>();
_solverIndices = new int[100];
_localDirections = new Vector3[100];
_localOffsets = new Vector3[100];
}
public float BreakThreshold
{
get { return _frictionForce; }
set
{
if (!Mathf.Approximately(value, _frictionForce))
{
_frictionForce = value;
for (int i = 0; i < _solverIndices.Length; ++i)
batch.breakThresholds[i] = _frictionForce;
}
}
}
private void OnEnable()
{
if (_rope == null || _isRegistered) return;
_rope.OnPrepareStep += Actor_OnPrepareStep;
_rope.OnEndStep += Actor_OnEndStep;
_isRegistered = true;
}
private void OnDisable()
{
if (_rope == null || !_isRegistered) return;
_rope.OnPrepareStep -= Actor_OnPrepareStep;
_rope.OnEndStep -= Actor_OnEndStep;
_isRegistered = false;
}
public void TryCreateContact(ObiRope rope, HashSet<int> particles)
{
if (_isBound || rope == null) return;
Matrix4x4 solver2Grabber = transform.worldToLocalMatrix * rope.solver.transform.localToWorldMatrix;
// and its inverse:
Matrix4x4 grabber2Solver = solver2Grabber.inverse;
//First find longest unbroken link
int longestStart = 0;
int longestEnd = 0;
int currentStart = 0;
for (int i = 0; i < rope.elements.Count; i++)
{
if (particles.Contains(rope.elements[i].particle1)) continue;
if (i - currentStart > longestEnd - longestStart)
{
longestStart = currentStart;
longestEnd = i;
}
currentStart = i + 1;
}
if (longestStart >= longestEnd) return;
_rope = rope;
_spline.Spline.Clear();
for (int i = longestStart; i < longestEnd; i++)
{
Vector3 position =
_rope.solver.positions[_rope.solverIndices[i]];
_spline.Spline.Add(new BezierKnot(solver2Grabber.MultiplyPoint3x4(position)), TangentMode.AutoSmooth);
}
_elementIndex = longestStart;
_position = _elementIndex;
OnEnable();
}
public void ReleaseContact()
{
OnDisable();
if (_isBound && _rope != null)
{
var pinConstraints = _rope.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<ObiPinConstraintsBatch>;
pinConstraints.RemoveBatch(batch);
batch.Clear();
_rope.SetConstraintsDirty(Oni.ConstraintType.Pin);
_isBound = false;
}
_rope = null;
}
void Actor_OnPrepareStep(ObiActor act, float stepTime)
{
// Attachments must be updated at the start of the step, before performing any simulation.
if (_prevPosition != _position)
{
float elementLength = 1f;//_rope.elements[0].restLength;
_elementIndex = Mathf.FloorToInt(_position / elementLength);
_positionInElement = Mathf.Repeat(_position, elementLength) / elementLength;
UpdateConstraints(stepTime);
_prevPosition = _position;
}
}
private void Actor_OnEndStep(ObiActor act, float stepTime)
{
UpdateMovement(stepTime);
}
private void UpdateMovement(float stepTime)
{
if (!IsBound || !enabled || !_rope.isLoaded) return;
int startElement = Mathf.Clamp(_elementIndex - 1, 0, _rope.elements.Count - 1);
int endElement = Mathf.Clamp(_elementIndex + batch.activeConstraintCount - 1, 0, _rope.elements.Count - 1);
if (startElement >= endElement) return;
var solver = _rope.solver;
var solverConstraints = solver.GetConstraintsByType(Oni.ConstraintType.Distance) as ObiConstraints<ObiDistanceConstraintsBatch>;
float sqrTime = stepTime * stepTime;
float forwardForce = -SolverLambda(startElement, solverConstraints) / sqrTime;
float backwardForce = -SolverLambda(endElement, solverConstraints) / sqrTime;
float totalForce = forwardForce - backwardForce;
if (totalForce > ForwardFriction) ApplyForceToMovement(totalForce - ForwardFriction, sqrTime);
else if (totalForce < -BackwardFriction) ApplyForceToMovement(totalForce + BackwardFriction, sqrTime);
}
//Vector3 pullStart = LocalPullDirection(0, -1).normalized;
//Vector3 pullEnd = LocalPullDirection(batch.activeConstraintCount - 1, 1).normalized;
//_forwardsPull = pullStart * forwardForce;
//_backwardsPull = pullEnd * backwardForce;
/*
var actorConstraints = _rope.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<ObiDistanceConstraintsBatch>;
var solverConstraints = solver.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<ObiPinConstraintsBatch>;
if (actorConstraints != null && batch != null)
{
int pinBatchIndex = actorConstraints.batches.IndexOf(batch);
if (pinBatchIndex >= 0 && pinBatchIndex < _rope.solverBatchOffsets[(int)Oni.ConstraintType.Pin].Count)
{
int offset = _rope.solverBatchOffsets[(int)Oni.ConstraintType.Pin][pinBatchIndex];
var solverBatch = solverConstraints.batches[pinBatchIndex];
Vector3 pullStart = LocalPullDirection(0, -1).normalized;
Vector3 pullEnd = LocalPullDirection(batch.activeConstraintCount - 1, 1).normalized;
float sqrTime = stepTime * stepTime;
float forwardForce = -solverBatch.lambdas[offset * 4 + 3] / sqrTime;
float backwardForce = -solverBatch.lambdas[(offset + batch.activeConstraintCount - 1) * 4 + 3] / sqrTime;
//forwardForce *= Vector3.Dot(pullStart, _localDirections[0]);
//backwardForce *= Vector3.Dot(pullEnd, _localDirections[batch.activeConstraintCount - 1]);
_forwardsPull = pullStart * forwardForce;
_backwardsPull = pullEnd * backwardForce;
float totalForce = forwardForce - backwardForce;
if (totalForce > ForwardFriction) ApplyForceToMovement(totalForce - ForwardFriction, sqrTime);
else if (totalForce < -BackwardFriction) ApplyForceToMovement(totalForce + BackwardFriction, sqrTime);
}
}*/
private float SolverLambda(int elementIndex, ObiConstraints<ObiDistanceConstraintsBatch> solverConstraints)
{
int batchCount = solverConstraints.GetBatchCount();
int index = elementIndex / batchCount;
int batchIndex = elementIndex % batchCount;
int offset = _rope.solverBatchOffsets[(int)Oni.ConstraintType.Distance][batchIndex];
return solverConstraints.batches[batchIndex].lambdas[offset + index];
}
private Vector3 LocalPullDirection(int pinIndex, int offset)
{
var pAttachment = _solverIndices[pinIndex];
var nextP = _solverIndices[pinIndex] + offset;
var pAttachmentPos= _rope.solver.positions[pAttachment];
var nextPPos= _rope.solver.positions[nextP];
Vector3 diff = pAttachmentPos - nextPPos;
return transform.InverseTransformDirection(diff);
}
private void ApplyForceToMovement(float force, float sqrTime)
{
float movement = (force) * sqrTime;
movement = Mathf.Lerp(movement, 0, _movemementDamping);
int startElement = Mathf.Clamp(_elementIndex - 1, 0, _rope.elements.Count - 1);
float movementInElement = movement / _rope.elements[startElement].restLength;
_positionInElement += movementInElement;
if (_positionInElement < 0)
{
_positionInElement += 1f;
_positionInElement *= _rope.elements[startElement].restLength;
_elementIndex--;
_positionInElement /= _rope.elements[startElement].restLength;
} else if (_positionInElement > 1f)
{
_positionInElement -= 1f;
_positionInElement *= _rope.elements[startElement].restLength;
_elementIndex++;
_positionInElement /= _rope.elements[startElement].restLength;
}
_position = _elementIndex + _positionInElement;
}
private void UpdateConstraints(float stepTime)
{
float splineLength = _spline != null ? _spline.CalculateLength() : 0f;
var pinConstraints = _rope.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<ObiPinConstraintsBatch>;
pinConstraints.RemoveBatch(batch);
batch.Clear();
float firstParticleRestLength = _rope.elements[0].restLength;
int pinCount = 0;
if (_elementIndex >= _rope.elements.Count)
{
batch.activeConstraintCount = pinCount;
pinConstraints.AddBatch(batch);
_isBound = true;
_rope.SetConstraintsDirty(Oni.ConstraintType.Pin);
return;
}
float accumLength = (1 - _positionInElement) * (_elementIndex >= 0 ? _rope.elements[_elementIndex].restLength : firstParticleRestLength);
float remainderLength = accumLength;
float clampLength = _spline != null
? _spline.Spline.CalculateLength(_rope.transform.worldToLocalMatrix)
: _clampLength;
float pinCompress = _spline != null ? _pinCompress / _spline.transform.lossyScale.x : _pinCompress;
for (int i = 0; accumLength < clampLength; i++)
{
int elementIndex = _elementIndex + i;
if (elementIndex < 0 || elementIndex >= _rope.activeParticleCount)
{
accumLength += firstParticleRestLength * pinCompress;
continue;
}
float restLength = _rope.elements[i].restLength;
remainderLength = remainderLength % restLength;
Vector3 offset = _offset + _direction * accumLength;
Vector3 direction = _direction;
if (_spline != null)
{
float t = _spline.Spline.ConvertIndexUnit(
accumLength, PathIndexUnit.Distance,
PathIndexUnit.Normalized);
offset = transform.InverseTransformPoint(_spline.EvaluatePosition(t));
direction = transform.InverseTransformDirection(_spline.EvaluateTangent(t));
}
accumLength += restLength * pinCompress;
float complianceFade = 0;
if (i == 0) complianceFade = 1f - remainderLength / restLength;
else if (accumLength >= clampLength) complianceFade = remainderLength / restLength;
remainderLength += restLength;
_solverIndices[pinCount] = _rope.solverIndices[elementIndex];
batch.AddConstraint(_solverIndices[pinCount], _obiCollider, offset, Quaternion.identity, complianceFade * _fadeToCompliance, 100, float.PositiveInfinity);
_localDirections[pinCount] = direction.normalized;
_localOffsets[pinCount] = offset;
pinCount++;
}
batch.activeConstraintCount = pinCount;
pinConstraints.AddBatch(batch);
_isBound = true;
_rope.SetConstraintsDirty(Oni.ConstraintType.Pin);
}
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
if (!Application.isPlaying) return;
if (!_isBound) return;
Gizmos.DrawLine(transform.TransformPoint(_localOffsets[0]),
transform.TransformPoint(_localOffsets[0]) + transform.TransformDirection(_forwardsPull)*.001f);
Gizmos.DrawLine(transform.TransformPoint(_localOffsets[batch.activeConstraintCount - 1]),
transform.TransformPoint(_localOffsets[batch.activeConstraintCount - 1]) + transform.TransformDirection(_backwardsPull)*.001f);
}
#endif
}
Posts: 6,348
Threads: 24
Joined: Jun 2017
Reputation:
400
Obi Owner:
09-08-2024, 11:18 AM
(This post was last modified: 09-08-2024, 11:19 AM by josemendez.)
(08-08-2024, 01:39 PM)Ferhat123 Wrote: Hi,
I want to achieve the goal shown in the video above. The first thing I want to do is strip the rope. Is it possible to do this with Obi Rope? If so, how can I achieve it?
Hi!
No, it's not possible. The reason is that a cable is not a single body but two: the copper core and the insulation around, which is a hollow plastic tube. You will have to "fake" this: one way I can think of is by using a custom vertex shader that pushes vertices inwards in the direction opposite to their normal, and change their color. This will allow you to make it look like the cable has had the insulation removed.
(08-08-2024, 01:39 PM)Ferhat123 Wrote: Additionally, I want to make my rope behave like the cables in the video, but I need to be able to cut the ropes and bind them together.
You can cut / bind Obi ropes, but can't cut rods because they do not support topological changes (see ropes/rods). Rigid cables must be simulated using rods - otherwise they would be "flaccid"- so I'm afraid you're out of luck here.
kind regards,
|