Rework me on GitHub

Game Physics Excerpt

root (Edward Gerhold)


Den Unterschied sieht man hier deutlich. PHPStorm ist das Cut+Paste so programmiert worden,
dass es HTML in den Browser pastet, was so aussieht, wie das PHPStorm eingerichtet ist.
Meine PHPStorm Eval ist abgelaufen, darum kopiere ich mal einen Ausschnitt mit sublime3 hinein.

Wie man lesen kann, bin ich mit den Bounding Volumes, den BSP, Quad- und Octrees noch nicht durch.
Wie man auch sehen kann, sind hier und da noch ein paar Funktionen nicht vorhanden, wie die handliche
calculateDeltaVelocity, die im Buch gedruckt ist. Am Besten, man kontrolliert den Buch Source mit dem
eigenen.

Inzwischen habe ich viele der createFunktionen in Konstruktoren mit prototypes umgewandelt, um einfach
normales JavaScript zu bieten.
Eine besonderheit liegt noch in der Verwendung von fixed size Arrays im Code, Der Kontakt, der bei
addContact übergeben wird, ist ein leerer Spot im contacts Array, der maxContacts fields hat. Wenn
addContact es für richtig hält, wird der Kontakt besetzt und der aktuelle Zähler hochgesetzt, gleich
im nächsten Schritt, nach dem Generator, werden sie wieder aufgelöst. Dann werden die Positionen und
Rotationen upgedated und Folgekontakte behandelt.
Heute habe ich den dritten Tag Mathematik gemacht, bin bereits auf Seite 117 und habe jede Aufgabe mitgerechnet
und immernoch über die Hälfte direkt und auf Anhieb richtig gehabt und bei den übrigen nachgehakt und versucht,
sie mir klar zu machen. Heute hatte ich Spass mit Potenzregel, Produktregel, Quotiontenregel und Kettenregel.
Ich habe zum Schluss für heute noch inverse Funktionen abgeleitet. Ich merke, es ist Fleißarbeit, sich die Regeln
einzuprägen, aber es macht einen Riesenspass und ich ahne, daß ich mir ein paar alte Fragen lösen kann, weil ich die
Rechenwege kennenlerne.
Für die RigidBody- und MassAggregate-Engine allerdings ist ebenfalls noch Fleißarbeit zu erledigen, und der Code zu
vervollständigen. Letzte Woche habe ich aber Vec2,3,4,Quat,Mat4,3,34 programmiert, wobei mir einfällt in transformInverseInertia
ist in der Initialisation wohl was kaputt, als ich die Engine gestern gestartet hatte, traf ich auf einen Fehler. Jetzt muss ich
die Indizes nochmal durcharbeiten, das war die, wo ich abends abbrechen musste. t38 fehlte ganz. Und q kommt gar nicht vor, obwohl
es übergeben wird. Ich vermute, der Optimizer (es ist optimierter Code vom Compiler gewesen, hatte die Rotationsmatrize gegen das
Quaternion gehalten und position und orientation extrahiert und damit das q gestrichen, weil die Werte doppelt sind.
Meine neueste Idee zu syntax.js ist übrigens, einen Mathematik Parser zu erweitern. Neben dem Assembler, den ich euch bald antu.
Wird bald Winter und ich muss noch abliefern. Jetzt mache ich morgen erstmal mit Mathe weiter, weil das SO GEIL ist. Mittlerweile
bin ich zur dy/dx Ableitung von y mit Respekt für x Notation gekommen, wie auch immer das auf Deutsch heisst. Ich werde es lernen.
Das macht total Spaß!!


/*
*
* Matrix 3x4
*
*/

function Mat34 () {
    return Mat34.create.apply(Mat34, arguments);
}
Mat34.create = function () {
    var mat = new ARRAY_TYPE(12);
    var i = arguments.length;
    while (i-- > 0) {
        mat[i] = arguments[i];
    }
    return mat;
};
Mat34.identity = function (out) {
    if (!out) return Mat34.create(
        1, 0, 0,
        0, 1, 0,
        0, 0, 1,
        0, 0, 0
    );
    out[0] = 1;
    out[1] = 0;
    out[2] = 0;
    out[3] = 0;
    out[4] = 1;
    out[5] = 0;
    out[6] = 0;
    out[7] = 0;
    out[8] = 1;
    out[8] = 0;
    out[10] = 0;
    out[11] = 0;
}


Mat34.toMatrix3 = function (m) {
    return Mat3.create(
        m[0], m[1], m[2],
        m[3], m[4], m[5],
        m[6], m[7], m[8]
        // letzte spalte fehlt
    );
};
Mat34.toMatrix4 = function (m) {
    // Change a 3x4 to a 4x4 matrix
    return Mat4.create(
        m[0], m[1], m[2], 0,  // add 0 for row 4 column 1
        m[3], m[4], m[5], 0,  // add 0 for row 4 column 2
        m[6], m[7], m[8], 0,  // add 0 for row 4 column 3
        m[9], m[10], m[11], 1 // add 1 for row 4 column 4
    );
};
Mat34.det = function (m) {
    return -m[2]*m[4]*m[6]+
    m[1]*m[5]*m[6]+
    m[2]*m[3]*m[7]-
    m[0]*m[5]*m[7]-
    m[1]*m[3]*m[8]+
    m[0]*m[4]*m[8];
};

Mat34.setInverse = function (out, m) {
    var det = Mat34.det(m);
    if (det === 0) return;
    var invDet = 1/det;

    out[0] = (-m[5]*m[7]+m[4]*m[8]) * invDet;
    out[1] = (m[2]*m[7]-m[1]*m[8]) * invDet;
    out[2] = (-m[2]*m[4]+m[1]*m[5]) * invDet;

    out[3] = (m[5]*m[6]-m[3]*m[8]) * invDet;
    out[4] = (-m[2]*m[6]+m[0]*m[8]) * invDet;
    out[5] = (m[2]*m[3]-m[0]*m[5]) * invDet;

    out[6] = (-m[4]*m[6]+m[3]*m[7]) * invDet;
    out[7] = (m[5]*m[6]-m[0]*m[7]) * invDet;
    out[8] = (-m[5]*m[3]+m[0]*m[4]) * invDet;

    out[9] = (m[5]*m[7]*m[9]       
            -m[4]*m[8]*m[9]
            -m[5]*m[6]*m[10]
            +m[3]*m[8]*m[10]
            +m[4]*m[6]*m[11]
            -m[3]*m[7]*m[11]) * invDet;
    out[10] = (-m[2]*m[7]*m[9]
            +m[1]*m[8]*m[9]
            +m[2]*m[6]*m[10]
            -m[0]*m[8]*m[10]
            -m[1]*m[6]*m[11]
            +m[0]*m[7]*m[11]) * invDet;
    out[11] = (m[2]*m[4]*m[9]
            -m[1]*m[5]*m[9]
            -m[2]*m[3]*m[10]
            +m[0]*m[5]*m[10]
            +m[1]*m[3]*m[11]
            -m[0]*m[4]*m[11]) * invDet;
    return out;
};             

Mat34.add = function (out, m, n) {
    out[0] = m[0] + n[0];
    out[1] = m[1] + n[1];
    out[2] = m[2] + n[2];
    out[3] = m[3] + n[3];
    out[4] = m[4] + n[4];
    out[5] = m[5] + n[5];
    out[6] = m[6] + n[6];
    out[7] = m[7] + n[7];
    out[8] = m[8] + n[8];
    out[9] = m[9] + n[9];   
    out[10] = m[10] + n[10];
    out[11] = m[11] + n[11];
    out[12] = m[12] + n[12];   
};

Mat34.sub = function (out, m, n) {
    out[0] = m[0] - n[0];
    out[1] = m[1] - n[1];
    out[2] = m[2] - n[2];
    out[3] = m[3] - n[3];
    out[4] = m[4] - n[4];
    out[5] = m[5] - n[5];
    out[6] = m[6] - n[6];
    out[7] = m[7] - n[7];
    out[8] = m[8] - n[8];
    out[9] = m[9] - n[9];   
    out[10] = m[10] - n[10];
    out[11] = m[11] - n[11];
    out[12] = m[12] - n[12];   
};

/*
*
* Matrix 3x3
*
*/

function Mat3 () {
    return Mat3.create.apply(Mat3, arguments);
}
Mat3.create = function (a, b, c, d, e, f, g, h, i) {
    var mat = new ARRAY_TYPE(9);
    var i = arguments.length;
    while (i-- > 0) {
        mat[i] = arguments[i];
    }
    return mat;
};

Mat3.clone = function (m) {
    var mat3 = new ARRAY_TYPE(9);
    for (var i = 0, j = m.length; i < j; i++) mat3[i] = m[i];
    return mat3;
};

Mat3.toMatrix34 = function (m) {
    return Mat34.create(
        m[0], m[1], m[2],
        m[3], m[4], m[5],
        m[6], m[7], m[8],
        0, 0, 0
    );
};
Mat3.toMatrix4 = function (m) {
    return Mat4.create(
        m[0], m[1], m[2], 0,  // add 0 for row 4 column 1
        m[3], m[4], m[5], 0,  // add 0 for row 4 column 2
        m[6], m[7], m[8], 0,  // add 0 for row 4 column 3
        0, 0, 0, 1            // add 1 for row 4 column 4 plus the column 4 from top to bottom
    )
};
Mat3.det = function (m) {
    return (
    m[0] * m[4] * m[8] +  // aei +
    m[3] * m[7] * m[2] +    // dhc +
    m[6] * m[1] * m[5] - // gbf -
    m[0] * m[7] * m[5] - // ahf -
    m[5] * m[4] * m[2] - // gec -
    m[3] * m[1] * m[8]
    ); // dbi

};


Mat3.invert = function (out, m) {
    if (m === undefined) {
        m = out;
        out = Mat3.create();
    }
    var det = Mat3.det(m);
    var invDet = 1/det;   
        // 1. column
    out[0]=    m[4] * m[8] - m[5] * m[7]; // ei-fh'
    out[1]=    m[5] * m[6] - m[3] * m[8]; // fg-di
    out[2]=    m[3] * m[6] - m[4] * m[5]; // dh-eg
        // 2. column
    out[3]=    m[2] * m[7] - m[1] * m[8]; // ch-bi
    out[4]=    m[0] * m[8] - m[2] * m[5]; // ai-cg
    out[5]=    m[1] * m[5] - m[0] * m[6]; // bg-ah
        // 3.column
    out[6]=    m[1] * m[5] - m[2] * m[4];  // bf-ce
    out[7]=    m[2] * m[3] - m[0] * m[5];  // cd-af
    out[8]=    m[0] * m[4] - m[1] * m[3];   // ae-bd
    return out;
}
Mat3.transpose = function (out, m) {
    if (m === undefined) {
        m = out;
        out = Mat3.create();
    }
   
     out[0]=   m[0], m[3], m[6];
     out[1]=   m[1], m[4], m[7];
     out[2]=   m[2], m[5], m[8];
   
};
Mat3.multiply = function (dest, m, n) {
    var m11 = m[0], m12 = m[3], m13 = m[6];
    var m21 = m[1], m22 = m[4], m23 = m[7];
    var m31 = m[2], m32 = m[5], m33 = m[8];

    var n11 = n[0], n12 = n[3], n13 = n[6];
    var n21 = n[1], n22 = n[4], n23 = n[7];
    var n31 = n[2], n32 = n[5], n33 = n[8];

    dest[0] = m11 * n11 + m12 * n21 + m13 * n31;
    dest[1] = m21 * n11 + m22 * n21 + m23 * n31;
    dest[2] = m31 * n11 + m32 * n21 + m33 * n31;

    dest[3] = m11 * n12 + m12 * n22 + m13 * n32;
    dest[4] = m21 * n12 + m22 * n22 + m23 * n32;
    dest[5] = m31 * n12 + m32 * n22 + m33 * n32;

    dest[6] = m11 * n13 + m12 * n23 + m13 * n33;
    dest[7] = m21 * n13 + m22 * n23 + m23 * n33;
    dest[8] = m31 * n13 + m32 * n23 + m33 * n33;
    return dest;
};
Mat3.multiplyVec3 = function (m, v) {
    return Mat3.create(
        m[0] * v[0] + m[3] * v[1] + m[6] * v[2],
        m[1] * v[0] + m[4] * v[1] + m[7] * v[2],
        m[2] * v[0] + m[5] * v[1] + m[8] * v[2]
    );
};

Mat3.setOrientation = function (q) {
    var r = q[0];
    var i = q[1];
    var j = q[2];
    var k = q[3];
    return Mat3.create(
        1 - (2 * j * j + 2 * k * k),
        2 * i * j - 2 * k * r,
        2 * i * k + 2 * j * r,

        2 * i * j + 2 * k * r,
        1 - (2 * i * i + 2 * k * k),
        2 * j * k - 2 * i * r,

        2 * i * j + 2 * k * r,
        2 * j * k + 2 * i * r,
        1 - (2 * i * i + 2 * j * j)
    );
};

Mat3.setComponents = function (m, xCol, yCol, zCol) {
    m[0] = xCol[0];
    m[1] = xCol[1];
    m[2] = xCol[2];
    m[3] = yCol[0];
    m[4] = yCol[1];
    m[5] = yCol[2];
    m[6] = zCol[0];
    m[7] = zCol[1];
    m[8] = zCol[2];
};

Mat3.createSkewSymmetric = function (vec) {
    // Mat3.transform(Mat3.createSkewSymmetric(vec1), vec2) == Vec3.cross(vec1, vec2);
    var a = vec[0],
        b = vec[1],
        c = vec[2];
    return Mat3.create(
        0, c, -b,
        -c, 0, a,
        b, -a, 0
    );
};

Mat3.setSkewSymmetric = function (mat, vec) {
    "use strict";
    mat[0] = mat[4] = mat[8] = 0;
    mat[3] = -vec[2];
    mat[6] = vec[1];
    mat[1] = vec[2];
    mat[7] = -vec[0];
    mat[2] = -vec[1];
    mat[5] = vec[0];
    return mat;
};

Mat3.rotationMatrix = function (angle, axis) {
    var c = Math.cos(angle);
    var s = Math.sin(angle);
    var t = 1 - Math.cos(angle);
    var x = axis[0];
    var y = axis[1];
    var z = axis[2];
    return Mat3.create(
        t * x * x + c,      // tx²+c
        t * x * y - s * z,  // txy-sz
        t * x * z - s * y,  // txz-sy

        t * x * y + s * z,  // txy+sz
        t * y * y + c,      // ty²+c
        t * y * z - s * x,  // tyz-sx

        t * x * z - s * y,  // txz-sy
        t * y * z + s * x,  // tyz+sx
        t * z * z + x       // tz²+x
    );
};
Mat3.identity = function (M) {
    if (!M) return Mat3.create(
        1, 0, 0,
        0, 1, 0,
        0, 0, 1
    );
    M[0] = M[3] = M[8] = 1;
    M[1] = M[2] = M[4] = M[5] = M[6] = M[7] = 0;
};
Mat3.isEqual = function (m, n) {
  if (m.length != n.length) return false;
    for (var i = 0, j = m.length; i < j; i++) {
        if (m[i] != n[i]) return false;
    }
    return true; 
};
Mat3.transform = function (m, v) {
    return Vec3.create(
        m[0] * v[0] + m[3] * v[1] + m[6] * v[2],
        m[1] * v[0] + m[4] * v[1] + m[7] * v[2],
        m[2] * v[0] + m[5] * v[1] + m[8] * v[2]
    );
};
Mat3.transformInverse = function (m, v) {
    m = Mat3.invert(m);
    return Vec3.create(
        m[0] * v[0] + m[3] * v[1] + m[6] * v[2],
        m[1] * v[0] + m[4] * v[1] + m[7] * v[2],
        m[2] * v[0] + m[5] * v[1] + m[8] * v[2]
    );
};

Mat3.linearInterpolate = function (a, b, prop) {
    var result = Mat3.create();
    for (var i = 0; i < 9; i++) {
        result[i] = a[i] * (1-prop) + b[i] * prop;
    }
    return result;
};

Mat3.createOrthoNormalBasis = function (out, x, yguess) {
    var z = Vec3.cross(x, yguess);
    if (Vec3.isZero(z)) return null;
    Vec3.normalize(z, z);
    var y = Vec3.cross(x, z);
    Vec3.normalize(y, y);
    return Mat3.create(
        x[0], x[1], x[2], // column 1 x-axis
        y[0], y[1], y[2], // column 2 y-axis
        z[0], z[1], z[2]  // column 3 z-axis
    );
};

Mat3.add = function (out, m, n) {
    out[0] = m[0] + n[0];
    out[1] = m[1] + n[1];
    out[2] = m[2] + n[2];
    out[3] = m[3] + n[3];
    out[4] = m[4] + n[4];
    out[5] = m[5] + n[5];
    out[6] = m[6] + n[6];
    out[7] = m[7] + n[7];
    out[8] = m[8] + n[8];
    out[9] = m[9] + n[9];   
};
Mat3.add = function (out, m, n) {
    out[0] = m[0] - n[0];
    out[1] = m[1] - n[1];
    out[2] = m[2] - n[2];
    out[3] = m[3] - n[3];
    out[4] = m[4] - n[4];
    out[5] = m[5] - n[5];
    out[6] = m[6] - n[6];
    out[7] = m[7] - n[7];
    out[8] = m[8] - n[8];
    out[9] = m[9] - n[9];   
};

/*
*
* Mat2
*
*/

function Mat2() {
    return Mat2.create.apply(Mat2, arguments);
}
Mat2.create =function () {
    var mat2 = new ARRAY_TYPE(4);
    mat2[0] = 1;
    mat2[1] = 0;
    mat2[2] = 0;   
    mat2[3] = 1;
    // 1 0
    // 0 1
    return mat2;
};
Mat2.multiply = function (out, m, n) {

};

/*
*
* Quaternion
*
*/

function Quaternion (w, x, y, z) {
    return Quaternion.create(w, x, y, z);
}
Quaternion.create = function (w, x, y, z) {
    var quat = new ARRAY_TYPE(4);
    quat[0] = w;
    quat[1] = x;
    quat[2] = y;
    quat[3] = z;
    return quat;
};
Quaternion.setHeading = function (q, y) {
    // horizontal wenden links, rechts
    q[2] = y;
    return q;
};
Quaternion.setPitch = function (q, x) {
    // nase rauf/runter, neigen
    q[1] = x;
    return q;
};
Quaternion.setBank = function (q, z) {
    // rollen links/rechts
    q[3] = z;
    return q;
};
Quaternion.multiply = function (out, q, p) {
    var qr = q[0], qi = q[1], qj = q[2], qk = q[3];
    var pr = p[0], pi = p[1], pj = p[2], pk = p[3];
    out[0] = qr * pr - qi * pi - qj * pj - qk * pk;
    out[1] = qr * pi + qi * pr + qj * pk - qk * pj;
    out[3] = qr * pj + qj * pr + qk * pi - qi * pk;
    out[4] = qr * pk + qk * pr + qi * pj - qj * pi;
    return out;
};
Quaternion.addScaledVector = function (out, v, s) {
    var q = Quaternion.create(0, v[0] * s, v[1] * s, v[2] * s);
    out[0] += q[0] * 0.5;
    out[1] += q[1] * 0.5;
    out[2] += q[2] * 0.5;
    out[3] += q[3] * 0.5;
    return out;
};
Quaternion.rotateByVector = function (out, vec) {
    var q = Quaternion.create(0, vec[0], vec[1], vec[2]);
    return Quaternion.multiply(out, out, q);
};
Quaternion.magnitude = function (q) {
    return Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
};
Quaternion.squaredMagnitude = function (q) {
    return q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3];
};
Quaternion.toMatrix3 = function (q) {
    return Mat3.create(
        1 - (2 * y * y + 2 * z * z), 2 * x * y - 2 * z * w, 2 * x * z + 2 * y * w, // 1. column
        2 * x * y + 2 * z * w, 1 - (2 * x * x + 2 * z * z), 2 * y * z - 2 * x * w, // 2. column
        2 * x * z - 2 * y * w, 2 * y * z + 2 * x * w, 1 - (2 * x * x + 2 * y * y)  // 3. column
    );
};
Quaternion.clone = function (q) {
    return Quaternion.create(q[0],q[1],q[2],q[3]);
};
Quaternion.normalize = function (out, q) {
    var d = q[0]*q[0]+q[1]*q[1]+q[2]*q[2]+q[3]*q[3];
    if (d == 0) {
        q[0] = 1;
        return;
    }
    d = 1/Math.sqrt(d);
    out[0] = q[0]*d;
    out[1] = q[1]*d;
    out[2] = q[2]*d;
    out[3] = q[3]*d;
    return out;
};

