添加插件
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df6783efff96747d7b949f3538d1cee7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi.Samples
|
||||
{
|
||||
public class CharacterControl2D : MonoBehaviour
|
||||
{
|
||||
|
||||
public float floorRaycastDistance = 1.2f;
|
||||
|
||||
[Header("Grounded")]
|
||||
public float acceleration = 80;
|
||||
public float maxSpeed = 6;
|
||||
public float damping = 0.005f;
|
||||
public float jumpPower = 10;
|
||||
|
||||
[Header("Airborne")]
|
||||
public float airAcceleration = 16;
|
||||
public float airMaxSpeed = 12;
|
||||
public float extraGravity = -12;
|
||||
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82b0f164ac14247fd8a566109fbbe771
|
||||
labels:
|
||||
- ObiRope
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi.Samples
|
||||
{
|
||||
public class CraneController : MonoBehaviour
|
||||
{
|
||||
|
||||
ObiRopeCursor cursor;
|
||||
ObiRope rope;
|
||||
public float speed = 1;
|
||||
|
||||
// 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(-speed * 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.D))
|
||||
{
|
||||
transform.Rotate(0, -Time.deltaTime * 15f, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5551d37f45fd044ff8768019501c20a5
|
||||
labels:
|
||||
- ObiRope
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi.Samples
|
||||
{
|
||||
[RequireComponent(typeof(ObiRope))]
|
||||
public class CursorController : MonoBehaviour
|
||||
{
|
||||
public float minLength = 0.1f;
|
||||
public float speed = 1;
|
||||
|
||||
private ObiRopeCursor cursor;
|
||||
private ObiRope rope;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
rope = GetComponent<ObiRope>();
|
||||
cursor = GetComponent<ObiRopeCursor>();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
float change = 0;
|
||||
|
||||
if (Input.GetKey(KeyCode.W) && cursor != null)
|
||||
{
|
||||
change -= speed * Time.deltaTime;
|
||||
}
|
||||
|
||||
if (Input.GetKey(KeyCode.S) && cursor != null)
|
||||
{
|
||||
change += speed * Time.deltaTime;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83c7e82b3aac04e408bc8bfe9027c983
|
||||
labels:
|
||||
- ObiRope
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,227 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
/**
|
||||
* Sample component that shows how to use Obi Rope to create a grappling hook for a 2.5D game.
|
||||
* 95% of the code is the grappling hook logic (user input, scene raycasting, launching, attaching the hook, etc) and parameter setup,
|
||||
* to show how to use Obi completely at runtime. This might not be practical for real-world scenarios,
|
||||
* but illustrates how to do it.
|
||||
*
|
||||
* Note that the choice of using actual rope simulation for grapple dynamics is debatable. Usually
|
||||
* a simple spring works better both in terms of performance and controllability.
|
||||
*
|
||||
* 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 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()
|
||||
{
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
// We actually hit something, so attach the hook!
|
||||
StartCoroutine(AttachHook());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void LayParticlesInStraightLine(Vector3 origin, Vector3 direction)
|
||||
{
|
||||
// placing all particles in a straight line, respecting rope 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;
|
||||
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();
|
||||
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()
|
||||
{
|
||||
// Set the rope blueprint to null (automatically removes the previous blueprint from the solver, if any).
|
||||
rope.ropeBlueprint = null;
|
||||
rope.GetComponent<ObiRopeExtrudedRenderer>().enabled = false;
|
||||
}
|
||||
|
||||
|
||||
void Update()
|
||||
{
|
||||
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
{
|
||||
if (!rope.isLoaded)
|
||||
LaunchHook();
|
||||
else
|
||||
DetachHook();
|
||||
}
|
||||
|
||||
if (rope.isLoaded)
|
||||
{
|
||||
if (Input.GetKey(KeyCode.W))
|
||||
{
|
||||
cursor.ChangeLength(-hookExtendRetractSpeed * Time.deltaTime);
|
||||
}
|
||||
if (Input.GetKey(KeyCode.S))
|
||||
{
|
||||
cursor.ChangeLength(hookExtendRetractSpeed * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b77478c97cefb4dec9cc85236f7c5fca
|
||||
labels:
|
||||
- ObiRope
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,155 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/**
|
||||
* Sample component that shows how to use Obi Rope to create a grappling hook for a 2.5D game.
|
||||
* 95% of the code is the grappling hook logic (user input, scene raycasting, launching, attaching the hook, etc) and parameter setup,
|
||||
* to show how to use Obi completely at runtime. This might not be practical for real-world scenarios,
|
||||
* but illustrates how to do it.
|
||||
*
|
||||
* Note that the choice of using actual rope simulation for grapple dynamics is debatable. Usually
|
||||
* a simple spring works better both in terms of performance and controllability.
|
||||
*
|
||||
* 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 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()
|
||||
{
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
// We actually hit something, so attach the hook!
|
||||
StartCoroutine(AttachHook());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 (!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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94f9aa141b5964f9f91dadcc1919618c
|
||||
labels:
|
||||
- ObiRope
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,15 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50979842bbc734acdb11f0f01062fe92
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,84 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi.Samples
|
||||
{
|
||||
[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.OnSimulationStart += Rope_OnBeginStep;
|
||||
}
|
||||
void OnDisable()
|
||||
{
|
||||
rope.OnSimulationStart -= Rope_OnBeginStep;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
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 = rope.solver.transform.TransformPoint(section.position);
|
||||
waterEmitter.transform.rotation = rope.solver.transform.rotation * (Quaternion.LookRotation(section.tangent, section.binormal));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d71cdcf242c9b44fb874af7b7d8fd2b9
|
||||
timeCreated: 1518879675
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,50 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de82c5da5889a437bbca59463285c6a3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89f7ec50c6e36440593469d980ba7830
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 411d9eba4bb0042bf9302873985bc6e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,71 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi.Samples
|
||||
{
|
||||
public class RopeBetweenTwoPoints : MonoBehaviour
|
||||
{
|
||||
public Transform start;
|
||||
public Transform end;
|
||||
public ObiSolver solver;
|
||||
|
||||
public bool fixedParticleCount = true;
|
||||
public int numParticles = 5;
|
||||
|
||||
void Start()
|
||||
{
|
||||
// Generate rope synchronously:
|
||||
Generate();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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();
|
||||
|
||||
if (fixedParticleCount)
|
||||
blueprint.resolution = numParticles / (blueprint.path.Length / blueprint.thickness);
|
||||
|
||||
// Generate particles/constraints:
|
||||
blueprint.GenerateImmediate();
|
||||
|
||||
// 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>();
|
||||
|
||||
// Load the default rope section for rendering:
|
||||
ropeRenderer.section = Resources.Load<ObiRopeSection>("DefaultRopeSection");
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9eaa9e75d3f0a4e119decb19dce797be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
130
Assets/Obi/Samples/RopeAndRod/SampleResources/Scripts/RopeNet.cs
Normal file
130
Assets/Obi/Samples/RopeAndRod/SampleResources/Scripts/RopeNet.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi.Samples
|
||||
{
|
||||
/**
|
||||
* 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()
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
GameObject rb = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PinRope(ObiRope rope, ObiCollider bodyA, ObiCollider bodyB)
|
||||
{
|
||||
var A = rope.gameObject.AddComponent<ObiParticleAttachment>();
|
||||
var B = rope.gameObject.AddComponent<ObiParticleAttachment>();
|
||||
|
||||
A.attachmentType = ObiParticleAttachment.AttachmentType.Dynamic;
|
||||
B.attachmentType = ObiParticleAttachment.AttachmentType.Dynamic;
|
||||
|
||||
A.target = bodyA.transform;
|
||||
B.target = bodyB.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83e341aa1a9a14825ab84ad04cd5f44c
|
||||
labels:
|
||||
- ObiRope
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,150 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi.Samples
|
||||
{
|
||||
[RequireComponent(typeof(ObiRope))]
|
||||
public class RopeSweepCut : MonoBehaviour
|
||||
{
|
||||
|
||||
public Camera cam;
|
||||
|
||||
ObiRope rope;
|
||||
LineRenderer lineRenderer;
|
||||
Vector3 cutStartPosition;
|
||||
Vector3 cutEndPosition;
|
||||
bool cut;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
rope = GetComponent<ObiRope>();
|
||||
AddMouseLine();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
DeleteMouseLine();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88cb6f64c607f46b3b0d7a30a99ed18b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi.Samples
|
||||
{
|
||||
public class RopeTenser : MonoBehaviour
|
||||
{
|
||||
public float force = 10;
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
GetComponent<Rigidbody>().AddForce(Vector3.down * force);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a01ab42f187a43818168cd1cd11d3cf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi.Samples
|
||||
{
|
||||
[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()
|
||||
{
|
||||
rope = GetComponent<ObiRope>();
|
||||
var rend = GetComponent<ObiRopeLineRenderer>();
|
||||
localMaterial = Instantiate(rend.material);
|
||||
rend.material = localMaterial;
|
||||
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51f5e34b2316b45c6bdf971b375b5f2e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,126 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi.Samples
|
||||
{
|
||||
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()
|
||||
{
|
||||
rope = GetComponent<ObiRope>();
|
||||
solver = rope.solver;
|
||||
|
||||
// subscribe to solver events (to update surface information)
|
||||
rope.OnSimulationStart += ResetSurfaceInfo;
|
||||
solver.OnCollision += AnalyzeContacts;
|
||||
solver.OnParticleCollision += AnalyzeContacts;
|
||||
}
|
||||
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
rope.OnSimulationStart -= ResetSurfaceInfo;
|
||||
solver.OnCollision -= AnalyzeContacts;
|
||||
solver.OnParticleCollision -= AnalyzeContacts;
|
||||
}
|
||||
|
||||
private void ResetSurfaceInfo(ObiActor a, float simulatedTime, float substepTime)
|
||||
{
|
||||
|
||||
if (traction == null)
|
||||
{
|
||||
traction = new float[rope.activeParticleCount];
|
||||
surfaceNormal = new Vector3[rope.activeParticleCount];
|
||||
}
|
||||
|
||||
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] / 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5d46d2f0b9f9496f9ed59231aa6982d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi.Samples
|
||||
{
|
||||
[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()
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e76488b5e11ef47ba9c0746db5c47115
|
||||
labels:
|
||||
- ObiRope
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,88 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: beb10e70cc06a4b019ecf88348a5f7fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 866212c246a9c4c5797069099ad745aa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,138 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1cdc16773c1f4ebe94e32c3ae0005bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,114 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50c674b3f8f0045249ac099130c6a6b2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Obi.Samples
|
||||
{
|
||||
[RequireComponent(typeof(ObiSolver))]
|
||||
public class WrapRopeGameController : MonoBehaviour
|
||||
{
|
||||
|
||||
ObiSolver solver;
|
||||
|
||||
public Wrappable[] wrappables;
|
||||
public UnityEvent onFinish = new UnityEvent();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
solver = GetComponent<ObiSolver>();
|
||||
}
|
||||
|
||||
// Start is called before the first frame update
|
||||
void OnEnable()
|
||||
{
|
||||
solver.OnCollision += Solver_OnCollision;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
solver.OnCollision -= Solver_OnCollision;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (allWrapped)
|
||||
onFinish.Invoke();
|
||||
}
|
||||
|
||||
private void Solver_OnCollision(ObiSolver s, ObiNativeContactList 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92b1832c67f564e9fa43a180598111e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi.Samples
|
||||
{
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class WrapRopePlayerController : MonoBehaviour
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fdf58ad80e1cd4860aa12e8da0aa833c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi.Samples
|
||||
{
|
||||
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);
|
||||
|
||||
Material localMaterial;
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
localMaterial = GetComponent<MeshRenderer>().material;
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
{
|
||||
Destroy(localMaterial);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
wrapped = false;
|
||||
localMaterial.color = normalColor;
|
||||
}
|
||||
|
||||
public void SetWrapped()
|
||||
{
|
||||
wrapped = true;
|
||||
localMaterial.color = wrappedColor;
|
||||
}
|
||||
|
||||
public bool IsWrapped()
|
||||
{
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39f0f8f454a2049f3af7bad3dda0481e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user