Vertex position relative to normal - unity3d

In a surface shader, given the world's up axis (and the others too), a world space position and a normal in world space, how can we rotate the worldspace position into the space of the normal?
That is, given a up vector and a non-orthogonal target-up vector, how can we transform the position by rotating its up vector?
I need this so I can get the vertex position only affected by the object's rotation matrix, which I don't have access to.
Here's a graphical visualization of what I want to do:
Up is the world up vector
Target is the world space normal
Pos is arbitrary
The diagram is bidimensional, but I need to solve this for a 3D space.

Looks like you're trying to rotate pos by the same rotation that would transform up to new_up.
Using the rotation matrix found here, we can rotate pos using the following code. This will work either in the surface function or a supplementary vertex function, depending on your application:
// Our 3 vectors
float3 pos;
float3 new_up;
float3 up = float3(0,1,0);
// Build the rotation matrix using notation from the link above
float3 v = cross(up, new_up);
float s = length(v); // Sine of the angle
float c = dot(up, new_up); // Cosine of the angle
float3x3 VX = float3x3(
0, -1 * v.z, v.y,
v.z, 0, -1 * v.x,
-1 * v.y, v.x, 0
); // This is the skew-symmetric cross-product matrix of v
float3x3 I = float3x3(
1, 0, 0,
0, 1, 0,
0, 0, 1
); // The identity matrix
float3x3 R = I + VX + mul(VX, VX) * (1 - c)/pow(s,2) // The rotation matrix! YAY!
// Finally we rotate
float3 new_pos = mul(R, pos);
This is assuming that new_up is normalized.
If the "target up normal" is a constant, the calculation of R could (and should) only happen once per frame. I'd recommend doing it on the CPU side and passing it into the shader as a variable. Calculating it for every vertex/fragment is costly, consider what it is you actually need.
If your pos is a vector-4, just do the above with the first three elements, the fourth element can remain unchanged (it doesn't really mean anything in this context anyway).
I'm away from a machine where I can run shader code, so if I made any syntactical mistakes in the above, please forgive me.

Not tested, but should be able to input a starting point and an axis. Then all you do is change procession which is a normalized (0-1) float along the circumference and your point will update accordingly.
using UnityEngine;
using System.Collections;
public class Follower : MonoBehaviour {
Vector3 point;
Vector3 origin = Vector3.zero;
Vector3 axis = Vector3.forward;
float distance;
Vector3 direction;
float procession = 0f; // < normalized
void Update() {
Vector3 offset = point - origin;
distance = offset.magnitude;
direction = offset.normalized;
float circumference = 2 * Mathf.PI * distance;
angle = (procession % 1f) * circumference;
direction *= Quaternion.AngleAxis(Mathf.Rad2Deg * angle, axis);
Ray ray = new Ray(origin, direction);
point = ray.GetPoint(distance);
}
}

Related

Basic idea of Quaternion y axis 90 degree rotate

I want to rotate the object around y axis 90 degree.
What I want to do is very simple
getPosition(Quaternion rotation)
rotation.y = rotation.y + 90.0f;
_rotateTarget.rotation = rotation;
it shows error.
I googled around and found I need to understand Quaternion
However I can't find first clue to rotate object simply 90 degree.
Is there any help??
If you want to rotate an object smoothly to a target rotation you can use Quaternion.Slerp. The following script lets you set the x, y and z rotation.
[Header("Settings")]
public Vector3 targetRotation;
public float rotationSpeed = 5;
private Quaternion currentTargetRotation;
// Start is called before the first frame update
void Update()
{
if (currentTargetRotation.eulerAngles != targetRotation)
currentTargetRotation.eulerAngles = targetRotation;
transform.rotation = Quaternion.Slerp(transform.rotation, currentTargetRotation, rotationSpeed * Time.deltaTime);
}
A Quaternion has not three but rather four components x, y, z and w! And they all move in the range -1 to 1.
=> Never touch the individual components of a Quaternion except you really know exactly what you are doing!
For rotating the object on the global Y-axis you rather want to use e.g. Quaternion.Euler as the name suggests generates a Quaternion rotation from the given Euler space angles.
Returns a rotation that rotates z degrees around the z axis, x degrees around the x axis, and y degrees around the y axis; applied in that order.
rotation *= Quaternion.Euler(0, 90, 0);
// Or also
//rotation *= Quaternion.Euler(Vector3.up * 90);
_rotateTarget.rotation = rotation;
Or if you want to rotate in any given axis you can also use Quaternion.AngleAxis
rotation *= Quaternion.AngleAxis(90, Vector3.up);
_rotateTarget.rotation = rotation;
And then you always combine two Quaternion using the multiplication * operator.
var final = rotA * rotB;
basically means first apply the rotation rotA then starting from there additionally apply the rotB.
rotation *= rotB;
is just a shorthand for writing
rotation = rotation * rotB;

HLSL: Unitys Vector3.RotateTowards(...)

I need to rotate a direction vector towards another with a maximum angle in a compute shader, just like the Vector3.RotateTowards(from, to, maxAngle, 0) function does. This needs to happen inside the compute shader, since I cannot send the needed values from and to the GPU for performance reasons. Any suggestions on how to implement this?
This is adapted from a combination of this post on the Unity forums by vc1001 and this shadertoy entry by demofox. I haven't tested this and it has been a while since I've done HLSL/cg coding, sop lease let me know if there are bugs--especially syntax errors.
float3 slerp(float3 current, float3 target, float maxAngle)
{
// Dot product - the cosine of the angle between 2 vectors.
float dot = dot(current, target);
// Clamp it to be in the range of Acos()
// This may be unnecessary, but floating point
// precision can be a fickle mistress.
dot = clamp(dot, -1, 1);
// Acos(dot) returns the angle between start and end,
// And multiplying that by percent returns the angle between
// start and the final result.
float delta = acos(dot);
float theta = min(1.0f, maxAngle / delta);
float3 relativeVec = normalize(target - current*dot); // Orthonormal basis
float3 slerped = ((start*cos(theta)) + (relativeVec*sin(theta)));
}

Ball Mechanics - Is this the best approach?

Good day,
I'd like to program a constantly moving ball (object3) being passed between two stationary objects (object1, object2), with the ability to set the max height Y of the pass trajectory dynamically.
What would you argue is the best way to program the ball physics for this concept?
I've looked at using addForce on a default sphere w/ a rigidbody. It seems like there should be an equation that expresses the trajectory of a pass of object3 from object1's x to object2's x... at a known, given speed, with a known, set mass, and a known gravity environment.
However, currently I have a Vector3.Lerp interpolating the ball between the two objects on each FixedUpdate() with t expressed as:
`(Mathf.Sin(speed * Time.time) + 1.0f) / 2.0f;`
It works and all, but with this approach, it seems there's no clear way to add height to the trajectory of the ball path. I've considered adding the height to the Y value in object2 until the ball is half way there, and then setting it back to the original Y position... but it just feels wrong! Thoughts?
Thanks!
Okey so if I understand you correctly currently you are doing
privte void FixedUpdate()
{
var factor = (Mathf.Sin(speed * Time.time) + 1.0f) / 2.0f;
object1.MovePosition(Vector3.Lerp(object2.position, object3.position, factor));
}
which moves the ball pingpong between object1 and object2 position but only planar.
Assuming for now the objects will only be moving within the XZ plane and never have different Y position in order to rather get a curve with height you could treat the separatly:
- Interpolate between both positions as before
- Separately calculate the Y position with sinus or any other mathematical curve function - for realistic physics probably rather a parabola actually
Could look somhow like
public class Example : MonoBehaviour
{
public Rigidbody object1;
public Transform object2;
public Transform object3;
// adjust in the Inspector
public float speed = 1;
public float Amplitude = 0;
// Just for debug
[Range(0, 1)] [SerializeField] private float linearFactor;
[SerializeField] private float yPosition;
private void FixedUpdate()
{
// This always returns a value between 0 and 1
// and linearly pingpongs forth and back
linearFactor = Mathf.PingPong(Time.time * speed, 1);
// * Mathf.PI => gives now a value 0 - PI
// so sinus returns correctly 0 - 1 (no need for +1 and /2 anymore)
// then simply multiply by the desired amplitude
var sinus = Mathf.Sin(linearFactor * Mathf.PI);
yPosition = sinus * Amplitude;
// As before interpolate between the positions
// later we will ignore/replace the Y component
var position = Vector3.Lerp(object2.position, object3.position, linearFactor);
object1.MovePosition(new Vector3(position.x, yPosition, position.z));
}
}
You could optionally also try and add some dumping in the Y direction in order to make the vertical movement more realistic (slow down when reaching the peak). I tried a bit using inverted SmoothStep like
// just for debug
[Range(0, 1)] [SerializeField] private float dampedSinusFactor;
[Range(0, 1)] [SerializeField] private float linearFactor;
[SerializeField] private float yPosition;
private void FixedUpdate()
{
// Use two different factros:
// - a linear one for movement in XZ
// - a smoothed one for movement in Y (in order to slow down when reaching the peak ;) )
linearFactor = Mathf.PingPong(Time.time * speed, 1);
dampedSinusFactor = InvertSmoothStep(linearFactor);
// * Mathf.PI => gives now a value 0 - PI
// so sinus returns correctly 0 - 1 ()
// then simply multiply by the desired amplitude
var sinus = Mathf.Sin(dampedSinusFactor * Mathf.PI);
yPosition = sinus * Amplitude;
// later we will ignore/replace the Y component
var position = Vector3.Lerp(object2.position, object3.position, linearFactor);
object1.position = new Vector3(position.x, yPosition, position.z);
}
// source: https://stackoverflow.com/a/34576808/7111561
private float InvertSmoothStep(float x)
{
return x + (x - (x * x * (3.0f - 2.0f * x)));
}
However for slow movements this looks a bit strange yet. But you can come up with any other maths curve that results in the expected behavior for x=[0,1] ;)

