Allow empty attribute syntax to mean "same command as ID".
[featherfall2.git] / src / main.js
index debe1b5..6c1505d 100644 (file)
@@ -12,33 +12,47 @@ 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] };
+function camelizeCase (s) {
+    return s.replace(/_([a-z])/g, function (m) {
+        return m[1].toUpperCase();
+    });
+}
+
+function wrapEmscriptenType (T) {
+    var P = T.prototype;
+    var keys = Object.keys(P);
+    var members = keys.filter(function (k) { return k.startsWith("get_"); });
+    var accessors = keys.filter(function (k) { return k.startsWith("Get"); });
+    members.forEach(function (k) {
+        var name = camelizeCase(k.slice(4));
+        if (!name || name in P) return;
+        Object.defineProperty(P, name, {
+            get: P[k], set: P['s' + k.slice(1)]
+        });
+            
+    });
+    accessors.forEach(function (k) {
+        var name = k.slice(3);
+        name = name[0].toLowerCase() + name.slice(1);
+        if (!name || name in P) return;
+        Object.defineProperty(P, name, {
+            get: P[k], set: P['S' + k.slice(1)]
+        });
+            
+    });
 }
 
+yf.each(wrapEmscriptenType, [
+    Box2D.b2Vec2, Box2D.b2BodyDef, Box2D.b2Body,
+    Box2D.b2JointDef, Box2D.b2RevoluteJointDef,
+    Box2D.b2Joint, Box2D.b2DistanceJoint, Box2D.b2RevoluteJoint,
+]);
+
 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;
 
