Setup an helical movement when interacting using the MRTK - unity3d

I am trying to achieve an helical movement with min/max limiation :
positive rotation > negative transform
negative rotation > positive transform
Both positive & negative rotation would be done with the MRTK hands interactions provided script, so this is not my problem.
I (think) that I am struggling with the logic.
tl;dr : I want to screw something using Unity, MRTK and virtual hands.

I ended up solving my problem, after days of scratching my head really, really hard.
To be honest, I probably wouldn't have succeeded on my own, because of a logic problem (this is very often the case, isn't it?).
Initial Setup
In my case, the gameObject to interact with, is a screw.
I wanted it to perform a positive translation when a positive rotation occurs, vice-versa.
On this gameObject are attached some components :
a collider (mesh, box, capsule)
a rigidbody
a PointerHandler.cs (MRTK)
a ObjectManipulator.cs (MRTK)
a NearInteractionGrabbable.cs (MRTK)
the TwistingRotation.cs found below
The rigidbody, PointerHandler.cs, ObjectManipulator.cs and the TwistingRotation.cs configurations can be found here :
The magic appear with the PointerHandler.cs that allow us to detect when the near interaction happen with the screw, thx to the OnPointerDragged event.
TwistingRotation.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Translation of the object while rotating it when grabbed using the MRTK.ObjectManipulator
/// Boundaries and axis restrictions ongoing
/// </summary>
public class TwistingRotation : MonoBehaviour
{
/*******CACHE REFERENCE*******/
private Transform _myTransform;
[SerializeField] private float translationFactor = 90f;
private Vector3 _minPosition;
private Vector3 _maxPosition;
private Vector3 _previousVector;
private Rigidbody _rb;
private void Start()
{
// Cache reference
_myTransform = gameObject.transform;
_rb = gameObject.GetComponent<Rigidbody>();
// Reference for the previous rotation vector
_previousVector = _myTransform.up;
// Default position is the maximum transform.position (unscrewed)
_maxPosition = _myTransform.position;
// Minimum position is default transform.position + 1unit in local space direction
_minPosition = _maxPosition + Vector3.forward;
}
/// <summary>
/// Move the object according to the rotation angle value
/// A positive rotation leads to a positive translation, and vice-versa
/// </summary>
public void TranslateRotation()
{
// Retrieve the angle on a defined local axis when the rotation occur
var currentVector = _myTransform.up;
// Compute the angle between the previous and the current vector on a defined local axis
// Difference between the previous rotation vector, and the actual, on a global axis
var angle = Vector3.SignedAngle(_previousVector, currentVector, Vector3.forward);
// Move object proportional to its rotation
var translation = Vector3.forward * (angle / translationFactor);
_myTransform.Translate(translation, Space.Self);
// Get the GO current position
var currentPosition = _myTransform.position;
// Clamp for each axis between _minPosition and _maxPosition (the default spawn position)
// Doing a Mathf.Min/Max inside the Mathf.Clamp to insure that the min and max values are correct
var x = Mathf.Clamp(currentPosition.x, Mathf.Min(_minPosition.x, _maxPosition.x), Mathf.Max(_minPosition.x, _maxPosition.x));
var y = Mathf.Clamp(currentPosition.y, Mathf.Min(_minPosition.y, _maxPosition.y), Mathf.Max(_minPosition.y, _maxPosition.y));
var z = Mathf.Clamp(currentPosition.z, Mathf.Min(_minPosition.z, _maxPosition.z), Mathf.Max(_minPosition.z, _maxPosition.z));
// Compute the new position while taking the boundaries into consideration
var newPosition = new Vector3(x, y, z);
_myTransform.position = newPosition;
// Save position for the next frame
_previousVector = currentVector;
}
}
What is happening ?
Quite simple : the screw is translating over 1 (unity) unit while rotating.
The value of the rotation depend on the value of the translationFactor variable.
In my case, the value is 360 so a complete rotation over a 1 (unity) unit translation.
Result
It is far from being perfect, probably very much "meh" but hey, it is working (not as intended tho, but still) and it allowed me to move forward and I made my presentation.

Related

Trying to get Mouse to control camera in Unity

