Moving a 3D object after rotation - c#

I'm having a problem with moving a 3D object after I apply a rotation. Both the move and rotation functions work perfectly on their own. But the problem is when I move an object after a rotation, the object doesn't follow the mouse and goes in weird directions. If anyone can see my flaw, I'd appreciate it. Thanks! Here's my code:
private void Rotate()
{
double angle;
bool willangle = Double.TryParse(AngleRot.Text.ToString(), out angle);
RectangleVisual3D rect = (RectangleVisual3D)LastSelectedObject;
AxisAngleRotation3D r = new AxisAngleRotation3D(new Vector3D(0, 0, 1), angle);
RotateTransform3D rot = new RotateTransform3D(r, rect.Origin);
rect.Transform = Transform3DHelper.CombineTransform(rect.Transform, rot);
LastSelectedObject = rect as ModelVisual3D;
}
private void MoveObject(MouseEventArgs e)
{
if (LastSelectedObject is RectangleVisual3D)
{
RectangleVisual3D rect = (RectangleVisual3D)LastSelectedObject;
Point3D? origin = GetPoints(e);
if (origin == null)
return;
rect.Origin = (Point3D)origin;
LastSelectedObject = rect as ModelVisual3D;
}
}

I hope this help: The order of rotation and move is very important. If you move, then rotate, then it move according to the x,y,z co-ordinates. If you rotate, then move, then it will move according to the rotations co-ordinates.

