Kategorien
JavaScript & jQuery Programmierung

WebDev für Fortgeschrittene: Mit Oimo.js eine coole WebGL Babylon.js Demo bauen und dabei Kollisionen & Physik verstehen lernen

Heute steigen wir in die Grundlagen von Kollisionen, Physik & Collider ein. Dafür spielen wir ein wenig mit der WebGL-basierenden Engine babylon.js und nutzen begleitend dazu die Physik-Engine namens oimo.js.

oimobabylonteaser_DE

So sieht die Demo aus, die wir gemeinsam bauen werden: Babylon.js Espilit Physik Demo mit Oimo.js. Cool, oder?

Die Demo läuft in allen WebGL-kompatiblen Browsern, wie IE11, Firefox, Chrome, Opera Safari 8 oder wahlweise unter Nutzung von Microsoft Edge in Windows 10 Technical Preview. Innerhalb der Demo kann man sich wie in einem Ego-Shooter-Spiel bewegen. Drücken Sie die „s„-Taste, um ein paar Kugeln in die Szenerie zu werfen und die b„-Taste, um ein paar Kisten fliegen zu lassen. Mit der linken Maustaste kann man die Objekte zudem in Bewegung setzen.

Kollisionen verstehen

Im Wikipedia-Eintrag zum Thema Kollisionserkennung kann man unter anderem Folgendes lesen:

„Als Kollisionserkennung oder Kollisionsabfrage wird in der Algorithmischen Geometrie das Erkennen des Berührens oder Überlappens zweier oder mehrerer geometrischer (starrer oder deformierbarer) Objekte im zwei- oder dreidimensionalen Raum verstanden. Einer Kollisionserkennung folgt die Kollisionsantwort oder Kollisionsbehandlung, wodurch eine Reaktion auf die Kollision eingeleitet wird. Kollisionserkennungsmethoden werden beispielsweise bei der Bildgenerierung von Animationsfilmen, in physikalischen Simulationen, bei Computerspielen, zur Pfadplanung in der Robotik oder bei Haptics eingesetzt. (…) Das Erkennen einer Kollision löst die Kollisionsantwort aus (siehe auch Physik-Engine, Ragdoll-Engine). (…) Eine exakte Kollisionserkennung kann sehr hohen Rechenaufwand verursachen, weshalb bei einer großer Anzahl von Objekten auf effiziente approximative Algorithmen zurückgegriffen werden muss.

Die Wikipedia-Definition hebt außerdem hervor, dass Systeme zur Kollisionserkennung auch Zeitpunkt und Dauer eines Aufpralls und mehrfache Kontaktpunkte berechnen können. Zudem seien für die Lösung von Aufgaben der Kollisionserkennung vielfältige Konzepte aus der linearen Algebra und der Algorithmischen Geometrie erforderlich.

Jetzt wird es aber Zeit, die graue Theorie zu verlassen und in eine coole 3D-Landschaft einzutauchen, die unser Ausgangspunkt für dieses Tutorial ist.

In diesem tollen Museum kann man sich wie in der realen Welt bewegen. Keiner fällt durch den Fußboden, niemand läuft durch die Wände oder fliegt durch den Raum. Wir simulieren Schwerkraft. Das alles erscheint einleuchtend und normal, erfordert aber eine Menge Rechenleistung, um es in einer virtuellen 3D-Welt abzubilden. Wenn wir über Kollisionserkennung nachdenken, sollten wir zunächst die Frage klären, wie komplex diese sein soll. Man benötigt schon eine anständige Prozessorleistung, um zu prüfen, ob zwei Objekte oder Strukturen aufeinanderprallen. Das gilt umso mehr für eine JavaScript-Engine, bei der es eine komplexe Aufgabe ist, dies außerhalb des UI Thread umzusetzen.

Um besser zu verstehen, wie wir diese Komplexität in den Griff bekommen, bewegen wir uns innerhalb des Espilit-Museums in die Nähe des Schreibtisches:

Am Tisch kommt man einfach nicht vorbei, obwohl auf der rechten Seite anscheinend Platz genug ist. Ist unser Kollisionsalgorithmus fehlerhaft? Nein. (Babylon.js hat natürlich keine Bugs! ;-) ) Der Grund ist vielmehr, dass Michel Rousseau, der 3D-Künstler, der diese Szenerie erschuf, das genau so wollte. Für die Vereinfachung der Kollisionserkennung nutzte er hier einen speziellen „Collider“.

