Jump to content

Basic Math Knowledge for Video Games


ProfessorF
 Share

Recommended Posts

Two quick questions.

 

If I want to use a transformation matrix on a mesh (instead of using the mesh members: position, translation, scale),

 

1) After I create the transformation matrix (ex: mat) do I use mesh.setPivotMatrix(mat)?

2) Matrix.m is a single-dimensional array.  Is the order  0=m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, 15=m44?

 

Thanks in advance

Link to comment
Share on other sites

1. Just also be sure that position = (0,0,0), rotation = (0,0,0) and scaling = (1,1,1) otherwise the corresponding matrix (equals mesh._worldMatrix if the mesh doesn't have a parent) will be different than Identity and will be "added" to your pivot matrix to get the world matrix.

 

2. Be aware in BabylonJS, like in the XNA framework, matrices use a row vector layout. Whereas you do A * B when matrices use a column vector layout (what we use in France in mathematics), you have to do B * A when they use a row vector layout. That's why in BabylonJS B * A = B.multiply(A) (A * B for french maths). For example, wolrdMatrix = localWorld.multiply(parent.worldMatrix) (wm = lw * pwm whereas in french maths it would be wm = pwm * lw)

Link to comment
Share on other sites

Sorry for the delay in implementing this, I want it to be fast! I have two algorithms and any feedback would be appreciated.  I'm actually leaning towards the slow version because it's less complex, and "plays nicely" with the other mesh variables.  The fast version sets the pivot matrix and is all vector math. Anyway, here they are.
 

1. Algorithm one is based on Euler angles and works flawlessly for static and skinned models BUT is slow because it uses trig functions. 

// This Euler version works flawlessly. Haven't made into a function yet, so// Globals: looker mesh, target mesh, cor-rection for rotationvar dv = target.position.subtract(looker.position);var yaw = -Math.atan2(dv.z, dv.x)-cor; // -because rots are oppositevar len = Math.sqrt(dv.x * dv.x + dv.z * dv.z);var roll = Math.atan2(dv.y, len); //-any corrections if mesh is vertical TBI, similar to Vector.Uplooker.rotationQuaternion = BABYLON.Quaternion.RotationYawPitchRoll(yaw, 0, roll);

2. Algorithm two is vector based (transforming the basis vectors, similar to GLULookAt in OpenGL), is fast, works as expected with static models, but is wonky when it comes to skinned models. "Wonky" means it works, but not in a way I expect, which bothers me.  I'm pretty sure the problem is that I need to transform the skeleton as well.  Also, since I am using the pivot matrix, I need to store and reset: position, rotation, and scale before using this code.

var v3up = new BABYLON.Vector3(0, 1, 0);var v3look = new BABYLON.Vector3(lpx, lpy, lpz); // lpx,lpy,lpz=looker.position, can't use .position because using pivotMatrixvar v3newz = target.position.subtract(v3look);v3newz.normalize();var v3newx = BABYLON.Vector3.Cross(v3newz, v3up);v3newx.normalize();var v3newy = BABYLON.Vector3.Cross(v3newx, v3newz);v3newy.normalize();var mat = BABYLON.Matrix.Identity();mat.m = [ v3newx.x,  v3newx.y,  v3newx.z, 0,          v3newy.x,  v3newy.y,  v3newy.z, 0,         -v3newz.x, -v3newz.y, -v3newz.z, 0,                 0,         0,         0, 1];looker.setPivotMatrix(BABYLON.Matrix.RotationY(cor).multiply(mat).multiply(BABYLON.Matrix.Scaling(scl,scl,scl).multiply(BABYLON.Matrix.Translation(lpx,lpy,lpz)))); // works, but have to fold back in position, rotation, and scale 
Link to comment
Share on other sites

1. Just also be sure that position = (0,0,0), rotation = (0,0,0) and scaling = (1,1,1) otherwise the corresponding matrix (equals mesh._worldMatrix if the mesh doesn't have a parent) will be different than Identity and will be "added" to your pivot matrix to get the world matrix.

 

To clearify my previous post (please Deltakosh, correct me if I'm wrong):

 

position, rotation and scaling are actually the vectors (expressed in the parent space of the mesh) used to compute the matrix which represents the pivot space of the mesh (you multiply by this matrix to pass from the pivot space to the parent space) and pivotMatrix is the matrix which expresses the mesh in this pivot space.

 

In 2D

                                                    +

                                                    +

                                                M +  +  +

 

                      +                     +

+                      +               +

+                         +        +

+  +  +                     +P

O

 

O: origin of the parent space (or world if no parent)

P: pivot of the mesh: mesh.position = (3,0,0), mesh.rotation = (0,0,PI/4), mesh.scaling = (1.3, 1.3, 1.3)

M: set by pivotMatrix: (1/1.3,a,b,c, d,1/1.3,e,f, g,h,1/1.3,0, 1.25,0,0,1) where a,b,c,d,e,f,g,h are the coefficients for a z rotation of -PI/4

Link to comment
Share on other sites

I actually finished the code a while ago, I just haven't finished the documentation.  

 

The documentation is necessary because, unlike camera.lookAt, a mesh.lookAt has to take into account the different ways people can model meshes.  

 

For example: if someone models a spaceship or character pointing to the right (>), then mesh.lookAt(point) works fine, because zero degrees is defined relative to a vector pointing to the right.  

 

But if someone models a spaceship or character pointing towards the screen (V), then you need to add a yaw correction: mesh.lookAt(point, -90 degrees)

 

And to make matters even more complex, you can correct for pitch and roll as well.

 

Anyway, I feel that if I don't provide the documentation and a working sample, then DeltaKosh and team will be deluged with bug reports.

 

Once I'm finished with the documentation/sample code,  I will push and issue a merge request.

Link to comment
Share on other sites

Just as a test, I went ahead and implemented your first code for LookAt with a custom Atan2 method.  It works well and I wonder if what the performance difference would be with that custom atan2 method.  I haven't done any tests with it, but it does work and thought I'd throw it out there:

 

Atan2 method:

NEO.Coeff_1 = Math.PI / 4.0;NEO.Coeff_2 = 3.0 * (Math.PI / 4.0);NEO.atan2 = function(y, x) {	var abs_y = y < 0 ? y*-1 : y; // abs sub	var angle;	if (x >= 0) {			var r = (x - abs_y) / (x + abs_y);			angle = NEO.Coeff_1 - NEO.Coeff_1 * r;	} else {			var r = (x + abs_y) / (abs_y - x);			angle = NEO.Coeff_2 - NEO.Coeff_1 * r;	}	return y < 0 ? -angle : angle;}

and here's the method implemented:

BABYLON.Mesh.prototype.LookAt = function (target, adjustment){	adjustment = adjustment == null ? 0 : adjustment;	var dv = target.getAbsolutePosition().subtract(this.position);	var yaw = -NEO.atan2(dv.z, dv.x) - adjustment; // -because rots are opposite	var len = Math.sqrt(dv.x * dv.x + dv.z * dv.z);	var roll = NEO.atan2(dv.y, len); //-any corrections if mesh is vertical TBI, similar to Vector.Up	this.rotationQuaternion = BABYLON.Quaternion.RotationYawPitchRoll(yaw, 0, roll);};

sample call:

box.LookAt(moon);

Check it out:

http://www.rockonflash.com/webGL/babylon/demo/index.html

Link to comment
Share on other sites

Okay, this is what I submitted a pull request for.  As Gwenael, pointed out to me in e-mail, this assumes that the targetPoint is in the same space as the Mesh.

Typical usage would be mesh..lookAt(targetPoint),

 

But I added optional parameters yawCor, pitchCor, rollCor -- to give you the flexibility to adjust the model after it orients to the targetPoint.  For example, in neoRiley's cool demo, the square can be rolling while it follows the orbiting planet, by doing mesh.lookAt(targetPoint,0,0,++roll) -- assuming roll is a global.

    BABYLON.Mesh.prototype.lookAt = function (targetPoint, yawCor, pitchCor, rollCor) {        /// <summary>Orients a mesh towards a target point. Mesh must be drawn facing user.</summary>        /// <param name="targetPoint" type="BABYLON.Vector3">The mesh to look at</param>        /// <param name="yawCor" type="Number">optional yaw (y-axis) correction in radians</param>        /// <param name="pitchCor" type="Number">optional pitch (x-axis) correction in radians</param>        /// <param name="rollCor" type="Number">optional roll (z-axis) correction in radians</param>        /// <returns>Mesh oriented towards targetMesh</returns>        yawCor   = yawCor   || 0; // default to zero if undefined         pitchCor = pitchCor || 0;        rollCor  = rollCor  || 0;        var dv = targetPoint.subtract(this.position);        var yaw = -Math.atan2(dv.z, dv.x) - Math.PI / 2;        var len = Math.sqrt(dv.x * dv.x + dv.z * dv.z);        var pitch = Math.atan2(dv.y, len);        this.rotationQuaternion = BABYLON.Quaternion.RotationYawPitchRoll(yaw + yawCor, pitch + pitchCor, rollCor);    };

I'll post two working samples shortly.

Link to comment
Share on other sites

You're right about using getAbsolute!  Sorry about that - the reason I did that was because when I was trying to get world coordinates by accessing target.position, it was returning the same value which seemed to be the local position in the log and so the cube would do the initial look at the starting point of the moon, but would not rotate because it was not returning updated world coordinates.  Using getAbsolutePosition() gave me the updated world coordinates of the moon.  Why would that be?

Link to comment
Share on other sites

Okay I finished a tutorial for mesh.lookAt that combines movement (w,a,s,d,f,v moves the cube, spacebar moves the spaceship)

 

Video:

 

Code:

<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><head>    <title>Tutorial - mesh.lookAt - ProfessorF </title>    <script src="js/babylon.js"></script>    <script src="js/hand.js"></script>    <script>        window.addEventListener("load", start); // 1st line of code executed        window.addEventListener("keydown", handleKeyDown);        window.addEventListener("change", handleChange);        var canvas, engine, scene, light, light2, camera;  // Engine Globals        var looker, yawCor, rollCor, pitchCor, moveLooker; // Looker Globals        var target;                         function start() {            canvas = document.getElementById("cvRAVE");            engine = new BABYLON.Engine(canvas, true);            scene = new BABYLON.Scene(engine);            light = new BABYLON.DirectionalLight("sun", new BABYLON.Vector3(1, -1, 1), scene);            light2 = new BABYLON.DirectionalLight("sun2", new BABYLON.Vector3(0, -1, 0), scene);            light2.intensity = 0.5;            camera = new BABYLON.FreeCamera("cam", new BABYLON.Vector3(0, 50, -50), scene);            camera.rotation.x = Math.PI / 4; // look down 45 degrees            ground = new BABYLON.Mesh.CreateGround("ground", 1024, 1024, 1, scene);            ground.material = new BABYLON.StandardMaterial("texGround", scene);            ground.material.diffuseTexture = new BABYLON.Texture("geotrigtexture.png", scene);            BABYLON.SceneLoader.ImportMesh("", "assets/pointer/", "pointer.babylon", scene, function (meshes, particles, skeletons) {                looker = meshes[0];                looker.position = new BABYLON.Vector3(-5, 5, 5); // you can set this yourself                yawCor =0;                 pitchCor = 0;                rollCor = 0;                scl = 1;                moveLooker = false;            });            target = BABYLON.Mesh.CreateBox("orangeBox", 5, scene);            target.position.x = 20;            target.position.z = 0;            target.position.y = 0;            target.material = new BABYLON.StandardMaterial("orangeMaterial", scene);            target.material.diffuseColor = new BABYLON.Color3(1, .5, 0);                     scene.activeCamera.attachControl(canvas);            engine.runRenderLoop(update);        }        function update() {            if (target && looker) {                looker.lookAt(target.position);                if (moveLooker) {                    var dv = target.position.subtract(looker.position);                    if (dv.length() > 1) { // if distance from target is > epsilon, move closer                        dv.normalize();    // otherwise you jump to the position                        looker.position = looker.position.add(dv);                    } else moveLooker = false;                }            }            scene.render();        }        function handleKeyDown(event) {            ch = String.fromCharCode(event.keyCode);            if (ch == "W")                target.position.z += 1;            if (ch == "S")                target.position.z -= 1;            if (ch == "D")                target.position.x += 1;            if (ch == "A")                target.position.x -= 1;            if (ch == "F")                target.position.y += 1;            if (ch == "V")                target.position.y -= 1;            if (ch == " ") {                moveLooker = true;            }        }        function handleChange(event) {            var x = event.target;            if (looker) looker.dispose();            if (x.value == "Kesha") {                BABYLON.SceneLoader.ImportMesh("", "assets/pointer/", "pointer.babylon", scene, function (meshes, particles, skeletons) {                    looker = meshes[0];                    looker.position = new BABYLON.Vector3(-5, 5, 5);                    yawCor = 0;                     pitchCor = 0;                    rollCor = 0;                });            }            else {                BABYLON.SceneLoader.ImportMesh("him", "assets/dude/", "dude.babylon", scene, function (meshes, particles, skeletons) {                    looker = meshes[0];                    looker.position = new BABYLON.Vector3(-5, 0, 5);                     yawCor = 0;                    pitchCor = 0;                    rollCor = 0;                    scl = .25;                    looker.scaling = new BABYLON.Vector3(scl, scl, scl);                    scene.beginAnimation(skeletons[0], 0, 100, true, 1.0);                });            }        }    </script>    <style>        html, body {            width: 100%;            height: 100%;            padding: 0;            margin: 0;        }        #cvRAVE {            width: 100%;            height: 90%;        }        #dvMenu {            width: 100%;            height: 10%;            font:10pt arial;        }    </style></head><body>    <div id="dvMenu">        Target Keys: W,A,S,D,F (up),V (down)<br />        Looker Keys: SPACE BAR (moves towards target)<br />        Model: <select id="seLooker"><option>Kesha</option><option>Dude</option></select><br />    </div>    <canvas id="cvRAVE"></canvas></body></html>

I attached a zip of the complete code w/ all assets.  I hope this will be useful.  Any new changes will be at: https://github.com/professorf/MeshLookAt  Good luck!

MeshLookAtTutorial.zip

Link to comment
Share on other sites

Thanks for adding this feature and taking the time to work on it with us, I really appreciate your time!

 

I did pull the latest and updated my demo, and I'm still hitting the same issue with moon.position not being updated (moon is parented to earth btw).  For the demo to work, I have to use moon.getAbsolutePosition() instead of moon.position like you show in your demo code above.

 

Here's my simple render loop - any idea why moon.position's value isn't being updated?

engine.runRenderLoop(function () {	earth.rotation.y += .01;        //box.lookAt(moon.position); // is not updating as parent rotates	box.lookAt(moon.getAbsolutePosition()); // works	scene.render();});
Link to comment
Share on other sites

"moon is parented to earth btw"

 

You have your answer :). You don't move moon, you rotate its parent that generates a movement for it in world space but not in local space since its position relative to its parent hasn't changed.

 

More about it here http://www.html5gamedevs.com/topic/3083-spaces-world-parent-pivot-local/?p=19938

 

Thanks gwenael, I appreciate the link and the clarification with your link

 

I was confused because I'd used the getPositionExpressedInLocalSpace() method previously, so my assumption was that "position" was always world space.  The docs don't specify either local or world space for the "position" property on the Mesh object.

 

Thanks for the help gwenael,

 

John

Link to comment
Share on other sites

I'll be glad to contribute once I've found time. Deltakosh asked me to write a wiki page talking about spaces http://www.html5gamedevs.com/topic/3083-spaces-world-parent-pivot-local/

 

You're the space expert!  I really like your jsdiff.  But yeah, that's the problem--finding time.  Especially now that the semester has started for me.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...