Switch physics to Box2D.
[featherfall2.git] / src / main.js
index 4f888ba..debe1b5 100644 (file)
@@ -1,8 +1,70 @@
 "use strict";
 
+var b2Vec2 = yf.argcd(
+    function (a) {
+        return b2Vec2.call(this, a[0] || a.x || 0.0, a[1] || a.y || 0.0);
+    },
+    Box2D.b2Vec2
+);
+
+
+yf.ipairs(function (k, v) { Box2D[k] = Box2D[v]; }, {
+    DYNAMIC_BODY: 'b2_dynamicBody'
+});
+
+function accessor (T, p) {
+    if (p[0].toLowerCase() === p[0])
+        return { get: T.prototype['get_' + p],
+                 set: T.prototype['set_' + p] };
+    else
+        return { get: T.prototype['Get' + p],
+                 set: T.prototype['Set' + p] };
+}
+
+yT.defineProperties(Box2D.b2Vec2.prototype, {
+    x: accessor(Box2D.b2Vec2, 'x'),
+    y: accessor(Box2D.b2Vec2, 'y'),
+    0: { alias: 'x' },
+    1: { alias: 'y' },
+});
+
+yT.defineProperties(Box2D.b2BodyDef.prototype, {
+    type: accessor(Box2D.b2BodyDef, 'type'),
+    position: accessor(Box2D.b2BodyDef, 'position'),
+});
+
+yT.defineProperties(Box2D.b2Body.prototype, {
+    position: accessor(Box2D.b2Body, 'Position'),
+    linearVelocity: accessor(Box2D.b2Body, 'LinearVelocity'),
+    angle: accessor(Box2D.b2Body, 'Angle'),
+});
+
+
 var storage;
 
