XNA Rotate a bone with an offset translation - matrix

I have a model that has a bone which has a translated offset from the parent bone (it is not positioned at the tail of the parent bone). In Blender, where the model was made, I can rotate the bone and see the attached mesh rotate correctly. However, when I attempt to rotate the bone in my code, it seems to rotate it about the tail of the parent. I'm messing something up in the Matrix math (my worst enemy).
Update:
protected override void Update(GameTime gameTime)
{
bone.Transform = Matrix.CreateFromYawPitchRoll(0f, 0.5f, 0f);
base.Update(gameTime);
}
Pretty standard, but if it matters, Draw:
private void DrawModel(Model model, GraphicsDevice graphics, Matrix viewMatrix, Matrix projectionMatrix)
{
Matrix[] boneTransforms = new Matrix[model.Bones.Count];
model.CopyAbsoluteBoneTransformsTo(boneTransforms);
Matrix worldMatrix = orientation * Matrix.CreateTranslation(position);
foreach (ModelMesh mesh in model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.World = boneTransforms[mesh.ParentBone.Index] * worldMatrix;
effect.View = viewMatrix;
effect.Projection = projectionMatrix;
effect.EnableDefaultLighting();
effect.PreferPerPixelLighting = true;
// Set the fog to match the black background color
effect.FogEnabled = true;
effect.FogColor = Vector3.Zero;
effect.FogStart = 1000;
effect.FogEnd = 3200;
}
mesh.Draw();
}
}
Here's screen shots to show the problem
http://s1231.photobucket.com/albums/ee516/Neovivacity/?action=view&current=boneRotation.png

Ok, this may not be the most straightforward way of solving this, but I have a fix for anyone who's also having a similar issue.
When loading the Model, store the original transform. Then you multiply the bone's transform like I did. Finally you get the original transforms Translation, oringalTranform.Translation for example, and get bone's new transform. To adjust for the offset, bone.Transform *= Matrix.CreateTranslation(originalTranslation - newTranslation).
Here's a code snippet from my solution:
public void LoadContent(ContentManager content)
{
Model = content.Load<Model>(ModelName);
//Set the Bone pointers and transform matrices
AileronLBone = Model.Bones["LAileron"];
AileronRBone = Model.Bones["RAileron"];
ElevatorBone = Model.Bones["Elevator"];
RudderBone = Model.Bones["Rudder"];
FlapsBone = Model.Bones["Flaps"];
if (AileronLBone != null) AileronLTransform = AileronLBone.Transform;
if (AileronRBone != null) AileronRTransform = AileronRBone.Transform;
if (ElevatorBone != null) ElevatorTransform = ElevatorBone.Transform;
if (RudderBone != null) RudderTransform = RudderBone.Transform;
if (FlapsBone != null) FlapsTransform = FlapsBone.Transform;
}
public void Update(GameTime gameTime)
{
SetOffsetRotation(ElevatorBone, ElevatorTransform, ElevatorRotation);
}
public void SetOffsetRotation(ModelBone bone, Matrix transform, float rotation)
{
Vector3 oringalTrans = transform.Translation;
bone.Transform *= Matrix.CreateRotationX(rotation);
Vector3 newTrans = bone.Transform.Translation;
bone.Transform *= Matrix.CreateTranslation(oringalTrans - newTrans);
}

Related

Unity3d HTC Vive Radial Menu - Weird Glitching