Was ist ein Collider?

Statt die Kollisionen mit den vollständigen Objektstrukturen zu testen, werden die Objekte in einfache, unsichtbare Geometrien verpackt. Diese Collider stehen vereinfachend für die Gesamtstruktur und werden von der Kollisions-Engine zur Berechnung genutzt. Meistens wird man kaum einen Unterschied bemerken und es spart jede Menge Prozessorleistung, da die dahinter liegende Mathematik viel weniger komplex ist.

Jede Engine unterstützt mindestens zwei Arten von Collider: den Quader oder Würfel und die Kugel. Dieses Bild zeigt es sehr deutlich:


Quelle: Computer Visualization, Ray Tracing, Video Games, Replacement of Bounding Boxes

Diese Ente gilt es darzustellen, aber statt mögliche Kollisionen aus allen erdenklichen Winkeln zu berechnen, können wir sie in den bestmöglichen Collider verpacken. In diesem Fall scheint ein Quader besser geeignet zu sein als eine Kugel, das hängt aber letztlich von der Gitterstruktur des einzelnen Objekts ab.

Nun zurück zur Espilit-Szenerie, in der nun die unsichtbare Geometrie hier halb-transparent in Rot dargestellt ist:

Jetzt wird klar, warum man an diesem Tisch nicht rechts vorbei kann: Man stößt mit diesem Quader zusammen (oder zumindest die Babylon.js-Kamera tut es). Es wäre jedoch kein Problem, die Breite des Quaders auf die Breite des Tisches zu verkleinern.

Übrigens: Wer Lust hat Babylon.js ernsthaft zu erlernen, kann unseren kostenlosen Kurs an der Microsoft Virtual Academy (MVA) besuchen. So kann man zum Beispiel direkt in die „Introduction to WebGL 3D with HTML5 and Babylon.js – Using Babylon.js for Beginners“ einsteigen. Auch der Code unserer interaktiven Spielwiese kann begutachtet werden: Babylon.js playground – Collisions sample.

Abhängig davon wie komplex die Kollisionen sind und welche Art von Physik-Engine verwendet wird, sind auch weitere Collider möglich: Kapseln und Gitternetze zum Beispiel.

Quelle: Getting Started with Unity – Colliders & UnityScript

Kapseln sind gut geeignet für die Darstellung von Menschen oder menschenähnlichen Figuren, da sie eher dieser Form entsprechen als ein Quader oder eine Kugel. Gitter werden in der Regel nie die vollständige Struktur abbilden, sondern nur eine vereinfachte Version, trotzdem sind sie weitaus genauer als Quader, Kugel oder Kapsel.

Die Startszene laden

Um die Espilit-Landschaft zu laden, gibt es zwei Möglichkeiten:

Option 1: Herunterladen von unserem GitHub Repository und dann im Modul „Introduction to WebGL 3D with HTML5 and Babylon.js – Loading Assets“ unseres MVA Kurses sehen, wie man eine .babylon Szene lädt. Prinzipiell muss man die Assets und die Babylon.js Engine auf einen Web Server spielen und die entsprechenden MIME-Typen für die .babylon Erweiterung festlegen.

Option 2: Herunterladen dieser vorgefertigten Visual Studio-Lösung. (.zip file).

Hinweis: Wer mit Visual Studio noch nicht so viel anzufangen weiß, dieser Artikel hilft weiter: Web developers, Visual Studio could be a great free tool to develop with. Übrigens: Die Pro-Version ist derzeit kostenlos für eine Reihe von Szenarien erhältlich. Sie heißt Visual Studio 2013 Community Edition.

Wer keine Lust hat, mit Visual Studio zu arbeiten, kann natürlich einfach weiter diesem Tutorial folgen. Hier kommt der Code, um die Szene zu laden. Wie gesagt, die meisten Browser unterstützen heutzutage WebGL, auch beim Mac lohnt es sich, die Kompatibilität mit dem Internet Explorer zu testen.