Moving your object by setting its origin is generally a bad move. If your helper library ( I don't think Transform3DHelper is .Net? ) is doing matrix math in the basic way, then you're messing it up by setting rect.Origin.
Instead, try finding the distance vector moved and apply that translation matrix.
I'm assuming
Vector2D dist=new Vector2D((oldPosition - newPosition).x, (oldPosition - newPosition).y);
TranslateTransform3D trans = new TranslateTransform3D(dist.x,dist.y,0);
rect.Transform = Transform3DHelper.CombineTransform(rect.Transform, trans);
The other possible error is that CombineTransform should reverse rect.Transform and rot, but I'm not sure if the API is handling that for you. See if an overloaded version of the method allows you to reverse those two.

Related

How do I match the rotation of a cube to a quad so it stays flat on it as I move it?

To keep it simple, I'm just trying to draw an arrow prefab along a quad that is at an angle. I need it to be flat against it (slightly raised) whatever position it's in.
What I'm doing right now is I've got a cube for the body of the arrow and a "triangle" prefab for the head. I click on the screen to get the "start position" (using a raycast hit point to make sure I'm over the my rotated quad below), then as I drag the mouse around it updates the "end position" vector 3. I simply calculate the distance between both points to see what "localScale.y" to expand it by, which works perfectly for the arrow length. For the head, I just attach it to the "end point", so it just follows the mouse.
The problem I have is that it isn't staying "flat" against the quad, which is rotated at X by 70 (in the inspector). And because of this, it rotates around length axis as I move and rotate the mouse in a circle.
Here my code:
if (Physics.Raycast(mouseRay, out hit))
{
arrowEndPoint = hit.point;
// Get rotation of Quad
quadRotation = rotatedQuad.transform.rotation.eulerAngles.x;
var arrowBodyRotation = simpleArrowBody.transform.rotation.eulerAngles;
var arrowHeadRotation = simpleArrowHead.transform.rotation.eulerAngles;
arrowBodyRotation.x = quadRotation; // Doesn't seem to be affecting it
arrowHeadRotation.x = quadRotation;
// Follow arrow head to mouse
simpleArrowHead.transform.position = arrowEndPoint;
// Get distance between Start and End point
arrowLength = Vector3.Distance(arrowStartPoint, arrowEndPoint);
// Direction based on the start and end points
var direction = (arrowEndPoint - arrowStartPoint).normalized;
// Adjust scale and rotation of Body
simpleArrowBody.transform.localScale = new Vector3(simpleArrowBody.transform.localScale.x, arrowLength, simpleArrowBody.transform.localScale.z);
simpleArrowBody.transform.up = direction;
// Adjust rotation of Head
simpleArrowHead.transform.up = direction;
simpleArrowHead.transform.up = -rotatedQuad.transform.forward; // Didn't work
}
quadRotation = rotatedQuad.transform.rotation.eulerAngles.x;
var arrowBodyRotation = simpleArrowBody.transform.rotation.eulerAngles;
var arrowHeadRotation = simpleArrowHead.transform.rotation.eulerAngles;
arrowBodyRotation.x = quadRotation; // Doesn't seem to be affecting it
arrowHeadRotation.x = quadRotation;
of course this has no effect at all!
You are only assigning this to a local variable Vector3 (which is a struct and thereby a copied value).
If you wanted to actually apply this back you would e.g. use
quadRotation = rotatedQuad.transform.rotation.eulerAngles.x;
var arrowBodyRotation = simpleArrowBody.transform.rotation.eulerAngles;
var arrowHeadRotation = simpleArrowHead.transform.rotation.eulerAngles;
arrowBodyRotation.x = quadRotation;
arrowHeadRotation.x = quadRotation;
simpleArrowBody.transform.rotation.eulerAngles = arrowBodyRotation;
simpleArrowHead.transform.rotation.eulerAngles = arrowHeadRotation;
BUT note that eulerAngles are pretty unreliable for his!
Working with rotation directly is often quite tricky. So I usually prefer to wok with vectors simply because I understand them better ;)
What I would do is
Rather design your arrow in a way that it is "normally" oriented. Meaning that it points into local Z (forward) direction and the one facing towards you is its local up direction.. Unity provides helper methods in this case.
And then you can simply use Quaternion.LookRotation with your direction and as the up vector pass in the -rotatedQuad.transform.forward
Something like e.g.
public class Example : MonoBehaviour
{
public Transform rotatedQuad;
public Transform simpleArrowHead;
public Transform simpleArrowBody;
private Vector3? arrowStartPoint;
private void Update()
{
var mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Input.GetMouseButtonDown(0) && Physics.Raycast(mouseRay, out var hit))
{
arrowStartPoint = hit.point;
}
else if (arrowStartPoint.HasValue && Input.GetMouseButton(0) && Physics.Raycast(mouseRay, out hit))
{
var arrowEndPoint = hit.point;
// Get distance between Start and End point
var delta = arrowEndPoint - arrowStartPoint.Value;
var arrowLength = delta.magnitude;
// Direction based on the start and end points
var direction = delta.normalized;
// Adjust position, scale and rotation of Body
var scale = simpleArrowBody.localScale;
scale.z = arrowLength;
simpleArrowBody.localScale = scale;
// make the forward point into the direction while maintaining the
// up vector aligned with the quad surface
simpleArrowBody.rotation = Quaternion.LookRotation(direction, -rotatedQuad.forward);
// place at center between start and end
simpleArrowBody.position = arrowStartPoint.Value + delta / 2f;
// Follow arrow head to mouse and adjust rotation of Head
simpleArrowHead.position = arrowEndPoint;
// make the forward point into the direction while maintaining the
// up vector aligned with the quad surface
simpleArrowHead.rotation = Quaternion.LookRotation(direction, -rotatedQuad.forward);
}
}
}

Place objects based on coordinates, not on the central coordinates in Unity