So i wrote this radial Menu controlled by the trackpad on the left-hand wand. It determine which button to magnify by my fingers position on trackpad.
The Weird movement can be seen here.
Here i attacked my code related to this problem, the code for left wand.
SteamVR_TrackedObject obj; //The wand
public GameObject buttonHolder; //All the buttons will be children of this object
public bool buttonEnabled;
void Awake() {
obj = GetComponent<SteamVR_TrackedObject>(); //this will be left hand controller
}
void Update() {
var device = SteamVR_Controller.Input((int)obj.index);
//if touchpad touched
if (device.GetTouch(SteamVR_Controller.ButtonMask.Touchpad))
{
if (buttonEnabled) //if radial menu is open
{
//touchPadAngle: Get the angle between touch coord and X-axis
Vector2 touchedCoord = device.GetAxis(EVRButtonId.k_EButton_Axis0); //what is this line each variable
float touchPadAngle = VectorAngle(new Vector2(1, 0), touchedCoord); //(1, 0) is X-axis
// ------------------- Find closest button ------------------------
//Description: The process will be done by calculating the angle between button_Vector2 and X-axis (button_V2_to_10)
// And then find the button with the closest angler difference with (touchPadAngle).
float minAngle = float.PositiveInfinity;
Transform minButton = transform; //Temperatry assign wand tranform to it.
float pad_N_button_Angle = 0.0f; //Angle between touchPadAngle and buttonAngle.
Vector2 button_V2_to_10;
float button_Angle;
foreach (Transform bt in buttonHolder.transform)
{
button_V2_to_10 = new Vector2(transform.position.x, transform.position.z) - new Vector2(bt.position.x, bt.position.z);
button_Angle = VectorAngle(new Vector2(1, 0), button_V2_to_10);
pad_N_button_Angle = Mathf.Abs(button_Angle - touchPadAngle);
//Both buttonAngle and touchPadAngle range from -180 to 180, avoid Abs(170 - (-170)) = 340
pad_N_button_Angle = (pad_N_button_Angle > 180) ? Mathf.Abs(pad_N_button_Angle - 360) : pad_N_button_Angle;
if (pad_N_button_Angle < minAngle)
{
minButton = bt;
minAngle = pad_N_button_Angle;
}
}
//Magnify the closest button
foreach (Transform bt in buttonHolder.transform)
{
GameObject btGO = bt.gameObject;
if (!btGO.GetComponentInChildren<ButtomHandler>().onHover && bt == minButton) {
//Magnify
}
else if (bt != minButton && btGO.GetComponentInChildren<ButtomHandler>().onHover)
{
//minify
}
}
}
else {
activateButtonMenu();
}
}
//dis-hover all button if leave touch pad
if (device.GetTouchUp(SteamVR_Controller.ButtonMask.Touchpad)) {
//Hover the closest button
foreach (Transform bt in buttonHolder.transform)
{
GameObject btGO = bt.gameObject;
if (btGO.GetComponentInChildren<ButtomHandler>().onHover)
{
//minify
}
}
}
I'm quite stucked here, Any help would really be appreciated
"the closest angler difference with (touchPadAngle)"
shouldn't you consider more than one axis for a radial dial?

When dragging an object with rigidbody2D it passes through colliders (walls)

Ok, so I'm making this game where the user can drag a ball around the screen, but it's not supposed to leave the play area. I'm getting the following problem though, when I push it towards the colliders it bounces back, and if I push too hard it simply goes off screen (I need to make it do not go off screen. the user is free to drag it all over the place, but within the screen of course).
any tips on how I could solve this issue?
Here is the code for dragging which I'm using:
using UnityEngine;
using System.Collections;
public class CircleManager : MonoBehaviour {
private bool dragging = false;
private Vector3 screenPoint;
private Vector3 offset;
// Pressionando
void OnMouseDown()
{
dragging = true;
screenPoint = Camera.main.WorldToScreenPoint(gameObject.transform.position);
offset = gameObject.transform.position - Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, screenPoint.z));
}
// Arrastando
void OnMouseDrag()
{
Vector3 cursorPoint = new Vector3(Input.mousePosition.x, Input.mousePosition.y, screenPoint.z);
Vector3 cursorPosition = Camera.main.ScreenToWorldPoint(cursorPoint) + offset;
//i tried with both below.
//transform.position = cursorPosition;
transform.GetComponent<Rigidbody2D>().MovePosition(cursorPosition);
}
// Soltando
void OnMouseUp()
{
dragging = false;
}
}
Thanks!
You could try to do something like,
if( transform.position.x > xMaxPos )
{
transform.position.x = new Vector3( xMaxPos, transform.position.y, transform.position.z );
}
You could set up for each min and max. Then when you create the xMaxPos variables, create them like:
[serializeField]
private float xMaxPos;
That way they will appear in the inspector and you can tweak their values as you please. You could also throw in an offset that's the width of the ball i.e.
transform.position.x = new Vector3( xMaxPos - transform.localscale.x/2, transform.position.y, transform.position.z );
Try using velocity
public class CircleManager : MonoBehaviour {
private bool dragging = false;
private Vector3 screenPoint;
private Vector3 offset;
public float speed = 5.0f;
// Pressionando
void OnMouseDown()
{
dragging = true;
Vector3 cursorPosition = Camera.main.ScreenToWorldPoint(ToDepth(Input.mousePosition, transform.position.z));
offset = gameObject.transform.position - cursorPosition;
}
// Arrastando
void OnMouseDrag()
{
Vector3 cursorPosition = Camera.main.ScreenToWorldPoint(ToDepth(Input.mousePosition, transform.position.z)) + offset;
Vector3 direction = (transform.position - cursorPosition).normalized;
transform.GetComponent<Rigidbody2D>().velocity = direction * speed * Time.deltaTime;
}
// Soltando
void OnMouseUp()
{
dragging = false;
}
Vector3 ToDepth(Vector3 value, float depth)
{
return new Vector3(value.x, value.y, depth);
}
}
Few things to note:
You don't have to write out gameObject.transform.position i see you did that a few times, as well as calling transform... directly. Its both the same thing, so you don't need the gameObject part.
Also your getting the screenPoint of the transform, then using the z value of that later on, which doesn't really make much sense to me.
Anyways, i don't see why this shouldn't work for you, i haven't tested it though.