/// 
var engine;
var canvas;
var scene;
document.addEventListener("DOMContentLoaded", startGame, false);
function startGame() {
    if (BABYLON.Engine.isSupported()) {
        canvas = document.getElementById("renderCanvas");
        engine = new BABYLON.Engine(canvas, true);
        BABYLON.SceneLoader.Load("Espilit/", "Espilit.babylon", engine, function (loadedScene) {
            scene = loadedScene;
   
            // Wait for textures and shaders to be ready
            scene.executeWhenReady(function () {
                // Attach camera to canvas inputs
                scene.activeCamera.attachControl(canvas);
                
                // Once the scene is loaded, just register a render loop to render it
                engine.runRenderLoop(function () {
                    scene.render();
                });
            });
        }, function (progress) {
            // To do: give progress feedback to user
        });
    }
}

Mithilfe dieses Tutorials kann man von der in Babylon.js eingebetteten Kollisions-Engine in jedem Fall profitieren. Zumal sich diese Engine von einer Physik-Engine unterscheidet. Die Kollisions-Engine ist hauptsächlich dafür da, dass die Kamera mit der Umgebung in der Szene interagiert. Man kann die Schwerkraft an der Kamera an- oder ausstellen und man kann die Option „checkCollision“ an der Kamera aktivieren bezüglich der verschiedenen Strukturen. Die Kollisions-Engine teilt auch mit, ob zwei Strukturen aufeinanderprallen. Und das ist schon alles (naja, eigentlich ist es ganz schön viel). Die Kollisions-Engine löst weder Aktionen, Stöße noch Impulse aus, falls zwei Babylon.js Objekte zusammenstoßen. Erst eine Physik-Engine bringt die Objekte in Bewegung.

Wir haben diese Physik mittels eines Plugins in Babylon.js integriert. Mehr Informationen dazu gibt es hier: Adding your own physics engine plugin to babylon.js. Wir unterstützen zwei Open Source Physik-Engines: cannon.js und oimo.js. Oimo ist jetzt die bevorzugte, voreingestellte Physik-Engine.

Wer oben Option 1 gewählt hat, um die Szenerie zu laden, muss jetzt von unserem GitHub Oimo.js herunterladen. Es gibt dort die aktualisierte Version, die Babylon.js noch besser unterstützt. Wer Option 2 gewählt hat, findet in der VS-Lösung die bereits referenzierte Fassung im Scripts-Ordner.

Physik-Unterstützung aktivieren & Collider in physikalische Simulationen umwandeln

Als Erstes sollte man die Physik in der Szene aktivieren. Dazu einfach diesen Code einfügen:

scene.enablePhysics(new BABYLON.Vector3(0, -10, 0), new BABYLON.OimoJSPlugin());
//scene.enablePhysics(new BABYLON.Vector3(0, -10, 0), new BABYLON.CannonJSPlugin());

Man legt damit den Grad der Schwerkraft fest (-10 auf der Y-Achse in diesem Beispiel-Code, was ungefähr der Realität auf der Erde entspricht) und bestimmt die Physik-Engine, die genutzt werden soll. Wir greifen auf Oimo.js zurück, aber die kommentierte Zeile zeigt, wie man auch cannon.js einbinden kann.

Jetzt wiederholen wir das für alle nicht sichtbaren Collider, welche die Kollisions-Engine verwendet und aktivieren entsprechend die physikalischen Eigenschaften. Dafür einfach alle Strukturen suchen, bei denen „checkCollisions“ auf „true“ gesetzt sind, die aber in der Szene nicht sichtbar sind:

for (var i = 1; i < scene.meshes.length; i++) {
    if (scene.meshes[i].checkCollisions && scene.meshes[i].isVisible === false) {
        scene.meshes[i].setPhysicsState(BABYLON.PhysicsEngine.BoxImpostor, { mass: 0, 
                                        friction: 0.5, restitution: 0.7 });
        meshesColliderList.push(scene.meshes[i]);
    }
}

Bitte auch die meshesColliderList angeben:

var meshesColliderList = [];

Und das war es auch schon! Jetzt aber schnell ein paar Objekte in den Raum werfen und ein bisschen Chaos in dieses schöne – aber doch etwas sehr aufgeräumte – Museum bringen.

Kugeln & Quader mit physikalischen Zuständen erzeugen

Jetzt gehen wir daran, der Szene einige Kugeln (mit einer Amiga-Textur) und Kisten (also Quader, hier mit einer Holzstruktur) hinzuzufügen. Die physikalischen Eigenschaften dieser Strukturen sind festgelegt. Das heißt zum Beispiel, dass sie abprallen, wenn sie in die Luft geworfen werden und dann auf den Boden treffen oder voneinander wegspringen, wenn zwei Objekte miteinander kollidieren. Die Physik-Engine muss dafür wissen, welche Collider für die Strukturen angewendet werden sollen (Fläche, Kugel oder Quader), welche Masse und Reibung sie besitzen.