-var PlayerController = new yT(yuu.C, {
+var BodyC = yT(yuu.C, {
+    SLOTS: ['transform'],
+
+    constructor: function (body, size) {
+        this._body = body;
+        this._matrix = mat4.create();
+    },
+
+    position: { alias: '_body.position' },
+    linearVelocity: { alias: '_body.linearVelocity' },
+    ApplyForce: { proxy: '_body.ApplyForce' },
+
+    matrix: { get: function () {
+        var mat = this._matrix;
+        mat4.identity(mat);
+        mat4.rotateZ(mat, mat, this._body.angle);
+        var pos = this._body.position;
+        mat4.translate(mat, mat, [pos.x, pos.y, 0]);
+        return mat;
+    } }
+});
+
+var PlayerController = yT(yuu.C, {
     constructor: function (body, left, right) {
         this.body = body;
         this.left = left;
@@ -13,8 +75,6 @@ var PlayerController = new yT(yuu.C, {
         this.free = 0;
         this.leftPivot = 0;
         this.rightPivot = 0;
-        this.x = this.lastX = body.x;
-        this.y = this.lastY = body.y;
         this.commands = {
             dleftLeft: yuu.propcmd(this, 'dleftLeft'),
             dleftRight: yuu.propcmd(this, 'dleftRight'),
@@ -36,107 +96,117 @@ var PlayerController = new yT(yuu.C, {
     _updateTransforms: function () {
         this.left.yaw = -this.leftPivot * Math.PI / 2;
         this.right.yaw = this.rightPivot * Math.PI / 2;
-        this.body.x = this.x;
-        this.body.y = this.y;
     },
 
     tick: function () {
         this._updatePivots();
-        var GRAVITY = -0.0004;
-        var THRUST = 0.00035;
-        var FRICTIONK = 3.5;
-        var FRICTIONS = 5.0;
+        var THRUST = 3.5;
         var DRAG_FREE = 0.01;
-        var DRAG_OPEN = 5.0;
-        var DRAG_LOCK = 10.0;
-        var CORRECTION = 3.0;
+        var DRAG_OPEN = 0.5;
+        var DRAG_LOCK = 1;
+        var CORRECTION = 1;
 
         var leftAngle = (1 - this.leftPivot) * Math.PI / 2;
         var rightAngle = (1 - this.rightPivot) * Math.PI / 2;
 
-        var vx = this.x - this.lastX;
-        var vy = this.y - this.lastY;
-
         var cleft = Math.cos(leftAngle);
         var cright = Math.cos(rightAngle);
         var sleft = Math.sin(leftAngle);
         var sright = Math.sin(rightAngle); 
 
-        var ax = 0;
-        var ay = GRAVITY;
-
         var thrust = +!this.free * +this.up * THRUST;
-        ax += thrust * (cleft - cright); 
-        ay += thrust * (sright + sleft);
+        var ax = thrust * (cleft - cright); 
+        var ay = thrust * (sright + sleft);
 
+        var v = this.body.linearVelocity;
         var drag = this.up ? DRAG_OPEN : this.free ? DRAG_FREE : DRAG_LOCK;
-        ax += drag * Math.max(cleft, cright) * vx * vx * -Math.sign(vx);
-        ay += drag * (sleft + sright) * vy * vy * -Math.sign(vy);
+        ax += drag * Math.max(cleft, cright) * v.x * v.x * -Math.sign(v.x);
+        ay += drag * (sleft + sright) * v.y * v.y * -Math.sign(v.y);
 
         if (!this.up || this.free)
-            ax += CORRECTION * (cleft - cright) * vy * vy * Math.sign(vy);
-
-        var origX = this.x;
-        var origY = this.y;
-        this.y += vy + ay;
-        var collided = this.y < 0;
-        if (collided) {
-            var friction = -Math.sign(vx) * Math.abs(ay)
-                * (Math.abs(vx) < 0.001 ? FRICTIONS : FRICTIONK);
-            ax += Math.sign(friction) * Math.min(Math.abs(friction), Math.abs(vx));
-        }
-        this.x += vx + ax;
-        this.y = Math.max(0, this.y);
-        this.lastX = origX;
-        this.lastY = origY;
+            ax += CORRECTION * (cleft - cright) * v.y * v.y * Math.sign(v.y);
+
+        this.body.ApplyForce(new b2Vec2(ax, ay), this.body.position);
+
         this._updateTransforms();
     },
 
     TAPS: ['tick'],
 });
 
+function bodyFromAABB (world, position, aabb, density) {
+    var bd = new Box2D.b2BodyDef();
+    var shape = new Box2D.b2PolygonShape();
+    shape.SetAsBox(aabb.w / 2, aabb.h / 2);
+    if (density)
+        bd.type = Box2D.DYNAMIC_BODY;
+    bd.position = new b2Vec2(position[0], position[1]);
+    var body = world.CreateBody(bd);
+    body.CreateFixture(shape, density || 0);
+    return body;
+}
+
+function bodyFromLine (world, p0, p1) {
+    var bd = new Box2D.b2BodyDef();
+    var shape = new Box2D.b2EdgeShape();
+    shape.Set(new b2Vec2(p0), new b2Vec2(p1));
+    var body = world.CreateBody(bd);
+    body.CreateFixture(shape, 0);
+    return body;
+}
+
 var GameScene = yT(yuu.Scene, {
     constructor: function () {
         yuu.Scene.call(this);
 
-        var zoom = 2;
+        var zoom = 10;
         this.layer0.resize(
             zoom * -1.3333333333/2, zoom * -0.2, zoom * 1.3333333333, zoom * 1);
 
+        var world = new Box2D.b2World(new b2Vec2(0, -5));
+
         var body, left, right;
-        this.player = new yuu.E(body = new yuu.Transform()
-                                .setScale([0.081, 0.091, 1]),
-                                new yuu.QuadC('@player')
-                                .setAnchor('bottom')
-                                .setPosition([0, 0]));
+        this.player = new yuu.E(
+            body = new BodyC(bodyFromAABB(
+                world, [0, 5], new yuu.AABB(0.89, 1.0), 1.0)),
+            new yuu.QuadC('@player')
+                .setSize([0.89, 1.0])
+        );
+
         var leftWing = new yuu.E(left = new yuu.Transform()
-                                 .setPosition([-0.3, 0.65, 0])
-                                 .setScale([0.45, 0.22, 1]),
+                                 .setPosition([-0.25, 0.15, 0]),
                                  new yuu.QuadC('@left')
                                  .setZ(-1)
                                  .setAnchor("right")
+                                 .setSize([0.45, 0.22])
                                  .setPosition([0, 0]));
         var rightWing = new yuu.E(right = new yuu.Transform()
-                                  .setPosition([0.3, 0.65, 0])
-                                  .setScale([0.45, 0.22, 1]),
+                                  .setPosition([0.25, 0.15, 0]),
                                   new yuu.QuadC('@right')
                                   .setZ(-1)
                                   .setAnchor('left')
+                                  .setSize([0.45, 0.22])
                                   .setPosition([0, 0]));
         this.player.addChildren(leftWing, rightWing);
         this.entity0.addChild(this.player);
 
-        var ground = new yuu.E(new yuu.Transform()
-                               .setPosition([0, -10, 1])
-                               .setScale([100, 20, 1]),
-                               new yuu.QuadC()
-                               .setColor([0, 0.5, 0, 1]));
+        var ground = new yuu.E(
+            new BodyC(bodyFromLine(world, [-100, 0], [100, 0])),
+            new yuu.QuadC()
+                .setAnchorAtPosition('top')
+                .setSize([100, 1])
+                .setColor([0, 0.5, 0, 1]));
         this.entity0.addChild(ground);
 
         this.player.attach(
             this.controller = new PlayerController(body, left, right));
         Object.assign(this.commands, this.controller.commands);
 
+        this.entity0.attach(new yuu.Ticker(function () {
+            world.Step(1/60, 4, 4);
+            return true;
+        }, 1));
+
         this.ready = yuu.ready([
             new yuu.Material('@player'),
             new yuu.Material('@left'),