修改水

This commit is contained in:
2026-01-01 22:00:33 +08:00
parent 040a222bd6
commit 9ceffccd39
1800 changed files with 103929 additions and 139495 deletions

View File

@@ -1,33 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Obi.Samples
{
public class CableCarController : MonoBehaviour
{
public ObiPinhole pinhole;
public float carSpeed = 1;
// Update is called once per frame
void Update()
{
float speed = 0;
if (Input.GetKey(KeyCode.W))
{
speed = carSpeed;
}
if (Input.GetKey(KeyCode.S))
{
speed = -carSpeed;
}
pinhole.motorSpeed = speed;
if (Input.GetKeyDown(KeyCode.Space))
{
pinhole.friction = pinhole.friction > 0.5f ? 0 : 1;
}
}
}
}

View File

@@ -2,78 +2,27 @@
using System.Collections.Generic;
using UnityEngine;
namespace Obi.Samples
{
public class CharacterControl2D : MonoBehaviour
{
public class CharacterControl2D : MonoBehaviour {
public float floorRaycastDistance = 1.2f;
public float acceleration = 10;
public float maxSpeed = 8;
public float jumpPower = 2;
[Header("Grounded")]
public float acceleration = 80;
public float maxSpeed = 6;
public float damping = 0.005f;
public float jumpPower = 10;
private Rigidbody unityRigidbody;
public void Awake(){
unityRigidbody = GetComponent<Rigidbody>();
}
[Header("Airborne")]
public float airAcceleration = 16;
public float airMaxSpeed = 12;
public float extraGravity = -12;
void FixedUpdate () {
[Header("Auto upright")]
public Vector3 centerOfMass = new Vector3(0, -0.25f, 0);
public float P = 2;
public float D = 0.1f;
private Rigidbody unityRigidbody;
private float axis;
private bool grounded;
private float error;
private float prevError;
public void Awake()
{
unityRigidbody = GetComponent<Rigidbody>();
unityRigidbody.centerOfMass = centerOfMass;
}
private void Update()
{
axis = Input.GetAxisRaw("Horizontal");
grounded = Physics.Raycast(new Ray(transform.position, -Vector3.up), floorRaycastDistance);
if (Input.GetButtonDown("Jump") && grounded)
unityRigidbody.AddForce(Vector3.up * jumpPower, ForceMode.VelocityChange);
}
private void FixedUpdate()
{
var velocity = unityRigidbody.linearVelocity;
prevError = error;
error = Vector3.SignedAngle(unityRigidbody.transform.up, Vector3.up, Vector3.forward);
if (grounded)
{
float accel = axis * acceleration * Time.deltaTime;
if ((velocity.x < maxSpeed && accel > 0) || (velocity.x > -maxSpeed && accel < 0))
velocity.x += accel;
if (Mathf.Approximately(axis, 0))
velocity.x *= Mathf.Pow(damping, Time.deltaTime);
unityRigidbody.AddTorque(new Vector3(0, 0, error * P + (error - prevError) / Time.deltaTime * D));
}
else
{
float accel = axis * airAcceleration * Time.deltaTime;
if ((velocity.x < airMaxSpeed && accel > 0) || (velocity.x > -airMaxSpeed && accel < 0))
velocity.x += accel;
velocity.y += extraGravity * Time.deltaTime;
}
unityRigidbody.linearVelocity = velocity;
}
}
}
unityRigidbody.AddForce(new Vector3(Input.GetAxis("Horizontal")*acceleration,0,0));
unityRigidbody.linearVelocity = Vector3.ClampMagnitude(unityRigidbody.linearVelocity,maxSpeed);
if (Input.GetButtonDown("Jump")){
unityRigidbody.AddForce(Vector3.up * jumpPower,ForceMode.VelocityChange);
}
}
}

View File

@@ -1,46 +1,36 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Obi;
namespace Obi.Samples
{
public class CraneController : MonoBehaviour
{
public class CraneController : MonoBehaviour {
ObiRopeCursor cursor;
ObiRope rope;
public float speed = 1;
ObiRopeCursor cursor;
ObiRope rope;
// Use this for initialization
void Start()
{
cursor = GetComponentInChildren<ObiRopeCursor>();
rope = cursor.GetComponent<ObiRope>();
}
// Use this for initialization
void Start () {
cursor = GetComponentInChildren<ObiRopeCursor>();
rope = cursor.GetComponent<ObiRope>();
}
// Update is called once per frame
void Update () {
if (Input.GetKey(KeyCode.W)){
if (rope.restLength > 6.5f)
cursor.ChangeLength(rope.restLength - 1f * Time.deltaTime);
}
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.W))
{
if (rope.restLength > 6.5f)
cursor.ChangeLength(-speed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.S)){
cursor.ChangeLength(rope.restLength + 1f * Time.deltaTime);
}
if (Input.GetKey(KeyCode.S))
{
cursor.ChangeLength(speed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.A)){
transform.Rotate(0,Time.deltaTime*15f,0);
}
if (Input.GetKey(KeyCode.A))
{
transform.Rotate(0, Time.deltaTime * 15f, 0);
}
if (Input.GetKey(KeyCode.D))
{
transform.Rotate(0, -Time.deltaTime * 15f, 0);
}
}
}
}
if (Input.GetKey(KeyCode.D)){
transform.Rotate(0,-Time.deltaTime*15f,0);
}
}
}

View File

@@ -1,53 +1,45 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Obi;
namespace Obi.Samples
[RequireComponent(typeof(ObiRope))]
public class CursorController : MonoBehaviour
{
[RequireComponent(typeof(ObiRope))]
public class CursorController : MonoBehaviour
ObiRopeCursor cursor;
ObiRope rope;
public float minLength = 0.1f;
public float speed = 1;
// Use this for initialization
void Start ()
{
public float minLength = 0.1f;
public float speed = 1;
private ObiRopeCursor cursor;
private ObiRope rope;
void OnEnable()
rope = GetComponent<ObiRope>();
cursor = GetComponent<ObiRopeCursor>();
}
// Update is called once per frame
void Update ()
{
if (Input.GetKey(KeyCode.W) && cursor != null)
{
rope = GetComponent<ObiRope>();
cursor = GetComponent<ObiRopeCursor>();
}
if (rope.restLength > minLength)
cursor.ChangeLength(rope.restLength - speed * Time.deltaTime);
}
void Update()
if (Input.GetKey(KeyCode.S) && cursor != null)
{
float change = 0;
cursor.ChangeLength(rope.restLength + speed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.W) && cursor != null)
{
change -= speed * Time.deltaTime;
}
if (Input.GetKey(KeyCode.A)){
rope.transform.Translate(Vector3.left * Time.deltaTime,Space.World);
}
if (Input.GetKey(KeyCode.S) && cursor != null)
{
change += speed * Time.deltaTime;
}
if (Input.GetKey(KeyCode.D)){
rope.transform.Translate(Vector3.right * Time.deltaTime,Space.World);
}
if (rope.restLength + change < minLength)
change = minLength - rope.restLength;
cursor.ChangeLength(change);
if (Input.GetKey(KeyCode.A))
{
rope.transform.Translate(Vector3.left * Time.deltaTime, Space.World);
}
if (Input.GetKey(KeyCode.D))
{
rope.transform.Translate(Vector3.right * Time.deltaTime, Space.World);
}
}
}
}
}

View File

