Rework me on GitHub

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.