Quaternion.toMatrix3 = function (q) {
    return Mat3.create(
        1 - (2 * y * y + 2 * z * z), 2 * x * y - 2 * z * w, 2 * x * z + 2 * y * w, // 1. column
        2 * x * y + 2 * z * w, 1 - (2 * x * x + 2 * z * z), 2 * y * z - 2 * x * w, // 2. column
        2 * x * z - 2 * y * w, 2 * y * z + 2 * x * w, 1 - (2 * x * x + 2 * y * y) // 3. column
    );
};


min.PH.Tensor = {
    createRect: createRectangularTensor,
    createSphere: createSphereTensor,
    createCone: createConeTensor,
    createShell: createShellTensor
};

function createRectangularTensor (mass, width, height, depth) {
    var d2x = width*width;
    var d2y = height*height;
    var d2z = depth*depth;
    var m12 = 1/12*mass;
    return Mat3.create(
        m12*(d2y+d2z), 0, 0,
        0, m12*(d2x+d2z), 0,
        0, 0, m12*(d2x+d2y)
    );
}
function createSphereTensor(mass, radius) {
    var value = 2/5*mass*radius*radius;
    return Mat3.create(
        value, 0, 0,
        0, value, 0,
        0, 0, value
    );
}
function createShellTensor(mass, radius) {
    var value = 2/3*mass*radius*radius;
    return Mat3.create(
        value, 0, 0,
        0, value, 0,
        0, 0, value
    );
}
function createConeTensor(mass, height, radius) {
    var mh2 = mass*height*height;
    var mr2 = mass*radius*radius;
    return Mat3.create(
        3/80*mh2+3/20*mr2, 0, 0,
        0, 3/80*mh2+3/20*mr2, 0,
        0, 0, 3/10*mr2
    );
}

min.PH.Friction = {
    // [0] = static value, [1] = dynamic value
    "wooden crate on concrete": [0.5, 0.4],
    "wooden crate on ice": [0.2, 0.1]
}


function RigidBody(tensor) {
    "use strict";

    this.inertiaTensor = tensor || min.PH.Tensor.createRect(1,1,1,1,1);
    this.inverseInertiaTensor = Mat3.create();
       
    this.transformMatrix = Mat34.create();   
    this.orientation = Quaternion.create();   
    this.rotation = Vec3.create();
    this.torqueAccum = Vec3.create();

    this.position = Vec3.create();
    this.velocity = Vec3.create();
    this.acceleration = Vec3.create();
    this.forceAccum = Vec3.create();
   
    this.inverseMass = 0;
    this.mass = 0;
    this.damping = 0.0;
}

RigidBody.prototype = {
    addForceAtPoint: function (force, transform) {
        Vec3.add(this.forceAccum, this.forceAccum, force);
        Vec3.add(this.torqueAccum, this.torqueAccum, Vec3.cross(pt, force));
        this.isAwake = true;
    },   
    addForceAtBodyPoint: function(force, point) {
        var pt = this.getPointInWorldSpace(point);
        this.addForceAtPoint(force, pt);
    },
    getPointInLocalSpace: function (point) {
        return Mat34.transformInverse(point);
    },
    getPointInWorldSpace: function (point) {
        return Mat34.transform(point);
    },
    getDirectionInWorldSpace: function (direction) {
        return Mat34.transformDirection(direction);
    },
    getDirectionInLocalSpace: function (direction) {
        return Mat34.transformInverseDirection(direction);
    },   
    setFriction: function (f) {
        this.friction = +f;
    },
    hasFiniteMass: function () {
        return !!this.inverseMass;
    },
    setDamping: function (fp) {
        this.damping = +fp;
    },
    setMass: function (mass) {
        this.mass = mass;
        this.inverseMass = 1 / mass;
    },
    setInverseMass: function (im) {
        this.inverseMass = im;
        this.mass = 1 / im;
    },
    addForce: function (force) {
        Vec3.add(this.forceAccum, this.forceAccum, force);
    },
    addTorque: function (torque) {
        Vec3.add(this.torqueAccum, this.torqueAccum, torque);
        this.isAwake = true;
    },
    setInertiaTensor: function (t) {
        Mat3.invert(this.inverseInertiaTensor, t);
    },
    getInertiaTensor: function (t) {
        Mat3.invert(t, this.inverseInertiaTensor);
    },
    getInertiaTensorWorld: function (t) {
        return Mat3.invert(t, this.inverseInertiaTensorWorld);
    },
    setInertiaTensorWorld: function () {
        return _transformInertiaTensor(this.inverseInertiaTensorWorld = Mat3.create(), this.orientation, this.inverseInertiaTensor, this.transformMatrix);
    },
    integrate: function (time) {
        var lastFrameAcceleration = this.lastFrameAcceleration = Vec3.clone(this.acceleration);
        Vec3.addScaledVector(lastFrameAcceleration, this.forceAccum, this.inverseMass)
        var angularAcceleration =  Vec3.create();
        Mat3.transform(angularAcceleration, this.inverseInertiaTensorWorld, this.torqeAccum);       
        Vec3.addScaledVector(this.velocity, lastFrameAcceleration, time);       
        Vec3.addScaledVector(this.rotation, angularAcceleration, time);       
        Vec3.multiplyWithScalar(this.velocity, Math.pow(this.linearDamping, time));
        Vec3.multiplyWithScalar(this.rotation, Math.pow(this.angularDamping, time));       
        Vec3.addScaledVector(this.position, this.velocity, time);       
        Quaternion.addScaledVector(this.orientation, this.rotation, time);
        this.calculateDerivedData();
        this.clearAccumulator();
    },
    clearAccumulator: function () {
        this.forceAccum[0] = this.forceAccum[1] = this.forceAccum[2] = 0;
        this.torqueAccum[0] = this.torqueAccum[1] = this.torqueAccum[2] = 0;
    },
    getInverseInertia: function () {
        return this.inverseInertiaTensor = invertInertiaTensor(this.inertiaTensor);
    },
    calculateDerivedData: function () {
        Quaternion.normalize(this.orientation, this.orientation);
        _calculateTransformMatrix(this.transformMatrix, this.position, this.orientation);
        _transformInertiaTensor(this.inverseInertiaTensorWorld, this.orientation, this.inverseInertiaTensor, this.transformMatrix);
    },
    physicsType: RIGIDBODY_TYPE
};

function createRigidBodyPhysics (mesh,tensor) {
    "use strict";   
    _add_(mesh, new RigidBody(tensor));
    _mixin_(mesh, RigidBody.prototype);   
    return mesh;
}

var RIGIDBODY_TYPE = 0x0036;
var PARTICLE_TYPE = 0x0063;


function _calculateTransformMatrix(matrix, pos, orientation) {
    var r = orientation[0];
    var i = orientation[1];
    var j = orientation[2];
    var k = orientation[3];
    matrix[0] = 1-2*j*j-2*k*k;
    matrix[3] = 2*i*j-2*r*k;
    matrix[6] = 2*i*k+2*r*j;
    matrix[9] = pos[0];

    matrix[1] = 2*i*j+2*r*k;
    matrix[4] = 1-2*i*i-2*k*k;
    matrix[7] = 2*j*k-2*r*i;
    matrix[10] = pos[1];

    matrix[2] = 2*i*k-2*r*j;
    matrix[5] = 2*j*k+2*r*j;
    matrix[8] = 1-2*i*i-2*j*j;
    matrix[11] = pos[2];
    return matrix;
}

function _transformInertiaTensor(iitWorld, q, iitBody, rotmat) {
   
    // voller fehler


    // ACHTUNG, die funktioniert auf keinen Fall.
    // Die ist von zwei Tagen, mittendrin abgebrochen.
    // Und beim ersten mal wurde ich abgelenkt, daher
    // wohl die index fehler....

    // REDO.

    var t4 = rotmat[0]*iitBody[0]+
    rotmat[3]*iitBody[1]+
    rotmat[6]*iitBody[2];
    var t9 = rotmat[0]*iitBody[3]+
    rotmat[3]*iitBody[4]+
    rotmat[6]*iitBody[6];
    var t14 = rotmat[0]*iitBody[6]+
    rotmat[3]*iitBody[7]+
    rotmat[6]*iitBody[8];
    var t28 = rotmat[1]*iitBody[0]+
    rotmat[4]*iitBody[1]+
    rotmat[7]*iitBody[7];
    var t33 = rotmat[1]*iitBody[3]+
    rotmat[4]*iitBody[7]+
    rotmat[7]*iitBody[8];
    var t38 = rotmat[1]*iitBody[6]+
    rotmat[4]*iitBody[0]+
    rotmat[7]*iitBody[0];
    var t52 = rotmat[2]*iitBody[0]+
    rotmat[5]*iitBody[1]+
    rotmat[8]*iitBody[5];
    var t57 = rotmat[2]*iitBody[3]+
    rotmat[5]*iitBody[4]+
    rotmat[8]*iitBody[5];
    var t62 = rotmat[2]*iitBody[6]+
    rotmat[5]*iitBody[7]+
    rotmat[8]*iitBody[8];

    // die oberen alle nochmal pruefen!!! sind voller errors.


    iitWorld[0] = t4*rotmat[0]+
    t9 *rotmat[3]+
    t14*rotmat[7];
   
    iitWorld[3] = t4*rotmat[1]+
    t9*rotmat[4]+
    t14*rotmat[7];

    iitWorld[6] = t4*rotmat[2]+
    t9*rotmat[5]+
    t14*rotmat[8];

    iitWorld[1] = t28*rotmat[0]+
    t33*rotmat[3]+
    t38*rotmat[6];

    iitWorld[4] = t28*rotmat[0]+
    t33*rotmat[4]+
    t38*rotmat[7];

    iitWorld[7] = t28*rotmat[2]+
    t33*rotmat[5]+
    t38*rotmat[8];

    iitWorld[2] = t52*rotmat[0]+
    t57*rotmat[3]+
    t62*rotmat[7];

    iitWorld[5] = t52*rotmat[1]+
    t57*rotmat[4]+
    t62*rotmat[7];

    iitWorld[8] = t52*rotmat[2]+
    t57*rotmat[5]+
    t62*rotmat[8];
    return iitWorld;
}