@@ -46,35 +60,36 @@ var BodyC = yT(yuu.C, {
     SLOTS: ['transform'],
 
     constructor: function (body, size) {
-        this._body = body;
+        this.body = body;
         this._matrix = mat4.create();
     },
 
-    position: { alias: '_body.position' },
-    linearVelocity: { alias: '_body.linearVelocity' },
-    ApplyForce: { proxy: '_body.ApplyForce' },
+    position: { alias: 'body.position' },
+    angle: { alias: 'body.angle' },
+    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;
+        var pos = this.body.position;
         mat4.translate(mat, mat, [pos.x, pos.y, 0]);
+        mat4.rotateZ(mat, mat, this.body.angle);
         return mat;
     } }
 });
 
 var PlayerController = yT(yuu.C, {
-    constructor: function (body, left, right) {
+    constructor: function (body, left, right, leftJoint, rightJoint) {
         this.body = body;
         this.left = left;
         this.right = right;
+        this.leftJoint = leftJoint;
+        this.rightJoint = rightJoint;
         this.dleftLeft = this.dleftRight = 
             this.drightLeft = this.drightRight = 0;
         this.up = 0;
         this.free = 0;
-        this.leftPivot = 0;
-        this.rightPivot = 0;
         this.commands = {
             dleftLeft: yuu.propcmd(this, 'dleftLeft'),
             dleftRight: yuu.propcmd(this, 'dleftRight'),
@@ -86,38 +101,35 @@ var PlayerController = yT(yuu.C, {
     },
 
     _updatePivots: function () {
-        var PIVOT_SPEED = 0.05;
-        var leftSpeed = (this.dleftRight - this.dleftLeft) * PIVOT_SPEED;
-        var rightSpeed = (this.drightLeft - this.drightRight) * PIVOT_SPEED;
-        this.leftPivot = yf.clamp(this.leftPivot + leftSpeed, 0, 1);
-        this.rightPivot = yf.clamp(this.rightPivot + rightSpeed, 0, 1);
+        var PIVOT_SPEED = 2;
+        this.leftJoint.motorSpeed =
+            (this.dleftRight - this.dleftLeft) * PIVOT_SPEED;
+        this.rightJoint.motorSpeed =
+            (this.drightRight - this.drightLeft) * PIVOT_SPEED;
     },
 
     _updateTransforms: function () {
-        this.left.yaw = -this.leftPivot * Math.PI / 2;
-        this.right.yaw = this.rightPivot * Math.PI / 2;
     },
 
     tick: function () {
         this._updatePivots();
         var THRUST = 3.5;
-        var DRAG_FREE = 0.01;
+/*        var DRAG_FREE = 0.01;
         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 cleft = Math.cos(leftAngle);
-        var cright = Math.cos(rightAngle);
-        var sleft = Math.sin(leftAngle);
-        var sright = Math.sin(rightAngle); 
+        var CORRECTION = 1;*/
 
+        var leftAngle = this.leftJoint.GetJointAngle();
+        var rightAngle = this.rightJoint.GetJointAngle();
         var thrust = +!this.free * +this.up * THRUST;
-        var ax = thrust * (cleft - cright); 
-        var ay = thrust * (sright + sleft);
-
+        var leftThrust = new b2Vec2(
+            Math.sin(leftAngle) * thrust, Math.cos(leftAngle) * thrust);
+        var rightThrust = new b2Vec2(
+            Math.sin(rightAngle) * thrust, Math.cos(rightAngle) * thrust);
+        this.body.body.ApplyForceToCenter(leftThrust);
+        this.body.body.ApplyForceToCenter(rightThrust);
+
+        /*
         var v = this.body.linearVelocity;
         var drag = this.up ? DRAG_OPEN : this.free ? DRAG_FREE : DRAG_LOCK;
         ax += drag * Math.max(cleft, cright) * v.x * v.x * -Math.sign(v.x);
@@ -125,8 +137,7 @@ var PlayerController = yT(yuu.C, {
 
         if (!this.up || this.free)
             ax += CORRECTION * (cleft - cright) * v.y * v.y * Math.sign(v.y);
-
-        this.body.ApplyForce(new b2Vec2(ax, ay), this.body.position);
+        */
 
         this._updateTransforms();
     },
@@ -134,27 +145,39 @@ var PlayerController = yT(yuu.C, {
     TAPS: ['tick'],
 });
 
-function bodyFromAABB (world, position, aabb, density) {
-    var bd = new Box2D.b2BodyDef();
+function bodyFromAABB (world, position, aabb, density, center) {
+    var dfn = 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);
+    if (center)
+        shape.SetAsBox(aabb.hw, aabb.hh, new b2Vec2(center), 0);
+    else
+        shape.SetAsBox(aabb.hw, aabb.hh);
+    if (density !== undefined)
+        dfn.type = Box2D.DYNAMIC_BODY;
+    dfn.position = new b2Vec2(position[0], position[1]);
+    var body = world.CreateBody(dfn);
+    body.CreateFixture(shape, density || 0.0001);
     return body;
 }
 
 function bodyFromLine (world, p0, p1) {
-    var bd = new Box2D.b2BodyDef();
+    var dfn = new Box2D.b2BodyDef();
     var shape = new Box2D.b2EdgeShape();
     shape.Set(new b2Vec2(p0), new b2Vec2(p1));
-    var body = world.CreateBody(bd);
+    var body = world.CreateBody(dfn);
     body.CreateFixture(shape, 0);
     return body;
 }
 
+function pinJoint (world, bodyA, bodyB, anchor) {
+    var dfn = new Box2D.b2RevoluteJointDef();
+    dfn.Initialize(bodyA, bodyB, new b2Vec2(anchor));
+    dfn.maxMotorTorque = 100.0;
+    dfn.motorSpeed = 0.1;
+    dfn.enableMotor = true;
+    return Box2D.castObject(world.CreateJoint(dfn), Box2D.b2RevoluteJoint);
+}
+
 var GameScene = yT(yuu.Scene, {
     constructor: function () {
         yuu.Scene.call(this);
@@ -173,20 +196,21 @@ var GameScene = yT(yuu.Scene, {
                 .setSize([0.89, 1.0])
         );
 
-        var leftWing = new yuu.E(left = new yuu.Transform()
-                                 .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.25, 0.15, 0]),
-                                  new yuu.QuadC('@right')
-                                  .setZ(-1)
-                                  .setAnchor('left')
-                                  .setSize([0.45, 0.22])
-                                  .setPosition([0, 0]));
+        var leftWing = new yuu.E(
+            left = new BodyC(bodyFromAABB(
+                     world, [-0.275, 5.15], new yuu.AABB(0.45, 0.22), 0,
+                [-0.45/2, 0.0])),
+            new yuu.QuadC('@left')
+                .setAnchorAtPosition("right")
+                .setZ(-1)
+                .setSize([0.45, 0.22]));
+        var leftJoint = pinJoint(world, left.body, body.body, [0.1, 5.15]);
+        var rightWing = new yuu.E(right = new BodyC(
+            bodyFromAABB(world, [0.50, 5.15], new yuu.AABB(0.45, 0.22), 0)),
+            new yuu.QuadC('@right')
+                .setZ(-1)
+                .setSize([0.45, 0.22]));
+        var rightJoint = pinJoint(world, right.body, body.body, [0.1, 5.15]);
         this.player.addChildren(leftWing, rightWing);
         this.entity0.addChild(this.player);
 
@@ -199,11 +223,12 @@ var GameScene = yT(yuu.Scene, {
         this.entity0.addChild(ground);
 
         this.player.attach(
-            this.controller = new PlayerController(body, left, right));
+            this.controller = new PlayerController(body, left, right,
+                                                   leftJoint, rightJoint));
         Object.assign(this.commands, this.controller.commands);
 
         this.entity0.attach(new yuu.Ticker(function () {
-            world.Step(1/60, 4, 4);
+            world.Step(1/60, 8, 8);
             return true;
         }, 1));