I'm creating mesh objects dynamically based on the screen.
So objects that contain mesh objects are always the same size, but mesh objects have different shapes and sizes.
I want you to see my picture and understand it. In fact, blue area is transparent.
I am currently using a mobile camera to shoot Ray at the floor, and I want to place the object at the point where the Ray has hitted.
But this seems to require a lot of calculations.
I think we should use other coordinates than the object's central coordinates first.
And I think we should place the object a little bit above the collision point. Half the size of the mesh object,
So I tried this, but I failed. How can I solve this?
Below is my source code.
Vector3 hitPositon = hit.Pose.position;
Vector3 meshObjectCenter = ObjectPrefab.GetComponent<Renderer>().bounds.center;
Vector3 meshObjectSize = ObjectPrefab.GetComponent<Renderer>().bounds.size;
Vector3 CenterPointRevision = meshObjectCenter - hitPositon;
Vector3 YAxisRevision = new Vector3(0, meshObjectSize.y / 2, 0);
Vector3 NewPoint = ARObjectPrefab.transform.position - CenterPointRevision + YAxisRevision;
ObjectPrefab.transform.position = NewPoint;
Object is in this format, and the picture above looks successful but fail case.
The position is just the hit location minus the offset to center plus the y-axis offset:
Vector3 hitPositon = hit.Pose.position;
Vector3 meshObjectCenter = ObjectPrefab.GetComponent<Renderer>().bounds.center;
Vector3 meshObjectSize = ObjectPrefab.GetComponent<Renderer>().bounds.size;
Vector3 YAxisRevision = new Vector3(0, meshObjectSize.y / 2, 0);
ObjectPrefab.transform.position = hitPositon - meshObjectCenter + YAxisRevision;

Show object in front of the player always

I have stuck in this simple problem but unable to understand that why i am unable to control it.
I have these line of code which is displaying my canvas object in front of my player(camRotationToWatch object name in code) at certain rotation of the player.
if (camRotationToWatch.transform.localEulerAngles.x >= navigationCanvasXMinmumLimit && camRotationToWatch.transform.localEulerAngles.x <= navigationCanvasXMaximumLimit)
{
if (!navCanvasHasDisplay)
{
navigationCanvas.SetActive(true);
//Debug.Log(camRotationToWatch.transform.forward);
Vector3 navCanvas = camRotationToWatch.transform.position + camRotationToWatch.transform.forward * navCanvasDisplayDistanceFromCam;
navCanvas = new Vector3(navCanvas.x, 2f, navCanvas.z);
navigationCanvas.transform.position = new Vector3(navCanvas.x, navCanvas.y, navCanvas.z);
navigationCanvas.transform.rotation = camRotationToWatch.transform.rotation;
navCanvasHasDisplay = true;
}
}
else
{
//navigationCanvas.SetActive(false);
if (locationPanel.activeSelf == false && infoPanel.activeSelf == false) {
navigationCanvas.SetActive(false);
navCanvasHasDisplay = false;
}
}
This code is actually work fine when camRotationToWatch object rotate from down to up and Canvas show at correct position but as I try to to rotate camRotationToWatch from up to down it display(active) Canvas at very top position. How can I restrict canvas to show at same position (No matter player rotate from up to down or down to up) but display on front of the player object?
Kinda hard trying to figure out what exactly you want to do. But this did what I think you where trying to do
public GameObject follow; // The object you want to rotate around
public float distance = 2; // Distance to keep from object
private void Update() {
Vector3 forward = follow.transform.forward;
forward.y = 0; // This will result in Vector3.Zero if looking straight up or down. Carefull
transform.position = follow.transform.position + forward * distance;
transform.rotation = Quaternion.LookRotation(forward, Vector3.up);
}
I believe your "unexpected behavior" is due to the use of euler angles since they are not always entirely predictable. Try using Quaternions or Vector3.Angle() when possible.
If you want to limit the angle (say... if looking down or up more than 45° disable the object) you could do the following:
if (Vector3.Angle(forward, follow.transform.forward) > maxAngle) { ... }
This probably isn't a complete answer but something that might help. This line:
Vector3 navCanvas = camRotationToWatch.transform.position + camRotationToWatch.transform.forward * navCanvasDisplayDistanceFromCam;
You are creating a position at a fixed distance from camRotationToWatch. But if that object is looking up or down, that position is not horizontally at navCanvasDisplayDistanceFromCam. If it's looking straight up, then this position is in fact directly above.
So when you do this to set a fixed vertical height:
navCanvas = new Vector3(navCanvas.x, 2f, navCanvas.z);
you aren't getting the distance from camRotationToWatch that you think you are.
Instead of using camRotationToWatch.transform.forward, create a vector from it and zero out the Y component, and normalize before using it to offset the position. (You will need to watch out for zero length vectors with that though).
Whether that fixes your problem or not, it's too hard to guess but it should help improve your results some.
EDIT: Here is an example of how you can avoid the issue with the canvas being too close:
Vector3 camForward = camRotationToWatch.transform.forward;
camForward.y = 0;
if (camForward.magnitude == 0)
{
//TODO: you'll need to decide how to deal with a straight up or straight down vector
}
camForward.Normalize();
//Note: now you have a vector lying on the horizontal plane, pointing in
//the direction of camRotationToWatch
Vector3 navCanvas = camRotationToWatch.transform.position + camForward *
navCanvasDisplayDistanceFromCam;
//Note: if you want the canvas to be at the player's height (or some offset from),
//use the player's y
navCanvas = new Vector3(navCanvas.x, Player.transform.y, navCanvas.z);
navigationCanvas.transform.position = navCanvas;
Again, this might not fix all your issues but will help to ensure your canvas lies at a set distance horizontally from the Player and will also compensate for the player's up and down motion.