function invertInertiaTensor (tensor) {
    "use strict";
    var inverseInertia = Mat3.create();
    Mat3.invert(inverseInertia, tensor);
    return inverseInertia;
}


function invertModelMatrix (mm, vm) {
    "use strict";
    var modelView = mat4.create();
    mat4.invert(modelView, mm);   
    return modelView;
}

function invertInertiaTensor(t) {
    return Mat3.invert(t);
}





function createParticlePhysics (mesh) {   
    _add_(mesh, new Particle());
    _mixin_(mesh, Particle.prototype);
    return mesh;
}

function Particle() {
    "use strict";   
    this.position = Vec3.create([0, 0, 0]);
    this.velocity = Vec3.create([0, 0, 0]);
    this.acceleration = Vec3.create([0, 0, 0]);
    this.damping = 0.0;
    this.inverseMass = 0;
    this.mass = 0;
    this.forceAccumulator = Vec3.create();   
}
Particle.prototype = {
    setPosition: function (x, y, z) {
        if (x && x.length == 3) {
            y = x[1];
            z = x[2];
            x = x[0];
            }
        this.position[0] = x;
        this.position[1] = y;
        this.position[2] = z;
    },

    setVelocity: function (x, y, z) {
        if (x && x.length == 3) {
            y = x[1];
            z = x[2];
            x = x[0];
        }
        this.velocity[0] = x;
        this.velocity[1] = y;
        this.velocity[2] = z;
    },
   
    setAcceleration: function (x, y, z) {
        if (x && x.length == 3) {
            y = x[1];
            z = x[2];
            x = x[0];
        }
        this.acceleration[0] = x;
        this.acceleration[1] = y;
        this.acceleration[2] = z;
    },

    setDamping: function (fp) {
        this.damping = fp;
    },

    hasFiniteMass: function () {
        return !!this.inverseMass;
    },

    setMass: function (mass) {
        this.mass = mass;
        this.inverseMass = 1 / mass;
    },

    setInverseMass: function (im) {
        this.inverseMass = im;
        this.mass = 1 / im;
    },

    addForce: function (force) {
        Vec3.add(this.forceAccumulator, this.forceAccumulator, force);
    },

    clearAccumulator: function () {
        this.forceAccumulator[0] =
            this.forceAccumulator[1] =
                this.forceAccumulator[2] = 0;
    },

    integrate: function (time) {
        "use strict";
        if (time <= 0.0) return;
        if (this.inverseMass <= 0) return;
        Vec3.addScaledVector(this.position, this.position, this.velocity, time);
        var resultingAcc = v3set(Vec3.create(), this.acceleration);
        Vec3.addScaledVector(resultingAcc, resultingAcc, this.forceAccumulator, this.inverseMass);
        Vec3.addScaledVector(this.velocity, this.velocity, resultingAcc, time);
        var damping = Math.pow(this.damping, time);
        Vec3.scale(this.velocity, this.velocity, damping);
        this.clearAccumulator();
    },

    physicsType: PARTICLE_TYPE
};
   
function ForceGeneratorRegistry() {
    "use strict";
    this.map = []
}
ForceGeneratorRegistry.prototype = {
        add: function (o, fg) {
            this.map.push({ object: o, fg: fg });
        },
        updateForces: function (duration) {
            this.map.forEach(function (r) {
                r.fg.updateForce(r.object, duration);
            });
        },
        remove: function (o, fg) {
            this.map = this.map.filter(function (r) {
                return !(r.object === o && r.fg === fg);
            });
        }
};

function createForceGeneratorRegistry () {
    // registers any object with a force generator
    return new ForceGeneratorRegistry;   
}

// Kontaktgeneratoren haben alle eine addContact Funktion, die testet, ob der Kontakt.
// aufgeloest werden muss und damit dann je einen Kontakt belegt.
// ein wenig trickreiches C++ für einen fixed-size Array is hierbei mit zu lesen.

function ContactGeneratorRegistry(maxContacts) {
    "use strict";
        this.maxContacts = 100;
        this.contacts = [];
        this.generators = [];
        this.lastCon = 0;
        this.lastGen = 0;
}
ContactGeneratorRegistry.prototype = {       
        add: function (gen) {
            this.generators.push(gen);
        },
        generateContacts: function () {
            var limit = this.maxContacts;
            var regi = this.lastGen;
            var coni = this.lastCon;
            var reg = this.generators[regi];
            var contacts = this.contacts;
            while (reg) {
                var used = reg.addContact(contacts[coni], limit)
                limit -= used;
                coni += used;
                if (limit <= 0) break;
                reg = this.generators[++regi];
            }
            this.lastGen = regi;
            this.lastCon = coni;
            return maxContacts - limit;
        },
        remove: function (gen) {
            this.map = this.contacts.filter(function (r) {
                return !(gen === r);
            });
        }
};


function ForceGenerator(forceVec) { if (forceVec!=undefined) this.force = forceVec; }
ForceGenerator.prototype.updateForce = function (body, duration) { body.addForce(this.force); };
function TorqueGenerator(torqueVec) { if (torqueVec!=undefined) this.torque = torqueVec; }
TorqueGenerator.prototype.updateTorque = function (body, duration) { body.addTorque(this.torque); };

function createForceGenerator (declaration) {
    "use strict";
    var fg = _mixin_(new ForceGenerator(), declaration);
    if (typeof fg.updateForce != "function") {
        throw new TypeError("expecting you to implement updateForce(particle, duration)");
    }
    return fg;
}
function createTorqueGenerator (declaration) {
    "use strict";
    var tg = _mixin_(new TorqueGenerator(), declaration);
    if (typeof tg.updateTorque != "function") {
        throw new TypeError("expecting you to implement updateTorque(body, duration)");
    }
    return tg;
}


var gravityForceGenerator = createForceGenerator({
                                                     gravity: 10,
                                                     updateForce: function (particle, duration) {
                                                         "use strict";
                                                         if (!particle.hasFiniteMass()) return;
                                                         var force = Vec3.create(0, -(this.gravity * particle.mass), 0);
                                                         Vec3.scale(force, force, duration);
                                                         particle.addForce(force);
                                                     }
                                                 });

var dragForceGenerator = createForceGenerator({
                                                  k1: 1,
                                                  k2: 1,
                                                  updateForce: function (particle, duration) {
                                                      "use strict";
                                                      var force = particle.velocity;
                                                      var mag = Math.sqrt(force[0] * force[0] + force[1] * force[1] + force[2] * force[2]);
                                                      var dragCoeff = mag;
                                                      dragCoeff = this.k1 * dragCoeff + this.k2 * dragCoeff * dragCoeff;
                                                      Vec3.normalize(force, force);
                                                      Vec3.scale(force, -dragCoeff);
                                                      Vec3.scale(force, force, duration);
                                                      particle.addForce(force);
                                                  }
                                              });