Getting compass-like behavior from quaternion

Suppose you have a camera projection matrix, i.e. camera translation vector + rotation quaternion, like every typical camera, it is able to move and rotate in any direction. And independent of it's rotation whether it is looking forward, upward or downward I need to show a compass-like gauge pointing where the camera is targeted at.
The problem is that when the camera is pointed downwards the rotation of camera around it's optical center defines the value of the compass, but when the camera points forward, the rotation of camera around it's center no longer affects the value of compass, in this case the direction of camera defines the value of compass.
It get's more ugly when the camera is tilted downwards only 45 degrees, in this case it is not even clear whether the rotation around camera center affects rotation of compass.
So is there an elegant way of getting the compass value based on arbitrary camera projection matrix / quaternion?
Thank you in advance!
If you want just an arrow pointing at the target its:
Transform camera = Camera.main.transform;
Transform target = Target.transform;
Vector3 relativePosition = target.position - camera.position;
Vector3 targetRelative = Vector3.ProjectOnPlane(relativePosition, camera.forward);
float angle = Angle360(camera.up, targetRelative, camera.forward);
Compass.transform.rotation = Quaternion.Euler(0, 0, angle);
The angle function is:
float Angle360(Vector3 from, Vector3 to, Vector3 normal)
{
float dot = Vector3.Dot(from, to);
float det = Vector3.Dot(normal, Vector3.Cross(from, to));
return Mathf.Atan2(det, dot)*Mathf.Rad2Deg;
}
Here is how you can get the direction of the compass in worldspace:
Project the camera direction and target position on the XZ plane
Transform camera = Camera.main.transform;
Transform target = Target.transform;
Vector3 cameraWorldDirXZ = Vector3.ProjectOnPlane(camera.forward, Vector3.up).normalized;
Vector3 targetWorldDirXZ = Vector3.ProjectOnPlane(target.position, Vector3.up).normalized;
The angle between the cameraWorldDirXZ and targetWorldDirXZ is the angle of your compass needle.
But i don't think this will behave like you think it will. This gives you the angle that you need to rotate the camera.forward vector around the y axis to face the target. If you rotate around camera.forward you don't change either the camera.forward vector or the y axis so the compass wont change.
You might want to try a compass in local space. For that you project onto the camera XZ plane:
Vector3 cameraLocalDirXZ = camera.forward;
Vector3 targetLocalDirXZ = Vector3.ProjectOnPlane(target.position, camera.up).normalized;
Again the angle between the cameraLocalDirXZ and targetLocalDirXZ is the angle of your compass needle. This gives you the angle you need to rotate camera.forward around camera.up to face the target. Note that when you rotate around camera.forward it will change camera.up so it will change the compass direction.
If anyone stumbles upon this problem, the solution (thanks to #Pluto) is very simple, multiply your camera quaternion over three axis vectors (0,0,1), (0,1,0), (1,0,0), you will get three vectors defining coordinate system of your camera, project those three vectors onto your plane, find centroid of your three projected points and voila you have compass direction.
Here's the piece of code for that:
var rotation = /* Your quaternion */;
var cameraOrtX = rotation * new Vector3 (1, 0, 0);
var cameraOrtY = rotation * new Vector3 (0, 1, 0);
var cameraOrtZ = rotation * new Vector3 (0, 0, 1);
var cameraOrtPX = Vector3.ProjectOnPlane(cameraOrtX, new Vector3(0, 1, 0));
var cameraOrtPY = Vector3.ProjectOnPlane(cameraOrtY, new Vector3(0, 1, 0));
var cameraOrtPZ = Vector3.ProjectOnPlane(cameraOrtZ, new Vector3(0, 1, 0));
var centroid = (cameraOrtPX + cameraOrtPY + cameraOrtPZ) / 3.0f;

picking in 3D with ray-tracing using NinevehGL or OpenGL i-phone

I couldn't find the correct and understandable expression of picking in 3D with method of ray-tracing. Has anyone implemented this algorithm in any language? Share directly working code, because since pseudocodes can not be compiled, they are genereally written with lacking parts.
What you have is a position in 2D on the screen. The first thing to do is convert that point from pixels to normalized device coordinates — -1 to 1. Then you need to find the line in 3D space that the point represents. For this, you need the transformation matrix/ces that your 3D app uses to create a projection and camera.
Typically you have 3 matrics: projection, view and model. When you specify vertices for an object, they're in "object space". Multiplying by the model matrix gives the vertices in "world space". Multiplying again by the view matrix gives "eye/camera space". Multiplying again by the projection gives "clip space". Clip space has non-linear depth. Adding a Z component to your mouse coordinates puts them in clip space. You can perform the line/object intersection tests in any linear space, so you must at least move the mouse coordinates to eye space, but it's more convenient to perform the intersection tests in world space (or object space depending on your scene graph).
To move the mouse coordinates from clip space to world space, add a Z-component and multiply by the inverse projection matrix and then the inverse camera/view matrix. To create a line, two points along Z will be computed — from and to.
In the following example, I have a list of objects, each with a position and bounding radius. The intersections of course never match perfectly but it works well enough for now. This isn't pseudocode, but it uses my own vector/matrix library. You'll have to substitute your own in places.
vec2f mouse = (vec2f(mousePosition) / vec2f(windowSize)) * 2.0f - 1.0f;
mouse.y = -mouse.y; //origin is top-left and +y mouse is down
mat44 toWorld = (camera.projection * camera.transform).inverse();
//equivalent to camera.transform.inverse() * camera.projection.inverse() but faster
vec4f from = toWorld * vec4f(mouse, -1.0f, 1.0f);
vec4f to = toWorld * vec4f(mouse, 1.0f, 1.0f);
from /= from.w; //perspective divide ("normalize" homogeneous coordinates)
to /= to.w;
int clickedObject = -1;
float minDist = 99999.0f;
for (size_t i = 0; i < objects.size(); ++i)
{
float t1, t2;
vec3f direction = to.xyz() - from.xyz();
if (intersectSphere(from.xyz(), direction, objects[i].position, objects[i].radius, t1, t2))
{
//object i has been clicked. probably best to find the minimum t1 (front-most object)
if (t1 < minDist)
{
minDist = t1;
clickedObject = (int)i;
}
}
}
//clicked object is objects[clickedObject]
Instead of intersectSphere, you could use a bounding box or other implicit geometry, or intersect a mesh's triangles (this may require building a kd-tree for performance reasons).
[EDIT]
Here's an implementation of the line/sphere intersect (based off the link above). It assumes the sphere is at the origin, so instead of passing from.xyz() as p, give from.xyz() - objects[i].position.
//ray at position p with direction d intersects sphere at (0,0,0) with radius r. returns intersection times along ray t1 and t2
bool intersectSphere(const vec3f& p, const vec3f& d, float r, float& t1, float& t2)
{
//http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection
float A = d.dot(d);
float B = 2.0f * d.dot(p);
float C = p.dot(p) - r * r;
float dis = B * B - 4.0f * A * C;
if (dis < 0.0f)
return false;
float S = sqrt(dis);
t1 = (-B - S) / (2.0f * A);
t2 = (-B + S) / (2.0f * A);
return true;
}
vec4f from = toWorld * vec4f(mouse, -1.0f, 1.0f);
vec4f to = toWorld * vec4f(mouse, 1.0f, 1.0f);
I'm assuming that 'from' is the position of the mouse cursor? If so then why is its z negative one, if we are assuming openGL coordinates.
Also in this way do we assume that the depth at this time is -1 to +1 right? Rather than the depth of our frustrum.