Für die Option 1 kann man hier zwei Texturen herunterladen: physicsassets.zip

Zudem muss dieser Code dem Projekt hinzugefügt werden:

function CreateMaterials() {
    materialAmiga = new BABYLON.StandardMaterial("amiga", scene);
    materialAmiga.diffuseTexture = new BABYLON.Texture("assets/amiga.jpg", scene);
    materialAmiga.emissiveColor = new BABYLON.Color3(0.5, 0.5, 0.5);
    materialAmiga.diffuseTexture.uScale = 5;
    materialAmiga.diffuseTexture.vScale = 5;
    materialWood = new BABYLON.StandardMaterial("wood", scene);
    materialWood.diffuseTexture = new BABYLON.Texture("assets/wood.jpg", scene);
    materialWood.emissiveColor = new BABYLON.Color3(0.5, 0.5, 0.5);
}
function addListeners() {
    window.addEventListener("keydown", function (evt) {
        // s for sphere
        if (evt.keyCode == 83) {
            for (var index = 0; index < 25; index++) {
                var sphere = BABYLON.Mesh.CreateSphere("Sphere0", 10, 0.5, scene);
                sphere.material = materialAmiga;
                sphere.position = new BABYLON.Vector3(0 + index / 10, 3, 5 + index / 10);
                sphere.setPhysicsState(BABYLON.PhysicsEngine.SphereImpostor, { mass: 1 });
            }
        }
        // b for box
        if (evt.keyCode == 66) {
            for (var index = 0; index < 10; index++) {
                var box0 = BABYLON.Mesh.CreateBox("Box0", 0.5, scene);
                box0.position = new BABYLON.Vector3(0 + index / 5, 3, 5 + index / 5);
                box0.material = materialWood;
                box0.setPhysicsState(BABYLON.PhysicsEngine.BoxImpostor, { mass: 4 });
            }
        }
    });
}

Deutlich wird hier, dass die Kisten viermal schwerer als die Kugeln sind.

Tipp: Wer besser verstehen will, wie unterschiedliche Materialien in Babylon.js agieren, schaut am besten dieses Modul an: Introduction to WebGL 3D with HTML5 and Babylon.js – Understanding Materials and Inputs oder probiert es gleich selbst mit unserem Online-Muster aus: Babylon.js Playground – Materials sample

Diese zwei Zeilen Code werden dann nach der Zeile scene.enablePhysics eingefügt:

CreateMaterials();
addListeners();

Dann das Web-Projekt starten, in die Mitte unseres Museums gehen und die S- und B-Tasten der Tastatur drücken. Dieser Spaß sollte daraufhin zu sehen sein:

Auswahlfunktion den Strukturen hinzufügen

Auch ein cooles Feature: die Möglichkeit, auf ein Objekt zu klicken und es wegzuwerfen. Dafür muss ein Strahl von den 2D-Koordinaten der Maus innerhalb der Szene gesendet und überprüft werden, ob dieser Strahl eine bestimmte Struktur trifft, und wenn ja, muss dann ein Impuls ausgelöst werden, um das Objekt zu bewegen.

Tipp: Wer mehr dazu erfahren will, sieht sich am besten dieses MVA-Modul an: Introduction to WebGL 3D with HTML5 and Babylon.js – Advanced Features oder probiert es gleich selbst mit unserem Online-Beispiel aus: Babylon.js Playground – Picking sample.

Der folgende Code wird in die addListeners() Funktion eingefügt:

canvas.addEventListener("mousedown", function (evt) {
    var pickResult = scene.pick(evt.clientX, evt.clientY, function (mesh) {
        if (mesh.name.indexOf("Sphere0") !== -1 || mesh.name.indexOf("Box0") !== -1) {
            return true;
        }
        return false;
    });
    if (pickResult.hit) {
        var dir = pickResult.pickedPoint.subtract(scene.activeCamera.position);
        dir.normalize();
        pickResult.pickedMesh.applyImpulse(dir.scale(1), pickResult.pickedPoint);
    }
});