Converting from Matrix4 to Vec3,Quaternion,Vec3 and back again

Libgdx uses a Matrix4 to express the world space position, rotation and scaleing of objects.
As I am animating these objects a lot in my code I wish to convert them to a format that keeps Position, Rotation and Scale Separate.
This makes it easier to interpolate correctly in order to animate them.
I am storing the Scale and Position both as Vector3s.
And the rotation as a Quaternion.
Here is my class that stores these 3 and converts two and from a Matrix4;
public class PosRotScale {
private static String logstag="ME.PosRotScale";
Vector3 position = new Vector3();
Quaternion rotation = new Quaternion();
Vector3 scale = new Vector3(1f,1f,1f);
public PosRotScale(Vector3 position, Quaternion rotation, Vector3 scale) {
super();
this.position = position;
this.rotation = rotation;
this.scale = scale;
}
public PosRotScale(Matrix4 setToThis) {
this.setToMatrix(setToThis);
}
#Override
public String toString(){
Vector3 axis = new Vector3();
float angle = rotation.getAxisAngle(axis);
String stateAsString = "["+position.x+","+position.y+","+position.z+"]"
+ "["+axis.x+","+axis.y+","+axis.z+","+angle+"]"
+ "["+scale.x+","+scale.y+","+scale.z+"]";
return stateAsString;
}
public void setToMatrix(Matrix4 lastLocation) {
lastLocation.getTranslation(position);
lastLocation.getRotation(rotation);
lastLocation.getScale(scale);
}
public PosRotScale setToRotation(float i, float j, float k, float angleInDeg) {
rotation.set(new Vector3(i,j,k), angleInDeg);
return this;
}
public PosRotScale setToPosition(Vector3 newposition) {
position = newposition.cpy();
return this;
}
public PosRotScale setToScaling(Vector3 newscale) {
scale = newscale.cpy();
return this;
}
public Matrix4 createMatrix() {
return new Matrix4(position,rotation.nor(),scale);
}
}
(I apologise for the snippet above having a "run as" and being interpreted as javascript - I didn't see a option in the code snippet box for Java)
Anyway, in order to test if this was working I created a test state and converted to and from it.
Unfortunately the values clearly didn't match.
I expected the rotation to be off a bit (seems to be the nature of rotations) but scale is clearly wayyyy of. Any pointers as to what I am doing wrong?
Test code;
PosRotScale startScaleAndRotation = new PosRotScale();
startScaleAndRotation.setToPosition(new Vector3(30f, 40f, 50f));
startScaleAndRotation.setToRotation(0f, 0f, 1f, 45);
startScaleAndRotation.setToScaling(new Vector3(0.5f, 2.5f,0.5f));
Gdx.app.log(logstag, " setting to: "+startScaleAndRotation.toString());
Matrix4 test = startScaleAndRotation.createMatrix();
PosRotScale test2 = new PosRotScale(test);
Gdx.app.log(logstag, " check after conversion: "+test2.toString());
Results in:
setting to: [30.0,40.0,50.0][0.0,0.0,0.99999994,45.000004][0.5,2.5,0.5]
check after conversion: [30.0,40.0,50.0][0.0,0.0,1.811493,35.83953][1.8027757,1.8027754,0.5]

How to control an animation by touch position along a path in Unity3D?

I have a GameObject that I want to animate along a specific path/curve, but the animation should be controlled by mouse/touch position. So when I touch/click on the GameObject and move the finger/mouse on/near the path (or maybe its easier to just move down) the GameObject should follow its defined path.
I like iTween, but I think it is not possible to find a solution using it here, right?
edit: added image:
It's quite a simpler task than what you might think.
Basically it's a question of remapping a function (that takes the input as parameter) to another function (that express a position along a path).
There are several ways of doing that, depending on the precise effect you want to implement.
The most important choices you have to take are:
How the describe the path/curve
How to handle input
Example
For the path an easy and flexible way is to use some sort of spline curves, such as cubic Bézier curve. It's easy to implement and Unity3D provides built-in functions to draw them. Have a look at Handles.DrawBezier.
Basically a Bézier function takes as input a parameter t in the domain [0,1] and return as a result a point in the space (2D or 3D as you prefer). B(0) gives the point at the begin of the curve, B(1) the end point. (Side note: the function is not linear so in the general case incrementing at a constant rate t doesn't produce a movement at constant speed along the curve. This paper might be useful).
For what concern the input the simpler solution that comes up to my mind is the following:
Accumulate somewhere the vector describing the offset from the position when the touch started to the current touch position. (Here's how to handle touches, have a look at deltaPosition).
Something like:
if (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Moved)
{
offsetFromStartPos += Input.GetTouch(0).deltaPosition;
}
Let's say you want to swipe up/down your finger for moving forward/back an object along a path.Choose a "travel" distance (the domain of the input function) for your finger in order to complete the movement along the curve and normalize the offset using such distance in order to remap the input into the [0,1] domain.
float t = offsetFromStartPos.y / maxDistanceAlongYAxis;
Vector3 pos = CalculateBezier(t);
transform.position = pos;
It's just an hint to put you in the right direction.
I tried with keyboard and its working fine,
but not with mouse or touch
using System;
using UnityEngine;
public class Collector : MonoBehaviour
{
public Transform startPoint;
public Transform middlePoint;
public Transform endPoint;
public float curveSpeed = 0.5f;
//public float speed = 0f;
private int _direction = 1;
private bool _isObjectSelected;
private Vector3 _mouseLastPosition;
private float _journeyLength;
private Vector3 _offsetPos;
private float _currentTime = 0;
private void Start()
{
_journeyLength = Vector3.Distance(startPoint.position,
endPoint.position);
UpdateJourney(0);
}
private void OnMouseDown()
{
if (_isObjectSelected)
return;
_offsetPos = Vector3.zero;
_mouseLastPosition = Input.mousePosition;
_isObjectSelected = true;
}
private void OnMouseUp()
{
_isObjectSelected = false;
}
private void OnMouseExit()
{
_isObjectSelected = false;
}
private void OnMouseDrag()
{
if (_isObjectSelected)
{
Debug.LogError("Mouse drag");
Vector3 currentPosition = Input.mousePosition;
_offsetPos += currentPosition - _mouseLastPosition;
float distCovered = _offsetPos.y / _journeyLength;
UpdateJourney(distCovered);
_mouseLastPosition = currentPosition;
}
}
private void UpdateJourney(float time)
{
if (time < 0)
time = 0;
else if (time > 1)
time = 1;
_currentTime = time;
transform.position =
QuadraticCurve(startPoint.position,
middlePoint.position,
endPoint.position,
_currentTime);
transform.rotation = Quaternion.Euler(
new Vector3(0, 0,
QuadraticCurve(0, 45, 90, _currentTime)));
}
private void Update()
{
// moving on path using keyboard input
float direction = Input.GetAxisRaw("Horizontal");
if (Math.Abs(direction) > 0.1f)
{
_currentTime += Time.deltaTime * curveSpeed * direction;
UpdateJourney(_currentTime);
}
}
private static Vector3 Lerp(Vector3 start, Vector3 end, float time)
{
return start + (end - start) * time;
}
private static Vector3 QuadraticCurve(Vector3 start, Vector3 middle, Vector3 end, float time)
{
Vector3 point0 = Lerp(start, middle, time);
Vector3 point1 = Lerp(middle, end, time);
return Lerp(point0, point1, time);
}
private static float QuadraticCurve(float start, float middle, float end, float time)
{
float point0 = Mathf.Lerp(start, middle, time);
float point1 = Mathf.Lerp(middle, end, time);
return Mathf.Lerp(point0, point1, time);
}
}

Rotation in Unity3D

This is a simplified code from what I'm trying to do:
var angle = 1.57;
if ( this.transform.rotation.y > angle ){
this.transform.rotation.y--;
} else if ( this.transform.rotation.y < angle ){
this.transform.rotation.y++;
}
I'm used to code in AS3, and if I do that in flash, it works perfectly, though in Unity3D it doesn't, and I'm having a hard time figuring out why, or how could I get that effect.
Can anybody help me? Thanks!
edit:
my object is a rigidbody car with 2 capsule colliders driving in a "bumpy" floor, and at some point he just loses direction precision, and I think its because of it's heirarchical rotation system.
(thanks to kay for the transform.eulerAngles tip)
transform.rotation retrieves a Quaternion. Try transform.rotation.eulerAngles.y instead.
Transform Rotation is used for setting an angle, not turning an object, so you would need to get the rotation, add your change, and then set the new rotation.
Try using transform.rotate instead.
Check the Unity3d scripting reference here:
http://unity3d.com/support/documentation/ScriptReference/Transform.Rotate.html
I see two problems so far. First the hierarchical rotation of Unity. Based on what you are trying to achieve you should manipulate either
transform.localEulerAngles
or
transform.eulerAngles
The second thing is, you can't modify the euler angles this way, as the Vectors are all passed by value:
transform.localEulerAngles.y--;
You have to do it this way:
Vector3 rotation = transform.localEulerAngles;
rotation.y--;
transform.localEulerAngles = rotation;
You need to create a new Quaternion Object
transform.rotation = Quaternion.Euler ( transform.rotation.x, transform.rotation.y++, transform.rotation.z );
You can also use transform.Rotate function.
The above suggestion to use transform.Rotate( ) is probably what you're going to need to do to actually make it rotate, BUT the variables of transform.Rotate( ) are velocity/speed rather than direction, so transform.Rotate( ) will have to use more than one axis if you want an angled rotation. Ex:
class Unity // Example is in C#
{
void Update( )
{
gameObject.transform.Rotate(0, 1, 0);
}
}
This will rotate the object around its y-axis at a speed of 1.
Let me know if this helps - and if it hinders I can explain it differently.
You should try multiplyng your rotation factor with Time.deltaTime
Hope that helps
Peace
Here is my script for GameObject rotation with touch
//
// RotateController.cs
//
// Created by Ramdhan Choudhary on 12/05/13.
//
using UnityEngine;
using System;
public class RotateController
{
private float RotationSpeed = 9.5f;
private const float mFingerDistanceEpsilon = 1.0f;
public float MinDist = 2.0f;
public float MaxDist = 50.0f;
private Transform mMoveObject = null;
private bool isEnabledMoving = false;
//************** Rotation Controller Constructor **************//
public RotateController (Transform goMove)
{
isEnabledMoving = true;
mMoveObject = goMove;
if (mMoveObject == null) {
Debug.LogWarning ("Error! Cannot find object!");
return;
}
}
//************** Handle Object Rotation **************//
public void Update ()
{
if (!isEnabledMoving && mMoveObject != null) {
return;
}
Vector3 camDir = Camera.main.transform.forward;
Vector3 camLeft = Vector3.Cross (camDir, Vector3.down);
// rotate
if (Input.touchCount == 1) {
mMoveObject.Rotate (camLeft, Input.touches [0].deltaPosition.y * RotationSpeed * Time.deltaTime, Space.World);
mMoveObject.Rotate (Vector3.down, Input.touches [0].deltaPosition.x * RotationSpeed * Time.deltaTime, Space.Self);
}
}
}

Resources