I'm working with this tutorial on using a mouse to test a VR game, and for the most part I got it working, except it seems to "reset" the X or Y position every time I switch which axis I'm moving on. For example, if I move the mouse x axis, it rotates the view, but then if I move the y axis, the x-position (.localRotation in the code) goes back to 0 (looking straight ahead in the original position). Then if I move the x axis again, the y-position goes back to zero, but the x position picks up where I left it last time.
I have a feeling it isn't this code that's doing it, but I'll paste here just in case:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class MouseLook : MonoBehaviour
{
public InputActionReference horizontalLook;
public InputActionReference verticalLook;
public float lookSpeed = 0.25f;
public Transform cameraTransform;
float pitch;
float yaw;
// Start is called before the first frame update
void Start()
{
Cursor.lockState = CursorLockMode.Locked;
horizontalLook.action.performed += HandleHorizontalLookChange;
verticalLook.action.performed += HandleVerticalLookChange;
}
void HandleHorizontalLookChange(InputAction.CallbackContext obj)
{
yaw += obj.ReadValue<float>();
transform.localRotation = Quaternion.AngleAxis(yaw * lookSpeed, Vector3.up);
}
void HandleVerticalLookChange(InputAction.CallbackContext obj)
{
pitch += obj.ReadValue<float>();
transform.localRotation = Quaternion.AngleAxis(pitch * lookSpeed, Vector3.right);
}
}
You set the rotation to explicit values based on a sum of mouse position or delta (isn't clear without seeing the Input Asset, but I assume delta), thus your rotation gets "reset" to the exact rotations only given by the current axis everytime.
Solution 1
Add the new rotation to your existing rotation by multiplying it to the left of your current rotation, e.g. localRotation = Quaternion.AngleAxis(...) * localRotation;
Solution 2 (recommended)
Since Quaternions are not as intuitively understood as angles, Unity provides a convenience function called Rotate(axis, angle) on every Transform object
=> transform.Rotate(<Vector3 as axis>, <your angle value>);
In both approaches, however, the angle value should be calculated from mouse delta and not absolute screen position nor a sum of deltas.
Make sure to read through the Public Methods section on this manual page for a thorough understandung of how to manipulate transforms in Unity: https://docs.unity3d.com/ScriptReference/Transform.html

Why do objects get stuck together in Unity?

I have a 2D scene that consists of 4 sprites, each with a box collider.
I then have a prefab that is simply a sprite of a circle with a 2D circle collider.
Finally I have a script named Atmosphere attached to my main camera that will fill the box with lots of instances of the prefab and give them a velocity in a random direction.
using UnityEngine;
public class Atmosphere : MonoBehaviour
{
public GameObject Molecule;
void Start()
{
float x = -4.5f;
while (x < 4.5f)
{
float y = -4.5f;
while (y < 4.5f)
{
var inst = GameObject.Instantiate(Molecule);
inst.transform.position = new Vector3(x, y, 0);
var rb = inst.GetComponent<Rigidbody2D>();
float xForce = Random.value * 2f - 1f;
float yForce = Random.value * 2f - 1f;
rb.AddForce(new Vector2(xForce, yForce) * 100f);
y += 0.5f;
}
x += 0.5f;
}
}
}
For a while the dots bounce around against each other and the edges of the box.
But after a while they eventually get stuck to the edges of the box.
Both the box and balls and the box walls have the same physics material
Which has zero friction and a bounciness of 1.
Why do they stick to the walls, and how can I stop it from happening?
Download Unity 3D demo
UPDATE
If I drop a single molecule into the box with the following script attached it gets stuck to the wall immediately.
[RequireComponent(typeof(Rigidbody2D))]
public class Molecule : MonoBehaviour
{
Rigidbody2D RigidBody2D;
void Start()
{
transform.position = new Vector3(0, 1, 0);
RigidBody2D = GetComponent<Rigidbody2D>();
float xForce = Random.value * 2f - 1f;
float yForce = Random.value * 2f - 1f;
RigidBody2D.AddForce(new Vector2(-.25f, -0.25f) * 100f);
}
}
Unity3D has a velocity threshold. Objects with a velocity below this threshold won't bounce etc.
The solution is to go to Edit > Project Settings > Physics 2D and to set the Velocity Threshold lower.
EDIT - The real solution:
So I was doing some reading on integration techniques and it hit me: The energy you're losing is quite likely coming from numerical inaccuracies stemming from several approximation techniques compounded.
I'm guessing the continuous collision detection algorithm is mostly to blame, floating-point error accumulation, but probably also from numerical integration techniques.
What you're trying to achieve is elastic collision, which means that it satisfies conservation of energy as well as momentum. The trick is going to be for you to add a script to every object that preserves the energy of the system by adding error-correcting velocity over time. Whether you actually use the energy route or not is up to you, there are a number of ways to track and modify the energy resulting in a change of velocity. Momentum would be the would be the easiest to track in my opinion. What you're trying to achieve is elastic collision, which means that it satisfies conservation
i.e.
Make a component that preserves the scalar quantity of kinetic energy or momentum (it'll probably produce the same results)
When you initialize all the particles with a velocity, make sure to save the starting momentum in the particle's script. I would save the scalar quantity for simplicity, so just use the length of velocity
Every frame, in the update event check the velocity of the rigid body. Find the change in momentum or change in energy and add the -difference and apply it to the object.
I doubt you'll have to work on collision events as well, especially if you use the exact same mass for every object. Here's a link with an example where someone corrected unity's friction-less physics using conservation of energy https://answers.unity.com/questions/168393/frictionless-physics.html
colliders may be stuck in each other for the reason of low contact offset, go to edit-project settings-physics2d and increase the default contact offset a little bit, 0.12 for example, it was work for me.
cheers

transform.LookAt() causes Y angle to increase by 180

I am trying to create a third person spaceship movement.
The spaceship rotates about all axes at its position, and has a throttle control to move in forward direction. There is a camera which is always behind it. I am not making the camera a child because I want the camera to NOT follow the rotation about z axes.
The camera has a script, which keeps its position a fixed distance behind the spaceship, and then calls transform.LookAt(spaceShipTarget).
The problem is that as I rotate the ship around global x axes 90 degrees, the y axis of camera suddenly does a 180 degree rotation. The camera control script is below:
using UnityEngine;
namespace UnityStandardAssets.Utility
{
public class FollowBehind : MonoBehaviour
{
public Transform target;
public float distance;
public float delay;
private Vector3 velocity = Vector3.zero;
private void LateUpdate()
{
Vector3 offset = target.transform.TransformVector(0, 0, distance);
Vector3 currentPosition = transform.position;
Vector3 finalPosition = target.position + offset;
transform.position = Vector3.SmoothDamp(currentPosition,
finalPosition, ref velocity, delay);
transform.LookAt(target);
}
}
}
Why would that happen and how can I fix it?
The problem you have with the rotation of the camera is probably caused by the script you use to make the camera follow the spaceship, probably because when you rotate the spaceship the rotation (and probably the position) of the camera are affected.
What you could do instead is make both the spaceship and camera child of another object, and then add a script to this parent object. Now you can put some code in the script of the parent to move the parent itself (this way both camera and spaceship will move together, and you don't need to keep them together manually) and also in the script of the parent you can put some code to rotate the spaceship and camera individually or together based on specific inputs.

assigning velocity to rigidbody doesn't do anything

I have a script attached to a mesh with a kinematic, rigid body with a convex mesh collider that I'd like to move around. Here's what I call in my update function:
if (Input.GetKey(forwards)) {
Debug.Log("forwards!!");
//get current velocity in local space
Vector3 localVel = transform.InverseTransformDirection(body.velocity);
//alter so that forward component = speed
localVel = new Vector3(localVel.x, localVel.y, linearSpeed);
//convert back into world space and set to body
Vector3 worldVel = transform.TransformDirection(localVel);
body.velocity = worldVel;
}
Other Info:
body is a Rigidbody variable that I assign in Start() using GetComponent<Rigidbody>();
linearSpeed is a float with value 1
I'm getting the Debug.Log output, but my mesh is not moving. Is there anything obvious I'm missing here? This is my first script for a 3D, as opposed to a 2D game.
public float speed = 20;
Rigidbody r;
void Start(){
r = gameObject.GetComponent<Rigidbody> (); //Put's reference to local rigidbody into variable "r"
}
void FixedUpdate () {
Vector3 direction = Vector3.zero; //set's current direction to none
//Adds vectors in case that user is pressing the button
if(Input.GetKey(KeyCode.W)) direction += Vector3.forward;
if(Input.GetKey(KeyCode.S)) direction -= Vector3.forward;
if(Input.GetKey(KeyCode.D)) direction += Vector3.right;
if(Input.GetKey(KeyCode.A)) direction -= Vector3.right;
//Normalizez direction (put's it's magnitude to 1) so object moves at same speeds in all directions
r.AddForce (direction.normalized * speed); //Adds direction with magnitude of "speed" to rigidbody
}
Rigidbody MUST be attached to same GO as this script. This script uses world directions, because working with local directions is much harder (the object is rotating and changes directions rapidly, you can use it if you want just by replacing reference to Vector3 to transform like this:
if(Input.GetKey(KeyCode.W)) direction += transform.forward;
Of course for all direction.
This is very basic way to move the object along it's local axises, to do it more better you need to write specific scripts for specific sets of objects, it all depends what kind of object are you moving and how you want to move it. (is it sphere, cube..., will it ever fly up, should it rotate....).
If the RigidBody is Kinematic it is meant to be moved by means other than the physics system; animations, transform.position, etc. Make your rigid body non-kinematic and it should move when you set velocity.

Unity3D sphere rotation and always keeping rotation direction

I have a sphere gameobject with 5 cubes placed on different points on the surface of the sphere. When a key is pressed, i would like the sphere to spin for a few seconds and then slowly stops on the first cube point on the cube and always keeping the same direction in rotation. The issue i am facing now is that, Quaternion.Slerp always takes the shortest path to the next cube which means sometimes the rotation direction is changed. Any ideas?
Thanks
You can easily handle the rotation as a Vector3 of Euler angles.
This way you can Linearly Interpolate the angles to the correct value. And you can use coterminal angles so that you're always interpolating to a higher value ( so no backwards rotations would occur ). After every rotational step you might want to normalize the angles to the 0, 360 range with this approach though.
Example Code :
using UnityEngine;
using System.Collections;
public class Rotation : MonoBehaviour {
public Transform firstPosition;
public Transform secondPosition;
public float rotationDuration = 3;
void Start () {
transform.rotation = firstPosition.rotation;
StartCoroutine(Rotate());
}
IEnumerator Rotate() {
var deltaTime = 0.0f;
var distance = firstPosition.rotation.eulerAngles - secondPosition.rotation.eulerAngles;
while(deltaTime < rotationDuration) {
var rotation = transform.rotation.eulerAngles;
rotation = firstPosition.rotation.eulerAngles + deltaTime/rotationDuration*distance;
transform.rotation = Quaternion.Euler(rotation);
deltaTime += Time.deltaTime;
yield return null;
}
transform.rotation = secondPosition.rotation;
}
}