Make a point follow a rotating sprite

I have a rectangle with a square at its bottom. I also have code that makes the rectangle rotate around its origin which is at the top of this rectangle. Im trying to make the square at the bottom to always stay at the end of this rectangle even when its rotated. Hers a picture to illustrate my problem:
I see now that it wasn't such a good idea to make the square at the bottom white. So when i rotate the rectangle upwards to the right or upwards to the left, I want the square to keep staying at the end of this rectangle. Maybe there is a simple solution, but my knowledge isn't as good as it should be on this subject. Hope someone could point me in the right direction.
Something like this aught to get you there.
float pendulumAngle;
Vector2 origin;
Vector2 squareTLcorner;//top left corner of square
Vector2 squareOffset;
void Reset()
{
pendulumAngle = 0;
origin = new Vector2(?.?f, ?.?f);// set to whatever you need
squareTLcorner = new Vector2(?.?f, ?.?f); // set to whatever you need
squareOffset = squareTLcorner - origin;
}
void UpdatePendulum(float angleMovedSinceLastFrame)
{
pendulumAngle += angleMovedSinceLastFrame;
}
void UpdateSquarePosition()
{
squareTLcorner = Vector2.Transform(squareOffset, Matrix.CreateRotationZ(pendulumAngle) + origin;
}
void DrawSquare()
{
spriteBatch.Draw(sqTex,squareTLcorner, , ,pendulumAngle, , , , );// overload 6 of 7
}
The easiest way is passing a transformation matrix to the sprite batch.
Rectangle Black = new Rectangle(0,0, 20, 100);
Rectangle White = new Rectangle( Black.Left, Black.Bottom, Black.Width, Black.width);
Vector2 Pivot= new Vector(100,100);
vector2 Origin = new Vector2( 10,10);
Matrix transform = Matrix.CreateTranslation(-Origin.X, -Origin.Y)
* Matrix.CreateRotationZ(angle)
* Matrix.CreateTranslation(Pivot);
SpriteBatch.begin(...., transform)
SpriteBatch.Draw( Texture, Black, Color);
SpriteBatch.Draw( Texture, White, Color);
SpriteBatch.end();
Basically you are working in a different space that is rotated ans translated as you need, realize that Black rectangle location is (0,0).
Code it's not tested but should work as expected. ;)

How to determine which side of a rectangle collides with a circle

Before you point out that there are other answers to this question, i have looked at if not all, most of the other answers to this question or a similar question and i haven't found the solution i require.
Basically all i want to be able to do is when the circle/ball collides with a rectangle, i want to determine which side of the rectangle this collision has occured at. I want to find this out so that i can enforce a bit more realistic physics, e.g. if the ball hits the top of the rectangle, inverse it's Y velocity only... instead of both.
I have tried comparing the X and Y positions of the ball and the rectangle and even the location of both of their bounding boxes... testing even if the bottom of the ball's box has intersected with the rectangles top... using 'if ball.boundingBox.Bottom >= rectangle.boundingBox.Top'.
I have attached a picture to this to show what i am trying to achieve... just in case it's a bit confusing, as it's not detailed... the red what look like v's is the path if the ball comes in from one side, i want the movement upon impact to travel in the opposite way but this depends on the side of the rectangle as to what component of the ball's velocity i will have to change...
FYI i have also looked at vector normalisation... i haven't used it before so forgive me if this could be solved using this...
Thanks v.much for reading
EDIT as i am in a rush, i have used an different image instead... this still shows the behaviour i am trying to achieve, as the physics shown on the diagram is how i want the ball to behave when it collides with the other sides...
LINK TO IMAGE: http://codeincomplete.com/posts/2011/6/12/collision_detection_in_breakout/bounce2.v283.png
This code might be more comprehensive than you need and can be refactored to suit your needs but it is a complete answer and is flexible to use with moving bounding rectangles along with moving circles.
here is a graphic to give a visual aid to what the code is doing.
the red circle is intersecting with the black rectangle. visualize two imaginary lines going through opposite corners. If you know which side of each of the 2 lines the circle is on, you can deduce the collided edge.
first declare class scope private members
Rectangle CollisionBoxRect;
Rectangle circleRect;
Dictionary<string, Vector2> corners;
In your update after you've moved the circle and set its location and the potential intersected box's location it does a basic check to see if the circle's bounding rect is involved with the block's bounding rect. If so, it then alters the ball's velocity with the appropriate collision normal depending on which side of the rect the circle collided with.
if (CollisionBoxRect.Intersects(circleRect))
{
ballVelocity = Vector2.Reflect(ballVelocity, GetCollisionNormal(CollisionBoxRect));
}
The following methods support getting the proper side (the normal actually). Some of these methods can be done once in the initialize phase if they never change (like the get corners method);
private Vector2 GetCollisionNormal(Rectangle boxBeingIntersected)
{
getCorners(boxBeingIntersected);
bool isAboveAC = isOnUpperSideOfLine(corners["bottomRight"], corners["topLeft"], getBallCenter());
bool isAboveDB = isOnUpperSideOfLine( corners["topRight"], corners["bottomLeft"], getBallCenter());
if (isAboveAC)
{
if (isAboveDB)
{
//top edge has intersected
return -Vector2.UnitY;
}
else
{
//right edge intersected
return Vector2.UnitX;
}
}
else
{
if (isAboveDB)
{
//left edge has intersected
return -Vector2.UnitX;
}
else
{
//bottom edge intersected
return Vector2.UnitY;
}
}
}
public bool isOnUpperSideOfLine(Vector2 corner1, Vector2 oppositeCorner, Vector2 ballCenter)
{
return ((oppositeCorner.X - corner1.X) * (ballCenter.Y - corner1.Y) - (oppositeCorner.Y - corner1.Y) * (ballCenter.X - corner1.X)) > 0;
}
private Vector2 getBallCenter()
{
return new Vector2(circleRect.Location.X + circleRect.Width / 2, circleRect.Location.Y + circleRect.Height / 2);
}
private void getCorners(Rectangle boxToGetFrom)
{
corners.Clear();
Vector2 tl = new Vector2(boxToGetFrom.X, boxToGetFrom.Y);
Vector2 tr = new Vector2(boxToGetFrom.X + boxToGetFrom.Width, boxToGetFrom.Y);
Vector2 br = new Vector2(boxToGetFrom.X + boxToGetFrom.Width, boxToGetFrom.Y + boxToGetFrom.Height);
Vector2 bl = new Vector2(boxToGetFrom.X, boxToGetFrom.Y + boxToGetFrom.Height);
corners.Add("topLeft", tl);
corners.Add("topRight", tr);
corners.Add("bottomRight", br);
corners.Add("bottomLeft", bl);
}

Categories

Resources