I am currently building a scene graph in OpenGL and I trying to model parent transformations on their children, but it does not seem to be working. :(
My main problem is that the child's rotation does not follow the parent's rotation correctly. The child rotates around the parent object (center), which is what it is supposed to do, but the speed of it orbiting around the parent increases as the parent's rotation increase and slows back down once the parent does a full circle (when the parent's rotation reaches back to the rotation it started of at). Also I did have some problems with the correct translation of the child in relation to the parent, but I managed to temporarily fix them (I think). More info in the code. Sorry for the lengthy explanation, I would have definitely attached a video if I could, of the problem.
This is my transform class, where the transformations are set, particularly the getTransformation()
method:
public class Transform {
private Vector3f position, lastPosition;
private Vector3f rotation, lastRotation;
private Vector3f scale, lastScale;
private Transform parent;
private Matrix4f parentMatrix;
public Transform() {
this(null, null, null);
}
public Transform(Vector3f position, Vector3f rotation, Vector3f scale) {
this.position = position != null ? position : new Vector3f(0, 0, 0);
this.rotation = rotation != null ? rotation : new Vector3f(0, 0, 0);
this.scale = scale != null ? scale : new Vector3f(1, 1, 1);
this.lastPosition = new Vector3f(0, 0, 0);
this.lastRotation = new Vector3f(0, 0, 0);
this.lastScale = new Vector3f(1, 1, 1);
parentMatrix = new Matrix4f().identity();
}
public boolean requireTMUpdate() { //checks if the matrix has changed and requires an update
if(parent != null) {
return parent.requireTMUpdate();
}
if(!position.equals(lastPosition)) {
lastPosition.set(position);
return true;
}
if(!rotation.equals(lastRotation)) {
lastRotation.set(rotation);
return true;
}
if(!scale.equals(lastScale)) {
lastScale.set(scale);
return true;
}
return false;
}
public Matrix4f getTransformation() {
if((parent != null) && (requireTMUpdate())) {
if(!(getParent().equals(getParent().getParent()))) {
parentMatrix.set(parent.getTransformation());
//The above line sets the updated parentMatrix
setPosition(parentMatrix.transformPosition(position));
//The above line sets the position to where child is supposed to be in
//relation the parent, otherwise once the key is stopped being pressed,
//it will return to the position at the start of the game / program.
}
}else {
parentMatrix.rotationXYZ(0, 0, 0).translation(0, 0, 0).scale(1);
// The above line is supposed to reset the matrix, otherwise the previous
//transformations or rotations add up each frame and the it just gets messed
//up.
}
//System.out.println(parentMatrix.toString());
return parentMatrix.mul(Matrices.transformationMatrix(position, rotation, scale))
// The transformationMatrix() method above from the Matrices class is
//supposed to return a worldMatrix.
}
public Vector3f getPosition() {
return position;
}
public void setPosition(Vector3f position) {
this.position = position;
}
public Vector3f getRotation() {
return rotation;
}
public void setRotation(Vector3f rotation) {
this.rotation = rotation;
}
public Vector3f getScale() {
return scale;
}
public void setScale(Vector3f scale) {
this.scale = scale;
}
public Transform getParent() {
return parent;
}
public void setParent(Transform parent) {
this.parent = parent;
}
}
Matrices class:
public class Matrices {
public static Matrix4f transformationMatrix(Vector3f pos, Vector3f rot, Vector3f scale) {
Matrix4f result = new Matrix4f();
result.identity();
result.translate(pos.x, pos.y, pos.z);
Quaternionf rotation =
new Quaternionf().
identity().
rotateX((float) Math.toRadians(rot.x)).
rotateY((float) Math.toRadians(rot.y)).
rotateZ((float) Math.toRadians(rot.z));
/*result.rotate((float) Math.toRadians(rot.getX()), 1, 0, 0);
result.rotate((float) Math.toRadians(rot.getY()), 0, 1, 0);
result.rotate((float) Math.toRadians(rot.getZ()), 0, 0, 1);*/
result.rotate(rotation);
result.scale(scale.x, scale.y, scale.z);
return result;
}
public static Matrix4f transformationMatrix(Vector3f pos, Quaternionf rotation, Vector3f scale) {
Matrix4f result = new Matrix4f();
result.identity();
result.translate(pos.x, pos.y, pos.z);
/*Quaternionf rotation =
new Quaternionf().
identity().
rotateX((float) Math.toRadians(rot.x)).
rotateY((float) Math.toRadians(rot.y)).
rotateZ((float) Math.toRadians(rot.z));*/
/*result.rotate((float) Math.toRadians(rot.getX()), 1, 0, 0);
result.rotate((float) Math.toRadians(rot.getY()), 0, 1, 0);
result.rotate((float) Math.toRadians(rot.getZ()), 0, 0, 1);*/
result.rotate(rotation);
result.scale(scale.x, scale.y, scale.z);
return result;
}
public static Matrix4f viewMatrix(Vector3f pos, Vector3f rot) {
Matrix4f result = new Matrix4f();
result.identity();
Quaternionf rotation =
new Quaternionf().
identity().
rotateX((float) Math.toRadians(rot.x)).
rotateY((float) Math.toRadians(rot.y)).
rotateZ((float) Math.toRadians(rot.z));
/*result.rotate((float) Math.toRadians(rot.getX()), new org.joml.Vector3f(1, 0, 0));
result.rotate((float) Math.toRadians(rot.getY()), new org.joml.Vector3f(0, 1, 0));*/
result.rotate(rotation);
result.translate(-pos.x, -pos.y, -pos.z);
return result;
}
public static Matrix4f viewMatrix(Vector3f pos, float pitch, float yaw, float roll) {
Matrix4f result = new Matrix4f();
result.identity();
Quaternionf rotation =
new Quaternionf().
identity().
rotateX((float) Math.toRadians(pitch)).
rotateY((float) Math.toRadians(yaw)).
rotateZ((float) Math.toRadians(roll));
/*result.rotate((float) Math.toRadians(rot.getX()), new org.joml.Vector3f(1, 0, 0));
result.rotate((float) Math.toRadians(rot.getY()), new org.joml.Vector3f(0, 1, 0));*/
result.rotate(rotation);
result.translate(-pos.x, -pos.y, -pos.z);
return result;
}
public static Matrix4f projectionMatrix(float FOV, float aspectRatio,
float NearPlaneDis, float FarPlaneDis) {
Matrix4f result = new Matrix4f();
result.identity();
result.perspective(FOV, aspectRatio, NearPlaneDis, FarPlaneDis);
/*float y_scale = (float) ((1f / Math.tan(Math.toRadians(FOV / 2f))) * aspectRatio);
float x_scale = y_scale / aspectRatio;
float frustum_length = FarPlaneDis - NearPlaneDis;
result.m00(x_scale);
result.m11(y_scale);
result.m22(-((FarPlaneDis + NearPlaneDis) / frustum_length));
result.m23(-1);
result.m32(-((2 * NearPlaneDis * FarPlaneDis) / frustum_length));
result.m33(0);*/
return result;
}
public static Matrix4f translate(Vector3f vec, Matrix4f src, Matrix4f dest) {
if (dest == null)
dest = new Matrix4f();
dest.m30(dest.m30() + src.m00() * vec.x + src.m10() * vec.y + src.m20() * vec.z);
dest.m31(dest.m31() + src.m01() * vec.x + src.m11() * vec.y + src.m21() * vec.z);
dest.m32(dest.m32() + src.m02() * vec.x + src.m12() * vec.y + src.m22() * vec.z);
dest.m33(dest.m33() + src.m03() * vec.x + src.m13() * vec.y + src.m23() * vec.z);
return dest;
}
public static Vector4f transform(Matrix4f left, Vector4f right, Vector4f dest) {
if (dest == null)
dest = new Vector4f(0, 0, 0, 0);
float x = left.m00() * right.x + left.m10() * right.y + left.m20() * right.z + left.m30() * right.w;
float y = left.m01() * right.x + left.m11() * right.y + left.m21() * right.z + left.m31() * right.w;
float z = left.m02() * right.x + left.m12() * right.y + left.m22() * right.z + left.m32() * right.w;
float w = left.m03() * right.x + left.m13() * right.y + left.m23() * right.z + left.m33() * right.w;
dest.x = x;
dest.y = y;
dest.z = z;
dest.w = w;
return dest;
}
public static Vector3f scale(Vector3f vector, float scale) {
vector.x *= scale;
vector.y *= scale;
vector.z *= scale;
return vector;
}
public static float[] getAll(Matrix4f matrix) {
float[] f = new float[16];
return matrix.get(f);
}
public static float barryCentric(Vector3f p1, Vector3f p2, Vector3f p3, Vector2f pos) {
float det = (p2.z - p3.z) * (p1.x - p3.x) + (p3.x - p2.x) * (p1.z - p3.z);
float l1 = ((p2.z - p3.z) * (pos.x - p3.x) + (p3.x - p2.x) * (pos.y - p3.z)) / det;
float l2 = ((p3.z - p1.z) * (pos.x - p3.x) + (p1.x - p3.x) * (pos.y - p3.z)) / det;
float l3 = 1.0f - l1 - l2;
return l1 * p1.y + l2 * p2.y + l3 * p3.y;
}
}
Finally, the Renderer class, where the objects are initialized:
public class Renderer {
private Model model;
private Model model2;
private GameObject root;
private GameObject child;
public Camera camera;
private float time = 0;
/*private float vertices[] = {
0.5f, 0.5f, 0f, //0 - top right
0.5f, -0.5f, 0f, //1 - bottom right
-0.5f, -0.5f, 0f, //2 - bottom left
-0.5f, 0.5f, 0f //3 - top left
};
private int indices[] = {
0, 1, 3, // first triangle
3, 1, 2 // second triangle
};*/
public Renderer() {
model = new Model("/backpack.obj");
model2 = new Model("/backpack.obj");
camera = new Camera(new Vector3f(0, 0, 0), new Vector3f(0, 0, 0));
root = new GameObject();
child = new GameObject();
root.addComponent(camera);
root.addComponent(model);
child.addComponent(model2);
root.addChild(child);
model.getTransform().setPosition(new Vector3f(0, 0, 0));
model2.getTransform().setPosition(new Vector3f(10, 0, 10));
}
public void render(Shader shader) {
if(Input.isKeyDown(GLFW.GLFW_KEY_RIGHT)) {
model.getTransform().setRotation(new Vector3f(0, time += 1f, 0));
}
//model2.getTransform().setRotation(new Vector3f(0, time += 0.01f, 0));
shader.bind();
root.input();
root.update();
root.render(shader, camera);
shader.unbind();
}
public void cleanUp() {
for(Mesh mesh: model.getMeshes()) {
mesh.cleanUp();
}
}
}
What exactly am I doing wrong here? Any help is appreciated!