@@ -1,7 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Obi;
/**
* Sample component that shows how to use Obi Rope to create a grappling hook for a 2.5D game.
@@ -15,213 +15,199 @@ using UnityEngine;
* If complex interaction is required with the scene, a purely geometry-based approach (ala Worms ninja rope) can
* be the right choice under certain circumstances.
*/
namespace Obi.Samples
public class ExtendableGrapplingHook : MonoBehaviour
{
public class ExtendableGrapplingHook : MonoBehaviour
public ObiSolver solver;
public ObiCollider character;
public Material material;
public ObiRopeSection section;
[Range(0, 1)]
public float hookResolution = 0.5f;
public float hookExtendRetractSpeed = 2;
public float hookShootSpeed = 30;
public int particlePoolSize = 100;
private ObiRope rope;
private ObiRopeBlueprint blueprint;
private ObiRopeExtrudedRenderer ropeRenderer;
private ObiRopeCursor cursor;
private RaycastHit hookAttachment;
void Awake()
{
public ObiSolver solver;
public ObiCollider character;
// Create both the rope and the solver:
rope = gameObject.AddComponent<ObiRope>();
ropeRenderer = gameObject.AddComponent<ObiRopeExtrudedRenderer>();
ropeRenderer.section = section;
ropeRenderer.uvScale = new Vector2(1, 4);
ropeRenderer.normalizeV = false;
ropeRenderer.uvAnchor = 1;
rope.GetComponent<MeshRenderer>().material = material;
public Material material;
public ObiRopeSection section;
// Setup a blueprint for the rope:
blueprint = ScriptableObject.CreateInstance<ObiRopeBlueprint>();
blueprint.resolution = 0.5f;
blueprint.pooledParticles = particlePoolSize;
[Range(0, 1)]
public float hookResolution = 0.5f;
public float hookExtendRetractSpeed = 2;
public float hookShootSpeed = 30;
public int particlePoolSize = 100;
// Tweak rope parameters:
rope.maxBending = 0.02f;
private ObiRope rope;
private ObiRopeBlueprint blueprint;
private ObiRopeExtrudedRenderer ropeRenderer;
// Add a cursor to be able to change rope length:
cursor = rope.gameObject.AddComponent<ObiRopeCursor>();
cursor.cursorMu = 0;
cursor.direction = true;
}
private ObiRopeCursor cursor;
private void OnDestroy()
{
DestroyImmediate(blueprint);
}
private RaycastHit hookAttachment;
/**
* Raycast against the scene to see if we can attach the hook to something.
*/
private void LaunchHook()
{
void Awake()
// Get the mouse position in the scene, in the same XY plane as this object:
Vector3 mouse = Input.mousePosition;
mouse.z = transform.position.z - Camera.main.transform.position.z;
Vector3 mouseInScene = Camera.main.ScreenToWorldPoint(mouse);
// Get a ray from the character to the mouse:
Ray ray = new Ray(transform.position, mouseInScene - transform.position);
// Raycast to see what we hit:
if (Physics.Raycast(ray, out hookAttachment))
{
// Create both the rope and the solver:
rope = gameObject.AddComponent<ObiRope>();
ropeRenderer = gameObject.AddComponent<ObiRopeExtrudedRenderer>();
ropeRenderer.section = section;
ropeRenderer.uvScale = new Vector2(1, 4);
ropeRenderer.normalizeV = false;
ropeRenderer.uvAnchor = 1;
ropeRenderer.material = material;
// Setup a blueprint for the rope:
blueprint = ScriptableObject.CreateInstance<ObiRopeBlueprint>();
blueprint.resolution = 0.5f;
blueprint.pooledParticles = particlePoolSize;
// Tweak rope parameters:
rope.maxBending = 0.02f;
// Add a cursor to be able to change rope length:
cursor = rope.gameObject.AddComponent<ObiRopeCursor>();
cursor.cursorMu = 0;
cursor.direction = true;
// We actually hit something, so attach the hook!
StartCoroutine(AttachHook());
}
private void OnDestroy()
}
private IEnumerator AttachHook()
{
yield return null;
// Clear pin constraints:
var pinConstraints = rope.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<ObiPinConstraintsBatch>;
pinConstraints.Clear();
Vector3 localHit = rope.transform.InverseTransformPoint(hookAttachment.point);
// Procedurally generate the rope path (just a short segment, as we will extend it over time):
int filter = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 0);
blueprint.path.Clear();
blueprint.path.AddControlPoint(Vector3.zero, Vector3.zero, Vector3.zero, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "Hook start");
blueprint.path.AddControlPoint(localHit.normalized * 0.5f, Vector3.zero, Vector3.zero, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "Hook end");
blueprint.path.FlushEvents();
// Generate the particle representation of the rope (wait until it has finished):
yield return blueprint.Generate();
// Set the blueprint (this adds particles/constraints to the solver and starts simulating them).
rope.ropeBlueprint = blueprint;
// wait one frame:
yield return null;
rope.GetComponent<MeshRenderer>().enabled = true;
// set masses to zero, as we're going to override positions while we extend the rope:
for (int i = 0; i < rope.activeParticleCount; ++i)
solver.invMasses[rope.solverIndices[i]] = 0;
float currentLength = 0;
// while the last particle hasn't reached the hit, extend the rope:
while (true)
{
DestroyImmediate(blueprint);
}
// calculate rope origin in solver space:
Vector3 origin = solver.transform.InverseTransformPoint(rope.transform.position);
/**
* Raycast against the scene to see if we can attach the hook to something.
*/
private void LaunchHook()
{
// update direction and distance to hook point:
Vector3 direction = hookAttachment.point - origin;
float distance = direction.magnitude;
direction.Normalize();
// Get the mouse position in the scene, in the same XY plane as this object:
Vector3 mouse = Input.mousePosition;
mouse.z = transform.position.z - Camera.main.transform.position.z;
Vector3 mouseInScene = Camera.main.ScreenToWorldPoint(mouse);
// increase length:
currentLength += hookShootSpeed * Time.deltaTime;
// Get a ray from the character to the mouse:
Ray ray = new Ray(transform.position, mouseInScene - transform.position);
// Raycast to see what we hit:
if (Physics.Raycast(ray, out hookAttachment))
// if we have reached the desired length, break the loop:
if (currentLength >= distance)
{
// We actually hit something, so attach the hook!
StartCoroutine(AttachHook());
cursor.ChangeLength(distance);
break;
}
}
// change rope length (clamp to distance between rope origin and hook to avoid overshoot)
cursor.ChangeLength(Mathf.Min(distance, currentLength));
private void LayParticlesInStraightLine(Vector3 origin, Vector3 direction)
{
// placing all particles in a straight line, respecting rope length
// iterate over all particles in sequential order, placing them in a straight line while
// respecting element length:
float length = 0;
for (int i = 0; i < rope.elements.Count; ++i)
{
int p1 = rope.elements[i].particle1;
int p2 = rope.elements[i].particle2;
solver.prevPositions[p1] = solver.positions[p1] = origin + direction * length;
solver.positions[rope.elements[i].particle1] = origin + direction * length;
solver.positions[rope.elements[i].particle2] = origin + direction * (length + rope.elements[i].restLength);
length += rope.elements[i].restLength;
solver.prevPositions[p2] = solver.positions[p2] = origin + direction * length;
}
}
private IEnumerator AttachHook()
{
yield return null;
// Clear pin constraints:
var pinConstraints = rope.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<ObiPinConstraintsBatch>;
pinConstraints.Clear();
Vector3 localHit = rope.transform.InverseTransformPoint(hookAttachment.point);
// Procedurally generate the rope path (just a short segment, as we will extend it over time):
int filter = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 0);
blueprint.path.Clear();
blueprint.path.AddControlPoint(Vector3.zero, Vector3.zero, Vector3.zero, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "Hook start");
blueprint.path.AddControlPoint(localHit.normalized * 0.5f, Vector3.zero, Vector3.zero, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "Hook end");
blueprint.path.FlushEvents();
// Generate the particle representation of the rope (wait until it has finished):
yield return blueprint.Generate();
// Set the blueprint (this adds particles/constraints to the solver and starts simulating them).
rope.ropeBlueprint = blueprint;
rope.GetComponent<ObiRopeExtrudedRenderer>().enabled = true;
// wait for the solver to load the rope, after the next physics step:
yield return new WaitForFixedUpdate();
yield return null;
// set masses to zero, as we're going to override positions while we extend the rope:
for (int i = 0; i < rope.activeParticleCount; ++i)
solver.invMasses[rope.solverIndices[i]] = 0;
// while the last particle hasn't reached the hit, extend the rope:
Vector3 origin;
Vector3 direction;
while (true)
{
// calculate rope origin in solver space:
origin = solver.transform.InverseTransformPoint(rope.transform.position);
// update direction and distance to hook point:
direction = solver.transform.InverseTransformPoint(hookAttachment.point) - origin;
float distance = direction.magnitude;
direction.Normalize();
LayParticlesInStraightLine(origin, direction);
// increase length:
float distanceLeft = distance - cursor.ChangeLength(hookShootSpeed * Time.deltaTime);
// if we have exceeded the desired length, correct it and break the loop:
if (distanceLeft < 0)
{
cursor.ChangeLength(distanceLeft);
break;
}
// wait for next frame:
yield return null;
}
// wait for the last length change to take effect, and ensure the rope is straight:
yield return new WaitForFixedUpdate();
// wait one frame:
yield return null;
LayParticlesInStraightLine(origin, direction);
// restore masses so that the simulation takes over now that the rope is in place:
for (int i = 0; i < rope.activeParticleCount; ++i)
solver.invMasses[rope.solverIndices[i]] = 10; // 1/0.1 = 10
// Pin both ends of the rope (this enables two-way interaction between character and rope):
var batch = new ObiPinConstraintsBatch();
batch.AddConstraint(rope.elements[0].particle1, character, transform.localPosition, Quaternion.identity, 0, 0);
batch.AddConstraint(rope.elements[rope.elements.Count - 1].particle2, hookAttachment.collider.GetComponent<ObiColliderBase>(),
hookAttachment.collider.transform.InverseTransformPoint(hookAttachment.point), Quaternion.identity, 0, 0);
batch.activeConstraintCount = 2;
pinConstraints.AddBatch(batch);
rope.SetConstraintsDirty(Oni.ConstraintType.Pin);
}
private void DetachHook()
// restore masses so that the simulation takes over now that the rope is in place:
for (int i = 0; i < rope.activeParticleCount; ++i)
solver.invMasses[rope.solverIndices[i]] = 10; // 1/0.1 = 10
// Pin both ends of the rope (this enables two-way interaction between character and rope):
var batch = new ObiPinConstraintsBatch();
batch.AddConstraint(rope.elements[0].particle1, character, transform.localPosition, Quaternion.identity, 0, 0, float.PositiveInfinity);
batch.AddConstraint(rope.elements[rope.elements.Count-1].particle2, hookAttachment.collider.GetComponent<ObiColliderBase>(),
hookAttachment.collider.transform.InverseTransformPoint(hookAttachment.point), Quaternion.identity, 0, 0, float.PositiveInfinity);
batch.activeConstraintCount = 2;
pinConstraints.AddBatch(batch);
rope.SetConstraintsDirty(Oni.ConstraintType.Pin);
}
private void DetachHook()
{
// Set the rope blueprint to null (automatically removes the previous blueprint from the solver, if any).
rope.ropeBlueprint = null;
rope.GetComponent<MeshRenderer>().enabled = false;
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
// Set the rope blueprint to null (automatically removes the previous blueprint from the solver, if any).
rope.ropeBlueprint = null;
rope.GetComponent<ObiRopeExtrudedRenderer>().enabled = false;
if (!rope.isLoaded)
LaunchHook();
else
DetachHook();
}
void Update()
if (rope.isLoaded)
{
if (Input.GetMouseButtonDown(0))
if (Input.GetKey(KeyCode.W))
{
if (!rope.isLoaded)
LaunchHook();
else
DetachHook();
cursor.ChangeLength(rope.restLength - hookExtendRetractSpeed * Time.deltaTime);
}
if (rope.isLoaded)
if (Input.GetKey(KeyCode.S))
{
if (Input.GetKey(KeyCode.W))
{
cursor.ChangeLength(-hookExtendRetractSpeed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.S))
{
cursor.ChangeLength(hookExtendRetractSpeed * Time.deltaTime);
}
cursor.ChangeLength(rope.restLength + hookExtendRetractSpeed * Time.deltaTime);
}
}
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Obi;
/**
* Sample component that shows how to use Obi Rope to create a grappling hook for a 2.5D game.
@@ -14,142 +15,138 @@ using UnityEngine;
* If complex interaction is required with the scene, a purely geometry-based approach (ala Worms ninja rope) can
* be the right choice under certain circumstances.
*/
namespace Obi.Samples
public class GrapplingHook : MonoBehaviour
{
public class GrapplingHook : MonoBehaviour
public ObiSolver solver;
public ObiCollider character;
public float hookExtendRetractSpeed = 2;
public Material material;
public ObiRopeSection section;
private ObiRope rope;
private ObiRopeBlueprint blueprint;
private ObiRopeExtrudedRenderer ropeRenderer;
private ObiRopeCursor cursor;
private RaycastHit hookAttachment;
void Awake()
{
public ObiSolver solver;
public ObiCollider character;
public float hookExtendRetractSpeed = 2;
public Material material;
public ObiRopeSection section;
// Create both the rope and the solver:
rope = gameObject.AddComponent<ObiRope>();
ropeRenderer = gameObject.AddComponent<ObiRopeExtrudedRenderer>();
ropeRenderer.section = section;
ropeRenderer.uvScale = new Vector2(1, 4);
ropeRenderer.normalizeV = false;
ropeRenderer.uvAnchor = 1;
rope.GetComponent<MeshRenderer>().material = material;
private ObiRope rope;
private ObiRopeBlueprint blueprint;
private ObiRopeExtrudedRenderer ropeRenderer;
// Setup a blueprint for the rope:
blueprint = ScriptableObject.CreateInstance<ObiRopeBlueprint>();
blueprint.resolution = 0.5f;
private ObiRopeCursor cursor;
// Tweak rope parameters:
rope.maxBending = 0.02f;
private RaycastHit hookAttachment;
// Add a cursor to be able to change rope length:
cursor = rope.gameObject.AddComponent<ObiRopeCursor>();
cursor.cursorMu = 0;
cursor.direction = true;
}
void Awake()
private void OnDestroy()
{
DestroyImmediate(blueprint);
}
/**
* Raycast against the scene to see if we can attach the hook to something.
*/
private void LaunchHook()
{
// Get the mouse position in the scene, in the same XY plane as this object:
Vector3 mouse = Input.mousePosition;
mouse.z = transform.position.z - Camera.main.transform.position.z;
Vector3 mouseInScene = Camera.main.ScreenToWorldPoint(mouse);
// Get a ray from the character to the mouse:
Ray ray = new Ray(transform.position, mouseInScene - transform.position);
// Raycast to see what we hit:
if (Physics.Raycast(ray, out hookAttachment))
{
// Create both the rope and the solver:
rope = gameObject.AddComponent<ObiRope>();
ropeRenderer = gameObject.AddComponent<ObiRopeExtrudedRenderer>();
ropeRenderer.section = section;
ropeRenderer.uvScale = new Vector2(1, 4);
ropeRenderer.normalizeV = false;
ropeRenderer.uvAnchor = 1;
rope.GetComponent<MeshRenderer>().material = material;
// Setup a blueprint for the rope:
blueprint = ScriptableObject.CreateInstance<ObiRopeBlueprint>();
blueprint.resolution = 0.5f;
// Tweak rope parameters:
rope.maxBending = 0.02f;
// Add a cursor to be able to change rope length:
cursor = rope.gameObject.AddComponent<ObiRopeCursor>();
cursor.cursorMu = 0;
cursor.direction = true;
// We actually hit something, so attach the hook!
StartCoroutine(AttachHook());
}
private void OnDestroy()
}
private IEnumerator AttachHook()
{
yield return 0;
Vector3 localHit = rope.transform.InverseTransformPoint(hookAttachment.point);
int filter = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything,0);
// Procedurally generate the rope path (a simple straight line):
blueprint.path.Clear();
blueprint.path.AddControlPoint(Vector3.zero, -localHit.normalized, localHit.normalized, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "Hook start");
blueprint.path.AddControlPoint(localHit, -localHit.normalized, localHit.normalized, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "Hook end");
blueprint.path.FlushEvents();
// Generate the particle representation of the rope (wait until it has finished):
yield return blueprint.Generate();
// Set the blueprint (this adds particles/constraints to the solver and starts simulating them).
rope.ropeBlueprint = blueprint;
rope.GetComponent<MeshRenderer>().enabled = true;
// Pin both ends of the rope (this enables two-way interaction between character and rope):
var pinConstraints = rope.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<ObiPinConstraintsBatch>;
pinConstraints.Clear();
var batch = new ObiPinConstraintsBatch();
batch.AddConstraint(rope.solverIndices[0], character, transform.localPosition, Quaternion.identity, 0, 0, float.PositiveInfinity);
batch.AddConstraint(rope.solverIndices[blueprint.activeParticleCount - 1], hookAttachment.collider.GetComponent<ObiColliderBase>(),
hookAttachment.collider.transform.InverseTransformPoint(hookAttachment.point), Quaternion.identity, 0, 0, float.PositiveInfinity);
batch.activeConstraintCount = 2;
pinConstraints.AddBatch(batch);
rope.SetConstraintsDirty(Oni.ConstraintType.Pin);
}
private void DetachHook()
{
// Set the rope blueprint to null (automatically removes the previous blueprint from the solver, if any).
rope.ropeBlueprint = null;
rope.GetComponent<MeshRenderer>().enabled = false;
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
DestroyImmediate(blueprint);
if (!rope.isLoaded)
LaunchHook();
else
DetachHook();
}
/**
* Raycast against the scene to see if we can attach the hook to something.
*/
private void LaunchHook()
if (rope.isLoaded)
{
// Get the mouse position in the scene, in the same XY plane as this object:
Vector3 mouse = Input.mousePosition;
mouse.z = transform.position.z - Camera.main.transform.position.z;
Vector3 mouseInScene = Camera.main.ScreenToWorldPoint(mouse);
// Get a ray from the character to the mouse:
Ray ray = new Ray(transform.position, mouseInScene - transform.position);
// Raycast to see what we hit:
if (Physics.Raycast(ray, out hookAttachment))
if (Input.GetKey(KeyCode.W))
{
// We actually hit something, so attach the hook!
StartCoroutine(AttachHook());
cursor.ChangeLength(rope.restLength - hookExtendRetractSpeed * Time.deltaTime);
}
}
private IEnumerator AttachHook()
{
yield return 0;
Vector3 localHit = rope.transform.InverseTransformPoint(hookAttachment.point);
int filter = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 0);
// Procedurally generate the rope path (a simple straight line):
blueprint.path.Clear();
blueprint.path.AddControlPoint(Vector3.zero, -localHit.normalized, localHit.normalized, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "Hook start");
blueprint.path.AddControlPoint(localHit, -localHit.normalized, localHit.normalized, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "Hook end");
blueprint.path.FlushEvents();
// Generate the particle representation of the rope (wait until it has finished):
yield return blueprint.Generate();
// Set the blueprint (this adds particles/constraints to the solver and starts simulating them).
rope.ropeBlueprint = blueprint;
rope.GetComponent<MeshRenderer>().enabled = true;
// Pin both ends of the rope (this enables two-way interaction between character and rope):
var pinConstraints = rope.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<ObiPinConstraintsBatch>;
pinConstraints.Clear();
var batch = new ObiPinConstraintsBatch();
batch.AddConstraint(rope.solverIndices[0], character, transform.localPosition, Quaternion.identity, 0, 0);
batch.AddConstraint(rope.solverIndices[blueprint.activeParticleCount - 1], hookAttachment.collider.GetComponent<ObiColliderBase>(),
hookAttachment.collider.transform.InverseTransformPoint(hookAttachment.point), Quaternion.identity, 0, 0);
batch.activeConstraintCount = 2;
pinConstraints.AddBatch(batch);
rope.SetConstraintsDirty(Oni.ConstraintType.Pin);
}
private void DetachHook()
{
// Set the rope blueprint to null (automatically removes the previous blueprint from the solver, if any).
rope.ropeBlueprint = null;
rope.GetComponent<MeshRenderer>().enabled = false;
}
void Update()
{
if (Input.GetMouseButtonDown(0))
if (Input.GetKey(KeyCode.S))
{
if (!rope.isLoaded)
LaunchHook();
else
DetachHook();
}
if (rope.isLoaded)
{
if (Input.GetKey(KeyCode.W))
{
cursor.ChangeLength(rope.restLength - hookExtendRetractSpeed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.S))
{
cursor.ChangeLength(rope.restLength + hookExtendRetractSpeed * Time.deltaTime);
}
cursor.ChangeLength(rope.restLength + hookExtendRetractSpeed * Time.deltaTime);
}
}
}
}
}

