I am using Unity's Vector3d/Quaternion API to try and get the relative vector3 with two absolute vectors. I have a mothership and its absolute position, and I have the absolute position of a vehicle docked on said mothership. I want to get a vector that stays the same no matter the rotation and position of the mothership, given the docked vehicle stays in place on the ship. The position part is easy (just subtracting the vectors) but I cannot figure out how to manage the rotation. How can I do this?
Using some quaternion algebra:
StartingMothershipRot * relativeRot = StartingDockedShipRot
such that you can calculate the docked ship rotation later:
LaterMothershipRot * relativeRot = LaterDockedShiprot
multiply by Inverse(StartingMothershipRot) on left sides:
relativeRot = Inverse(StartingMothershipRot) * StartingDockedShipRot
Expressing in Unity code (putting with simple example of position code):
// Vector3 relativePos; (e.g.)
Quaternion relativeRot;
Transform mothership;
Transform dockedShip;
// ...
// ... when docking occurs
// relativePos = mothership.InverseTransformPoint(dockedShip.position); (e.g.)
relativeRot = Quaternion.Inverse(mothership.rotation) * dockedShip.rotation;
// ...
// ... on each frame update, move docked ship to correct pos & rotation
// dockedShip.position = mothership.TransformPoint(relativePos); (e.g.)
dockedShip.rotation = mothership.rotation * relativeRot;
Related
I am trying to determine the angle where the camera forward vector intersect with an object vector.
Sorry, not straight forward to explain with my knowledge, please find attached a diagram: The camera may not be looking directly at the object (OBJ) and I'd like to know the angle ( ? in the diagram) where the camera's forward vector (V1 in red) intersects with the vector of the object (V2 in red) (if it does), e.g. point A, or B, or C, etc depending on the x-rotation of the camera.
I tried calculating a normalized vector for the red lines, v1 and v2.
Then calculate the angle between two vectors https://onlinemschool.com/math/library/vector/angl/
But the results don't match the expected values when testing.
//v1
Vector3 hypoth = Camera.main.transform.forward.normalized;
//v2
Vector3 adjacent = (new Vector3(obj.transform.position.x, obj.transform.position.y, Camera.main.transform.position.z)
-obj.transform.position).normalized;
float dotProd = Vector3.Dot(adjacent, hypoth);
float cosOfAngle = dotProd / (Vector3.Magnitude(adjacent) * Vector3.Magnitude(hypoth));
double radAngle = Math.Acos(cosOfAngle);
float angle = (float)((180 / Math.PI) * radAngle);
Finding the angle between v1 and v2 gives you this angle, which doesn't match what you mark in your diagram:
Instead, solve for the angle between v1 and the plane normal to v2:
We can do this in unity by projecting v1 to the plane normal to v2 using Vector3.ProjectOnPlane, and then finding the angle between that projection and v1 using Vector3.Angle:
Vector3 projection = Vector3.ProjectOnPlane(hypoth, adjacent);
float angle = Vector3.Angle(projection, hypoth);
I've a similar situation where I wanted to set the collidars of the terrain units on the same height of the player Jet and at the same time it must be on the line of sight of the camera, otherwise when u shoot the terrain units , the bullets will appear like moving through enemy units on the ground , this only works when you work with prospective camera, on orthongal , u may dont need to do this at all, its just set the object on the same height as the camera and everything will be aligned .
Here is my code
void SetColliderLocation()
{
// Object on the ground
A = TerrainUnit.transform.position;
// Camera location
B = cam.transform.position;
// Enemy jet height
height = mainPlayerTransform.position.y;
// Unit Vector normalized between A and B
AB_Normalized = (A - B).normalized;
// The unit vector required to move the collider to maintain its hieght and line of sight with the camera
unitVector = (height - A.y) / AB_Normalized.y;
// Setting the location of the collidar .
collidarGameObject.transform.position = (AB_Normalized * unitVector) + A;
}
I hope its some how similar of what you are looking for.
Edit:
If you applied this script and instead of collider you put a box , you will see the box location will be always between the camera on the sky and object on the ground however the camera or the object on the ground is moving.
I need that the camera sees an object in the initial position after moving it on Y axis.
I use some images to explain myself better.
This is the initial position of the object. The main camera will not change position. At the coordinates (0, 0, 0) there is another camera I use for the background image. From this same point I draw Gizmo lines.
Now through my Editor I move a Plane on the Y axis from 0 to -2. The Y of my object is linked to the Y of the Plane, so it also goes down by 2 units.
Now comes the part I would like to automate. I want to move the object along the X and Z axis so that its feet appear to the camera as if they are in the same initial position. By manually moving it in Scene View on the X and Z axes, I put the feet in a place that looks like the same point as before and of course it is smaller as it is further away from the camera.
How can I calculate by code the X and Z coordinates to be assigned to my object's position at a given point on the Y axis, so that one point remains in the same position in screen space?
You can use rays and planes to calculate this.
Before the object moves, create a Ray from the camera to the point on the object you need to keep in the same position:
// Where the "feet" are relative to the object's origin
public Vector3 cameraKeepOffset = new Vector3(0f,-1f,0f);
public Ray perspectiveRay;
...
Vector3 positionToKeep = transform.position + cameraKeepOffset;
Vector3 cameraPosition = Camera.main.transform.position;
perspectiveRay = new Ray(cameraPosition, positionToKeep - cameraPosition);
The idea is that whenever the object moves, find where along that ray it can be placed. If we put a horizontal plane at the y position, wherever the ray intersects that plane is where the object should be placed.
So, when the object moves, create a Plane where the offset is, find where the perspectiveRay intersects it, then move the object so that its offset is at that point:
Plane yPlane = new Plane(Vector3.up, cameraKeepOffset + transform.position);
float distanceFromCam;
if ( !Raycast(perspectiveRay, out distanceFromCam)) {
Debug.log("Camera is not pointing at plane");
// Handle bugs here, return if necessary, etc.
} else {
Vector3 intersectionPoint = Ray.GetPoint(distanceFromCam);
transform.position = intersectionPoint - cameraKeepOffset;
}
I am making a top down game in Unity, how do I instantiate objects based on the players current position from the top of the camera view in Unity?
So far I have tried:
Instantiate(prefab, player.transform.position * 2 * Space.World)
but this didn't work. This just spawned objects from the center of the camera.
Space is simply an enum and has nothing to do with what you are trying! (Its purpose is to define the relative transform space for e.g. Transform.Rotate or Transform.Translate)
in that enum afaik
Space.World simply has the int value 0
Space.Self has the int value 1
so what you actually do is
Instantiate(prefab, player.transform.position * 2 * 0);
which equals
Instantiate(prefab, Vector3.zero);
which means the object is instantiated at World position 0,0,0.
Also using
Instantiate(prefab, player.transform.position * 2);
looks a bit strange. Are you sure you want to duplicate the actual position of the player? This would mean the spawned object is always on a line with the player and the World 0,0,0 and always double as far from the center as the player.
To me it sounds more like you rather want to spawn something in front of the player ... depending on the player's view direction (in topdown games usually player.transform.up or player.transform.right) so I guess what you are trying to do instead is something like
Instantiate(prefab, player.transform.position + player.transform.forward * 2);
which would instead spawn the object 2 Unity units in front of the player object
After your comment it sounds like instead you want to spawn the object at the player position on the X-axis but "over it" on the Y-axis so
Instantiate(prefab, player.transform.position + Vector3.Up * 2);
maybe you'll have to tweak the 2 depending how your player can move and how far it has to be to be "off screen". Alternatively you could also use a bit more complex using Camera.ScreenToWorldPoint
// get players position
var playerPos = player.transform.position;
// get camera's position
var cameraPos = Camera.main.transform.position;
// get difference on Z-axis
var cameraDistance = cameraPos.z - playerPos.z;
// Get the world 3d point for the upper camera border
// don't care about the X value
// and as distance we use the z-distance to the player
var cameraTopPoint = Camera.main.ScreenToWorldPoint(new Vector3(0, Camera.main.pixelHeight, cameraDistance);
// use the players X-axis position
cameraTopPoint.x = player.transform.x;
// to spawn it exactly on the Screen border above the player
Instantiate(prefab, cameraTopPoint);
// or to spawn it a bit higher
Instantiate(prefab, cameraTopPoint + Vector3.Up * 1);
Update
you said you want the prefab to be spawned on the "opposide" of the player position on the X axis.
If your Camera is static on world x=0 than this is actually quite simple:
cameraTopPoint.x = -player.transform.x;
If your camera is moving or not on x=0 than we have to calculate it already on the screen position level:
// get players position
var playerPos = player.transform.position;
// additionally convert the player position to screen space
var playerScreenPos = Camera.main.WorldToScreenPoint(playerPos);
var centerX = Camera.main.pixelWidth / 2.0f;
// get the difference between camera an player X (in screen space)
var differenceX = Mathf.Abs(playerScreenPos.x - centerX);
// here calculate the opposide side
var targetX = centerX + differenceX * (playerScreenPos.x < centerX ? 1 : -1);
// or alternatively if you want e.g. that the prefab always
// spawn with a bit of distance to the player even though he is very close
// to the center of the screen you could do something like
//var targetX = centerX + centerX / 2 * (playerScreenPos.x < centerX ? 1 : -1);
// Get the world 3d point for the upper camera border
// with the calculated X value
// and as distance we use the z-distance to the player
var cameraTopPoint = Camera.main.ScreenToWorldPoint(new Vector3(targetX, Camera.main.pixelHeight, playerScreenPos.z);
// to spawn it exactly on the Screen border above the player
Instantiate(prefab, cameraTopPoint);
// or to spawn it a bit higher
Instantiate(prefab, cameraTopPoint + Vector3.Up * 1);
Other Update
Ok so you want the prefab always spawn next to the player in a certain distance. The side depends on which side of the screen the player is so you could actually use the first approach again but just add the desired distance:
// Adjust this is the Inspector (in Unity units)
public float spawnDistanceToPlayer;
...
// get players position
var playerPos = player.transform.position;
// additionally convert the player position to screen space
var playerScreenPos = Camera.main.WorldToScreenPoint(playerPos);
var centerX = Camera.main.pixelWidth / 2.0f;
// Get the world 3d point for the upper camera border
// with the calculated X value
// and as distance we use the z-distance to the player
var cameraTopPoint = Camera.main.ScreenToWorldPoint(new Vector3(playerScreenPos.x, Camera.main.pixelHeight, playerScreenPos.z);
// now add or reduce spawnDistanceToPlayer
// depending on which side of the screen he is
cameraTopPoint.x += spawnDistanceToPlayer * (playerScreenPos.x < centerX ? 1 : -1);
// OR if your camera sits static on X=0 anyway you also could compare
// the player position directly without calculating in screenspace:
cameraTopPoint.x += spawnDistanceToPlayer * (playerPos.x < 0 ? 1 : -1);
// to spawn it exactly on the Screen border above the player
Instantiate(prefab, cameraTopPoint);
// or to spawn it a bit higher
Instantiate(prefab, cameraTopPoint + Vector3.Up * 1);
Typed on smartphone so might not be "copy-paste-able" ;)
For a project in Unity3D I'm trying to transform all objects in the world by changing frames. What this means is that the origin of the new frame is rotated, translated, and scaled to match the origin of the old frame, then this operation is applied to all other objects (including the old origin).
For this, I need a generalized, 3-dimensional (thus 4x4) Transformation-Matrix.
I have looked at using Unity's built-in Matrix4x4.TRS()-method, but this seems useless, as it only applies the Translation, Rotation & Scale to a defined point.
What I'm looking for, is a change of frames, in which the new frame has a different origin, rotation, AND scale, with regards to the original one.
To visualize the problem, I've made a small GIF (I currently have a working version in 3D, without using a Matrix, and without any rotation):
https://gyazo.com/8a7ab04dfef2c96f53015084eefbdb01
The values for each sphere:
Origin1 (Red Sphere)
Before:
Position (-10, 0, 0)
Rotation (0,0,0)
Scale (4,4,4)
After:
Position (10, 0, 0)
Rotation (0,0,0)
Scale (8,8,8)
-
Origin2 (Blue Sphere)
Before:
Position (-20, 0, 0)
Rotation (0,0,0)
Scale (2,2,2)
After:
Position (-10, 0, 0)
Rotation (0,0,0)
Scale (4,4,4)
-
World-Object (White Sphere)
Before:
Position (0, 0, 10)
Rotation (0,0,0)
Scale (2,2,2)
After:
Position (30, 0, 20)
Rotation (0,0,0)
Scale (4,4,4)
Currently I'm simply taking the Vector between the 2 origins, scaling that to the difference between the two origins, then applying that on top of the new position of the original (first) origin.
This will of course not work when rotation is applied to any of the 2 origins.
// Position in original axes
Vector3 positionBefore = testPosition.TestPosition - origin.TestPosition;
// Position in new axes
Vector3 positionAfter = (positionBefore * scaleFactor) + origin.transform.position;
What I'm looking for is a Matrix that can do this (and include rotation, such that Origin2 is rotated to the rotation Origin1 was in before the transformation, and all other objects are moved to their correct positions).
Is there a way to do this without doing the full calculation on every Vector (i.e. transforming the positionbefore-Vector)? It needs to be applied to a (very) large number of objects every frame, thus it needs to be (fairly) optimized.
Edit: Scaling will ALWAYS be uniform.
There might be other solutions but here is what I would do
Wrap your objects into the following hierarchy
WorldAnchorObject
|- Red Sphere
|- Blue Sphere
|- White Sphere
Make sure the WorldAnchorObject has
position: 0,0,0
rotation: 0,0,0
localScale: 1,1,1
position/rotate/scale the Spheres (this will now happen relative to WorldAnchorObject)
Now all that is left is to transform the WorldAnchorObject -> it will move, scale and rotate anything else and keeps the relative transforms intact.
How exactly you move the world anchor is your thing. I guess you want to allways center and normalize a certain child object. Maybe something like
public void CenterOnObject(GameObject targetCenter)
{
var targetTransform = targetCenter.transform;
// The localPosition and localRotation are relative to the parent
var targetPos = transform.localPosition;
var targetRot = targetTransform.localRotation;
var targetScale = targetTransform.localScale;
// First reset everything
transform.position = Vector3.zero;
transform.rotation = Quaternion.Idendity;
transform.localScale = Vector3.one;
// set yourself to inverted target position
// After this action the target should be on 0,0,0
transform.position = targetPos * -1;
// Scale yourself relative to 0,0,0
var newScale = Vector3.one * 1/targetScale.x;
ScaleAround(gameObject, Vector3.zero, newScale);
// Now everything should be scaled so that the target
// has scale 1,1,1 and still is on position 0,0,0
// Now rotate around 0,0,0 so that the rotation of the target gets normalized
transform.rotation = Quaternion.Inverse(targetRotation);
}
// This scales an object around a certain pivot and
// takes care of the correct position translation as well
private void ScaleAround(GameObject target, Vector3 pivot, Vector3 newScale)
{
Vector3 A = target.transform.localPosition;
Vector3 B = pivot;
Vector3 C = A - B; // diff from object pivot to desired pivot/origin
float RS = newScale.x / target.transform.localScale.x; // relative scale factor
// calc final position post-scale
Vector3 FP = B + C * RS;
// finally, actually perform the scale/translation
target.transform.localScale = newScale;
target.transform.localPosition = FP;
}
Now you call it passing one of the children like e.g.
worldAnchorReference.CenterOnObject(RedSphere);
should result in what you wanted to achieve. (Hacking this in on my smartphone so no warranties but if there is trouble I can check it as soon as I'm with a PC again. ;))
Nevermind..
Had to apply the rotation & scale to the Translation before creating the TRS
D'Oh
The following function calculates the target vector of my FPS camera to put in the OpenGL LookAt method. Camera orientation (in radians) (0,0,0) means the camera is parallel to the z axis looking in the negative direction and the camera right vector is parallel to the x axis in the positive direction.
static Matrix4 GetViewMatrix()
{
Vector3 cameraup = Vector3.Transform(Vector3.UnitY,(Quaternion.FromAxisAngle(LineOfSightVector, Orientation.Z)));
LineOfSightVector.X = (float)(Math.Sin((float)Orientation.X) * Math.Cos((float)Orientation.Y));
LineOfSightVector.Y = (float)Math.Sin((float)Orientation.Y);
LineOfSightVector.Z = (float)(Math.Cos((float)Orientation.X) * Math.Cos((float)Orientation.Y));
return Matrix4.LookAt(Position, Position + LineOfSightVector, cameraup) * View; //View = createperspectivefield of view matrix4
}
It works fine when the camera y axis is (0,1,0). However I have introduced a Z value to my camera orientation (roll). I use that to get the "cameraup" vector. I now need to adjust the 3 trig equations for the LineOfSightVector to take into account the change in the "up" vector so the camera controls go in the right direction. Can someone please advise me on this.
Thanks
Having
lineOfSight = vec3(sin(phi)*cos(ksi), sin(ksi), cos(phi)*cos(ksi));
you could compute right and up directions as follows:
right = vec3(cos(phi)*cos(ksi), 0, -sin(phi)*cos(ksi));
up = cross(lineOfSight, right);
up = normalize(up);
Notice that in such model cases of cos(ksi) == 0 should be handled separately.