package;
import haxe.Timer;
import js.Browser;
import js.Lib;
import js.html.CanvasElement;
import js.html.CanvasRenderingContext2D;
import js.html.MouseEvent;
class Main {
private
var ctx: CanvasRenderingContext2D;
private
var bodies: Array < RigidBody > ;
private
var isDown: Bool = false;
private
var rays: Array < Ray > ;
private
var testlist: Array < RigidBody > ;
private
var joint: JointConstraint;
private
var MAX_RAY_DIST: Float = 1000;
private
var cam_height: Float = 80;
private
var mo: Dynamic = {
x: 0,
y: 0
};
public
function new() {
var canv = cast(Browser.document.getElementById('canv'), CanvasElement);
ctx = canv.getContext2d();
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, 50, 50);
bodies = new Array < RigidBody > ();
rays = new Array < Ray > ();
testlist = new Array < RigidBody > ();
Canvas.setCanvas(ctx);
var vrtx: Array < Vec2D >= new Array < Vec2D > ();
vrtx.push(new Vec2D(-12, -30));
vrtx.push(new Vec2D(12, -30));
vrtx.push(new Vec2D(20, cam_height));
vrtx.push(new Vec2D(-20, cam_height));
var flash: RigidBody = new RigidBody(new Polygon(vrtx));
bodies.push(flash);
flash.p.x = 100;
flash.p.y = 100;
joint = new JointConstraint(.007, null);
var body: RigidBody = new RigidBody(new Polygon(Polygon.createVertices(3, 100)));
body.p.x = 300;
body.p.y = 300;
body.setAngle(Math.PI / 4);
bodies.push(body);
testlist.push(body);
body = new RigidBody(new Polygon(Polygon.createVertices(4, 60)));
body.p.x = 100;
body.p.y = 300;
body.setAngle(Math.PI / 4);
bodies.push(body);
testlist.push(body);
body = new RigidBody(new Polygon(Polygon.createVertices(5, 40)));
body.p.x = 280;
body.p.y = 150;
body.setAngle(Math.PI / 4);
bodies.push(body);
testlist.push(body);
body = new RigidBody(new Cirlce(50));
body.p.x = 380;
body.p.y = 50;
bodies.push(body);
testlist.push(body);
var o: Vec2D = flash.p;
var pi: Float = Math.PI;
var start: Float = pi / 14;
var step: Float = start / (5 * 2);
for (i in 0...20) {
var ray: Ray = new Ray(o, new Vec2D(Math.sin(start), Math.cos(start)));
start -= step;
rays.push(ray);
}
flash.torque = .1;
var timer = new Timer(2);
timer.run = loop;
canv.addEventListener('mousemove', onMouseMove);
canv.addEventListener('mousedown', onMouseDown);
canv.addEventListener('mouseup', onMouseUp);
}
public
function onMouseUp(e: MouseEvent) {
isDown = false;
}
public
function onMouseDown(e: MouseEvent) {
var m: Vec2D = new Vec2D(mo.x, mo.y);
for (i in 0...bodies.length) {
var bd: RigidBody = bodies[i];
var mat: Matrix2X2 = bd.matrix;
var p: Vec2D = new Vec2D(m.x, m.y);
if (bd.shape.contains(p, bd.p, bd.matrix)) {
joint.ref = p;
joint.body = bd;
isDown = true;
break;
}
}
}
public
function onMouseMove(e: MouseEvent) {
mo = {
x: e.offsetX,
y: e.offsetY
}
}
private
function sortF(a: RigidBody, b: RigidBody): Int {
var ray: Ray = rays[cast(rays.length / 2, Int)];
var dir: Vec2D = ray.dir;
var o: Vec2D = bodies[0].p;
var od: Float = dir.x * o.x + dir.y * o.y;
var ad: Float = (a.p.x * dir.x + a.p.y * dir.y) - od;
var bd: Float = (b.p.x * dir.x + b.p.y * dir.y) - od;
return cast(ad - bd);
}
public
function loop() {
Canvas.clear();
var bdi: RigidBody = bodies[0];
var mat: Matrix2X2 = bdi.matrix;
var x: Float;
var y: Float;
testlist.sort(this.sortF);
for (r in rays) {
var d: Vec2D = r.odir;
x = d.x * mat.c + d.y * mat.d;
y = d.x * mat.a + d.y * mat.b;
r.dir.x = x;
r.dir.y = y;
var bi: Vec2D = new Vec2D();
var ci: Vec2D = new Vec2D();
var LEN: Int = testlist.length;
var i: Int = 0;
var f: Bool = false;
while (i < LEN && f == false) {
var bo: RigidBody = testlist[i];
var t: Float;
if (bo.shape.getType() == ShapeType.POLYGON) {
if (Polygon.testRayPolygon(r.o, r.dir, cast(bo.shape, Polygon), bo.p, bo.matrix)) {
t = cast(r.o.userData, Float);
bi.x = r.dir.x * t;
bi.y = r.dir.y * t;
bi.x += r.o.x;
bi.y += r.o.y;
ci.x = r.o.x + (r.dir.x * cam_height);
ci.y = r.o.y + (r.dir.y * cam_height);
Canvas.drawLine(ci, bi, '#ff0011');
Canvas.drawDot(bi, 1, '#00ffff');
f = true;
break;
}
} else {
if (Ray.testAgainstCircle(r.o, r.dir, cast(bo.shape))) {
t = cast(r.dir.userData);
bi.x = r.dir.x * t;
bi.y = r.dir.y * t;
bi.x += r.o.x;
bi.y += r.o.y;
ci.x = r.o.x + (r.dir.x * cam_height);
ci.y = r.o.y + (r.dir.y * cam_height);
Canvas.drawLine(ci, bi, '#00aaff');
Canvas.drawDot(bi, 1, '#00ffff');
f = true;
break;
}
}
i++;
}
if (f == false) {
bi.x = r.dir.x * MAX_RAY_DIST;
bi.y = r.dir.y * MAX_RAY_DIST;
bi.x += r.o.x;
bi.y += r.o.y;
ci.x = r.o.x + (r.dir.x * cam_height);
ci.y = r.o.y + (r.dir.y * cam_height);
Canvas.drawLine(ci, bi, '#ffffff');
}
}
var m: Vec2D = new Vec2D(mo.x, mo.y);
for (b in bodies) {
Canvas.drawBody(b);
Canvas.drawDot(m);
if (isDown && joint.body == b) {
joint.loc.x = m.x;
joint.loc.y = m.y;
joint.update();
}
b.damping();
b.integrate();
}
}
static
function main() {
new Main();
}
}
class Canvas {
private static
var canvas: CanvasRenderingContext2D;
public static
function setCanvas(g: CanvasRenderingContext2D): Void {
canvas = g;
}
public static
function drawDot(v: Vec2D, t: Float = 1, c: String = '#aa0000', r: Float = 4): Void {
canvas.beginPath();
canvas.strokeStyle = 'white';
canvas.arc(v.x, v.y, r, 0, Math.PI * 2);
canvas.closePath();
canvas.stroke();
}
public static
function drawBody(body: RigidBody): Void {
var sh: IGeometricShape = body.shape;
if (sh.getType() == ShapeType.POLYGON) drawPoly(cast(sh, Polygon), body.p, body.matrix);
else drawCirlce(body.p, cast(sh, Cirlce).r);
}
public static
function clear(): Void {
canvas.fillStyle = 'black';
canvas.clearRect(0, 0, 480, 480);
canvas.fillRect(0, 0, 480, 480);
}
public static
function stroke() {
canvas.stroke();
}
public static
function drawLine(a: Vec2D, b: Vec2D, c: String = '0'): Void {
canvas.beginPath();
canvas.strokeStyle = c + '';
canvas.moveTo(a.x, a.y);
canvas.lineTo(b.x, b.y);
canvas.closePath();
canvas.stroke();
}
public static
function drawCirlce(c: Vec2D, r: Float, cl: String = '#ffffff'): Void {
canvas.beginPath();
canvas.strokeStyle = cl;
canvas.arc(c.x, c.y, r, 0, Math.PI * 2);
canvas.closePath();
canvas.stroke();
}
public static
function drawPoly(poly: Polygon, p: Vec2D, mat: Matrix2X2, c: String = '#eeeeee'): Void {
canvas.beginPath();
var n: Int = poly.vertices.length;
var v: Array < Vec2D >= poly.vertices;
var x: Float;
var y: Float;
var a: Vec2D = new Vec2D(v[0].x, v[0].y);
x = a.x * mat.c + a.y * mat.d;
y = a.x * mat.a + a.y * mat.b;
a.x = x;
a.y = y;
a.x += p.x;
a.y += p.y;
canvas.strokeStyle = c + '';
canvas.moveTo(a.x, a.y);
for (i in 0...n) {
var buf: Vec2D = v[(i + 1) == n ? 0 : i + 1];
a = new Vec2D(buf.x, buf.y);
x = a.x * mat.c + a.y * mat.d;
y = a.x * mat.a + a.y * mat.b;
a.x = x;
a.y = y;
a.x += p.x;
a.y += p.y;
canvas.lineTo(a.x, a.y);
}
canvas.closePath();
canvas.stroke();
}
}
class Polygon implements IGeometricShape {
public
var vertices: Array < Vec2D > ;
private
var transformedVrtx: Array < Vec2D > ;
private
var inertia: Float;
private
var center: Vec2D;
public
function new(vertices: Array < Vec2D > ) {
this.vertices = vertices;
this.calcOtherThings();
}
public
function getTransformedVertices(): Array < Vec2D > {
return null;
}
public static
function testRayPolygon(o: Vec2D, dir: Vec2D, poly: Polygon, pos: Vec2D, mat: Matrix2X2): Bool {
var len: Int = poly.vertices.length;
var v: Array < Vec2D >= poly.vertices;
var c: Int = 0;
var dist: Float = 1000;
for (i in 0...len) {
var bu: Vec2D = v[i];
var q: Vec2D = new Vec2D(bu.x, bu.y);
bu = (i + 1) < len ? v[i + 1] : v[0];
var p: Vec2D = new Vec2D(bu.x, bu.y);
var x: Float;
var y: Float;
var a: Vec2D = q;
x = a.x * mat.c + a.y * mat.d;
y = a.x * mat.a + a.y * mat.b;
a.x = x;
a.y = y;
a.x += pos.x;
a.y += pos.y;
a = p;
x = a.x * mat.c + a.y * mat.d;
y = a.x * mat.a + a.y * mat.b;
a.x = x;
a.y = y;
a.x += pos.x;
a.y += pos.y;
if (testLineIntesection(q, p, o, dir)) {
c++;
dist = Math.min(dist, Math.abs(cast(dir.userData, Float)));
}
}
if (c > 0 && (c & 1) == 0) {
o.userData = dist;
return true;
}
return false;
}
public static
function testLineIntesection(a: Vec2D, b: Vec2D, o: Vec2D, dir: Vec2D): Bool {
var n: Vec2D = new Vec2D(-dir.y, dir.x);
var ab: Vec2D = new Vec2D((b.x - a.x), (b.y - a.y));
var t: Float = ((o.x * n.x + o.y * n.y) - (a.x * n.x + a.y * n.y)) / (ab.x * n.x + ab.y * n.y);
if (t <= 1 && t >= 0) {
var t1: Float = ((a.x + (ab.x * t)) * dir.x + (a.y + (ab.y * t)) * dir.y);
t1 -= o.x * dir.x + o.y * dir.y;
if (t1 > 0) {
dir.userData = t1;
return true;
}
}
return false;
}
public
function contains(p: Vec2D, pos: Vec2D, mat: Matrix2X2): Bool {
var w: Vec2D = new Vec2D(p.x - pos.x, p.y - pos.y);
var x: Float;
var y: Float;
x = w.x * mat.c + w.y * mat.a;
y = w.x * mat.d + w.y * mat.b;
p.x = x;
p.y = y;
var vrtx: Array < Vec2D >= this.vertices;
var n: Int = vrtx.length;
var ab: Vec2D = new Vec2D();
var pp: Vec2D = new Vec2D();
for (i in 0...n) {
var p1: Vec2D = vrtx[i];
var p2: Vec2D = (i + 1) < n ? vrtx[i + 1] : vrtx[0];
var ec: Float = (p2.x - p1.x) * (p.y - p1.y) - (p2.y - p1.y) * (p.x - p1.x);
if (ec < 0) {
return false;
}
}
return true;
}
public
function getType(): UInt {
return ShapeType.POLYGON;
}
public
function calcOtherThings(): Void {
var d: Float = 1;
var center: Vec2D = new Vec2D();
var area: Float = 0;
var I: Float = 0;
var vrtx: Array < Vec2D >= this.vertices;
var n: Int = vrtx.length;
var inv3: Float = 1 / 3;
for (i in 0...n) {
var p1: Vec2D = vrtx[i];
var p2: Vec2D = (i + 1) < n ? vrtx[i + 1] : vrtx[0];
var triArea: Float = .5 * (p1.x * p2.y - p1.y * p2.x);
area += triArea;
center.x += (p1.x + p2.x) * inv3 * triArea;
center.y += (p1.y + p2.y) * inv3 * triArea;
I += triArea * ((p2.x * p2.x + p2.y * p2.y) + (p2.x * p1.x + p2.y * p1.y) + (p1.x * p1.x + p1.y * p1.y));
}
var m: Float = area * d;
this.inertia = I;
var tk: Float = 1 / area;
center.x *= tk;
center.y *= tk;
this.center = center;
}
public
function getMOI(): Float {
return this.inertia;
}
public
function getCenter(): Vec2D {
return this.center;
}
public static
function createVertices(n: Int, r: Float) {
var vrtx: Array < Vec2D >= new Array < Vec2D > ();
var _2PI: Float = Math.PI * 2;
var theta: Float = 0;
for (i in 0...n) {
theta = _2PI * (i / n);
vrtx[i] = new Vec2D(r * Math.cos(theta), r * Math.sin(theta));
}
return vrtx;
}
}
class RigidBody {
public
var a: Vec2D;
public
var p: Vec2D;
public
var v: Vec2D;
public
var alpha: Float;
public
var omega: Float;
public
var theta: Float;
public
var moi: Float;
public
var m: Float;
public
var torque: Float;
public
var shape: IGeometricShape;
public
var matrix: Matrix2X2;
public
function new(shape: IGeometricShape, x: Float = 1, y: Float = 0, mass: Float = .5) {
this.shape = shape;
this.m = mass;
this.p = new Vec2D(x, y);
if (shape.getType() == ShapeType.CIRCLE) {
cast(shape, Cirlce).center = p;
}
this.a = new Vec2D();
this.v = new Vec2D();
this.alpha = 0;
this.omega = 0;
this.theta = 0;
this.torque = 0;
this.moi = shape.getMOI();
matrix = new Matrix2X2(0);
}
public
function setAngle(t: Float): Void {
theta = t;
matrix.setAngle(t);
}
public
function integrate(): Void {
v.x += a.x;
v.y += a.y;
p.x += v.x;
p.y += v.y;
a.x = a.y = 0;
a.x *= 0;
a.y *= 0;
alpha += torque;
omega += alpha;
theta += omega;
torque = 0;
alpha = 0;
if (theta > Math.PI * 2 || theta < -Math.PI * 2) theta = 0;
this.setAngle(theta);
}
public
function damping(): Void {
var k: Float = -.03 / m;
var f: Vec2D = new Vec2D();
a.x += (v.x * k);
a.y += (v.y * k);
this.omega = this.omega * .95;
}
}
class JointConstraint {
public
var k: Float;
public
var body: RigidBody;
public
var loc: Vec2D;
public
var ref: Vec2D;
public
function new(k: Float, body: RigidBody) {
this.k = k;
this.body = body;
this.loc = new Vec2D();
}
public
function isActive(): Bool {
return this.ref != null;
}
public
function update(): Void {
if (isActive()) {
var x: Float;
var y: Float;
var u: Vec2D = new Vec2D(ref.x, ref.y);
var matrix: Matrix2X2 = this.body.matrix;
x = u.x * matrix.c + u.y * matrix.d;
y = u.x * matrix.a + u.y * matrix.b;
u.x = x;
u.y = y;
u.x += body.p.x;
u.y += body.p.y;
Canvas.drawDot(u);
var l: Vec2D = loc;
var f: Vec2D = new Vec2D((l.x - u.x) * k, (l.y - u.y) * k);
var torq: Float = -(f.x * (body.p.y - u.y) - f.y * (body.p.x - u.x));
var alp: Float = torq / (body.moi * .00001 * body.m);
body.a.x += f.x / body.m;
body.a.y += f.y / body.m;
body.alpha = alp;
}
}
}
class Ray {
public
var o: Vec2D;
public
var dir: Vec2D;
public
var odir: Vec2D;
public
function new(o: Vec2D, dir: Vec2D) {
this.o = o;
this.dir = dir;
this.odir = new Vec2D(dir.x, dir.y);
}
public static
function testAgainstCircle(o: Vec2D, dir: Vec2D, circle: Cirlce): Bool {
var cn: Vec2D = circle.getCenter();
var r2: Float = circle.r * circle.r;
var m: Vec2D = new Vec2D(o.x - cn.x, o.y - cn.y);
var b: Float = m.x * dir.x + m.y * dir.y;
var c: Float = (m.x * m.x + m.y * m.y) - r2;
if (c > 0 && b > 0) return false;
var delt: Float = (b * b) - c;
if (delt <= 0) return false;
var t: Float = -b - Math.sqrt(delt);
t = t < 0 ? 0 : t;
dir.userData = t;
return true;
}
}
class Cirlce implements IGeometricShape {
public
var center: Vec2D;
public
var I: Float;
public
var r: Float;
public
function new(r: Float) {
this.r = r;
this.I = (Math.PI / 2) / Math.pow(r, 4);
}
public
function getCenter(): Vec2D {
return this.center;
}
public
function getMOI(): Float {
return this.I;
}
public
function getType(): UInt {
return ShapeType.CIRCLE;
}
public
function contains(p: Vec2D, pos: Vec2D, mat: Matrix2X2): Bool {
var w: Vec2D = new Vec2D(p.x - pos.x, p.y - pos.y);
p.x = w.x;
p.y = w.y;
var o: Bool = ((w.x * w.x + w.y * w.y) <= r * r);
return o;
}
}
class ShapeType {
public static inline
var POLYGON = 1;
public static inline
var CIRCLE = 2;
}
interface IGeometricShape {
function getType(): UInt;
function getMOI(): Float;
function getCenter(): Vec2D;
function contains(p: Vec2D, pos: Vec2D, mat: Matrix2X2): Bool;
}
class Vec2D {
public
var x: Float;
public
var y: Float;
public
var userData: Dynamic;
public
function new(x: Float = 0, y: Float = 0): Void {
this.x = x;
this.y = y;
}
}
class Matrix2X2 {
public
var a: Float;
public
var b: Float;
public
var c: Float;
public
var d: Float;
public
function new(t: Float) {
this.setAngle(t);
}
public
function setAngle(t: Float) {
a = -Math.sin(t);
b = Math.cos(t);
c = b;
d = -a;
}
}