View File

@@ -1,15 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Obi.Samples
{
public class HighlightCollidingRopes : MonoBehaviour
{
public void Highlight(ActorActorCollisionDetector.ActorPair pair)
{
if (pair.actorA.TryGetComponent(out ActorBlinker blinkerA)) blinkerA.Blink(pair.particleA);
if (pair.actorB.TryGetComponent(out ActorBlinker blinkerB)) blinkerB.Blink(pair.particleB);
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 50979842bbc734acdb11f0f01062fe92
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,84 +1,83 @@
using UnityEngine;
using Obi;
namespace Obi.Samples
{
[RequireComponent(typeof(ObiRope))]
public class HosePump : MonoBehaviour
[RequireComponent(typeof(ObiRope))]
public class HosePump : MonoBehaviour {
[Header("Bulge controls")]
public float pumpSpeed = 5;
public float bulgeFrequency = 3;
public float baseThickness = 0.04f;
public float bulgeThickness = 0.06f;
public Color bulgeColor = Color.cyan;
[Header("Flow controls")]
public ParticleSystem waterEmitter;
public float flowSpeedMin = 0.5f;
public float flowSpeedMax = 7;
public float minEmitRate = 100;
public float maxEmitRate = 1000;
private ObiRope rope;
public ObiPathSmoother smoother;
private float time = 0;
void OnEnable()
{
rope = GetComponent<ObiRope>();
smoother = GetComponent<ObiPathSmoother>();
rope.OnBeginStep += Rope_OnBeginStep;
}
void OnDisable()
{
rope.OnBeginStep -= Rope_OnBeginStep;
}
[Header("Bulge controls")]
public float pumpSpeed = 5;
public float bulgeFrequency = 3;
public float baseThickness = 0.04f;
public float bulgeThickness = 0.06f;
public Color bulgeColor = Color.cyan;
private void Rope_OnBeginStep(ObiActor actor, float stepTime)
{
time += stepTime * pumpSpeed;
[Header("Flow controls")]
public ParticleSystem waterEmitter;
public float flowSpeedMin = 0.5f;
public float flowSpeedMax = 7;
public float minEmitRate = 100;
public float maxEmitRate = 1000;
float distance = 0;
float sine = 0;
private ObiRope rope;
public ObiPathSmoother smoother;
private float time = 0;
void OnEnable()
// iterate over all particles, changing their radius and color based on a sine wave:
// (note this would not support resizable / cuttable ropes, to add support for that use rope.elements instead)
for (int i = 0; i < rope.solverIndices.Length; ++i)
{
rope = GetComponent<ObiRope>();
smoother = GetComponent<ObiPathSmoother>();
rope.OnSimulationStart += Rope_OnBeginStep;
}
void OnDisable()
{
rope.OnSimulationStart -= Rope_OnBeginStep;
}
int solverIndex = rope.solverIndices[i];
private void Rope_OnBeginStep(ObiActor actor, float stepTime, float substepTime)
{
time += stepTime * pumpSpeed;
float distance = 0;
float sine = 0;
// iterate over all particles, changing their radius and color based on a sine wave:
// (note this would not support resizable / cuttable ropes, to add support for that use rope.elements instead)
for (int i = 0; i < rope.solverIndices.count; ++i)
if (i > 0)
{
int solverIndex = rope.solverIndices[i];
if (i > 0)
{
int previousIndex = rope.solverIndices[i - 1];
distance += Vector3.Distance(rope.solver.positions[solverIndex], rope.solver.positions[previousIndex]);
}
sine = Mathf.Max(0, Mathf.Sin(distance * bulgeFrequency - time));
rope.solver.principalRadii[solverIndex] = Vector3.one * Mathf.Lerp(baseThickness, bulgeThickness, sine);
rope.solver.colors[solverIndex] = Color.Lerp(Color.white, bulgeColor, sine);
int previousIndex = rope.solverIndices[i - 1];
distance += Vector3.Distance(rope.solver.positions[solverIndex],rope.solver.positions[previousIndex]);
}
// change particle emission rate/speed based on sine wave at the last particle:
if (waterEmitter != null)
{
var main = waterEmitter.main;
main.startSpeed = Mathf.Lerp(flowSpeedMin, flowSpeedMax, sine);
sine = Mathf.Max(0, Mathf.Sin(distance * bulgeFrequency - time));
var emission = waterEmitter.emission;
emission.rateOverTime = Mathf.Lerp(minEmitRate, maxEmitRate, sine);
}
rope.solver.principalRadii[solverIndex] = Vector3.one * Mathf.Lerp(baseThickness,bulgeThickness, sine);
rope.solver.colors[solverIndex] = Color.Lerp(Color.white, bulgeColor, sine);
}
public void LateUpdate()
// change particle emission rate/speed based on sine wave at the last particle:
if (waterEmitter != null)
{
if (smoother != null && waterEmitter != null)
{
ObiPathFrame section = smoother.GetSectionAt(1);
waterEmitter.transform.position = rope.solver.transform.TransformPoint(section.position);
waterEmitter.transform.rotation = rope.solver.transform.rotation * (Quaternion.LookRotation(section.tangent, section.binormal));
}
var main = waterEmitter.main;
main.startSpeed = Mathf.Lerp(flowSpeedMin,flowSpeedMax,sine);
var emission = waterEmitter.emission;
emission.rateOverTime = Mathf.Lerp(minEmitRate, maxEmitRate, sine);
}
}
}
public void LateUpdate()
{
if (smoother != null && waterEmitter != null)
{
ObiPathFrame section = smoother.GetSectionAt(1);
waterEmitter.transform.position = transform.TransformPoint(section.position);
waterEmitter.transform.rotation = transform.rotation * (Quaternion.LookRotation(section.tangent, section.binormal));
}
}
}

View File

@@ -1,50 +0,0 @@
using UnityEngine;
namespace Obi.Samples
{
public class PinholeRatchet : MonoBehaviour
{
public ObiPinhole pinhole;
public bool direction = false;
public float teethSeparation = 0.1f;
public float distanceToNextTooth { get; private set; }
void Update()
{
if (pinhole == null || pinhole.rope == null)
return;
float restLength = (pinhole.rope as ObiRopeBase).restLength;
float normalizedTeethDistance = Mathf.Max(0.001f, teethSeparation / restLength);
var range = pinhole.range;
if (direction)
{
distanceToNextTooth = (range.y - pinhole.position) * restLength;
while (distanceToNextTooth > teethSeparation)
{
range.y -= normalizedTeethDistance;
distanceToNextTooth -= teethSeparation;
}
}
else
{
distanceToNextTooth = (pinhole.position - range.x) * restLength;
while (distanceToNextTooth > teethSeparation)
{
range.x += normalizedTeethDistance;
distanceToNextTooth -= teethSeparation;
}
}
pinhole.range = range;
}
public void OnDisable()
{
if (pinhole != null)
pinhole.range = new Vector2(0, 1);
}
}
}

View File

@@ -1,23 +0,0 @@
using UnityEngine;
namespace Obi.Samples
{
public class RatchetController : MonoBehaviour
{
public PinholeRatchet ratchet;
public Transform ratchetVisualizer;
public float minAngle = 0;
public float maxAngle = 25;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
ratchet.enabled = !ratchet.enabled;
float angle = ratchet.enabled ? Mathf.LerpAngle(minAngle, maxAngle, ratchet.distanceToNextTooth / ratchet.teethSeparation) : maxAngle;
ratchetVisualizer.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 89f7ec50c6e36440593469d980ba7830
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,25 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Obi.Samples
{
[RequireComponent(typeof(LineRenderer))]
public class RenderLineBetweenTransforms : MonoBehaviour
{
public Transform transformA;
public Transform transformB;
LineRenderer line;
void Awake()
{
line = GetComponent<LineRenderer>();
}
void Update()
{
if (transformA != null && transformB != null)
line.SetPositions(new Vector3[] { transformA.position, transformB.position });
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 411d9eba4bb0042bf9302873985bc6e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,71 +1,63 @@
using UnityEngine;
using Obi;
namespace Obi.Samples
public class RopeBetweenTwoPoints : MonoBehaviour
{
public class RopeBetweenTwoPoints : MonoBehaviour
public Transform start;
public Transform end;
public ObiSolver solver;
void Start()
{
public Transform start;
public Transform end;
public ObiSolver solver;
// Generate rope synchronously:
Generate();
}
public bool fixedParticleCount = true;
public int numParticles = 5;
void Start()
void Generate()
{
if (start != null && end != null)
{
// Generate rope synchronously:
Generate();
}
// Adjust our transform:
transform.position = (start.position + end.position) / 2;
transform.rotation = Quaternion.FromToRotation(Vector3.right, end.position - start.position);
void Generate()
{
if (start != null && end != null)
{
// Adjust our transform:
transform.position = (start.position + end.position) / 2;
transform.rotation = Quaternion.FromToRotation(Vector3.right, end.position - start.position);
// Calculate control point positions and tangent vector:
Vector3 startPositionLS = transform.InverseTransformPoint(start.position);
Vector3 endPositionLS = transform.InverseTransformPoint(end.position);
Vector3 tangentLS = (endPositionLS - startPositionLS).normalized;
// Calculate control point positions and tangent vector:
Vector3 startPositionLS = transform.InverseTransformPoint(start.position);
Vector3 endPositionLS = transform.InverseTransformPoint(end.position);
Vector3 tangentLS = (endPositionLS - startPositionLS).normalized;
// Create the blueprint:
var blueprint = ScriptableObject.CreateInstance<ObiRopeBlueprint>();
// Create the blueprint:
var blueprint = ScriptableObject.CreateInstance<ObiRopeBlueprint>();
// Build the rope path:
int filter = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 0);
blueprint.path.AddControlPoint(startPositionLS, -tangentLS, tangentLS, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "start");
blueprint.path.AddControlPoint(endPositionLS, -tangentLS, tangentLS, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "end");
blueprint.path.FlushEvents();
// Build the rope path:
int filter = ObiUtils.MakeFilter(ObiUtils.CollideWithEverything, 0);
blueprint.path.AddControlPoint(startPositionLS, -tangentLS, tangentLS, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "start");
blueprint.path.AddControlPoint(endPositionLS, -tangentLS, tangentLS, Vector3.up, 0.1f, 0.1f, 1, filter, Color.white, "end");
blueprint.path.FlushEvents();
// Generate particles/constraints:
blueprint.GenerateImmediate();
if (fixedParticleCount)
blueprint.resolution = numParticles / (blueprint.path.Length / blueprint.thickness);
// Add rope actor / renderer / attachment components:
var rope = gameObject.AddComponent<ObiRope>();
var ropeRenderer = gameObject.AddComponent<ObiRopeExtrudedRenderer>();
var attachment1 = gameObject.AddComponent<ObiParticleAttachment>();
var attachment2 = gameObject.AddComponent<ObiParticleAttachment>();
// Generate particles/constraints:
blueprint.GenerateImmediate();
// Load the default rope section for rendering:
ropeRenderer.section = Resources.Load<ObiRopeSection>("DefaultRopeSection");
// Add rope actor / renderer / attachment components:
var rope = gameObject.AddComponent<ObiRope>();
var ropeRenderer = gameObject.AddComponent<ObiRopeExtrudedRenderer>();
var attachment1 = gameObject.AddComponent<ObiParticleAttachment>();
var attachment2 = gameObject.AddComponent<ObiParticleAttachment>();
// Set the blueprint:
rope.ropeBlueprint = blueprint;
// Load the default rope section for rendering:
ropeRenderer.section = Resources.Load<ObiRopeSection>("DefaultRopeSection");
// Attach both ends:
attachment1.target = start;
attachment2.target = end;
attachment1.particleGroup = blueprint.groups[0];
attachment2.particleGroup = blueprint.groups[1];
// Set the blueprint:
rope.ropeBlueprint = blueprint;
// Attach both ends:
attachment1.target = start;
attachment2.target = end;
attachment1.particleGroup = blueprint.groups[0];
attachment2.particleGroup = blueprint.groups[1];
// Parent the actor under a solver to start the simulation:
transform.SetParent(solver.transform);
}
// Parent the actor under a solver to start the simulation:
transform.SetParent(solver.transform);
}
}
}

View File

@@ -1,130 +1,129 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Obi;
namespace Obi.Samples
/**
* Sample component that procedurally generates a net using rigidbodies and ropes.
*/
public class RopeNet : MonoBehaviour
{
/**
* Sample component that procedurally generates a net using rigidbodies and ropes.
*/
public class RopeNet : MonoBehaviour
public Material material;
public Vector2Int resolution = new Vector2Int(5, 5);
public Vector2 size = new Vector2(0.5f, 0.5f);
public float nodeSize = 0.2f;
void Awake()
{
public Material material;
// create an object containing both the solver and the updater:
GameObject solverObject = new GameObject("solver", typeof(ObiSolver), typeof(ObiFixedUpdater));
ObiSolver solver = solverObject.GetComponent<ObiSolver>();
ObiFixedUpdater updater = solverObject.GetComponent<ObiFixedUpdater>();
updater.substeps = 2;
public Vector2Int resolution = new Vector2Int(5, 5);
public Vector2 size = new Vector2(0.5f, 0.5f);
public float nodeSize = 0.2f;
// adjust solver settings:
solver.particleCollisionConstraintParameters.enabled = false;
solver.distanceConstraintParameters.iterations = 8;
solver.pinConstraintParameters.iterations = 4;
solver.parameters.sleepThreshold = 0.001f;
solver.PushSolverParameters();
void Awake()
// add the solver to the updater:
updater.solvers.Add(solver);
// create the net (ropes + rigidbodies)
CreateNet(solver);
}
private void CreateNet(ObiSolver solver)
{
ObiCollider[,] nodes = new ObiCollider[resolution.x + 1, resolution.y + 1];
for (int x = 0; x <= resolution.x; ++x)
{
// create an object containing both the solver and the updater:
GameObject solverObject = new GameObject("solver", typeof(ObiSolver));
ObiSolver solver = solverObject.GetComponent<ObiSolver>();
solver.substeps = 2;
// adjust solver settings:
solver.particleCollisionConstraintParameters.enabled = false;
solver.distanceConstraintParameters.iterations = 8;
solver.pinConstraintParameters.iterations = 4;
solver.parameters.sleepThreshold = 0.001f;
solver.PushSolverParameters();
// create the net (ropes + rigidbodies)
CreateNet(solver);
}
private void CreateNet(ObiSolver solver)
{
ObiCollider[,] nodes = new ObiCollider[resolution.x + 1, resolution.y + 1];
for (int x = 0; x <= resolution.x; ++x)
for (int y = 0; y <= resolution.y; ++y)
{
for (int y = 0; y <= resolution.y; ++y)
{
GameObject rb = GameObject.CreatePrimitive(PrimitiveType.Cube);
rb.transform.position = new Vector3(x, y, 0) * size;
rb.transform.localScale = new Vector3(nodeSize, nodeSize, nodeSize);
GameObject rb = GameObject.CreatePrimitive(PrimitiveType.Cube);
rb.AddComponent<Rigidbody>();
rb.transform.position = new Vector3(x, y, 0) * size;
rb.transform.localScale = new Vector3(nodeSize, nodeSize, nodeSize);
rb.AddComponent<Rigidbody>();
nodes[x, y] = rb.AddComponent<ObiCollider>();
nodes[x, y].Filter = 1;
}
}
nodes[0, resolution.y].GetComponent<Rigidbody>().isKinematic = true;
nodes[resolution.x, resolution.y].GetComponent<Rigidbody>().isKinematic = true;
for (int x = 0; x <= resolution.x; ++x)
{
for (int y = 0; y <= resolution.y; ++y)
{
Vector3 pos = new Vector3(x, y, 0) * size;
if (x < resolution.x)
{
Vector3 offset = new Vector3(nodeSize * 0.5f, 0, 0);
var rope = CreateRope(pos + offset, pos + new Vector3(size.x, 0, 0) - offset);
rope.transform.parent = solver.transform;
PinRope(rope, nodes[x, y], nodes[x + 1, y]);
}
if (y < resolution.y)
{
Vector3 offset = new Vector3(0, nodeSize * 0.5f, 0);
var rope = CreateRope(pos + offset, pos + new Vector3(0, size.y, 0) - offset);
rope.transform.parent = solver.transform;
PinRope(rope, nodes[x, y], nodes[x, y + 1]);
}
}
nodes[x, y] = rb.AddComponent<ObiCollider>();
nodes[x, y].Filter = 1;
}
}
private void PinRope(ObiRope rope, ObiCollider bodyA, ObiCollider bodyB)
nodes[0, resolution.y].GetComponent<Rigidbody>().isKinematic = true;
nodes[resolution.x, resolution.y].GetComponent<Rigidbody>().isKinematic = true;
for (int x = 0; x <= resolution.x; ++x)
{
var A = rope.gameObject.AddComponent<ObiParticleAttachment>();
var B = rope.gameObject.AddComponent<ObiParticleAttachment>();
for (int y = 0; y <= resolution.y; ++y)
{
Vector3 pos = new Vector3(x, y, 0) * size;
if (x < resolution.x)
{
Vector3 offset = new Vector3(nodeSize * 0.5f, 0, 0);
var rope = CreateRope(pos + offset, pos + new Vector3(size.x, 0, 0) - offset);
rope.transform.parent = solver.transform;
A.attachmentType = ObiParticleAttachment.AttachmentType.Dynamic;
B.attachmentType = ObiParticleAttachment.AttachmentType.Dynamic;
PinRope(rope, nodes[x, y], nodes[x + 1, y], new Vector3(0.5f, 0, 0), -new Vector3(0.5f, 0, 0));
}
A.target = bodyA.transform;
B.target = bodyB.transform;
if (y < resolution.y)
{
Vector3 offset = new Vector3(0, nodeSize * 0.5f, 0);
var rope = CreateRope(pos + offset, pos + new Vector3(0, size.y, 0) - offset);
rope.transform.parent = solver.transform;
A.particleGroup = rope.ropeBlueprint.groups[0];
B.particleGroup = rope.ropeBlueprint.groups[1];
}
// Creates a rope between two points in world space:
private ObiRope CreateRope(Vector3 pointA, Vector3 pointB)
{
// Create a rope
var ropeObject = new GameObject("solver", typeof(ObiRope), typeof(ObiRopeLineRenderer));
var rope = ropeObject.GetComponent<ObiRope>();
var ropeRenderer = ropeObject.GetComponent<ObiRopeLineRenderer>();
rope.GetComponent<ObiRopeLineRenderer>().material = material;
rope.GetComponent<ObiPathSmoother>().decimation = 0.1f;
ropeRenderer.uvScale = new Vector2(1, 5);
// Setup a blueprint for the rope:
var blueprint = ScriptableObject.CreateInstance<ObiRopeBlueprint>();
blueprint.resolution = 0.15f;
blueprint.thickness = 0.02f;
blueprint.pooledParticles = 0;
// convert both points to the rope's local space:
pointA = rope.transform.InverseTransformPoint(pointA);
pointB = rope.transform.InverseTransformPoint(pointB);
// Procedurally generate the rope path (a simple straight line):
Vector3 direction = (pointB - pointA) * 0.25f;
blueprint.path.Clear();
blueprint.path.AddControlPoint(pointA, -direction, direction, Vector3.up, 0.1f, 0.1f, 1, 1, Color.white, "A");
blueprint.path.AddControlPoint(pointB, -direction, direction, Vector3.up, 0.1f, 0.1f, 1, 1, Color.white, "B");
blueprint.path.FlushEvents();
rope.ropeBlueprint = blueprint;
return rope;
PinRope(rope, nodes[x, y], nodes[x, y + 1], new Vector3(0, 0.5f, 0), -new Vector3(0, 0.5f, 0));
}
}
}
}
private void PinRope(ObiRope rope, ObiCollider bodyA, ObiCollider bodyB, Vector3 offsetA, Vector3 offsetB)
{
// Pin both ends of the rope (this enables two-way interaction between character and rope):
var pinConstraints = rope.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<ObiPinConstraintsBatch>;
pinConstraints.Clear();
var batch = new ObiPinConstraintsBatch();
batch.AddConstraint(rope.solverIndices[0], bodyA, offsetA, Quaternion.identity, 0, 999, float.PositiveInfinity);
batch.AddConstraint(rope.solverIndices[rope.activeParticleCount - 1], bodyB, offsetB, Quaternion.identity, 0, 999, float.PositiveInfinity);
batch.activeConstraintCount = 2;
pinConstraints.AddBatch(batch);
}
// Creates a rope between two points in world space:
private ObiRope CreateRope(Vector3 pointA, Vector3 pointB)
{
// Create a rope
var ropeObject = new GameObject("solver", typeof(ObiRope), typeof(ObiRopeLineRenderer));
var rope = ropeObject.GetComponent<ObiRope>();
var ropeRenderer = ropeObject.GetComponent<ObiRopeLineRenderer>();
rope.GetComponent<MeshRenderer>().material = material;
rope.GetComponent<ObiPathSmoother>().decimation = 0.1f;
ropeRenderer.uvScale = new Vector2(1, 5);
// Setup a blueprint for the rope:
var blueprint = ScriptableObject.CreateInstance<ObiRopeBlueprint>();
blueprint.resolution = 0.15f;
blueprint.thickness = 0.02f;
blueprint.pooledParticles = 0;
// convert both points to the rope's local space:
pointA = rope.transform.InverseTransformPoint(pointA);
pointB = rope.transform.InverseTransformPoint(pointB);
// Procedurally generate the rope path (a simple straight line):
Vector3 direction = (pointB - pointA) * 0.25f;
blueprint.path.Clear();
blueprint.path.AddControlPoint(pointA, -direction, direction, Vector3.up, 0.1f, 0.1f, 1, 1, Color.white, "A");
blueprint.path.AddControlPoint(pointB, -direction, direction, Vector3.up, 0.1f, 0.1f, 1, 1, Color.white, "B");
blueprint.path.FlushEvents();
rope.ropeBlueprint = blueprint;
return rope;
}
}

View File

@@ -1,150 +1,127 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Obi;
namespace Obi.Samples
[RequireComponent(typeof(ObiRope))]
public class RopeSweepCut : MonoBehaviour
{
[RequireComponent(typeof(ObiRope))]
public class RopeSweepCut : MonoBehaviour
public Camera cam;
ObiRope rope;
LineRenderer lineRenderer;
Vector3 cutStartPosition;
private void Awake()
{
rope = GetComponent<ObiRope>();
public Camera cam;
AddMouseLine();
}
ObiRope rope;
LineRenderer lineRenderer;
Vector3 cutStartPosition;
Vector3 cutEndPosition;
bool cut;
private void OnDestroy()
{
DeleteMouseLine();
}
private void Awake()
private void AddMouseLine()
{
GameObject line = new GameObject("Mouse Line");
lineRenderer = line.AddComponent<LineRenderer>();
lineRenderer.startWidth = 0.005f;
lineRenderer.endWidth = 0.005f;
lineRenderer.numCapVertices = 2;
lineRenderer.sharedMaterial = new Material(Shader.Find("Unlit/Color"));
lineRenderer.sharedMaterial.color = Color.cyan;
lineRenderer.enabled = false;
}
private void DeleteMouseLine()
{
if (lineRenderer != null)
Destroy(lineRenderer.gameObject);
}
private void LateUpdate()
{
// do nothing if we don't have a camera to cut from.
if (cam == null) return;
// process user input and cut the rope if necessary.
ProcessInput();
}
/**
* Very simple mouse-based input. Not ideal for multitouch screens as it only supports one finger, though.
*/
private void ProcessInput()
{
// When the user clicks the mouse, start a line cut:
if (Input.GetMouseButtonDown(0))
{
rope = GetComponent<ObiRope>();
AddMouseLine();
cutStartPosition = Input.mousePosition;
lineRenderer.SetPosition(0, cam.ScreenToWorldPoint(new Vector3(cutStartPosition.x, cutStartPosition.y, 0.5f)));
lineRenderer.enabled = true;
}
private void OnDestroy()
{
DeleteMouseLine();
}
if (lineRenderer.enabled)
lineRenderer.SetPosition(1, cam.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0.5f)));
private void OnEnable()
// When the user lifts the mouse, proceed to cut.
if (Input.GetMouseButtonUp(0))
{
rope.OnSimulationStart += Rope_OnBeginSimulation;
}
private void OnDisable()
{
rope.OnSimulationStart -= Rope_OnBeginSimulation;
}
private void AddMouseLine()
{
GameObject line = new GameObject("Mouse Line");
lineRenderer = line.AddComponent<LineRenderer>();
lineRenderer.startWidth = 0.005f;
lineRenderer.endWidth = 0.005f;
lineRenderer.numCapVertices = 2;
lineRenderer.sharedMaterial = new Material(Shader.Find("Unlit/Color"));
lineRenderer.sharedMaterial.color = Color.cyan;
ScreenSpaceCut(cutStartPosition, Input.mousePosition);
lineRenderer.enabled = false;
}
private void DeleteMouseLine()
{
if (lineRenderer != null)
Destroy(lineRenderer.gameObject);
}
private void LateUpdate()
{
// do nothing if we don't have a camera to cut from.
if (cam == null) return;
// process user input and cut the rope if necessary.
ProcessInput();
}
private void Rope_OnBeginSimulation(ObiActor actor, float stepTime, float substepTime)
{
if (cut)
{
ScreenSpaceCut(cutStartPosition, cutEndPosition);
cut = false;
}
}
/**
* Very simple mouse-based input. Not ideal for multitouch screens as it only supports one finger, though.
*/
private void ProcessInput()
{
// When the user clicks the mouse, start a line cut:
if (Input.GetMouseButtonDown(0))
{
cutStartPosition = Input.mousePosition;
lineRenderer.SetPosition(0, cam.ScreenToWorldPoint(new Vector3(cutStartPosition.x, cutStartPosition.y, 0.5f)));
lineRenderer.enabled = true;
}
if (lineRenderer.enabled)
lineRenderer.SetPosition(1, cam.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0.5f)));
// When the user lifts the mouse, proceed to cut.
if (Input.GetMouseButtonUp(0))
{
cutEndPosition = Input.mousePosition;
lineRenderer.enabled = false;
cut = true;
}
}
/**
* Cuts the rope using a line segment, expressed in screen-space.
*/
private void ScreenSpaceCut(Vector2 lineStart, Vector2 lineEnd)
{
// keep track of whether the rope was cut or not.
bool ropeCut = false;
// iterate over all elements and test them for intersection with the line:
for (int i = 0; i < rope.elements.Count; ++i)
{
// project the both ends of the element to screen space.
Vector3 screenPos1 = cam.WorldToScreenPoint(rope.solver.positions[rope.elements[i].particle1]);
Vector3 screenPos2 = cam.WorldToScreenPoint(rope.solver.positions[rope.elements[i].particle2]);
// test if there's an intersection:
if (SegmentSegmentIntersection(screenPos1, screenPos2, lineStart, lineEnd, out float r, out float s))
{
ropeCut = true;
rope.Tear(rope.elements[i]);
}
}
// If the rope was cut at any point, rebuilt constraints:
if (ropeCut) rope.RebuildConstraintsFromElements();
}
/**
* line segment 1 is AB = A+r(B-A)
* line segment 2 is CD = C+s(D-C)
* if they intesect, then A+r(B-A) = C+s(D-C), solving for r and s gives the formula below.
* If both r and s are in the 0,1 range, it meant the segments intersect.
*/
private bool SegmentSegmentIntersection(Vector2 A, Vector2 B, Vector2 C, Vector2 D, out float r, out float s)
{
float denom = (B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x);
float rNum = (A.y - C.y) * (D.x - C.x) - (A.x - C.x) * (D.y - C.y);
float sNum = (A.y - C.y) * (B.x - A.x) - (A.x - C.x) * (B.y - A.y);
if (Mathf.Approximately(rNum, 0) || Mathf.Approximately(denom, 0))
{ r = -1; s = -1; return false; }
r = rNum / denom;
s = sNum / denom;
return (r >= 0 && r <= 1 && s >= 0 && s <= 1);
}
}
}
/**
* Cuts the rope using a line segment, expressed in screen-space.
*/
private void ScreenSpaceCut(Vector2 lineStart, Vector2 lineEnd)
{
// keep track of whether the rope was cut or not.
bool cut = false;
// iterate over all elements and test them for intersection with the line:
for (int i = 0; i < rope.elements.Count; ++i)
{
// project the both ends of the element to screen space.
Vector3 screenPos1 = cam.WorldToScreenPoint(rope.solver.positions[rope.elements[i].particle1]);
Vector3 screenPos2 = cam.WorldToScreenPoint(rope.solver.positions[rope.elements[i].particle2]);
// test if there's an intersection:
if (SegmentSegmentIntersection(screenPos1, screenPos2, lineStart, lineEnd, out float r, out float s))
{
cut = true;
rope.Tear(rope.elements[i]);
}
}
// If the rope was cut at any point, rebuilt constraints:
if (cut) rope.RebuildConstraintsFromElements();
}
/**
* line segment 1 is AB = A+r(B-A)
* line segment 2 is CD = C+s(D-C)
* if they intesect, then A+r(B-A) = C+s(D-C), solving for r and s gives the formula below.
* If both r and s are in the 0,1 range, it meant the segments intersect.
*/
private bool SegmentSegmentIntersection(Vector2 A, Vector2 B, Vector2 C, Vector2 D, out float r, out float s)
{
float denom = (B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x);
float rNum = (A.y - C.y) * (D.x - C.x) - (A.x - C.x) * (D.y - C.y);
float sNum = (A.y - C.y) * (B.x - A.x) - (A.x - C.x) * (B.y - A.y);
if (Mathf.Approximately(rNum, 0) || Mathf.Approximately(denom, 0))
{ r = -1; s = -1; return false; }
r = rNum / denom;
s = sNum / denom;
return (r >= 0 && r <=1 && s >= 0 && s <= 1);
}
}

View File

@@ -2,16 +2,13 @@
using System.Collections.Generic;
using UnityEngine;
namespace Obi.Samples
public class RopeTenser : MonoBehaviour
{
public class RopeTenser : MonoBehaviour
{
public float force = 10;
public float force = 10;
// Update is called once per frame
void Update()
{
GetComponent<Rigidbody>().AddForce(Vector3.down * force);
}
// Update is called once per frame
void Update()
{
GetComponent<Rigidbody>().AddForce(Vector3.down * force);
}
}

View File

@@ -1,60 +1,56 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Obi;
namespace Obi.Samples
[RequireComponent(typeof(ObiRope))]
[RequireComponent(typeof(MeshRenderer))]
public class RopeTensionColorizer : MonoBehaviour
{
[RequireComponent(typeof(ObiRope))]
public class RopeTensionColorizer : MonoBehaviour
public float minTension = 0;
public float maxTension = 0.2f;
public Color normalColor = Color.green;
public Color tensionColor = Color.red;
public RopeTenser tenser;
public float tenserThreshold = -5;
public float tenserMax = 0.1f;
private ObiRope rope;
private Material localMaterial;
void Awake()
{
public float minTension = 0;
public float maxTension = 0.2f;
public Color normalColor = Color.green;
public Color tensionColor = Color.red;
rope = GetComponent<ObiRope>();
localMaterial = GetComponent<MeshRenderer>().material;
}
public RopeTenser tenser;
public float tenserThreshold = -5;
public float tenserMax = 0.1f;
private void OnDestroy()
{
Destroy(localMaterial);
}
private ObiRope rope;
private Material localMaterial;
void Update()
{
if (tenser == null)
return;
void Awake()
// Calculate how much past the threshold the tenser is, clamp the excess to 1
float tenserFactor = Mathf.Min((tenser.transform.position.y - tenserThreshold) / tenserMax,1);
// Check if the tenser is above the threshold, if so then check rope tension:
if (tenserFactor > 0)
{
rope = GetComponent<ObiRope>();
var rend = GetComponent<ObiRopeLineRenderer>();
localMaterial = Instantiate(rend.material);
rend.material = localMaterial;
// Calculate current tension as ratio between current and rest length, then subtract 1 to center it at zero.
float tension = rope.CalculateLength() / rope.restLength - 1;
// Use tension to interpolate between colors:
float lerpFactor = (tension - minTension) / (maxTension - minTension);
localMaterial.color = Color.Lerp(normalColor, tensionColor, lerpFactor * tenserFactor);
}
private void OnDestroy()
else
{
Destroy(localMaterial);
}
void Update()
{
if (tenser == null)
return;
// Calculate how much past the threshold the tenser is, clamp the excess to 1
float tenserFactor = Mathf.Min((tenser.transform.position.y - tenserThreshold) / tenserMax, 1);
// Check if the tenser is above the threshold, if so then check rope tension:
if (tenserFactor > 0)
{
// Calculate current tension as ratio between current and rest length, then subtract 1 to center it at zero.
float tension = rope.CalculateLength() / rope.restLength - 1;
// Use tension to interpolate between colors:
float lerpFactor = (tension - minTension) / (maxTension - minTension);
localMaterial.color = Color.Lerp(normalColor, tensionColor, lerpFactor * tenserFactor);
}
else
{
localMaterial.color = normalColor;
}
localMaterial.color = normalColor;
}
}
}

View File

@@ -0,0 +1,118 @@
using System;
using System.Collections;
using UnityEngine;
using Obi;
public class RuntimeRopeGenerator
{
//private ObiRope rope;
//private ObiRopeCursor cursor;
private ObiSolver solver;
private int pinnedParticle = -1;
/// <summary>
/// Creates a straight rope anchored to a transform at the top.
/// Transform may or may not move around and may or may not have a rigidbody.
/// When you call this the rope will appear in the scene and immediately interact with gravity and objects with ObiColliders.
/// Called from anywhere (main thread only)
/// </summary>
public IEnumerator MakeRope(Transform anchoredTo, Vector3 attachmentOffset, float ropeLength)
{
// create a new GameObject with the required components: a solver, a rope, and a curve.
// we also throw a cursor in to be able to change its length.
/*GameObject ropeObject = new GameObject("rope",typeof(ObiSolver),
typeof(ObiRope),
typeof(ObiCurve),
typeof (ObiRopeCursor));
// get references to all components:
rope = ropeObject.GetComponent<ObiRope>();
cursor = ropeObject.GetComponent<ObiRopeCursor>();
solver = ropeObject.GetComponent<ObiSolver>();
ObiCurve path = ropeObject.GetComponent<ObiCurve>();
// set up component references (see ObiRopeHelper.cs)
rope.Solver = solver;
rope.ropePath = path;
//rope.section = Resources.Load<ObiRopeSection>("DefaultRopeSection");
// set path control points (duplicate end points, to set curvature as required by CatmullRom splines):
path.controlPoints.Clear();
path.controlPoints.Add(new ObiCurve.ControlPoint(Vector3.zero,Vector3.up));
path.controlPoints.Add(new ObiCurve.ControlPoint(Vector3.zero,Vector3.up));
path.controlPoints.Add(new ObiCurve.ControlPoint(Vector3.down*ropeLength,Vector3.up));
path.controlPoints.Add(new ObiCurve.ControlPoint(Vector3.down*ropeLength,Vector3.up));
rope.pooledParticles = 2000;
// parent the rope to the anchor transform:
rope.transform.SetParent(anchoredTo,false);
rope.transform.localPosition = attachmentOffset;
// generate particles/constraints and add them to the solver (see ObiRopeHelper.cs)
yield return rope.StartCoroutine(rope.GeneratePhysicRepresentationForMesh());
rope.AddToSolver(null);
// get the last particle in the rope at its rest state.
pinnedParticle = rope.UsedParticles-1;
// add a tethers batch:
ObiTetherConstraintBatch tetherBatch = new ObiTetherConstraintBatch(true,false,0,1);
rope.TetherConstraints.AddBatch(tetherBatch);
//UpdateTethers();
// fix first particle in place (see http://obi.virtualmethodstudio.com/tutorials/scriptingparticles.html)
rope.Solver.invMasses[rope.particleIndices[0]] = rope.invMasses[0] = 0;*/
yield return 0;
}
/// <summary>
/// MakeRope and AddPendulum may NOT be called on the same frame. You must wait for the MakeRope coroutine to finish first, as creating a rope is an asynchronous operation.
/// Just adds a pendulum to the rope on the un-anchored end.
/// </summary>
public void AddPendulum(ObiCollider pendulum, Vector3 attachmentOffset)
{
// simply add a new pin constraint (see http://obi.virtualmethodstudio.com/tutorials/scriptingconstraints.html)
/*rope.PinConstraints.RemoveFromSolver(null);
ObiPinConstraintBatch batch = (ObiPinConstraintBatch)rope.PinConstraints.GetFirstBatch();
batch.AddConstraint(pinnedParticle, pendulum, attachmentOffset,Quaternion.identity, 1);
rope.PinConstraints.AddToSolver(null);*/
}
/// <summary>
/// RemovePendulum and AddPendulum may be called on the same frame.
/// </summary>
public void RemovePendulum()
{
// simply remove all pin constraints (see http://obi.virtualmethodstudio.com/tutorials/scriptingconstraints.html)
/*rope.PinConstraints.RemoveFromSolver(null);
rope.PinConstraints.GetFirstBatch().Clear();
rope.PinConstraints.AddToSolver(null);*/
}
/// <summary>
/// Like extending or retracting a winch.
/// </summary>
public void ChangeRopeLength(float changeAmount)
{
// the cursor will automatically add/remove/modify constraints and particles as needed to obtain the new length.
//cursor.ChangeLength(rope.RestLength + changeAmount);
//UpdateTethers();
}
private void UpdateTethers()
{
/*rope.TetherConstraints.RemoveFromSolver(null);
ObiTetherConstraintBatch batch = (ObiTetherConstraintBatch)rope.TetherConstraints.GetFirstBatch();
batch.Clear();
ObiDistanceConstraintBatch dbatch = rope.DistanceConstraints.GetFirstBatch();
for (int i = 0; i < dbatch.ConstraintCount; ++i)
batch.AddConstraint(0,dbatch.springIndices[i*2+1], rope.InterparticleDistance*i, 1, 1);
batch.Cook();
rope.TetherConstraints.AddToSolver(null);*/
}
}

View File

@@ -1,5 +1,7 @@
fileFormatVersion: 2
guid: de82c5da5889a437bbca59463285c6a3
guid: 0854563331ce443fa9b17a143c084806
labels:
- ObiRope
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections;
using UnityEngine;
using Obi;
public class RuntimeRopeGeneratorUse : MonoBehaviour
{
public ObiCollider pendulum;
RuntimeRopeGenerator rg;
public IEnumerator Start()
{
rg = new RuntimeRopeGenerator();
// Create a rope:
yield return rg.MakeRope(transform,Vector3.zero,1);
// Add a pendulum (you should adjust the attachment point depending on your particular pendulum object)
rg.AddPendulum(pendulum,Vector3.up*0.5f);
}
public void Update(){
if (Input.GetKey(KeyCode.W)){
rg.ChangeRopeLength(- Time.deltaTime);
}
if (Input.GetKey(KeyCode.S)){
rg.ChangeRopeLength( Time.deltaTime);
}
}
}

View File

@@ -1,5 +1,7 @@
fileFormatVersion: 2
guid: df6783efff96747d7b949f3538d1cee7
guid: 5e7b946ba720c495a8ee7c1d3e903063
labels:
- ObiRope
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -1,126 +1,122 @@
using UnityEngine;
using Obi;
namespace Obi.Samples
public class SnakeController : MonoBehaviour
{
public class SnakeController : MonoBehaviour
public Transform headReferenceFrame;
public float headSpeed = 20;
public float upSpeed = 40;
public float slitherSpeed = 10;
private ObiRope rope;
private ObiSolver solver;
private float[] traction;
private Vector3[] surfaceNormal;
private void Start()
{
public Transform headReferenceFrame;
public float headSpeed = 20;
public float upSpeed = 40;
public float slitherSpeed = 10;
rope = GetComponent<ObiRope>();
solver = rope.solver;
private ObiRope rope;
private ObiSolver solver;
private float[] traction;
private Vector3[] surfaceNormal;
// initialize traction array:
traction = new float[rope.activeParticleCount];
surfaceNormal = new Vector3[rope.activeParticleCount];
private void Start()
// subscribe to solver events (to update surface information)
solver.OnBeginStep += ResetSurfaceInfo;
solver.OnCollision += AnalyzeContacts;
solver.OnParticleCollision += AnalyzeContacts;
}
private void OnDestroy()
{
solver.OnBeginStep -= ResetSurfaceInfo;
solver.OnCollision -= AnalyzeContacts;
solver.OnParticleCollision -= AnalyzeContacts;
}
private void ResetSurfaceInfo(ObiSolver s, float stepTime)
{
// reset surface info:
for (int i = 0; i < traction.Length; ++i)
{
rope = GetComponent<ObiRope>();
solver = rope.solver;
// subscribe to solver events (to update surface information)
rope.OnSimulationStart += ResetSurfaceInfo;
solver.OnCollision += AnalyzeContacts;
solver.OnParticleCollision += AnalyzeContacts;
traction[i] = 0;
surfaceNormal[i] = Vector3.zero;
}
}
private void OnDestroy()
private void AnalyzeContacts(object sender, ObiSolver.ObiCollisionEventArgs e)
{
// iterate trough all contacts:
for (int i = 0; i < e.contacts.Count; ++i)
{
rope.OnSimulationStart -= ResetSurfaceInfo;
solver.OnCollision -= AnalyzeContacts;
solver.OnParticleCollision -= AnalyzeContacts;
}
private void ResetSurfaceInfo(ObiActor a, float simulatedTime, float substepTime)
{
if (traction == null)
var contact = e.contacts.Data[i];
if (contact.distance < 0.005f)
{
traction = new float[rope.activeParticleCount];
surfaceNormal = new Vector3[rope.activeParticleCount];
}
int simplexIndex = solver.simplices[contact.bodyA];
var particleInActor = solver.particleToActor[simplexIndex];
if (Input.GetKey(KeyCode.J))
{
for (int i = 1; i < rope.activeParticleCount; ++i)
if (particleInActor.actor == rope)
{
int solverIndex = rope.solverIndices[i];
int prevSolverIndex = rope.solverIndices[i - 1];
// using 1 here, could calculate a traction value based on the type of terrain, friction, etc.
traction[particleInActor.indexInActor] = 1;
// direction from current particle to previous one, projected on the contact surface:
Vector4 dir = Vector3.ProjectOnPlane(solver.positions[prevSolverIndex] - solver.positions[solverIndex], surfaceNormal[i]).normalized;
// move in that direction:
solver.velocities[solverIndex] += dir * traction[i] / solver.invMasses[solverIndex] * slitherSpeed * simulatedTime;
}
}
int headIndex = rope.solverIndices[0];
if (headReferenceFrame != null)
{
Vector3 direction = Vector3.zero;
// Determine movement direction:
if (Input.GetKey(KeyCode.W))
{
direction += headReferenceFrame.forward * headSpeed;
}
if (Input.GetKey(KeyCode.A))
{
direction += -headReferenceFrame.right * headSpeed;
}
if (Input.GetKey(KeyCode.S))
{
direction += -headReferenceFrame.forward * headSpeed;
}
if (Input.GetKey(KeyCode.D))
{
direction += headReferenceFrame.right * headSpeed;
}
// flatten out the direction so that it's parallel to the ground:
direction.y = 0;
solver.velocities[headIndex] += (Vector4)direction * simulatedTime;
}
if (Input.GetKey(KeyCode.Space))
solver.velocities[headIndex] += (Vector4)Vector3.up * simulatedTime * upSpeed;
// reset surface info:
for (int i = 0; i < traction.Length; ++i)
{
traction[i] = 0;
surfaceNormal[i] = Vector3.zero;
}
}
private void AnalyzeContacts(object sender, ObiNativeContactList e)
{
// iterate trough all contacts:
for (int i = 0; i < e.count; ++i)
{
var contact = e[i];
if (contact.distance < 0.005f)
{
int simplexIndex = solver.simplices[contact.bodyA];
var particleInActor = solver.particleToActor[simplexIndex];
if (particleInActor != null && particleInActor.actor == rope && traction != null)
{
// using 1 here, could calculate a traction value based on the type of terrain, friction, etc.
traction[particleInActor.indexInActor] = 1;
// accumulate surface normal:
surfaceNormal[particleInActor.indexInActor] += (Vector3)contact.normal;
}
// accumulate surface normal:
surfaceNormal[particleInActor.indexInActor] += (Vector3)contact.normal;
}
}
}
}
}
public void Update()
{
if (Input.GetKey(KeyCode.J))
{
for (int i = 1; i < rope.activeParticleCount; ++i)
{
int solverIndex = rope.solverIndices[i];
int prevSolverIndex = rope.solverIndices[i - 1];
// direction from current particle to previous one, projected on the contact surface:
Vector4 dir = Vector3.ProjectOnPlane(solver.positions[prevSolverIndex] - solver.positions[solverIndex], surfaceNormal[i]).normalized;
// move in that direction:
solver.velocities[solverIndex] += dir * traction[i] * slitherSpeed * Time.deltaTime;
}
}
int headIndex = rope.solverIndices[0];
if (headReferenceFrame != null)
{
Vector3 direction = Vector3.zero;
// Determine movement direction:
if (Input.GetKey(KeyCode.W))
{
direction += headReferenceFrame.forward * headSpeed;
}
if (Input.GetKey(KeyCode.A))
{
direction += -headReferenceFrame.right * headSpeed;
}
if (Input.GetKey(KeyCode.S))
{
direction += -headReferenceFrame.forward * headSpeed;
}
if (Input.GetKey(KeyCode.D))
{
direction += headReferenceFrame.right * headSpeed;
}
// flatten out the direction so that it's parallel to the ground:
direction.y = 0;
solver.velocities[headIndex] += (Vector4)direction * Time.deltaTime;
}
if (Input.GetKey(KeyCode.Space))
solver.velocities[headIndex] += (Vector4)Vector3.up * Time.deltaTime * upSpeed;
}
}

View File

@@ -1,53 +1,50 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Obi;
namespace Obi.Samples
{
[ExecuteInEditMode]
public class SpiralCurve : MonoBehaviour
[ExecuteInEditMode]
public class SpiralCurve : MonoBehaviour {
public float radius = 0.25f;
public float radialStep = 0.8f;
public float heightStep = 0.04f;
public float points = 30;
public float rotationalMass = 1;
public float thickness = 1;
void Awake ()
{
public float radius = 0.25f;
public float radialStep = 0.8f;
public float heightStep = 0.04f;
public float points = 30;
public float rotationalMass = 1;
public float thickness = 1;
void Awake()
{
Generate();
}
public void Generate()
{
var rod = GetComponent<ObiRopeBase>();
if (rod == null) return;
var blueprint = rod.sourceBlueprint as ObiRopeBlueprintBase;
if (blueprint == null) return;
blueprint.path.Clear();
float ang = 0;
float height = 0;
for (int i = 0; i < points; ++i)
{
Vector3 point = new Vector3(Mathf.Cos(ang) * radius, height, Mathf.Sin(ang) * radius);
// optimal handle length for circle approximation: 4/3 tan(pi/(2n))
Vector3 tangent = new Vector3(-point.z, heightStep, point.x).normalized * (4.0f / 3.0f) * Mathf.Tan(radialStep / 4.0f) * radius;
blueprint.path.AddControlPoint(point, -tangent, tangent, Vector3.up, 1, rotationalMass, thickness, 1, Color.white, "control point " + i);
ang += radialStep;
height += heightStep;
}
blueprint.path.FlushEvents();
}
Generate();
}
public void Generate()
{
var rod = GetComponent<ObiRopeBase>();
if (rod == null) return;
var blueprint = rod.sourceBlueprint as ObiRopeBlueprintBase;
if (blueprint == null) return;
blueprint.path.Clear();
float ang = 0;
float height = 0;
for (int i = 0; i < points; ++i)
{
Vector3 point = new Vector3(Mathf.Cos(ang) * radius, height, Mathf.Sin(ang) * radius);
// optimal handle length for circle approximation: 4/3 tan(pi/(2n))
Vector3 tangent = new Vector3(-point.z, heightStep, point.x).normalized * (4.0f / 3.0f) * Mathf.Tan(radialStep / 4.0f) * radius;
blueprint.path.AddControlPoint(point, -tangent, tangent, Vector3.up, 1, rotationalMass, thickness, 1, Color.white, "control point " + i);
ang += radialStep;
height += heightStep;
}
blueprint.path.FlushEvents();
}
}

View File

@@ -1,88 +0,0 @@
using System.Collections;
using UnityEngine;
namespace Obi.Samples
{
[RequireComponent(typeof(Rigidbody))]
public class TangledPeg : MonoBehaviour
{
public TangledPegSlot currentSlot;
public Collider floorCollider;
public ObiRope attachedRope;
[Header("Movement")]
public float stiffness = 200;
public float damping = 20;
public float maxAccel = 50;
public float minDistance = 0.05f;
public Rigidbody rb { get; private set; }
public ObiRigidbody orb { get; private set; }
void Awake()
{
rb = GetComponent<Rigidbody>();
orb = GetComponent<ObiRigidbody>();
// Ignore collisions with the floor:
Physics.IgnoreCollision(GetComponent<Collider>(), floorCollider);
// Initialize the peg's current slot, if any:
if (currentSlot != null)
{
currentSlot.currentPeg = this;
transform.position = currentSlot.transform.position;
}
}
public float MoveTowards(Vector3 position)
{
Vector3 vector = position - transform.position;
float distance = Vector3.Magnitude(vector);
// simple damped spring: F = -kx - vu
Vector3 accel = stiffness * vector - damping * rb.linearVelocity;
// clamp spring acceleration:
accel = Vector3.ClampMagnitude(accel, maxAccel);
rb.AddForce(accel, ForceMode.Acceleration);
return distance;
}
public void DockInSlot(TangledPegSlot slot)
{
StopAllCoroutines();
StartCoroutine(MoveTowardsSlot(slot));
}
public void UndockFromCurrentSlot()
{
if (currentSlot != null)
{
currentSlot.currentPeg = null;
rb.isKinematic = false;
}
}
private IEnumerator MoveTowardsSlot(TangledPegSlot slot)
{
float distance = float.MaxValue;
orb.kinematicForParticles = true;
while (distance > minDistance)
{
distance = MoveTowards(slot.transform.position);
yield return 0;
}
currentSlot = slot;
currentSlot.currentPeg = this;
transform.position = currentSlot.transform.position;
rb.isKinematic = true;
orb.kinematicForParticles = false;
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: beb10e70cc06a4b019ecf88348a5f7fa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,34 +0,0 @@
using UnityEngine;
namespace Obi.Samples
{
public class TangledPegSlot : MonoBehaviour
{
public TangledPeg currentPeg;
public Color tintColor;
private Material instance;
private Color normalColor;
public void Awake()
{
instance = GetComponent<Renderer>().material;
normalColor = instance.color;
}
public void Tint()
{
instance.color = tintColor;
}
public void ResetColor()
{
instance.color = normalColor;
}
public void OnDestroy()
{
Destroy(instance);
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 866212c246a9c4c5797069099ad745aa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,138 +0,0 @@
using UnityEngine;
using UnityEngine.Events;
namespace Obi.Samples
{
[RequireComponent(typeof(ObiSolver))]
public class TangledRopesGameController : MonoBehaviour
{
public TangledPegSlot[] pegSlots;
public float pegHoverHeight = 1;
public float maxPegDistanceFromSlot = 1.5f;
public int framesWithoutContactsToWin = 30;
public UnityEvent onFinish = new UnityEvent();
private TangledPeg selectedPeg;
private Plane floor = new Plane(Vector3.up, 0);
private int framesSinceLastContact = 0;
void OnEnable()
{
GetComponent<ObiSolver>().OnParticleCollision += Solver_OnParticleCollision;
}
private void OnDisable()
{
GetComponent<ObiSolver>().OnParticleCollision -= Solver_OnParticleCollision;
}
private TangledPegSlot FindCandidateSlot(TangledPeg peg)
{
// Go over all slots, find the closest one to the peg that's closer than
// maxPegDistanceFromSlot:
TangledPegSlot closest = null;
float closestDistance = float.MaxValue;
foreach (TangledPegSlot slot in pegSlots)
{
// reset slot color, to make sure it looks normal if it's not a candidate for our peg.
slot.ResetColor();
// ignore occupied slots:
if (slot.currentPeg != null)
continue;
Vector3 slotOnFloor = floor.ClosestPointOnPlane(slot.transform.position);
Vector3 pegOnFloor = floor.ClosestPointOnPlane(peg.transform.position);
float distance = Vector3.Distance(slotOnFloor, pegOnFloor);
if (distance < closestDistance && distance < maxPegDistanceFromSlot)
{
closest = slot;
closestDistance = distance;
}
}
return closest;
}
private void Update()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// User clicks, cast a ray towards the floor, see if it hits any peg.
if (Input.GetMouseButtonDown(0))
{
if (Physics.Raycast(ray, out RaycastHit hit))
{
// if the ray hit a peg, store it as the selected peg and lift it off from its current slot.
if (hit.transform.TryGetComponent(out TangledPeg peg) && peg.currentSlot != null)
{
selectedPeg = peg;
selectedPeg.UndockFromCurrentSlot();
}
}
}
if (selectedPeg != null)
{
// Make selected peg follow the mouse cursor:
if (floor.Raycast(ray, out float enter))
selectedPeg.MoveTowards(ray.GetPoint(enter) + Vector3.up * pegHoverHeight);
// Try to find a suitable slot to drop the peg:
TangledPegSlot closest = FindCandidateSlot(selectedPeg);
// If we found a candidate slot, tint it to give the player some visual feedback:
if (closest != null)
closest.Tint();
// Drop selected peg:
if (Input.GetMouseButtonUp(0))
{
// If we could find a free slot nearby, connect to it:
if (closest != null)
{
selectedPeg.currentSlot = null;
selectedPeg.DockInSlot(closest);
closest.ResetColor();
}
else // If we couldn't find one or if it's too far, return to current slot.
{
selectedPeg.DockInSlot(selectedPeg.currentSlot);
}
selectedPeg = null;
}
}
// If all ropes have been contact-free for a certain amount of frames, trigger finish event.
if (framesSinceLastContact >= framesWithoutContactsToWin && onFinish != null)
onFinish.Invoke();
}
private void Solver_OnParticleCollision(ObiSolver s, ObiNativeContactList e)
{
// Count contacts between different ropes (that is, exclude self-contacts):
int contactsBetweenRopes = 0;
for (int i = 0; i < e.count; ++i)
{
var ropeA = s.particleToActor[s.simplices[e[i].bodyA]].actor;
var ropeB = s.particleToActor[s.simplices[e[i].bodyB]].actor;
if (ropeA != ropeB)
contactsBetweenRopes++;
}
// If there's no contacts, bump the amount of frames we've been contact-free.
// Otherwise reset the amount of frames to zero.
if (contactsBetweenRopes == 0)
framesSinceLastContact++;
else
framesSinceLastContact = 0;
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: c1cdc16773c1f4ebe94e32c3ae0005bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,114 +0,0 @@
using UnityEngine;
namespace Obi.Samples
{
public class VineClimbController : MonoBehaviour
{
public ObiSolver solver;
public float climbSpeed = 1.5f;
ObiPinhole pinhole;
bool pressedSpace = false;
void Start()
{
solver.OnCollision += Solver_OnCollision;
}
public void Update()
{
if (pinhole != null)
{
float speed = 0;
if (Input.GetKey(KeyCode.W))
speed = climbSpeed;
if (Input.GetKey(KeyCode.S))
speed = -climbSpeed;
pinhole.motorSpeed = speed;
}
if (Input.GetKeyDown(KeyCode.Space))
{
pressedSpace = true;
DetachFromVine();
}
}
private void Solver_OnCollision(ObiSolver solver, ObiNativeContactList contacts)
{
if (pressedSpace)
{
int bestParticle = -1;
float closestDistance = float.MaxValue;
Vector3 closestOffset = Vector3.zero;
foreach (var c in contacts)
{
if (c.distance < 0.001f)
{
ObiCollider collider = ObiColliderWorld.GetInstance().colliderHandles[c.bodyB].owner as ObiCollider;
if (collider.sourceCollider.isTrigger)
{
int particle = solver.simplices[c.bodyA];
var worldPosition = solver.transform.TransformPoint(solver.positions[particle]);
Vector3 offset = worldPosition - collider.transform.position;
float distance = Vector3.Magnitude(offset);
if (distance < closestDistance)
{
closestDistance = distance;
closestOffset = offset;
bestParticle = particle;
}
}
}
}
if (bestParticle >= 0)
{
var actor = solver.particleToActor[bestParticle].actor;
AttachToVine(actor as ObiRope, bestParticle, closestOffset);
}
pressedSpace = false;
}
}
private float GetParticleMu(ObiRope rope, int solverParticleIndex)
{
for (int i = 0; i < rope.elements.Count; ++i)
{
if (rope.elements[i].particle1 == solverParticleIndex)
return i / (float)rope.elements.Count;
if (rope.elements[i].particle2 == solverParticleIndex)
return (i + 1) / (float)rope.elements.Count;
}
return 1;
}
// Update is called once per frame
void AttachToVine(ObiRope rope, int particle, Vector3 offset)
{
if (pinhole == null && rope != null)
{
transform.position += offset;
pinhole = rope.gameObject.AddComponent<ObiPinhole>();
pinhole.position = GetParticleMu(rope, particle);
pinhole.motorForce = Mathf.Infinity;
pinhole.friction = 1;
pinhole.target = this.transform;
}
}
void DetachFromVine()
{
if (pinhole != null)
{
GameObject.Destroy(pinhole);
pinhole = null;
pressedSpace = false;
}
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 50c674b3f8f0045249ac099130c6a6b2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -2,73 +2,71 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using Obi;
namespace Obi.Samples
[RequireComponent(typeof(ObiSolver))]
public class WrapRopeGameController : MonoBehaviour
{
[RequireComponent(typeof(ObiSolver))]
public class WrapRopeGameController : MonoBehaviour
{
ObiSolver solver;
ObiSolver solver;
public Wrappable[] wrappables;
public UnityEvent onFinish = new UnityEvent();
public Wrappable[] wrappables;
public UnityEvent onFinish = new UnityEvent();
private void Awake()
{
solver = GetComponent<ObiSolver>();
}
private void Awake()
{
solver = GetComponent<ObiSolver>();
}
// Start is called before the first frame update
void OnEnable()
{
solver.OnCollision += Solver_OnCollision;
}
// Start is called before the first frame update
void OnEnable()
{
solver.OnCollision += Solver_OnCollision;
}
private void OnDisable()
{
solver.OnCollision -= Solver_OnCollision;
}
private void OnDisable()
{
solver.OnCollision -= Solver_OnCollision;
}
private void Update()
{
bool allWrapped = true;
private void Update()
{
bool allWrapped = true;
// Test our win condition: all pegs must be wrapped.
foreach (var wrappable in wrappables)
{
if (!wrappable.IsWrapped())
{
allWrapped = false;
break;
}
}
// Test our win condition: all pegs must be wrapped.
foreach (var wrappable in wrappables)
{
if (!wrappable.IsWrapped())
{
allWrapped = false;
break;
}
}
if (allWrapped)
onFinish.Invoke();
}
if (allWrapped)
onFinish.Invoke();
}
private void Solver_OnCollision(ObiSolver s, ObiNativeContactList e)
{
// reset to unwrapped state:
foreach (var wrappable in wrappables)
wrappable.Reset();
private void Solver_OnCollision(ObiSolver s, ObiSolver.ObiCollisionEventArgs e)
{
// reset to unwrapped state:
foreach (var wrappable in wrappables)
wrappable.Reset();
var world = ObiColliderWorld.GetInstance();
foreach (Oni.Contact contact in e)
{
// look for actual contacts only:
if (contact.distance < 0.025f)
{
var col = world.colliderHandles[contact.bodyB].owner;
if (col != null)
{
var wrappable = col.GetComponent<Wrappable>();
if (wrappable != null)
wrappable.SetWrapped();
}
}
}
}
}
}
var world = ObiColliderWorld.GetInstance();
foreach (Oni.Contact contact in e.contacts)
{
// look for actual contacts only:
if (contact.distance < 0.025f)
{
var col = world.colliderHandles[contact.bodyB].owner;
if (col != null)
{
var wrappable = col.GetComponent<Wrappable>();
if (wrappable != null)
wrappable.SetWrapped();
}
}
}
}
}

View File

@@ -2,44 +2,41 @@
using System.Collections.Generic;
using UnityEngine;
namespace Obi.Samples
[RequireComponent(typeof(Rigidbody))]
public class WrapRopePlayerController : MonoBehaviour
{
[RequireComponent(typeof(Rigidbody))]
public class WrapRopePlayerController : MonoBehaviour
public float acceleration = 50;
Rigidbody rb;
void Awake()
{
public float acceleration = 50;
Rigidbody rb;
void Awake()
{
rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
Vector3 direction = Vector3.zero;
// Determine movement direction:
if (Input.GetKey(KeyCode.W))
{
direction += Vector3.up * acceleration;
}
if (Input.GetKey(KeyCode.A))
{
direction += Vector3.left * acceleration;
}
if (Input.GetKey(KeyCode.S))
{
direction += Vector3.down * acceleration;
}
if (Input.GetKey(KeyCode.D))
{
direction += Vector3.right * acceleration;
}
rb.AddForce(direction.normalized * acceleration, ForceMode.Acceleration);
}
rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
Vector3 direction = Vector3.zero;
// Determine movement direction:
if (Input.GetKey(KeyCode.W))
{
direction += Vector3.up * acceleration;
}
if (Input.GetKey(KeyCode.A))
{
direction += Vector3.left * acceleration;
}
if (Input.GetKey(KeyCode.S))
{
direction += Vector3.down * acceleration;
}
if (Input.GetKey(KeyCode.D))
{
direction += Vector3.right * acceleration;
}
rb.AddForce(direction.normalized * acceleration, ForceMode.Acceleration);
}
}

View File

@@ -2,43 +2,40 @@
using System.Collections.Generic;
using UnityEngine;
namespace Obi.Samples
public class Wrappable : MonoBehaviour
{
public class Wrappable : MonoBehaviour
{
private bool wrapped = false;
public Color normalColor = new Color(0.2f, 0.2f, 0.8f);
public Color wrappedColor = new Color(0.9f, 0.9f, 0.2f);
private bool wrapped = false;
public Color normalColor = new Color(0.2f,0.2f,0.8f);
public Color wrappedColor = new Color(0.9f, 0.9f, 0.2f);
Material localMaterial;
Material localMaterial;
public void Awake()
{
localMaterial = GetComponent<MeshRenderer>().material;
}
public void Awake()
{
localMaterial = GetComponent<MeshRenderer>().material;
}
public void OnDestroy()
{
Destroy(localMaterial);
}
public void OnDestroy()
{
Destroy(localMaterial);
}
public void Reset()
{
wrapped = false;
localMaterial.color = normalColor;
}
public void Reset()
{
wrapped = false;
localMaterial.color = normalColor;
}
public void SetWrapped()
{
wrapped = true;
localMaterial.color = wrappedColor;
}
public void SetWrapped()
{
wrapped = true;
localMaterial.color = wrappedColor;
}
public bool IsWrapped()
{
return wrapped;
}
public bool IsWrapped()
{
return wrapped;
}
}
}