Dann den Code im bevorzugten Browser starten. Jetzt kann man auf die physikalischen Gitterstrukturen klicken und alles ausprobieren.

Collider anzeigen um das Ganze noch besser zu verstehen

Abschließend erstellen wir eine Debug Szene, die es uns ermöglicht, die Collider anzuzeigen oder zu verbergen, sowie die physikalischen Eigenschaften zu aktivieren/deaktivieren.

Dazu fügen wir die UI in diese div ein:

<div id="lcContainer"></div>

Außerdem nutzen wir diese Funktion, um die UI zu bearbeiten:

function CreateCollidersHTMLList() {
    var listColliders = document.getElementById("listColliders");
    for (var j = 0; j < meshesColliderList.length; j++) {
        var newLi = document.createElement("li");
        var chkVisibility = document.createElement('input');
        chkVisibility.type = "checkbox";
        chkVisibility.name = meshesColliderList[j].name;
        chkVisibility.id = "colvis" + j;
        var chkPhysics = document.createElement('input');
        chkPhysics.type = "checkbox";
        chkPhysics.name = meshesColliderList[j].name;
        chkPhysics.id = "colphysx" + j;
        (function (j) {
            chkVisibility.addEventListener(
             "click",
             function (event) {
                 onChangeVisibility(j, event);
             },
             false
           );
            chkPhysics.addEventListener(
            "click",
            function (event) {
                onChangePhysics(j, event);
            },
            false
            );
        })(j)
        newLi.textContent = meshesColliderList[j].name + " visibility/physx ";
        newLi.appendChild(chkVisibility);
        newLi.appendChild(chkPhysics);
        listColliders.appendChild(newLi);
    }
    function onChangeVisibility(id, event) {
        if (!meshesColliderList[id].isVisible) {
            meshesColliderList[id].isVisible = true;
            meshesColliderList[id].material.alpha = 0.75;
            meshesColliderList[id].material.ambientColor.r = 1;
        }
        else {
            meshesColliderList[id].isVisible = false;
        }
    }
    function onChangePhysics(id, event) {
        if (!meshesColliderList[id].checkCollisions) {
            meshesColliderList[id].checkCollisions = true;
            meshesColliderList[id].setPhysicsState(BABYLON.PhysicsEngine.BoxImpostor, { mass: 0, 
                                                   friction: 0.5, restitution: 0.7 });
        }
        else {
            meshesColliderList[id].checkCollisions = false;
            meshesColliderList[id].setPhysicsState(BABYLON.PhysicsEngine.NoImpostor);
        }
    }
}

Okay, ich gebe es zu, die entstandene UI gewinnt keinen Schönheitswettbewerb. Aber ich war ehrlich gesagt zu faul, um darauf mehr Zeit zu verwenden. Wer will, kann es besser machen! :-P

Jetzt die neue Funktion aufrufen und das Web-Projekt starten. Dann zum Beispiel die Collider 12 & 17 anzeigen:

Mit dem zweiten Kontrollkästchen können zudem die physikalischen Eigenschaften aktiviert/deaktiviert werden. Wer sie zum Beispiel beim Collider 12 deaktiviert und die Kugeln losschickt, bei dem fliegen sie plötzlich durch die Wand. Im folgenden Screenshot ist das bei der rot umrandeten Kugel gut zu sehen:

Wer direkt im Browser dieses Debugging-Beispiel ein wenig austesten will, los geht´s: babylon.js Espilit Physics debug demo.

Sehr zu empfehlen ist auch diese fantastische Demo von Samuel Girardin, die ebenfalls Oimo.js benutzt, hier zusammen mit ein paar echt lustigen Charakterköpfen.

Wie Sie eine 3D-Szene auf einem Windows Phone 8.1 mit WebGL und Cordova erstellen, erfahren Sie in diesem Blogbeitrag des Microsoft Open Technologies Blog.

Ich hoffe, das Tutorial hat Spaß gemacht! Kommentare via meinem Twitter Account http://twitter.com/davrous sind sehr willkommen.

Dieser Artikel ist Teil der Web Dev Tech Series von Microsoft. Wir freuen uns, Microsoft Edge und seine neue EdgeHTML rendering engine mit euch zu teilen. Kostenlose Virtual Machines oder Remote Testings für Mac, iOS, Android oder Windows gibt es hier:dev.modern.IE.

(dpe)