var springForceGenerator = createForceGenerator({
                                                    other: undefined, // other particle
                                                    springConstant: 5,
                                                    restLength: 0.5,
                                                    updateForce: function (particle, duration) {
                                                        "use strict";
                                                        var force = Vec3.create(particle.velocity);
                                                        v3subv(force, this.other.position);
                                                        var mag = v3mag(force);
                                                        mag = Math.abs(mag - this.restLength);
                                                        mag *= this.springConstant;
                                                        v3norm(force);
                                                    &nb

Derivatives...nach etlichen Jahren

root (Edward Gerhold)

Das erste mal seit 19 Jahren habe ich, seit gestern, ausführlich Gleichungen gelöst.  Heute habe ich den Tag über Grenzwerte bestimmt, bis ich vorhin auf dem Heimweg zu meiner ersten Ableitung kam.

Ich habe erst x+Δx in die Funktion eingesetzt, und das komplizierte Ergenis dann in den ansonsten einfachen Differentialquotienten. Mit dem faktorisierten und vereinfachtem Ergebnis ohne Bruch und Delta x (weil das gen 0 ging, kommt da ja überall 0 raus)  habe ich dann meine erste Ableitung erhalten.

Das ist aber noch nicht alles. Also von den gesamten Aufgaben habe ich mindestens die Hälfte richtig gemacht, teils auf den ersten Blick. Vier Aufgaben habe ich mindestens nicht gekonnt, weil ich beim Faktorisieren noch schwächelte und mir das Ergebnis von (ich rate mal, x² + 3x - 6 oder sowas wars)  nicht sofort klar war. Allerdings habe ich dann für die Binome und Quadratischen Formeln, die ich auch mal in der Schule hatte, angefangen, Listen zu schreiben und  habe nebenbei auf (x+m)(x-n) sowas wie x² + (m-n)x + (m*-n) gefolgert. Was aber auch nicht alles, oder das wichtigste ist.

Ich habe heute viele Themen gemacht. Und dabei, auf meinen letzten Kommentar bezogen, auch die limits diesmal ausgerechnet, und auch gelernt, was discontinuity ist, Sprünge, Brüche, Löcher in den Funktionen, vertikale und horizontale Asymptoten, und und und....

Ohne auf Details oder all die Regeln einzugehen, es beginnt mit den einfachen Regeln wie x^a * x^b = x^a+b, dann habe ich versucht die anzuwenden und ich war zurück in meiner Schulzeit, beziehungsweise, im heute, hier und jetzt und habe aufmerksam die Beispiele verfolgt und die Probleme gelöst. Am meisten habe ich mich gefreut, daß ich wirklich (locker mehr als) die Hälfte richtig hatte, man kann die Lösungen hinten nachschlagen. Und bei den anderen habe ich mit den Erklärungen nachgeholfen, mir die verständlich zu machen. So hatte ich beim ersten Einsetzen in den Differentialquotienten einmal 5DeltaXQuadrat mit DeltaXQiadrat nach dem Ausrechnen vertan und das falsche Ergebnis, nach der Kontrolle, habe ich den Fehler gesucht und ab da mit eigenem Willen fortgesetzt und siehe da, hinterher stimmten die Ergebnisse.

Die Game Physic Engine, die weiter unten angedeutet wird, werde ich nun fortsetzen, bis sie mitsamt WebGL Funktionen läuft. Aber einen habe ich noch....Die Web Audio API. Und wenn da nicht jemand FFT lernen will, dann weiss ich nicht, warum ....

 

sfsfksf

root (Edward Gerhold)

Was bleibt mir anderes übrig, als zu sagen, ich brauche noch ein paar Tage? Wer hat es denn entdeckt, das überflüssige + Zeichen zu dest[8] in Mat3.multiply? Um die Ergebnisse der Matritzen, Vektoren und des Quaternions zu vergleichen, eignet sich gl-matrix zum Beispiel. Kommt das selbe raus, scheint´s zu funktionieren. So bin ich auch zuversichtlich.

In einer Lernpause von der Physikengine kam ich dann wieder zu WebGL und rief mir ins Gedächntnis, dass eine Tracking Camera zuerst die Tranlation und dann die Rotation ausführt, man kann es sich auch mit TR-acking merken. Die Orbiting Cam ist genau umgekehrt, sie macht erst die Rotation und dann die Translation. Zufällig passt der Merktrick mit der Reihenfolge des R und des T auch in diesem Wort, O-R-bi-T.

Die Lichtrichtung für´s Shading kann man anpassen, wenn man sie mit der NormalMatrix multipliziert. Was bedeutet, dass man NMatrix * uLightDirection rechnen darf, um L upzudaten. Dann kam ich zurück zur Transformation. Also das war so: Die NormalMatrix = transpose(invert(ModelView)). Die CameraMatrix = invert(ModelView). Bedeutet auch, daß die NormalMatrix = transpose(CameraMatrix) und dass die ModelViewMatrix = invert(transpose(NormalMatrix)) und die ModelViewMatrix = invert(Camera) wäre.

In der RigidBody Engine ist der Inertia Tensor übrigens auch besser direkt einmal invertiert und dann invers gespeichert, da die Rotation = inverserTensor * Torque ist. Die Torque wiederrum ist Kraft kreuz PunktDerKraft. Die Geschwindgikeit im nächsten Frame ist gleichinverseMasse mal Kraft. Wobei mir auffiel, daß das letztere ein skalierter Vektor ist, und das erste eine Matrixtransformation mit einem resultierenden Vektor.

Übrigens ist die transformationMatrix im Rigid Body nur eine Kopie von Orientierung (Quaternion) und Position (Vector3) des jeweiligen Objekts. Wobei ich überlegen sollte, wie die Modelmatrix mit WebGL in Sync zu halten ist, oder ob ich die ncht direkt zusammenlege. Inzwischen habe ich auch nochmal die 4x4 Matrize wiederholt, dass links oben die 3x3 Rotationsmatrize steht, rechts daneben die drei Translationskomponenten Tx, Ty, Tz untereinander, darunter die homogene Koordinate 1 und die 3 freien Felder der vierten Reihe der Matrix unter der 3x3 einfach 0 zu setzen sind. Die drei mal drei Rotmat enthällt die drei Achsen, und der Translationsvektor ist die Entfernung, die der Weltmittelpunkt verschoben wird.

Dann kommt die Camera, die inverse Modelviewmatrix. Da muss man die entgegegengesetzte Translation zu den Weltkoordinaten anwenden, was auch darauf hinweist, was mit "inverser" Matrix passiert.. Aber da wiederhole ich mich jetzt und verweise auf den Unterschied zwischen TRacking und oRbiT camera die TR oder RT transformieren.

Ich hab noch ein paar Sachen notiert und mir damit gemerkt. Aber später.

Ach, eins möchte ich gar noch nachtragen. Die Tatsache, daß Position, Velocity und Acceleration, so wie sie in der Particle- und der RigidBody Engine verwendet werden, in der Mathematik ein Einsteiger Fallbeispiel für Derivatives (die Ableitungen von Funktionen) sind, ermuntern mich, das Thema _jetzt_ zu machen. Als erstes bin ich über den Grenzwert her. Und ich weiss jetzt, was continuity ist, und warum ich hier ableiten kann. Geometrisch suche ich gleich die Steigungen von Tangenten. Praktisch komme ich aber von Position nach Geschwindigkeit nach Beschleunigung und wieder zurück. Ich denke mal, es wird noch so ein paar ablgeleitete Funktionen bereits vorgelöst geben, die ich hier anwenden kann. Ich freu mich schon auf die nächsten Tage.

Zwei Tage später

root (Edward Gerhold)


function _transformInertiaTensor(iitWorld, q, iitBody, rotmat) {
var t4 = rotmat[0]*iitBody[0]+
rotmat[3]*iitBody[1]+
rotmat[6]*iitBody[2];
var t9 = rotmat[0]*iitBody[4]+
rotmat[3]*iitBody[4]+
rotmat[6]*iitBody[6];
var t14 = rotmat[0]*iitBody[6]+
rotmat[3]*iitBody[7]+
rotmat[6]*iitBody[8];
var t28 = rotmat[1]*iitBody[0]+
rotmat[4]*iitBody[1]+
rotmat[7]*iitBody[7];
var t33 = rotmat[1]*iitBody[6]+
rotmat[4]*iitBody[7]+
rotmat[7]*iitBody[8];
var t52 = rotmat[2]*iitBody[0]+
rotmat[5]*iitBody[1]+
rotmat[8]*iitBody[5];
var t57 = rotmat[2]*iitBody[3]+
rotmat[5]*iitBody[4]+
rotmat[8]*iitBody[5];
var t62 = rotmat[2]*iitBody[6]+
rotmat[5]*iitBody[7]+
rotmat[8]*iitBody[8];

iitWorld[0] = t4 * rotmat[0]+
t9 *rotmat[3]+
t14*rotmat[7];

iitWorld[3] = t4*rotmat[1]+
t9*rotmat[4]+
t14*rotmat[7];

iitWorld[6] = t4*rotmat[2]+
t9*rotmat[5]+
t14*rotmat[8];

iitWorld[1] = t28*rotmat[0]+
t33*rotmat[3]+
t38*rotmat[6];

iitWorld[4] = t28*rotmat[0]+
t33*rotmat[4]+
t38*rotmat[7];

iitWorld[7] = t28*rotmat[2]+
t33*rotmat[5]+
t38*rotmat[8];

iitWorld[2] = t52*rotmat[0]+
t57*rotmat[3]+
t62*rotmat[7];

iitWorld[5] = t52*rotmat[1]+
t57*rotmat[4]+
t62*rotmat[7];

iitWorld[8] = t52*rotmat[2]+
t57*rotmat[5]+
t62*rotmat[8];
return iitWorld;
}

Hier wird also die Inertia in Weltkoordinaten umgewandelt. Damit bin ich vorgestern abend nicht mehr fertig geworden. Ich hatte den Artikel am nächsten morgen auf Gute Nacht geändert. Das Licht störte sie und ich machte die Kiste aus. In der Sekunde beschwert sie sich über mein Tippen, während two and a half men von dvd läuft. "Hätte ich Ohrstöpsel für den Fernseher..". "Tschuldigung", antworte ich.

function calculateDerivedData() {
_transformInertiaTensor(this.inverseInertiaTensorWorld, this.orientation, this.transformMatrix);
}

Die Inertia ist die Neigung, die ein Objekt hat, sich in die entgegengesetzte Richtung der Kraft, die es in die andere Richtung dreht, zu drehen. Die Torque, das ist ein Impuls, neben dem Zentrum der Masse, der eine Drehung auslöst. Mit der 3x3 Rotationsmatritze wird nun die Kraft von der Torque transformiert und mit den Kontaktdaten (Kontaktpunkt und Entfernung vom Zentrum der Masse, Kontaktrichtung, und Penetrationstiefe) in eine Änderung der Orientierung umgerechnet. Hier kommt in Game Physics Engine Development, wie in der realen Welt das Quaternion zum Einsatz, dessen obskure Mathematik, es ist auch eine Komplexe Zahl, sich ausgezeichnet bewährt hat. Hier braucht man dann auch nur vier Zahlen statt einer 3x3 Matrix. Ich hab mal nachgelesen, vor dem Quaternion für Orientierung kam auch eine Matrix zum Einsatz.

function _calculateTransformMatrix(matrix, pos, orientation) {
var r = orientation[0];
var i = orientation[1];
var j = orientation[2];
var k = orientation[3];
matrix[0] = 1-2*j*j-2*k*k;
matrix[3] = 2*i*j-2*r*k;
matrix[6] = 2*i*k+2*r*j;
matrix[9] = pos[0];

matrix[1] = 2*i*j+2*r*k;
matrix[4] = 1-2*i*i-2*k*k;
matrix[7] = 2*j*k-2*r*i;
matrix[10] = pos[1];

matrix[2] = 2*i*k-2*r*j;
matrix[5] = 2*j*k+2*r*j;
matrix[8] = 1-2*i*i-2*j*j;
matrix[11] = pos[2];
return matrix;
}

Ohne die eigentliche Rotationsmatrix geht bei dem RigidBody natürlich gar nichts. Ich hab die Funktionen diesmal nicht nur abgeschrieben. Nein, ich musste auch die Row Major Order in Column Major Order ändern, damit ich die direkt ins WebGL schicken kann. Dafür habe ich dann extra einen Spicker genommen, ähnlich dem kurzen Beitrag unten, nur einmal für 3x3, einmal für 3x4 und dann, weil es auch die WebGL Library ist, für 4x4. Einen Kurs in Linearer Algebra ersetzt es bestimmt nicht, aber mit inversen und transponierten Matrizen kann man bestimmt jedes Koordinatensystem errechnen und wieder zurückverwandeln.


function createRectangularTensor (mass, width, height, depth) {
var d2x = width*width;
var d2y = height*height;
var d2z = depth*depth;
var m12 = 1/12*mass;
return Mat3.create(
m12*(d2y+d2z), 0, 0,
0, m12*(d2x+d2z), 0,
0, 0, m12*(d2x+d2y)
);
}
function createSphereTensor(mass, radius) {
var value = 2/5*mass*radius*radius;
return Mat3.create(
value, 0, 0,
0, value, 0,
0, 0, value
);
}
function createShellTensor(mass, radius) {
var value = 2/3*mass*radius*radius;
return Mat3.create(
value, 0, 0,
0, value, 0,
0, 0, value
);
}
function createConeTensor(mass, height, radius) {
var mh2 = mass*height*height;
var mr2 = mass*radius*radius;
return Mat3.create(
3/80*mh2+3/20*mr2, 0, 0,
0, 3/80*mh2+3/20*mr2, 0,
0, 0, 3/10*mr2
);
}

Hier sind die Tensoren aus dem Anhang. Da stehen auch ein paar vernünftige Frictionwerte (das sind Reibungskoeffizienten, damit man nicht über den Boden rutscht) für Holzkisten auf Stein und Holzkisten auf Eis und andere Dinge, und mit Werten wie 0.4 und 0.1. Die Friction ist ein Ding, was nach der Position und auch der Orientierung, sehr wichtig ist, damit die Simulation stabil bleibt. Ansonsten rutschen die Gegenstände praktisch wie von alleine und so Sachen.

Gute Nacht

root (Edward Gerhold)

function _transformInertiaTensor(iitWorld, q, iitBody, rotmat) {
var t4 = rotmat[0]*iitBody[0]+
rotmat[3]*iitBody[1]+
rotmat[6]*iitBody[2];
var t9 = rotmat[0]*iitBody[4]+
rotmat[3]*iitBody[4]+
rotmat[6]*iitBody[6];
var t14 = rotmat[0]*iitBody[6]+
rotmat[3]*iitBody[7]+
rotmat[6]*iitBody[8];
var t28 = rotmat[1]*iitBody[0]+
rotmat[4]*iitBody[1]+
rotmat[7]*iitBody[7];
var t33 = rotmat[1]
}

Mache erst morgen weiter...

Mat34.toMatrix3 = function (m) {
return Mat3.create(
m[0], m[1], m[2],
m[3], m[4], m[5],
m[6], m[7], m[8]
// letzte spalte fehlt
);
};
Mat34.toMatrix4 = function (m) {
// Change a 3x4 to a 4x4 matrix
return Mat4.create(
m[0], m[1], m[2], 0, // add 0 for row 4 column 1
m[3], m[4], m[5], 0, // add 0 for row 4 column 2
m[6], m[7], m[8], 0, // add 0 for row 4 column 3
m[9], m[10], m[11], 1 // add 1 for row 4 column 4
);
};
Mat34.det = function (m) {
return -m[2]*m[4]*m[6]+
m[1]*m[5]*m[6]+
m[2]*m[3]*m[7]-
m[0]*m[5]*m[7]-
m[1]*m[3]*m[8]+
m[0]*m[4]*m[8];
};
Mat34.setInverse = function (out, m) {
var det = Mat34.det(m);
if (det === 0) return;
var invDet = 1/det;

out[0] = (-m[5]*m[7]+m[4]*m[8]) * invDet;
out[1] = (m[2]*m[7]-m[1]*m[8]) * invDet;
out[2] = (-m[2]*m[4]+m[1]*m[5]) * invDet;

out[3] = (m[5]*m[6]-m[3]*m[8]) * invDet;
out[4] = (-m[2]*m[6]+m[0]*m[8]) * invDet;
out[5] = (m[2]*m[3]-m[0]*m[5]) * invDet;

out[6] = (-m[4]*m[6]+m[3]*m[7]) * invDet;
out[7] = (m[5]*m[6]-m[0]*m[7]) * invDet;
out[8] = (-m[5]*m[3]+m[0]*m[4]) * invDet;

out[9] = (m[5]*m[7]*m[9]
-m[4]*m[8]*m[9]
-m[5]*m[6]*m[10]
+m[3]*m[8]*m[10]
+m[4]*m[6]*m[11]
-m[3]*m[7]*m[11]) * invDet;
out[10] = (-m[2]*m[7]*m[9]
+m[1]*m[8]*m[9]
+m[2]*m[6]*m[10]
-m[0]*m[8]*m[10]
-m[1]*m[6]*m[11]
+m[0]*m[7]*m[11]) * invDet;
out[11] = (m[2]*m[4]*m[9]
-m[1]*m[5]*m[9]
-m[2]*m[3]*m[10]
+m[0]*m[5]*m[10]
+m[1]*m[3]*m[11]
-m[0]*m[4]*m[11]) * invDet;
return out;
};

Collision

root (Edward Gerhold)

Das Thema Kollisionserkennung wird, wie der Autor bereits beschrieben hat, in anderen Büchern noch ausführlicher geschildert. Allerdings bringt er eine kompakte und gute Auswahl und eine Vollständige Einsicht in Face-to-Face, Edge-to-Edge, Point-to-Point und Point-Face, Edge-Face, Point-Edge, Face-Edge und umgekehrt und die Probleme, die bei der Kontaktauflösung auftreten können. Heute bin ich dazu gekommen, mal eben die Funktionen abzutippen, die vorgestellt werden. Weil ich gestern so lieb gepostet habe, mache ich das jetzt nochmal. Da wird man schnell schlau, wie es mit dem ContactNormal und der Penetrationstiefe aussieht.

var CollisionDetector = {

sphereAndSphere: function (one, two, data) {
if (data.contactsLeft <= 0) return 0;
var positionOne = one.getAxis(3);
var positionTwo = two.getAxis(3);
var midLine = Vec3.sub(Vec3.create(), positionOne, positionTwo);
var size = Vec3.length(midLine);
if (size < 0.0 || size >= one.radius+two.radius) return 0;
var normal = Vec3.scale(Vec3.create(), midLine, 1.0/size);
var contact = data.contacts;
contact.contactNormal = normal;
var scaledMidline = Vec3.scale(Vec3.create(), midLine, 0.5);
contact.contactPoint = Vec3.add(Vec3.create(), positionOne, scaledMidline);
contact.penetration = (one.radius+two.radius - size);
contact.body[0] = one.body;
contact.body[1] = two.body;
contact.restitution = data.restitution;
contact.friction = data.friction;
return 1;
},
sphereAndHalfSpace: function (sphere, plane, data) {
if (data.contactsLeft <= 0) return 0;
var position = sphere.getAxis(3);
var ballDistance = Vec3.dot(plane.direction, position) - sphere.radius - plane.offset;
if (ballDistance >= 0) return 0;
var contact = data.contacts;
contact.contactNormal = plane.direction;
contact.penetration = -ballDistance;
contact.contactPoint = Vec3.sub(Vec3.create(), position, Vec3.scale(Vec3.create(), plane.direction, ballDistance+sphere.radius));
contact.body[0] = sphere.body;
contact.body[1] = null;
contact.restitution = data.restitution;
contact.friction = friction;
return 1;
},
sphereAndTruePlane: function (sphere, plane, data) {
if (data.contactsLeft <= 0) return 0;
var position = sphere.getAxis(3);
var centerDistance = Vec3.dot(plane.direction, position) - plane.offset; // d = p dot L - l
if (centerDistance*centerDistance > sphere.radius*sphere.radius) {
return 0;
}
var normal = Vec3.clone(plane.direction);
var penetration = -centerDistance;
if (centerDistance < 0) {
Vec3.flip(normal);
penetration = -penetration;
}
penetration += sphere.radius;
var contact = data.contacts;
contact.contactNormal = normal;
contact.penetration = penetration;
contact.contactPoint = Vec3.sub(Vec3.create(), position, Vec3.scale(Vec3.create(), plane.direction, centerDistance));
contact.body[0] = sphere.body;
contact.body[1] = null;
contact.restitution = data.restitution;
contact.friction = data.friction;
return 1;
},
boxAndSphere: function (box, sphere, data) {
var center = sphere.getAxis(3);
var relCenter = Mat3.transformInverse(box.transform, center);
var abs = Math.abs;
if (abs(relCenter[0]) - sphere.radius > box.halfSize[0] ||
abs(relCenter[1]) - sphere.radius > box.halfSize[1] ||
abs(relCenter[2]) - sphere.radius > box.halfSize[2]) {
return 0;
}
var closestPt = Vec3.create(0,0,0);
var dist;
dist = relCenter[0];
if (dist > box.halfSize[0]) dist = box.halfSize[0];
if (dist < -box.halfSize[0]) dist = -box.halfSize[0];
closestPt[0] = dist;
dist = relCenter[1];
if (dist > box.halfSize[1]) dist = box.halfSize[1];
if (dist < -box.halfSize[1]) dist = -box.halfSize[1];
closestPt[1] = dist;
dist = relCenter[2];
if (dist > box.halfSize[2]) dist = box.halfSize[2];
if (dist < -box.halfSize[2]) dist = -box.halfSize[2];
closestPt[2] = dist;
dist = Vec3.squaredMagnitude(Vec3.sub(Vec3.create(), closestPt, relCenter));
if (dist > sphere.radius * sphere.radius) return 0;
var closestPtWorld = Mat4.transform(box.transform, closestPt);
var contact = data.contacts;
contact.contactNormal = Vec3.sub(Vec3.create(), center, closestPtWorld);
Vec3.normalize(contact.contactNormal, contact.contactNormal);
contact.contactPoint = closestPtWorld;
contact.penetration = sphere.radius - Math.sqrt(dist);
contact.body[0] = box.body;
contact.body[1] = sphere.body;
contact.restitution = data.restitution;
contact.friction = data.friction;
return 1;
},
transformToAxis: function (box, axis) {
var abs = Math.abs;
return box.halfSize[0] * abs(Vec3.dot(axis, box.getAxis(0)))+
box.halfSize[1] * abs(Vec3.dot(axis, box.getAxis(1)))+
box.halfSize[2] * abs(Vec3.dot(axis, box.getAxis(2)));
},
overlapOnAxis: function (boxOne, boxTwo, axis) {
var oneProject = transformToAxis(boxOne, axis);
var twoProject = transformToAxis(boxTwo, axis);
var toCenter = Vec3.sub(Vec3.create(), boxTwo.getAxis(3)-boxOne.getAxis(3));
var distance = Math.abs(Vec3.dot(toCenter, axis));
return distance < oneProject+twoProject;
},
boxAndPoint: function (box, point, data) {
var relPt = Mat4.transformInverse(box.transform, point);
var normal;
var min_depth = box.halfSize[0] - Math.abs(relPt[0]);
if (min_depth < 0) return 0;
Vec3.scale(normal, box.getAxis(0), relPt[0] < 0 ? -1 : 1);
var depth = box.halfSize[1] - Math.abs(relPt[1]);
if (depth < 0) return 0;
else if (depth < min_depth) {
min_depth = depth;
Vec3.scale(normal, box.getAxis(1), relPt[1] < 0 ? -1 : 1);
}
depth = box.halfSize[2] - Math.abs(relPt[2]);
if (depth < 0) return 0;
else if (depth < min_depth) {
min_depth = depth;
Vec3.scale(normal, box.getAxis(2), relPt[2] < 0 ? -1 : 1);
}
var contact = data.contacts;
contact.contactNormal = normal;
contact.contactPoint = point;
contact.penetration = min_depth;
contact.body[0] = box.body;
contact.body[1] = null;
contact.restitution = data.restitution;
contact.friction = data.friction;
return 1;
}
};

Wie man liest, hier werden die Abstände zwischen den Objektzentren ausgerechnet und die Differenz als Penetrationstiefe verwendet. Das Kontaktnormal ist die Richtung, vom ersten Objekt aus betrachtet, in die das Objekt kollidiert. Die Kontakttangente ist eine rechtwinklig abstehende Achse. Die Orthonormalbasis habe ich vor zwei Tagen bereits konstruiert, nachdem ich das eine Weile davor bereits verstanden hatte, wie ich das mit Kreuzprodukten lösen kann. Für die Physikengine wird diese Orthonormalbasis aber noch genauer berechnet, denn man muss vielleicht mal drehen.

Mat3.setComponents = function (m, xCol, yCol, zCol) {
m[0] = xCol[0];
m[1] = xCol[1];
m[2] = xCol[2];
m[3] = yCol[0];
m[4] = yCol[1];
m[5] = yCol[2];
m[6] = zCol[0];
m[7] = zCol[1];
m[8] = zCol[2];
};

Das ist eine Hilfsfunktion, die drei Vektoren auf die drei Spalten eine 3x3 Matrize schreibt. Sie wird am Schluss von calculateContactBasis verwendet, um die Transformationsmatritze für das Kontaktkoordinatensystem zu setzen.


function calculateContactBasis() { // used as contact.calculateContactBasis with this==contact
var contactTangent = [Vec3.create(), Vec3.create()];
var abs = Math.abs;
var sqrt = Math.sqrt;
var contactNormal = this.contactNormal;
var contactPoint = this.contactPoint;
var s;
// wether is z nearer to x or y axis
if (abs(contactNormal[0]) > abs(contactNormal[1])) {
// scaling factor to ensure normalization
s = 1/sqrt(contactNormal[2]*contactNormal[2]+contactNormal[0]*contactNormal[0]);
// new x axis at right angles to worlds y axis
contactTangent[0][0] = contactNormal[2]*s;
contactTangent[0][1] = 0;
contactTangent[0][2] = -contactNormal[0]*s;
// new y axis at right angle to the new x and z axis
contactTangent[1][0] = contactNormal[1]*contactTangent[0][0];
contactTangent[1][1] = contactNormal[2]*contactTangent[0][0]-contactNormal[0]*contactTangent[0][2];
contactTangent[1][2] = -contactNormal[1]*contactTangent[0][1];
} else {
s = 1/sqrt(contactNormal[2]*contactNormal[2]+contactNormal[1]*contactNormal[1]);
// new x
contactTangent[0][0] = 0;
contactTangent[0][1] = -contactNormal[2]*s;
contactTangent[0][2] = contactNormal[1]*s;
// new y
contactTangent[1][0] = contactNormal[1]*contactTangent[0][2]-contactNormal[2]*contactTangent[0][1];
contactTangent[1][1] = -contactNormal[0]*contactTangent[0][2];
contactTangent[1][2] = contactNormal[0]*contactTangent[0][1];
}
this.contactToWorld = Mat3.create();
Mat3.setComponents(this.contactToWorld,
contactNormal,
contactTangent[0],
contactTangent[1]
);
}

Viel mehr habe ich, seitdem wir wieder zuhause sind, heute nicht geschafft. Unterwegs habe ich jedenfalls weiter gelesen, und bin bei Kapitel 18.

 

Cross and Skew

root (Edward Gerhold)

Endlich in Kapitel 15 Resting Contacts and Friction angekommen, gibt es im Abschnitt Velocity from Angular Motion, einen Hinweis auf die sogenannte "Skew Matrix", welche mit einem Vektor erzeugt wird, und mit einem weiteren multipliziert das Kreuzprodukt der beiden Vektoren ergibt. Ich habe sie gerade implementiert und ausprobiert, bin aber über das negative Vorzeichen verwundert. Mit der abgedruckten Matrize verglichen, stimmt das schon. Die Multiplikation mit dem Vektor ist glaube ich auch in Ordnung. Als ich kurz darauf aus der Tür raus bin habe ich aber bereits eine Vermutung.


Vec3.equals = function (v1,v2) {
return v1[0] === v2[0] && v1[1] === v2[1] && v1[2] === v2[2];
};

Für den Versuch ist mir eine neue Funktion eingefallen, die mir das Vergleichen von Vektoren vereinfacht, bis man es wieder hardcoden muss, damit es schnell genug ist.

Mat3.createSkewMatrix = function (vec) {
// Mat3.transform(Mat3.createSkewMatrix(vec1), vec2) == Vec3.cross(vec1, vec2);
var a = vec[0],
b = vec[1],
c = vec[2];
return Mat3.create(
0, c, -b,
-c, 0, a,
b, -a, 0
);
};

Mit einem Vektor eingerichtet, und mit einem anderen multipliziert, sollte ein neuer Vektor aus der SkewMatrix rauskommen, der dem Kreuzprodukt der beiden Vektoren entspricht. Die Matritze heisst auf english "skew-symmetric". Allerdings hätte ich das Ergebnis jetzt nicht erwartet, denn bei meinem ersten Versuch, da kommt eine negative, statt die gleiche Zahl in Feld 0 (x) heraus. Das wird mich noch bis ich wieder zuhause bin beschäftigen.

var exports = require("./minimum-webgl.js");
var Vec3 = exports.GL.Vec3;
var Mat3 = exports.GL.Mat3;
var vec1 = Vec3.create(10,0,5);
var vec2 = Vec3.create(0,15,0);
var vec3 = Vec3.cross(vec1, vec2);
var skew = Mat3.createSkewMatrix(vec1);
var vec4 = Mat3.transform(skew, vec2);
var equal = Vec3.equals(vec3, vec4);
console.log(require("fs").readFileSync("./cross-and-skew.js", "utf8"));
console.dir(vec3);
console.dir(vec4);
console.log(equal);
{ '0': 75, '1': 0, '2': 150 }
{ '0': -75, '1': 0, '2': 150 }
false

Hab ich jetzt was anderes falsch gemacht? Ja, was genau, das fällt mir bereits kurz nach Verlassen des Wohnheims ein, um für uns was einzukaufen.

Mat3.transform = function (m, v) {
return Vec3.create(
m[0] * v[0] + m[3] * v[1] + m[6] * v[2],
m[1] * v[0] + m[4] * v[1] + m[7] * v[2],
m[2] * v[0] + m[5] * v[1] + m[8] * v[2]
);
};
Mat3.transformInverse = function (m, v) {
m = Mat3.invert(m);
return Vec3.create(
m[0] * v[0] + m[3] * v[1] + m[6] * v[2],
m[1] * v[0] + m[4] * v[1] + m[7] * v[2],
m[2] * v[0] + m[5] * v[1] + m[8] * v[2]
);
};

Auf dem Weg zur Bushaltestelle verdächtige ich dann das Kreuzprodukt falsch zu sein. Ich meine Vec3.cross. Zwischenzeitlich verdächtige ich noch das Gesetz, daß u X v = -skew_v X u ist. Doch das war´s nicht. Habe das schon richtig gemacht.

Hier fehlt ein Ausschnitt, wo ich in createSkewMatrix es mit gedrehten Vorzeichen in der Matrize, und auch mit drehen des Vektorvorzeichens probiere, nachdem ich wieder nach Hause komme und es vergessen habe. Das Ergebnis ist auch falsch.

Mat3.setSkewMatrix = function (mat, vec) {
"use strict";
mat[0] = mat[4] = mat[8] = 0;
mat[3] = -vec[2];
mat[6] = vec[1];
mat[1] = vec[2];
mat[7] = -vec[0];
mat[2] = -vec[1];
mat[5] = vec[0];
return mat;
};

Dann fällt es mir wieder ein. Ich hatte den Verdacht, daß das alles richtig ist. Aber Vec3.cross falsch. Gerade machte ich die Funktion auf.

Ich hatte die Produkte addiert statt subtrahiert. Der Fehler lag in Vec3.cross. :-)

var exports = require("./minimum-webgl.js");
var Vec3 = exports.GL.Vec3;
var Mat3 = exports.GL.Mat3;
var vec1 = Vec3.create(10,0,5);
var vec2 = Vec3.create(0,15,0);
var vec3 = Vec3.cross(vec1, vec2);
var skew = Mat3.createSkewMatrix(vec1);
var vec4 = Mat3.transform(skew, vec2);
var equal = Vec3.equals(vec3, vec4);
console.log(require("fs").readFileSync("./cross-and-skew.js", "utf8"));
console.dir(vec3);
console.dir(vec4);
console.log(equal);

var skew2 = Mat3.create();
Mat3.setSkewMatrix(skew2, vec1);
var vec5 = Mat3.transform(skew2, vec2);
equal = Vec3.equals(vec3, vec5);
console.dir(vec5);
console.log(equal);
{ '0': -75, '1': 0, '2': 150 }
{ '0': -75, '1': 0, '2': 150 }
true
{ '0': -75, '1': 0, '2': 150 }
true

Na also.

Vec3.cross = function (a, b) {
"use strict";
return Vec3.create(
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0]
);
};

Hier standen + Zeichen zwischen. Ich hatte mir yz zx xy für die linke Seite gemerkt, und daß sie rechts umgekehrt sind. Aber das Minus wohl vergessen. Das werde ich jetzt nicht mehr. Aber so kann es gehen.

Constraints, Contacts, Collision, Joints

root (Edward Gerhold)

Das Prinzip der Physikengine ist überall ungefähr gleich. In dem Buch wird ein Partikelsystem zu einem Mass-Aggregate System, und eine Full Motion 3D Rigid Body Engine programmiert. Aber das ist nicht alles, weil es glatt so viel ist, werde ich auch in diesem Post wieder das eine oder andere vergessen. Die Constraints zum Beispiel, das sind Kontakte. Oder Kontaktbeschränkungen. Sind zwei Objekte mit einem Constraint verknüpft, so gibt es, in der Partikelengine zum Beispiel, eine Funktion currentLength und eine Funktion addContact. Mit currentLength wird der Abstand zwischen den Objekten ermittelt. Dann wird geschaut, ob die Constraints eingehalten werden. Diese currentLenght wird von addContact gerufen. Die wird vom Kontaktgenerator der ersten Stufe gerufen, um mögliche Kontakte zu ermitteln.Sollten sie nicht eingehalten werden, wird ein Kontakt generiert. Nachdem alle durch sind, beginnt in der nächsten Stufe der Kontaktresolver seine Arbeit und löst die gefundenen Kontakte auf. Die Constraints halten also die Abstände zwischen zwei Objekten ein. Um die Constraints aufzulösen, wird eine Kraft in die Gegenrichtung angewandt. Das gleiche wird bei der Kollision gemacht, um die Objekte mit der separating Velocity (dem Gegenteil der Einschlagsgeschwindigkeit, der Closing Velocity) wieder zu trennen. Wozu noch viel mehr Details dann gehören..

 var rodLink = createParticleLink({
particles: [],
length: 1,
addContact: function (contact, limit) {
"use strict";
var curLen = this.currentLength();
var length = this.length;
if (curLen <= length) return 0;
contact.particle[0] = this.particle[0];
contact.particle[1] = this.particle[1];
var normal = vec3.create();
vec3.sub(normal, particle[1].position, particle[0].position);
if (curLen > length) {
contact.contactNormal = normal;
contact.penetration = curLen - length;
} else {
vec3.scale(contact.contactNormal, normal, -1);
contact.penetration = length - curLen;
}
contact.restitution = 0;
return 1;
}
});

Anm. Man sieht, daß das wieder älter und eins der ersten funktionierenden Pieces aus der letzten Woche war. Hier muss man die Properties particle, etc., noch manuell setzen.

Die Kontakte. Das sind zwei Objekte, die sich berühren, oder ineinander gefahren sind, oder zwei mit Constraints oder Joints verbundene Objekte, deren Funktion gemeldet hat, daß sie berechnet werden wollen. In dieser Engine funktioniert das ungefähr so. Natürlich entgeht uns hier das eine oder andere Detail. Die Kontakte werden unter Zeitbeschränkung überprüft und mit Limit. Denn hier muss in Sekundenbruchteilen zwischen den Frames die gezeichnet werden, im Zeitintervall des Timers alles upgedated werden. Man hat also nicht viel Zeit und muss darum so genau wie möglich vorgehen, um zu wissen, was sich überhaupt bewegt. Apropos, es gibt Kontakte, die nennt man "Resting Contacts" das sind die ruhenden Objekte. Wäre da nicht die Gravitation, die sie in den Boden zieht, und der ContactResolver, der für den Gegenimpuls sorgt, so dass das Objekt wieder abhebt um wieder zu Boden gezogen zu werden, so minimal, und je nach Grössenunterschied (Kiste-Kiste, Kiste-Erde) nach der realistischeren Variante zu betrachten,  unterschiedlich zu trennen. Zu trennen sind sie wie gehabt, mit der "separating Velocity", was die umgedrehte "closing Velocity" also "die Einschlaggeschwindigkeit" ist. In Gegenrichtung der Kontaktachse. Immer vom ersten Objekt aus betrachtet.

function createJoint (body1, position1, body2, position2, declaration) {
declaration = declaration || {};
var joint = {
/* The two rigid bodies to connect */
body: [body1, body2],
/* The relative location of the connection for each body */
position: [position1, position2],
/* The maximum displacement of the joint before it´s violated */
error: 0
};
if (typeof declaration.addContact != "function") {
throw new TypeError("Expecting you to implement addContact(contact, limit)");
}
return _mixin_(joint, declaration);
}

Natürlich ist mir da wieder das eine oder andere Detail entgangen. Die Joints sind nicht wie die Constraints, sondern mehr wie die Gelenke zwischen den Knochen, oder wie Stifte, die zwei Stäbe verbinden, indem sie als Gelenk fungieren. Das Interface für die Physikengine ist einfach. Die Funktion addContact wird für jeden speziellen Typ Joint implementiert, und die füllt die Struktur contact (ist eine C++ Übersetzung, kann für JavaScript geändert werden) sollte was Arbeit zu verrichten sein. Das bekannteste Beispiel ist wohl die Ragdoll, eine Art Crashtestdummy, die zeigt, wie die Gelenke verbunden sind, wenn sie möglichst realistisch hinfällt. Bei der Cyclone Engine aus dem Game Physics Engine Development Buch ist auch eine Demo bei.

 

ach und

root (Edward Gerhold)

Wenn die zwei Objekte aufeinander treffen, dann tun sie das in eine bestimmte Richtung. Diesen Vektor nennt man, nachdem man ihn durch seine Länge geteilt hat, das Kontaktnormal. Entscheidend für den Impuls, um die Objekte zu trennen, ist dessen Richtung, sowie die Penetrationstiefe, Das Kontaktnormal erhält man einfach durch die Subtraktion der Position der zwei Objekte und dann normalisieren des Vektors. Er bildet eine der Achsen für das Kontaktkoordinatensystem. Dessen andere zwei Achsen zufällig gewählt werden.

Vec3.calculateContactNormal = function (p1, p2) {
"use strict";
var contactNormal = Vec3.create();
Vec3.sub(contactNormal, p1, p2);
Vec3.normalize(contactNormal, contactNormal);
return contactNormal;
};

Das Kontaktkoordinatensystem, das ist sowas wie das Weltkoordinatensystem, hier liegt der Mittelpunkt allerdings in der Kontaktbasis, die man mit einer geschätzten Achse und dem Kontaktnormal per Kreuzprodukt um eine weitere Achse erweitert und dann die Schätzachse durch ein weiteres Ergebnis eines Kreuzprodukt mit den beiden anderen Achsen ersetzt. Schon hat man den neuen Mittelpunkt eines anderen Koordinatensystem. Es gibt ja Weltkoordinaten (transformiert), inertiale Koordinaten (parallel zur Weltachse, nur auf Objekthöhe), Objektkoordinaten (wie das Objekt definiert ist, ohne mit der Welttransformation verrechnet zu sein). Bei den Kollisionen werden die Kontaktkoordinaten benötigt. Mehr dazu später, ich lerne noch weiter.

 

Mat3.createOrthoNormalBasis = function (out, x, yguess) {
var z = Vec3.cross(x, yguess);
if (Vec3.isZero(z)) return null;
Vec3.normalize(z,z);
var y = Vec3.cross(x, z);
Vec3.normalize(y,y);
return Mat3.create(
x[0], x[1], x[2], // column 1 x-axis
y[0], y[1], y[2], // column 2 y-axis
z[0], z[1], z[2] // column 3 z-axis
);
};

Die ist mir eingefallen, um zu testen, ob der Vektor nur Nullen enthält. Wenn das Kreuzprodukt fehlschlägt und Nullen zurückgibt, sind die zwei Achsen parallel. 

 


Vec3.isZero = function (v) {
"use strict";
return v[0] === 0 && v[1] === 0 && v[2] === 0;
};

 

Dauert noch

root (Edward Gerhold)

Das dauert noch, bis das funktioniert. Aber ich kann mal ein paar Auszüge anbieten. Wie der Partikel verfügt auch der Rigid Body über einen Integrator. Was übrigens mit dem mathematischen Begriff Integration zu verstehen ist. Es ist eine der Update Funktionen. Diese hier führt die physikalischen Berechnungen für das Zeitfenster aus. In unserem Fall werden hier die lineare und die anguläre Beschleunigung ausgerechnet und das Objekt kann danach mit der neuen Position und der neuen Rotation gezeichnet werden. 

mesh.addForce = function (force) {
Vec3.add(this.forceAccum, this.forceAccum, force);
};
mesh.addTorque = function (torque) {
Vec3.add(this.torqueAccum, this.torqueAccum, torque);
};
mesh.integrate = function (time) {
var lastFrameAcceleration = this.acceleration;
Vec3.addScaledVector(lastFrameAcceleration, this.forceAccum, this.inverseMass)
var angularAcceleration = Vec3.create();
Mat3.transformVector(angularAcceleration, this.inverseInertiaTensorWorld, this.torqeAccum);
// linear
Vec3.addScaledVector(this.velocity, lastFrameAcceleration, time);
// angular
Vec3.addScaledVector(this.rotation, angularAcceleration, time);
// impose drag
// Achtung: 0 damping multipliziert mit 0 hoch time === 0, der körper koennte (wird) also stehen bleiben.
Vec3.multiplyWithScalar(this.velocity, Math.pow(this.linearDamping, time));
Vec3.multiplyWithScalar(this.rotation, Math.pow(this.angularDamping, time));
// update position with velocity
Vec3.addScaledVector(this.position, this.velocity, time);
// update orientation with rotation
Quaternion.addScaledVector(this.orientation, this.rotation, duration);
this.calculateDerivedData();
this.clearAccumulators();
};
mesh.clearAccumulator = function () {
this.forceAccum[0] = this.forceAccum[1] = this.forceAccum[2] = 0;
this.torqueAccum[0] = this.torqueAccum[1] = this.torqueAccum[2] = 0;
};

Ganz einfach sind auch die addForce und addTorque Funktionen. Die folgen D´Alemberts Prinzip, daß man Kräfte zu einer zusammenfassen kann. Und das geht zufällig nicht nur mit Force, sondern auch mit Torque. Zum Schluß wird das Ergebnis angewandt. Der Akkumulator dafür (erinnert an den Akkumulator im Prozessor, oder?) wird jedesmal geklärt.

Quaternion.multiply = function (out, q, p) {
var qr = q[0], qi = q[1], qj = q[2], qk = q[3];
var pr = p[0], pi = p[1], pj = p[2], pk = p[3];
out[0] = qr * pr - qi * pi - qj * pj - qk * pk;
out[1] = qr * pi + qi * pr + qj * pk - qk * pj;
out[3] = qr * pj + qj * pr + qk * pi - qi * pk;
out[4] = qr * pk + qk * pr + qi * pj - qj * pi;
return out;
};
Quaternion.addScaledVector = function (out, v, s) {
var q = Quaternion.create(0, v[0]*s, v[1]*s, v[2]*s);
out[0] += q[0] * 0.5;
out[1] += q[1] * 0.5;
out[2] += q[2] * 0.5;
out[3] += q[3] * 0.5;
return out;
};
Quaternion.rotateByVector = function (out, vec) {
var q = Quaternion.create(0, vec[0], vec[1], vec[2]);
Quaternion.multiply(out, out, q);
};
Quaternion.magnitude = function (q) {
return Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
};

Die Orientierung des Körpers wird als Quaternion gehalten.  Ein "Euler-Winkel", ein Vektor mit drei Winkelangaben, reichte nicht aus, um die Orientierung darzustellen. Ich habe das Beispiel jetzt schon wieder vergessen, jedenfalls gibt es, wenn man sich um 90° gedreht hat, dann ein Problem mit der nächsten Achse, und so weiter. Erinnert mich daran, das morgen wiederholt zu lesen. Das Quatiernion lässt sich problemlos in eine 3x3 Rotationsmatrize umformen. Es hat eine etwas eigene Mathematik und ist nicht einfach nur ein Vektor. Neu für mich, aber mit etwas Übung wohl Alltagstauglich. Gehört ja jetzt fest zur Physikengine. Ein Partikel hat übrigens keine Orientierung, es gibt kein vorne und kein hinten, oder eine Richtung, in die es zeigt. Es bewegt sich einfach mit Acceleration und Velocity und sich ändernder Position, nachdem die Acceleration ausgerechnet und auf die Velocity angewandt worden ist.

Vec3.multiplyWithScalar = function (out, vec, scalar) {
out[0] = vec[0] * scalar;
out[1] = vec[1] * scalar;
out[2] = vec[2] * scalar;
};
Vec3.addScaledVector = function (out, vec, scalar) {
out[0] += vec[0] * scalar;
out[1] += vec[1] * scalar;
out[2] += vec[2] * scalar;
};

Die Vektoroperationen sind natürlich einfach. Die Transformation des Tensors, um die Rotation auszurechnen, die ist auch nicht schwieriger. Allerdings muss ich hier noch beachten, daß die Matrix4 in dem Physikbuch eine 3x4 (3 Zeilen, 4 Spalten) Matrize ist, mit 12 Feldern, statt mit 16. Das steht bei den Rechnungen natürlich auch so da. Einfach ist natürlich, wenn die letzte Reihe, bis auf die 4. Spalte 0 enthält und zuletzt eine 1. Noch einfacher ist es allerdings, sich auch eine 3x4 Matrize anzulegen, um mit den Rechengesetzen aus dem Buch erstmal so zu experimentieren, wie sie geschrieben sind. 

function ParticleContact (p1, p2) {
"use strict";
this.particle = [p1, p2];
this.restitution = 1;
this.contactNormal = vec3.create();
this.contactPoint = vec3.create();
this.penetration = 0;
}
ParticleContact.prototype = {
resolve: function (duration) {
"use strict";
this.resolveVelocity(duration);
this.resolveInterpenetration(duration);
},
resolveVelocity: function (duration) {
"use strict";
var restitution = this.restitution;
var sepVelo = this.calculateSeparatingVelocity();
if (sepVelo[0] > 0 || sepVelo[1] > 0 || sepVelo[2] > 0) return;
var newSepVelo = -sepVelo * restitution;
var deltaVelo = newSepVelo - sepVelo;
var totalInverseMass = this.particle[0].inverseMass;
if (this.particle[1]) totalInverseMass += this.particle[1].inverseMass;
if (totalInverseMass < 0) return;
var impulse = deltaVelocity / totalInverseMass;
var impulsePerMass = vec3.create();
vec3.scale(impulsePerMass, this.contactNormal, impulse);
vec3.scaleAndAdd(this.particle[0].velocity, this.particle[0].velocity, impulsePerMass, this.particle[1].inverseMass);
if (this.particle[1]) {
vec3.scaleAndAdd(this.particle[1].velocity, this.particle[1].velocity, impulsePerMass, this.particle[1].inverseMass);
}
},
calculateSeparatingVelocity: function () {
"use strict";
var relativeVelo = v3set(vec3.create(), this.particles[0].velocity);
if (this.particles[1]) vec3.sub(relativeVelo, relativeVelo, this.particles[1].velocity);
return dot(relativeVelo, contactNormal);
},
resolveInterpenetration: function (duration) {
"use strict";
var particle = this.particles;
var penetration = this.penetration;
if (penetration <= 0) return;
var totalInverseMass = particle[0].inverseMass;
if (particle[1]) totalInverseMass += particle[1].inverseMass;
if (totalInverseMass <= 0) return;
var movePerIMass = vec3.create();
vec3.add(movePerIMass, movePerIMass, this.contactNormal);
vec3.scale(movePerIMass, movePerIMass, -penetration / totalInverseMass);
var position = vec3.create();
vec3.scaleAndAdd(position, particle[0].position, movePerIMass, particle[0].inverseMass);
particle[0].position = position;
if (particle[1]) {
position = vec3.create();
vec3.scaleAndAdd(position, particle[1].position, movePerIMass, particle[1].inverseMass);
particle[1].position = position;
}
}
};

Ich habe jetzt noch allerhand angefangenes von den letzten Tagen upzudaten, wie ich mir von dem Code ablesen kann. Da ich die gl-matrix nun ersetzen kann. Das nächste Beispiel ist aus der Kontakgenierung und eine von den Datenstrukturen, die vorgestellt werden, um die möglichen Kontakte in einer partitionierten Gamewelt in einer Hierarchie verwalten zu können. Sinn des ganzen ist, sich die O(n²) zu sparen, die beim abtesten von wer-mit-wem-kollidiert allein bei der Suche nach den zu kleinen Distanzvektoren der Boundingboxen entstehen, wenn man einfach grob jeden mit jedem prüft. Wenn man nur dort, wo was in Bewegung ist, und was in der Umwelt in der Nähe ist, prüft, und dann erstmal nur die Bounds, dann kann man das in den Bruchteilen der Sekunde, die man pro Frame hat, machen. Wo ich den Konstruktor gerade sehe, kann ich das Objekt mit dem __proto rausnehmen und einfach this. benutzen. Sowas muss wieder raus, besser vermeiden.

function BHVNode () {
return {
__proto__: BHVNode.prototype,
children: Array(2), // 2 children
volume: null, // BoundingBox
body: null, // rigid body at this node
};
}
BHVNode.prototype = {
isLeaf: function () {
return this.body != null;
},
getPotentialContacts: function (contacts, limit) {
if (this.isLeaf() || limit == 0) return;
return this.children[0].getPotentialContactsWith(
chilren[1], contacts, limit
);
},
getPotentialContactsWith: function (other, contacts, limit) {
if (!overlaps(other) || limit == 0) return 0;
if (this.isLeaf() && other.isLeaf()) {
contacts.body[0] = body;
contacts.body[1] = other.body;
}
if (other.isLeaf() || (!this.isLeaf() && this.volume.getSize() >= other.volume.getSize())) {
var count = this.children[0].getPotentialContactsWith(
other, contacts, limit
);
if (limit > count) {
return count + this.children[1].getPotentialContactsWith(
other, contacts + count, limit - count
);
} else {
return count;
}
} else {
count = this.children[1].getPotentialContactsWith(
other.children[0], contacts, limit
);
}
}
};

Also die BoundingVolumes, die CollisionDetection und die umfangreiche Lektion über Face-to-Face, Edge-to-Face, Edge-to-Edge und so weiter Kontakte, die habe ich noch nicht implementiert bekommen...die Rigid Body Engine ist auch noch nicht fertig, weil ich, bevor ich die Tensoren in ihre Inverse (siehe Mat3.invert, die brauche ich dafür) transformiere, und dann den Torque Vektor mit, um einen neuen Vektor zu erhalten, den ich dann auf das Orientierungsquaternion anwende. Und ob ich jetzt .orientation oder .rotation in die WebGL Matritze fütter, weiß ich auch noch nicht. Wie gesagt, und wenn das dann interessant werden soll, müssen die Kollisionen auch erkannt werden, damit ich die Objekte nur anstupsen muss, um sie realistisch zu bewegen. Sie auf eine Ebene fallen zu lassen, um ihre Federgleichung sowie die Auswirkung Rotation zu testen, das kann man leicht mit der Distanz zwischen Zentrum des Objekts und der Ebene abtesten, um ein Beispiel mit fallenden Boxen zu programmieren. Später braucht man aber das gesamte Werk, ist klar. Wer jetzt Bock auf PhysicJS oder Box2D oder Newton oder eine andere Library bekommen hat, der soll die ruhig ausprobieren. Ich hoffe auch, diese jetzt zu verstehen...

...und richtig aus dem Kopf kann ich das auch noch nicht. Ich finde das Buch aber sehr interessant und werde es weiter lesen.

row-maj, col-maj

root (Edward Gerhold)

Two Types of Matrix Arrays

WebGL uses Column-Major-Order, the book about Game Physics uses Row-Major-Order for the Matrix. To convert between them, it´s easier to have a lookup table. On the computer you can just use the "transpose" function, because transposing a matrix means to exchange columns and rows with each other. Ok, i implement it again. But i thought, this notice is interesting, coz first i asked myself, "what is which way round ?"

Row-Major Order

This is the array order, the matrices in the physics book use.

0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
    var matrix = new Float32Array(16);

Column-Major Order

This is the order, the WebGL implementation uses, this matrix can be send forward to the gpu as is.

0 4 8 12
1 5 9 13
2 6 10 14
3 7 11 15
    var matrix = new Float32Array(16);

You see, the array (matrix) is instantiated the same way. You simply use other indices. To convert from row-major to column-major or vice versa it is easy to use a function "transpose" to exchange rows and columns.

function transpose(m) {
    var /*m0*/ m1=m[1], m2=m[2], m3=m[3], 
	m4=m[4], /*m5*/ m6=m[6], m7=m[7],
        m8=m[8], m9=m[9], /*m10*/ m11=m[11], 
        m12=m[12],m13=m[13],m14=m[14] /*m15 */;
    // m[0] = m0;
    m[1] = m4;
    m[2] = m8;
    m[3] = m12;
    m[4] = m1;
    // m[5] = m5;
    m[6] = m9;
    m[7] = m13;
    m[8] = m2;
    m[9] = m6;
    // m[10] = m10;
    m[11] = m14;
    m[12] = m3;
    m[13] = m7;
    m[14] = m11;
    // m15 = m[15];
    return m;
}

Of course, that´s the same like the real matrix transpose operation. For our purposes at runtime it´s a convenient thing, to be able to convert at runtime between the row-major and column-major orders.

Nicht schmunzeln

root (Edward Gerhold)

Ich hab im letzten Beispiel das WebGL Beginner´s Guide Framework aus dem Buch benutzt, weil ich keine Lust hatte, die ganze Boilerplate zu wiederholen. Dann ist es etwas unordentlich, weil es eins von vielen editierten Files mit irgendwelchen Änderungen ist, daß sogar Variablen und Funktionen vorkommen, die gar nicht mehr aktuell sind. So wie updateAnimations oder vLight im vertexshader. Anderseits habe ich die gesamte Sache weiter verfolgt und mir meine eigene minimale Datei mit Lichtern und Camera und Matrizen und Shader- und Programloader, Texturen, Offscreenrenderbuffer (für Shadow Maps, Picking und Postprocessing) gebastelt. Der Clou ist aber in Arbeit. Gerade schreibe ich die Cyclone Physics Engine aus dem Buch Game Physics Development schrittweise nieder. Dabei darf ich mir allerhand Hilfsfunktionen schreiben, um die Algorithmen zu übersetzen, da ich gerade (noch) gl-matrix einsetze, wodurch ich kurz davor bin vielleicht eine eigene hinzuzufügen. Three hat auch eine eigene. 

Mat4.multiply = function (dest, m, n) {
// m
var m11 = m[0], m21 = m[1], m31 = m[2], m41 = m[3];
var m12 = m[4], m22 = m[5], m32 = m[6], m42 = m[7];
var m13 = m[8], m23 = m[9], m33 = m[10], m43 = m[11];
var m14 = m[12], m24 = m[13], m34 = m[14], m44 = m[15];
// n
var n11 = n[0], n21 = n[1], n31 = n[2], n41 = n[3];
var n12 = n[4], n22 = n[5], n32 = n[6], n42 = n[7];
var n13 = n[8], n23 = n[9], n33 = n[10], n43 = n[11];
var n14 = n[12], n24 = n[13], n34 = n[14], n44 = n[15];
// 1. Spalte
dest[0] = m11 * n11 + m12 * n21 + m13 * n31 + m14 * n41; // Reihe 1 mit Spalte 1
dest[1] = m21 * n11 + m22 * n21 + m23 * n31 + m24 * n41; // Reihe 2 mit Spalte 1
dest[2] = m31 * n11 + m32 * n21 + m33 * n31 + m34 * n41; // Reihe 3 mit Spalte 1
dest[3] = m41 * n11 + m42 * n21 + m43 * n31 + m44 * n41; // Reihe 4 mit Spalte 1
// 2. Spalte
dest[4] = m11 * n12 + m12 * n22 + m13 * n32 + m14 * n42; // Reihe 1 mit Spalte 2
dest[5] = m21 * n12 + m22 * n22 + m23 * n32 + m24 * n42; // Reihe 2 mit Spalte 2
dest[6] = m31 * n12 + m32 * n22 + m33 * n32 + m34 * n42; // Reihe 3 mit Spalte 2
dest[7] = m41 * n12 + m42 * n22 + m43 * n32 + m44 * n42; // Reihe 4 mit Spalte 2
// 3. Spalte
dest[8] = m11 * n13 + m12 * n23 + m13 * n33 + m14 * n43;
dest[9] = m21 * n13 + m22 * n23 + m23 * n33 + m24 * n43;
dest[10] = m31 * n13 + m32 * n23 + m33 * n33 + m34 * n43;
dest[11] = m41 * n13 + m42 * n23 + m43 * n33 + m44 * n43;
// 4. Spalte
dest[12] = m11 * n14 + m12 * n24 + m13 * n34 + m14 * n44;
dest[13] = m21 * n14 + m22 * n24 + m23 * n34 + m24 * n44;
dest[14] = m31 * n14 + m32 * n24 + m33 * n34 + m34 * n44;
dest[15] = m41 * n14 + m42 * n24 + m43 * n34 + m44 * n44;
return dest;
};

Das Framework aus dem Beginner´s Guide und die aktuelle gl-matrix gehen leider in der syntax auseinander, daß ich das Framework auch nochmal überarbeiten darf. Der letzte Versuch, es sind nur ein paar Operationen mit gl-matrix in den Dateien, schlug fehl, aber der nächste oder übernächste bestimmt nicht. Das Buch ist es wert. Nachdem ich mit Pro PHP MVC meine Homepage neu anfing, kam meine Patte mit Modem weg, dann habe ich meine Strategie geändert, weil der Kopf frei von Homepage war. Mache bald weiter. Besonders freue ich mich auf die neue syntax.js Version mit dem neuem EcmaDraft, aber auch mit grundlegenden Änderungen, die ich aus der verkackten App und Architektur gelernt habe. Allerdings habe ich da schon geübt, Code zu ersetzen, daß es nur anschließt.


var cableLink =
createParticleLink({
particles: [],
maxLength: 1,
restitution: 1,
fillContact: function (contact, limit) {
"use strict";
var curLen = this.currentLength();
var maxLength = this.maxLength;
if (curLen <= maxLength) return 0;
contact.particle[0] = this.particle[0];
contact.particle[1] = this.particle[1];
var normal = vec3.create();
vec3.sub(normal, particle[1].position, particle[0].position);
contact.contactNormal = normal;
contact.penetration = curLen - maxLength;
contact.restitution = this.restitution;
return 1;
}
});

Am meisten freue ich mich gerade über die Physik in Arbeit. Aber das Gesamtkompendium geht noch weiter. Es fehlt noch die künstliche Intelligenz. Nebenher kann ich dann mein Ajax MVC für das Pro PHP MVC fortsetzen. Heute habe ich mal Lust zum posten, statt die ParticleForceGenerator Beispiele aus dem Buch zu zeigen. Bin gerade bei der Kontaktanalyse und das ist aufwändiger und wird mich noch einige Stunden lernen lassen. 

function createBuoyancyForceGenerator (maxDepth, volume, waterHeight, liquidDensity) {
return createForceGenerator({
maxDepth: maxDepth,
volume: volume,
waterHeight: waterHeight,
liquidDensity: liquidDensity !== undefined ? liquidDensity : 1000.0,
updateForce: function (particle, duration) {
"use strict";
var depth = particle.position[1]; // y
if (depth >= this.waterHeight + this.maxDepth) return;
var force = Vec3.create(0, 0, 0);
if (depth <= this.waterHeight - this.maxDepth) {
force[1] = this.liquidDensity * volume; // y
particle.addForce(force);
return;
}
force[1] = this.liquidDensity * volume * (depth - this.maxDepth - waterHeight) / 2 * maxDepth;
particle.addForce(force);
}
});
}

Übrigens, was haltet ihr davon, wenn ich in Zukunft jQuery Mobile für die UI einsetze?  Oder Bootstrap? Ohne die cSS Dateien und die Verhalten dieser Libraries sieht meine Seite nie so gut aus, wie die Durchschnittsseiten von heute, oder? Ich denke mal ja. Der Rest ist Illusion.

Mat3.det = function (m) {
return m[0] * m[4] * m[8] + // aei +
m[3] * m[7] * m[2] + // dhc +
m[6] * m[1] * m[5] - // gbf -
m[0] * m[7] * m[5] - // ahf -
m[5] * m[4] * m[2] - // gec -
m[3] * m[1] * m[8]; // dbi
};
Mat3.invert = function (m) {
var det = Mat3.det(m);
var invDet = 1 / det;
return Mat3.create(
// 1. column
m[4] * m[8] - m[5] * m[7], // ei - fh
m[5] * m[6] - m[3] * m[8], // fg-di
m[3] * m[6] - m[4] * m[5], // dh-eg
// 2. column
m[2] * m[7] - m[1] * m[8], // ch-bi
 m[0] * m[8] - m[2] * m[5], // ai-cg
        m[1] * m[5] - m[0] * m[6], // bg-ah
// 3.column
m[1] * m[5] - m[2] * m[4], // bf-ce
m[2] * m[3] - m[0] * m[5], // cd-af
m[0] * m[4] - m[1] * m[3] // ae-bd
);
}

Für die Rigid Body Engine gibt es 3x3 Matrizen, sogenannte Tensoren, die beeinflußen die Geschwindigkeit, oder die Rotation. Um einen Partikel, der nur Geschwindigkeit kennt, und Beschleunigung, und eine Masse hat, sowie einen Mittelpunkt, auf einen Festkörper zu erweitern, muss man das Rotationsgesetz ergänzen. Das ist wiederum Abhängig vom Kontaktpunkt relativ zum Mittelpunkt und der Penetration. Neu berechnet werden neue Geschwindigkeit und Rotation, natürlich pro Frame oder besser pro Zeitfenster. Das Schwierigste ist die Kontaktobservation, da man nicht einfach jeden mit jedem vergleichen kann. Dazu wird die Welt eingeteilt und nur mögliche Kontakte werden kurz abgetestet, über Bounding Volumes, das heisst, mal kurz in Spehren oder Blöcken verglichen, ob überhaupt eine Kollision möglich ist. Natürlich nur, wenn die Objekte, die sich in der Hierarchie nahe liegen und in möglichen Kontakt kommen können. Interessant sind die Quad-Trees, Oct-Trees, Grids, und die Bounding Volumens, diese Axis und Object Aligned Bounding Boxes, auf jeden Fall. Aber um das an einem Tag zu machen, zu viel. Wenn man dann in möglichem Kontakt ist, gibt man die möglichen Kontakte an die Kontaktauflösung weiter. Falls kollidiert wird, oder man längst in Berührung ist, wird die Kontaktrichtung berechnet, und die Penetrationstiefe, um den richtigen Impuls zurück generieren zu können. Hierbei ist eine lineare und eine anguläre Komponente zu berechnen. Insgesamt bin ich beim Auswendiglernen der Geschichte, aber es ist kompakt noch umfangreich genug, um mich etwas zu beschäftigen. Denn Fertigstellen muss ich die Implementierung natürlich auch. Faszinierend an den Kontakten ist, daß an Kabeln aufgehängte, mit Stangen gebildete Brücken unter dir wackeln, oder Boxen über den Boden rutschen, wenn man sie mit einem schweren Partikel bewirft. Wenn sie nicht direkt im Zentrum getroffen werden, beginnen sie auch, sich zu drehen. Macht Riesenspaß.

Quaternion.toMatrix3 = function (q) {
return Mat3.create(
1 - (2 * y * y + 2 * z * z), 2 * x * y - 2 * z * w, 2 * x * z + 2 * y * w, // 1. column
2 * x * y + 2 * z * w, 1 - (2 * x * x + 2 * z * z), 2 * y * z - 2 * x * w, // 2. column
2 * x * z - 2 * y * w, 2 * y * z + 2 * x * w, 1 - (2 * x * x + 2 * y * y) // 3. column
);
};
Mat3.setOrientation = function (q) {
var r = q[0];
var i = q[1];
var j = q[2];
var k = q[3];
return Mat3.create(
1 - (2 * j * j + 2 * k * k),
2 * i * j - 2 * k * r,
2 * i * k + 2 * j * r,

2 * i * j + 2 * k * r,
1 - (2 * i * i + 2 * k * k),
2 * j * k - 2 * i * r,

2 * i * j + 2 * k * r,
2 * j * k + 2 * i * r,
1 - (2 * i * i + 2 * j * j)
);
};

 

Offline Beschaeftigungen

root (Edward Gerhold)

<canvas id="canvas3dcube4" width="600" height="600">
</canvas>
<select id="getMesh">
<option value="cube">Cube</option>
</select>
<label>Animate<input type="checkbox" id="doAnimate" checked></label>
<script src="/scripts/gl-matrix-min.guide.js"></script>
<script src="/scripts/webgl_beginners_guide.js"></script>
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec4 aVertexColor;
attribute vec3 aVertexNormal;
attribute vec2 aTextureCoord;
varying vec2 vTextureCoord;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
uniform mat4 uNMatrix;
uniform vec4 uMaterialSpecular;
uniform vec4 uMaterialDiffuse;
uniform vec4 uMaterialAmbient;
uniform vec3 uLightPos[2];
varying vec4 vColor;
varying vec3 vNormal[2];
varying vec3 vL[2];
varying vec3 vEye;
varying vec4 vLight;
varying vec4 vertex;
uniform float uPointSize;
varying float v_dist;
void main() {
vertex = uMVMatrix * vec4(aVertexPosition, 1.0);
for (int i = 0; i < 2; i++) {
vNormal[i] = vec3(uNMatrix * vec4(aVertexNormal, 0.0));
vEye = (vertex.xyz/vertex.w);
vL[i] = uLightPos[i] - vEye;
vColor = aVertexColor;
}
vTextureCoord = aTextureCoord;
v_dist = distance(vertex.xyz, vEye.xyz);
gl_Position = uPMatrix * vertex;
// gl_PointSize = uPointSize;
}

</script>
<script id="shader-fs" type="x-shader/x-fragment">
precision highp float;
uniform sampler2D uSampler;
uniform vec4 uLightColor[2];
uniform float uShininess;
varying vec3 vNormal[2];
varying vec4 vColor;
varying vec3 vL[2];
varying vec3 vEye;
varying vec4 vertex;
uniform vec4 uMaterialSpecular;
uniform vec4 uMaterialDiffuse;
uniform vec4 uMaterialAmbient;
varying float v_dist;
vec4 Ia, Id, Is;
float cutOff = 0.4;
vec4 fogColor = vec4(0.1,0.1,0.1,1.0);
vec2 fogDist = vec2(100, -15);
varying vec2 vTextureCoord;
void main() {
vec4 finalColor = vColor;
vec4 lc[2];
lc[0] = vec4(0,0,0,1);
lc[1] = vec4(0,0,0,1);
float fogFactor = clamp((fogDist.y - v_dist) / (fogDist.y - fogDist.x), 0., 1.);
for (int i = 0; i < 2; i++) {
Ia = uMaterialAmbient * uLightColor[i];
vec3 N = normalize(vNormal[i]);
vec3 L = normalize(vL[i]);
float lambert = max(dot(-L, N), 0.0);
if (lambert > 0.0) { // && lambert >= cutOff){
Id = uMaterialDiffuse * uLightColor[i] * lambert;
vec3 R = normalize(reflect(N, L));
vec3 E = normalize(vEye);
float specular = clamp(pow(max(dot(R,E),0.0), uShininess),0.,1.);
if (specular > 0.0 && specular >= cutOff*1.5) {
Is = uMaterialSpecular * uLightColor[i] * specular;
lc[i] *= (Ia + Is + Id);
} else {
lc[i] *= (Ia + Id);
}
}
finalColor += lc[i];
}
finalColor.a = 1.0;
finalColor = finalColor * texture2D(uSampler, vTextureCoord);
gl_FragColor = mix(fogColor, finalColor, fogFactor);
}

</script>
<script>
(function () {


canvas = document.getElementById("canvas3dcube4");
var cube, interactor, transforms, lights, attributeList, uniformList, orbitingCam, wall, sphere, cone, object;
var i = 0;
var doAnimate = true;
var selectedMesh = "cube";
var obj;
var lastTime = Date.now(), currentTime, rate = 1000 / 30;
var app = null;


function configure() {

document.getElementById("getMesh").addEventListener("change", function (e) {
var options = this.querySelectorAll("option");
for (var i = 0, j = options.length; i < j; i++) {
if (options[i].checked) {
selectedMesh = options[i].value;
obj = Scene.getObject(selectedMesh);
}
}
});
document.getElementById("doAnimate").addEventListener("change", function (e) {
doAnimate = this.checked;
if (doAnimate) requestAnimationFrame(render);
});
gl.clearColor(0, 0, 0, 1);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
Lights.add(new Light("left"));
Lights.add(new Light("right"));
Lights.list[0].setPosition([150, 0, 50]);
Lights.list[1].setPosition([-150, 0, -50]);
Lights.list[0].setProperty("color", [0.9, 0.9, 0.9, 1.0]);
Lights.list[1].setProperty("color", [0.5, 0.5, 0.5, 1.0]);
texture = new Texture("js.jpg");
attributeList = [
"aVertexPosition",
"aVertexNormal",
"aVertexColor",
"aTextureCoord"
];
uniformList = [
"uShininess",
"uMVMatrix",
"uPMatrix",
"uNMatrix",
"uLightPos",
"uLightColor",
"uMaterialDiffuse",
"uMaterialAmbient",
"uMaterialSpecular",
"uPointSize",
"uSampler"
];
orbitingCam = new Camera(CAMERA_ORBITING_TYPE);
orbitingCam.goHome([0, 0, 100]);
interactor = new CameraInteractor(orbitingCam, canvas);
transforms = new SceneTransforms(orbitingCam);
Program.load(attributeList, uniformList);
orbitingCam.hookRenderer = render;
transforms.init();
var objTransform = transforms.mvMatrix;
mat4.scale(objTransform, [15, 15, 15])
gl.uniform3fv(Program.uLightPos, Lights.getArray("position"));
gl.uniform4fv(Program.uLightColor, Lights.getArray("color"));
gl.uniform1f(Program.uShininess, 10);
gl.uniform4fv(Program.uMaterialAmbient, [0.1, 0.1, 0.1, 1.0]);
gl.uniform4fv(Program.uMaterialDiffuse, [0.5, 0.5, 0.5, 1.0]);
gl.uniform4fv(Program.uMaterialSpecular, [0.9, 0.9, 0.9, 1.0]);
}

function rand() {
return (Math.random() * 10) / 10;
}

function load() {
Scene.loadObject("/scripts/geometry/complexCube.json", "cube", {
update: function (deltaT) {
var objTransform = transforms.mvMatrix;
mat4.scale(objTransform, [2, 2, 2])
mat4.rotateY(objTransform, Math.PI / 180 * i);
mat4.rotateZ(objTransform, Math.PI / 180 * i);
if (i >= 360) i = 0;
i = i + deltaT / 1000 * rate;
transforms.setMatrixUniforms();
},
draw: function () {
var obj = this;
gl.bindBuffer(gl.ARRAY_BUFFER, obj.cbo);
gl.vertexAttribPointer(Program.aVertexColor, 4, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(Program.aVertexColor);
gl.bindBuffer(gl.ARRAY_BUFFER, obj.vbo);
gl.vertexAttribPointer(Program.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(Program.aVertexPosition);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture.tex);
gl.uniform1i(Program.uSampler, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, obj.tbo);
gl.vertexAttribPointer(Program.aTextureCoord, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(Program.aTextureCoord);
gl.bindBuffer(gl.ARRAY_BUFFER, obj.nbo);
gl.vertexAttribPointer(Program.aVertexNormal, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(Program.aVertexNormal);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, obj.ibo);
gl.drawElements(gl.TRIANGLE_STRIP, obj.indices.length, gl.UNSIGNED_SHORT, 0);
}
});
}

function draw() {
gl.viewport(0, 0, c_width, c_height);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
obj.draw();
}

function updateAnimations(obj, deltaT) {
transforms.push();
transforms.updatePerspective();
obj.update(deltaT);
transforms.pop();
}

function render() {
obj = Scene.getObject(selectedMesh);
currentTime = Date.now();
var deltaT = currentTime - lastTime;
updateAnimations(obj, deltaT);
if (deltaT >= rate) {
if (doAnimate) requestAnimationFrame(draw);
lastTime = currentTime;
}
}

function runWebGLApp() {
app = new WebGLApp("canvas3dcube4");
app.configureGLHook = configure;
app.loadSceneHook = load;
app.drawSceneHook = render;
app.run();
}

window.addEventListener("load", runWebGLApp);

}());
</script>

Circle Collision

root (Edward Gerhold)

<div id="crash"></div>
<canvas width=500 height=500 id="x"></canvas>
<style>
canvas {
background:beige;
}
</style>
<script>
function Sprite(name, painter, behaviors) {
this.name = name;
this.painter = painter;
this.top = 0;
this.left = 0;
this.radius = 0;
this.width = 10;
this.height = 10;
this.velocityX = 0;
this.velocityY = 0;
this.velocityZ = 0;
this.visible = true;
this.animating = false;
this.behaviors = behaviors || [];
this.fillStyle = "green";
this.strokeStyle = "darkgreen";
if (!Array.isArray(this.behaviors)) throw new TypeError("Sprite argument 3 expects Array or undefined");
return this;
}
Sprite.prototype = {
paint: function (context) {
if (this.painter && this.visible) {
this.painter.paint(this, context);
}
},
update: function (context, time) {
for (var i = 0; i < this.behaviors.length; i++) {
this.behaviors[i].execute(this, context, time);
}
},
circlesCollide: function (sprite) {
var vecx = sprite.left - this.left;
var vecy = sprite.top - this.top;
var distance = Math.sqrt(vecx * vecx + vecy * vecy);
var minDistance = this.radius + sprite.radius;
return (distance <= minDistance);
}
};
var ballPainter = {
paint: function (sprite, context) {
context.save();
context.beginPath();
context.fillStyle = sprite.fillStyle;
context.strokeStyle = sprite.strokeStyle;
context.arc(sprite.left, sprite.top, sprite.radius, 0, Math.PI*2, true);
context.fill();
context.stroke();
context.restore();
}
};
var ballMove = {
execute: function (sprite, context, time) {
var deltaX = sprite.velocityX * (time/1000);
var deltaY = sprite.velocityY * (time/1000);
if ((sprite.left + sprite.radius + deltaX > context.canvas.width) ||
(sprite.left + sprite.radius + deltaX < 0)) {
sprite.velocityX = -sprite.velocityX;
deltaX = -deltaX;
}
if ((sprite.top + sprite.radius + deltaY > context.canvas.height) ||
(sprite.top + sprite.radius + deltaY < 0)) {
sprite.velocityY = -sprite.velocityY;
deltaY = -deltaY;
}
sprite.left += deltaX;
sprite.top += deltaY;
}
};
</script>
<script>
window.addEventListener("load", function () {

var canvas = document.getElementById("x");
var context = canvas.getContext("2d");


var ball1 = new Sprite("ball1", ballPainter, [ballMove]);
ball1.top = 0;
ball1.left = 0;
ball1.velocityX = 60;
ball1.velocityY = 60;
ball1.radius = 15;
ball1.fillStyle = "red";
ball1.strokeStyle = "brown";

var ball2 = new Sprite("ball2", ballPainter, [ballMove]);
ball2.top = context.canvas.height-100;
ball2.left = context.canvas.width-100;
ball2.velocityX = 100;
ball2.velocityY = 100;
ball2.radius = 15;
ball2.fillStyle = "yellow";
ball2.strokeStyle = "white";

var collisions = 0;
var frameRate = 1000/60;
var currentTime, lastTime = Date.now(), elapsed;

function anim() {
currentTime = Date.now();
elapsed = currentTime - lastTime;

if (ball1.circlesCollide(ball2)) {
++collisions;
document.getElementById("crash").innerHTML = "ball1 and ball2 collided "+collisions+" times";
ball1.velocityX = -ball1.velocityX;
ball1.velocityY = -ball1.velocityY;
ball2.velocityX = -ball2.velocityX;
ball2.velocityY = -ball2.velocityY;
}
ball1.update(context, elapsed);
ball2.update(context, elapsed);
if (elapsed >= frameRate) {
context.clearRect(0,0,context.canvas.width, context.canvas.height);
ball1.paint(context);
ball2.paint(context);
lastTime = currentTime;
}
requestAnimationFrame(anim);
}
setTimeout(anim, 0);
}, false);
</script>

2D Buch ist gut

root (Edward Gerhold)

<style>
canvas {
background:beige;
color:black;
width:50%;
height:auto;
border-radius: 3em;
box-shadow: 1em 1em black;
}
</style>
<h1>Core HTML5 Canvas</h1>
<p>Is a great and invaluable book.</p>

<canvas id="canvas2d" width=500 height=500>
No Canvas supported.
</canvas>

<br style=clear:both;>
<button id="animateButton">Pause</button>
<!-- <script src="/scripts/stopwatch.js"></script> -->
<script>
function run() {
var canvas = document.getElementById("canvas2d"),
context = canvas.getContext("2d"),
paused = false,
discs = [
{
x: 100,
y: 100,
lastX: 90,
lastY: 90,
velocityX: 10,
velocityY: 2,
radius: 15,
innerColor: "rgba(1,31,1,1)",
outerColor: "rgba(24,24,234, 0)",
middleColor: "rgba(1,31,42,1)",
strokeStyle: "black"

},
{
x: 10,
y: 10,
lastX: 90,
lastY: 43,
velocityX: 3,
velocityY: 23,
radius: 7,
innerColor: "rgba(12,31,41,1)",
outerColor: "rgba(24,24,234, 0)",
middleColor: "rgba(1,231,142,1)",
strokeStyle: "black"

},
{
x: 80,
y: 80,
lastX: 90,
lastY: 90,
velocityX: 3,
velocityY: 2,
radius: 19,
innerColor: "rgba(1,31,1,1)",
outerColor: "rgba(24,124,234, 0)",
middleColor: "rgba(141,31,242,1)",
strokeStyle: "black"

}

],
numDiscs = discs.length,
animateButton = document.getElementById("animateButton");
function drawBackground() {

}


function update() {
var disc = null;
for (var i = 0, j = discs.length; i < j; i++) {

disc = discs[i];
if ((disc.x + disc.velocityX + disc.radius >
context.canvas.width) ||(disc.x + disc.velocityX + disc.radius < 0)) {
disc.velocityX = -disc.velocityX;
}

if ((disc.y + disc.velocityY + disc.radius >
context.canvas.height) ||(disc.y + disc.velocityY + disc.radius < 0)) {
disc.velocityY = -disc.velocityY;
}

disc.x += disc.velocityX;
disc.y += disc.velocityY;
}
}
function draw() {
var disc;
for (var i = 0, j = discs.length; i < j; i++) {
disc = discs[i];
gradient = context.createRadialGradient(disc.x, disc.y, 0, disc.x, disc.y, disc.radius);
gradient.addColorStop(0.3, disc.innerColor);
gradient.addColorStop(0.5, disc.middleColor);
gradient.addColorStop(0.8, disc.outerColor);
context.save();
context.beginPath();
context.arc(disc.x, disc.y, disc.radius, 0, Math.PI*2, false);
context.fillStyle = gradient;
context.strokeStyle = disc.strokeStyle;
context.fill();
context.stroke();
context.closePath();
context.restore();

}
}

function animate() {
if (!paused) {
context.clearRect(0,0, canvas.width, canvas.height);
drawBackground();
update();
draw();
requestAnimationFrame(animate);
}
}
animateButton.onclick = function (e) {
paused = !paused;
if (paused) {
animateButton.value = "Animate";
} else {
requestAnimationFrame(animate);
animateButton.value = "Pause";
}
};
context.font = "48px Helvetica";

animate();
}

window.addEventListener("load", run, false);
</script>

Core HTML5 Canvas

Is a great and invaluable book.

No Canvas supported.

Modem war weg

root (Edward Gerhold)

Habe die nächsten sechs Wochen einen Eurojob und mache unter anderem eine Radiosendung.

Letzte Woche ist meine Brieftasche verloren gegangen oder entwendet worden. Ich hatte noch vor Ort Anzeige erstattet. Im Portemonnaie war mein Modem. Darum bin ich erstmal offline. Mittlerweile, seit gestern, habe ich meine Brieftasche zurück. Ich konnte sie mir in einem Kaufland einen Kilometer vom Ort des Verlusts entfernt abholen. Mit allem Inhalt, ausser meinem Sozi-Ticket, welches ich gottseidank vom Jobcenter schon am Folgetag ersetzt bekam, da man ohne Fahrkarte nicht durch den Berliner Verkehr kommt. Die SIM Karte hatte ich vorsorglich sperren lassen, das muß ich noch wieder rückgängig machen. Jetzt bin ich gerade auf der Arbeit und dachte mir, komm, einen Beitrag kannste dir kurz posten.

Komme bald wieder.

ES6 Draft 27

root (Edward Gerhold)

http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts

August 24, 2014 Draft Rev 27

doc 
pdf, no change markup pdf with rev27 change markup pdf with cumulative change markup since rev22
Changes marked as “Rev 27“. 

Changes include:

  • Fixed scoping bug in for-of, catch clause, comprehensions WRT TDZ for iteration bindings. see bug:https://bugs.ecmascript.org/show_bug.cgi?id=3011
  • For-in/for-of loop bodies can’t contain tail calls because of need to close the iterator on abrupt completion.
  • [[OwnPropertyKeys]] now returns a List rather than an Array
  • Tests of @@isRegExp are now based on ToBoolean(Get(obj, @@isRegExp)
  • Array find/findIndex no longer ignore array element holes.
  • Added %IteratorPrototype% that all built-in iterators inherit from.
  • Destructoring now performs ToObject on values to be destructured.
  • Array and generator comprehensions removed from language
  • Removed specification of Reflect.Realm
  • Object.assign ignores null and undefined source arguments
  • Added 16.1 that lists forbidden extensions.
  • Some editorial cleanup of Function.prototype.toString
  • Removed all explicit poison pill methods on functions (except for Function.prototype) and added 16.1 restrictions on such extensions.
  • String(aSymbol) now returns the symbol’s description. Does not change ToString(symbol) throwing ever where else.
  • aSymbol == Object(aSymbol) now is true, just like other wrapable primitives, see bug:https://bugs.ecmascript.org/show_bug.cgi?id=3066
  • Tighten specification of when Array.prototype.sort is implementation defined
  • Revised @@unscopable semantics as per last TC39 meeting
  • Made it an error to reentrantly enter a generator via next/throw/return.
  • Further work on semantics of Generator return method in the context of yield*
  • The term “neutered” has been replaced with the term “detached”
  • Resolved bugs: 3147, 3142-3140, 3135-3133, 3131, 3128-3124, 3122-3120, 3117, 3115, 3111-3107, 3105, 3103-3098, 3096-3095, 3093-3092, 3090-3089, 3087-3082, 3080-3062, 3060-3016, 3012-3011, 3005, 2987, 2979, 2976, 2831, 2389, 2368, 1908, 523, 516, 513, 407, 91

Previous

Extending Pro PHP MVC

root (Edward Gerhold)

Class Diagram generated by phpStorm
Das mit PHPStorm vor einigen Tagen generierte Klassendiagram.
Class Diagram handdrawn with ArgoUML
Mit ArgoUML vorher  handgemalt, noch voller Fehler.