├── .gitattributes
├── .gitignore
├── LICENSE.md
├── README.md
├── adv.txt
├── advzip.exe
├── book
└── public_html
│ ├── Circle.js
│ ├── Circle_collision.js
│ ├── CollisionInfo.js
│ ├── Core.js
│ ├── MyGame.js
│ ├── Physics.js
│ ├── Rectangle.js
│ ├── Rectangle_collision.js
│ ├── RigidShape.js
│ ├── UserControl.js
│ ├── Vec2.js
│ ├── all.js
│ ├── all.min.js
│ ├── all.min.zip
│ └── index.html
├── index.html
├── index.min.js
├── index.min.zip
├── js1k
├── index.html
├── index.min.html
└── index.min.pack.html
└── min
└── engine.min.html
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows thumbnail cache files
2 | Thumbs.db
3 | ehthumbs.db
4 | ehthumbs_vista.db
5 |
6 | # Folder config file
7 | Desktop.ini
8 |
9 | # Recycle Bin used on file shares
10 | $RECYCLE.BIN/
11 |
12 | # Windows Installer files
13 | *.cab
14 | *.msi
15 | *.msm
16 | *.msp
17 |
18 | # Windows shortcuts
19 | *.lnk
20 |
21 | # =========================
22 | # Operating System Files
23 | # =========================
24 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | public domain
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Mini 2D Physics
2 | =====
3 |
4 | Mini 2D Physics is a minimalistic physics engine, designed for codegulf competitions such as js13kgames.
5 |
6 | More info on https://xem.github.io/codegolf/mini2Dphysics.html
7 |
8 | How it works
9 | -----
10 |
11 | The code here assumes that you use squares and circles in a 2D world under the influence of gravity.
12 | It features collision detection and respects reciprocal forces between objects.
13 |
14 | Each frame is drawn after the canvas element got cleared.
15 |
16 | mini2Dphysics in the wild
17 | -----
18 |
19 | If you use this code here, please fill a Pull Request to add your project to this list.
20 | I can't wait to see how you used it!
21 |
22 | * [Combat Scorched Earth from Outer Space][combat]
23 | * [Fresh Fruit Feeder][fff]
24 | * [O HIII BAD SKATEPARK][ohiii]
25 | * Your project name and a link to it here
26 |
27 | License
28 | -----
29 |
30 | This code is released into the Public Domain.
31 |
32 | [combat]: https://code.jaenis.ch/js13kgames/js13kgames-2021/src/branch/combat-scorched-earth-from-outer-space/journal/2021-08-14.md
33 | [fff]: https://feed-fresh-fruit.onrender.com
34 | [ohiii]: https://js13kgames.com/2024/games/o-hiii-bad-skatepark
35 |
--------------------------------------------------------------------------------
/adv.txt:
--------------------------------------------------------------------------------
1 | .\advzip.exe -z -4 .\index.min.zip
--------------------------------------------------------------------------------
/advzip.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xem/mini2Dphysics/d3d986643641662e302f431459707b2c972a9fd0/advzip.exe
--------------------------------------------------------------------------------
/book/public_html/Circle.js:
--------------------------------------------------------------------------------
1 | /*
2 | * File:Circle.js
3 | * define a circle
4 | *
5 | */
6 | /*jslint node: true, vars: true, evil: true, bitwise: true */
7 | "use strict";
8 | /* global RigidShape */
9 |
10 | var Circle = function (center, radius, mass, friction, restitution) {
11 | RigidShape.call(this, center, mass, friction, restitution);
12 | this.mType = "Circle";
13 | this.mRadius = radius;
14 | this.mBoundRadius = radius;
15 | //The start point of line in circle
16 | this.mStartpoint = new Vec2(center.x, center.y - radius);
17 | this.updateInertia();
18 | };
19 |
20 | var prototype = Object.create(RigidShape.prototype);
21 | prototype.constructor = Circle;
22 | Circle.prototype = prototype;
23 |
24 | Circle.prototype.move = function (s) {
25 | this.mStartpoint = this.mStartpoint.add(s);
26 | this.mCenter = this.mCenter.add(s);
27 | return this;
28 | };
29 |
30 | Circle.prototype.draw = function (context) {
31 | context.beginPath();
32 |
33 | //draw a circle
34 | context.arc(this.mCenter.x, this.mCenter.y, this.mRadius, 0, Math.PI * 2, true);
35 |
36 | //draw a line from start point toward center
37 | context.moveTo(this.mStartpoint.x, this.mStartpoint.y);
38 | context.lineTo(this.mCenter.x, this.mCenter.y);
39 |
40 | context.closePath();
41 | context.stroke();
42 | };
43 |
44 | //rotate angle in counterclockwise
45 | Circle.prototype.rotate = function (angle) {
46 | this.mAngle += angle;
47 | this.mStartpoint = this.mStartpoint.rotate(this.mCenter, angle);
48 | return this;
49 | };
50 |
51 | Circle.prototype.updateInertia = function () {
52 | if (this.mInvMass === 0) {
53 | this.mInertia = 0;
54 | } else {
55 | // this.mInvMass is inverted!!
56 | // Inertia=mass * radius^2
57 | // 12 is a constant value that can be changed
58 | this.mInertia = (1 / this.mInvMass) * (this.mRadius * this.mRadius) / 12;
59 | }
60 | };
61 |
--------------------------------------------------------------------------------
/book/public_html/Circle_collision.js:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | /*jslint node: true, vars: true, evil: true, bitwise: true */
7 | "use strict";
8 | /* global Circle */
9 |
10 | Circle.prototype.collisionTest = function (otherShape, collisionInfo) {
11 | var status = false;
12 | if (otherShape.mType === "Circle") {
13 | status = this.collidedCircCirc(this, otherShape, collisionInfo);
14 | } else {
15 | status = otherShape.collidedRectCirc(this, collisionInfo);
16 | }
17 | return status;
18 | };
19 |
20 | Circle.prototype.collidedCircCirc = function (c1, c2, collisionInfo) {
21 | var vFrom1to2 = c2.mCenter.subtract(c1.mCenter);
22 | var rSum = c1.mRadius + c2.mRadius;
23 | var dist = vFrom1to2.length();
24 | if (dist > Math.sqrt(rSum * rSum)) {
25 | //not overlapping
26 | return false;
27 | }
28 | if (dist !== 0) {
29 | // overlapping bu not same position
30 | var normalFrom2to1 = vFrom1to2.scale(-1).normalize();
31 | var radiusC2 = normalFrom2to1.scale(c2.mRadius);
32 | collisionInfo.setInfo(rSum - dist, vFrom1to2.normalize(), c2.mCenter.add(radiusC2));
33 | } else {
34 | //same position
35 | if (c1.mRadius > c2.mRadius) {
36 | collisionInfo.setInfo(rSum, new Vec2(0, -1), c1.mCenter.add(new Vec2(0, c1.mRadius)));
37 | } else {
38 | collisionInfo.setInfo(rSum, new Vec2(0, -1), c2.mCenter.add(new Vec2(0, c2.mRadius)));
39 | }
40 | }
41 | return true;
42 | };
43 |
44 |
--------------------------------------------------------------------------------
/book/public_html/CollisionInfo.js:
--------------------------------------------------------------------------------
1 | /*
2 | * File: CollisionInfo.js
3 | * normal: vector upon which collision interpenetrates
4 | * depth: how much penetration
5 | */
6 |
7 | /*jslint node: true, vars: true, evil: true, bitwise: true */
8 | "use strict";
9 |
10 | /**
11 | * Default Constructor
12 | * @memberOf CollisionInfo
13 | * @returns {CollisionInfo} New instance of CollisionInfo
14 | */
15 | function CollisionInfo() {
16 | this.mDepth = 0;
17 | this.mNormal = new Vec2(0, 0);
18 | this.mStart = new Vec2(0, 0);
19 | this.mEnd = new Vec2(0, 0);
20 | }
21 |
22 | /**
23 | * Set the depth of the CollisionInfo
24 | * @memberOf CollisionInfo
25 | * @param {Number} s how much penetration
26 | * @returns {void}
27 | */
28 | CollisionInfo.prototype.setDepth = function (s) {
29 | this.mDepth = s;
30 | };
31 |
32 | /**
33 | * Set the normal of the CollisionInfo
34 | * @memberOf CollisionInfo
35 | * @param {vec2} s vector upon which collision interpenetrates
36 | * @returns {void}
37 | */
38 | CollisionInfo.prototype.setNormal = function (s) {
39 | this.mNormal = s;
40 | };
41 |
42 | /**
43 | * Return the depth of the CollisionInfo
44 | * @memberOf CollisionInfo
45 | * @returns {Number} how much penetration
46 | */
47 | CollisionInfo.prototype.getDepth = function () {
48 | return this.mDepth;
49 | };
50 |
51 | /**
52 | * Return the depth of the CollisionInfo
53 | * @memberOf CollisionInfo
54 | * @returns {vec2} vector upon which collision interpenetrates
55 | */
56 | CollisionInfo.prototype.getNormal = function () {
57 | return this.mNormal;
58 | };
59 |
60 | /**
61 | * Set the all value of the CollisionInfo
62 | * @memberOf CollisionInfo
63 | * @param {Number} d the depth of the CollisionInfo
64 | * @param {Vec2} n the normal of the CollisionInfo
65 | * @param {Vec2} s the startpoint of the CollisionInfo
66 | * @returns {void}
67 | */
68 | CollisionInfo.prototype.setInfo = function (d, n, s) {
69 | this.mDepth = d;
70 | this.mNormal = n;
71 | this.mStart = s;
72 | this.mEnd = s.add(n.scale(d));
73 | };
74 |
75 | /**
76 | * change the direction of normal
77 | * @memberOf CollisionInfo
78 | * @returns {void}
79 | */
80 | CollisionInfo.prototype.changeDir = function () {
81 | this.mNormal = this.mNormal.scale(-1);
82 | var n = this.mStart;
83 | this.mStart = this.mEnd;
84 | this.mEnd = n;
85 | };
--------------------------------------------------------------------------------
/book/public_html/Core.js:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 |
7 |
8 | /*jslint node: true, vars: true, evil: true, bitwise: true */
9 | /*global requestAnimationFrame: false */
10 | /*global document,gObjectNum */
11 | "use strict"; // Operate in Strict mode such that variables must be declared before used!
12 |
13 | /**
14 | * Static refrence to gEngine
15 | * @type gEngine
16 | */
17 | var gEngine = gEngine || {};
18 | // initialize the variable while ensuring it is not redefined
19 | gEngine.Core = (function () {
20 | var mCanvas, mContext, mWidth = 800, mHeight = 450;
21 | mCanvas = document.getElementById('canvas');
22 | mContext = mCanvas.getContext('2d');
23 | mCanvas.height = mHeight;
24 | mCanvas.width = mWidth;
25 |
26 | var mGravity = new Vec2(0, 20);
27 | var mMovement = true;
28 |
29 | var mCurrentTime, mElapsedTime, mPreviousTime = Date.now(), mLagTime = 0;
30 | var kFPS = 60; // Frames per second
31 | var kFrameTime = 1 / kFPS;
32 | var mUpdateIntervalInSeconds = kFrameTime;
33 | var kMPF = 1000 * kFrameTime; // Milliseconds per frame.
34 | var mAllObjects = [];
35 |
36 | var updateUIEcho = function () {
37 | document.getElementById("uiEchoString").innerHTML =
38 | "
Selected Object::
" +
39 | "" +
40 | "- Id: " + gObjectNum + "
" +
41 | "- Center: " + mAllObjects[gObjectNum].mCenter.x.toPrecision(3) + "," + mAllObjects[gObjectNum].mCenter.y.toPrecision(3) + "
" +
42 | "- Angle: " + mAllObjects[gObjectNum].mAngle.toPrecision(3) + "
" +
43 | "- Velocity: " + mAllObjects[gObjectNum].mVelocity.x.toPrecision(3) + "," + mAllObjects[gObjectNum].mVelocity.y.toPrecision(3) + "
" +
44 | "- AngluarVelocity: " + mAllObjects[gObjectNum].mAngularVelocity.toPrecision(3) + "
" +
45 | "- Mass: " + 1 / mAllObjects[gObjectNum].mInvMass.toPrecision(3) + "
" +
46 | "- Friction: " + mAllObjects[gObjectNum].mFriction.toPrecision(3) + "
" +
47 | "- Restitution: " + mAllObjects[gObjectNum].mRestitution.toPrecision(3) + "
" +
48 | "- Positional Correction: " + gEngine.Physics.mPositionalCorrectionFlag + "
" +
49 | "- Movement: " + gEngine.Core.mMovement + "
" +
50 | "
" +
51 | "Control: of selected object
" +
52 | "" +
53 | "- Num or Up/Down Arrow: Select Object
" +
54 | "- WASD + QE: Position [Move + Rotate]
" +
55 | "- IJKL + UO: Velocities [Linear + Angular]
" +
56 | "- Z/X: Mass [Decrease/Increase]
" +
57 | "- C/V: Frictrion [Decrease/Increase]
" +
58 | "- B/N: Restitution [Decrease/Increase]
" +
59 | "- M: Positional Correction [On/Off]
" +
60 | "- ,: Movement [On/Off]
" +
61 | "
" +
62 | "F/G: Spawn [Rectangle/Circle] at selected object" +
63 | "H: Excite all objects
" +
64 | "R: Reset System
" +
65 | "
";
66 | };
67 | var draw = function () {
68 | mContext.clearRect(0, 0, mWidth, mHeight);
69 | var i;
70 | for (i = 0; i < mAllObjects.length; i++) {
71 | mContext.strokeStyle = 'blue';
72 | if (i === gObjectNum) {
73 | mContext.strokeStyle = 'red';
74 | }
75 | mAllObjects[i].draw(mContext);
76 | }
77 | };
78 | var update = function () {
79 | var i;
80 | for (i = 0; i < mAllObjects.length; i++) {
81 | mAllObjects[i].update(mContext);
82 | }
83 | };
84 | var runGameLoop = function () {
85 | requestAnimationFrame(function () {
86 | runGameLoop();
87 | });
88 |
89 | // compute how much time has elapsed since we last runGameLoop was executed
90 | mCurrentTime = Date.now();
91 | mElapsedTime = mCurrentTime - mPreviousTime;
92 | mPreviousTime = mCurrentTime;
93 | mLagTime += mElapsedTime;
94 |
95 | updateUIEcho();
96 | draw();
97 | // Make sure we update the game the appropriate number of times.
98 | // Update only every Milliseconds per frame.
99 | // If lag larger then update frames, update until caught up.
100 | while (mLagTime >= kMPF) {
101 | mLagTime -= kMPF;
102 | gEngine.Physics.collision();
103 | update();
104 | }
105 | };
106 | var initializeEngineCore = function () {
107 | runGameLoop();
108 | };
109 | var mPublic = {
110 | initializeEngineCore: initializeEngineCore,
111 | mAllObjects: mAllObjects,
112 | mWidth: mWidth,
113 | mHeight: mHeight,
114 | mContext: mContext,
115 | mGravity: mGravity,
116 | mUpdateIntervalInSeconds: mUpdateIntervalInSeconds,
117 | mMovement: mMovement
118 | };
119 | return mPublic;
120 | }());
--------------------------------------------------------------------------------
/book/public_html/MyGame.js:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | /*jslint node: true, vars: true, evil: true, bitwise: true */
7 | "use strict";
8 | /* global height, width, gEngine */
9 | function MyGame() {
10 | var r1 = new Rectangle(new Vec2(500, 200), 400, 20, 0, 0.3, 0);
11 | r1.rotate(2.8);
12 | var r2 = new Rectangle(new Vec2(200, 400), 400, 20, 0, 1, 0.5);
13 | var r3 = new Rectangle(new Vec2(100, 200), 200, 20, 0);
14 | var r4 = new Rectangle(new Vec2(10, 360), 20, 100, 0, 0, 1);
15 |
16 | for (var i = 0; i < 10; i++) {
17 | var r1 = new Rectangle(new Vec2(Math.random() * gEngine.Core.mWidth, Math.random() * gEngine.Core.mHeight / 2), Math.random() * 50 + 10, Math.random() * 50 + 10, Math.random() * 30, Math.random(), Math.random());
18 | r1.mVelocity = new Vec2(Math.random() * 60 - 30, Math.random() * 60 - 30);
19 | var r1 = new Circle(new Vec2(Math.random() * gEngine.Core.mWidth, Math.random() * gEngine.Core.mHeight / 2), Math.random() * 20 + 10, Math.random() * 30, Math.random(), Math.random());
20 | r1.mVelocity = new Vec2(Math.random() * 60 - 30, Math.random() * 60 - 30);
21 | }
22 | }
23 |
24 |
25 |
--------------------------------------------------------------------------------
/book/public_html/Physics.js:
--------------------------------------------------------------------------------
1 | /*
2 | The following is not free software. You may use it for educational purposes, but you may not redistribute or use it commercially.
3 | (C) Burak Kanber 2012
4 | */
5 | /* global objectNum, context, mRelaxationCount, mAllObjects, mPosCorrectionRate */
6 | /*jslint node: true, vars: true, evil: true, bitwise: true */
7 | "use strict";
8 | var gEngine = gEngine || {};
9 | // initialize the variable while ensuring it is not redefined
10 |
11 | gEngine.Physics = (function () {
12 |
13 | var mPositionalCorrectionFlag = true;
14 | var mRelaxationCount = 15; // number of relaxation iteration
15 | var mPosCorrectionRate = 0.8; // percentage of separation to project objects
16 |
17 | var positionalCorrection = function (s1, s2, collisionInfo) {
18 | var s1InvMass = s1.mInvMass;
19 | var s2InvMass = s2.mInvMass;
20 |
21 | var num = collisionInfo.getDepth() / (s1InvMass + s2InvMass) * mPosCorrectionRate;
22 | var correctionAmount = collisionInfo.getNormal().scale(num);
23 |
24 | s1.move(correctionAmount.scale(-s1InvMass));
25 | s2.move(correctionAmount.scale(s2InvMass));
26 | };
27 |
28 | var resolveCollision = function (s1, s2, collisionInfo) {
29 |
30 | if ((s1.mInvMass === 0) && (s2.mInvMass === 0)) {
31 | return;
32 | }
33 |
34 | // correct positions
35 | if (gEngine.Physics.mPositionalCorrectionFlag) {
36 | positionalCorrection(s1, s2, collisionInfo);
37 | }
38 |
39 | var n = collisionInfo.getNormal();
40 |
41 | //the direction of collisionInfo is always from s1 to s2
42 | //but the Mass is inversed, so start scale with s2 and end scale with s1
43 | var start = collisionInfo.mStart.scale(s2.mInvMass / (s1.mInvMass + s2.mInvMass));
44 | var end = collisionInfo.mEnd.scale(s1.mInvMass / (s1.mInvMass + s2.mInvMass));
45 | var p = start.add(end);
46 | //r is vector from center of object to collision point
47 | var r1 = p.subtract(s1.mCenter);
48 | var r2 = p.subtract(s2.mCenter);
49 |
50 | //newV = V + mAngularVelocity cross R
51 | var v1 = s1.mVelocity.add(new Vec2(-1 * s1.mAngularVelocity * r1.y, s1.mAngularVelocity * r1.x));
52 | var v2 = s2.mVelocity.add(new Vec2(-1 * s2.mAngularVelocity * r2.y, s2.mAngularVelocity * r2.x));
53 | var relativeVelocity = v2.subtract(v1);
54 |
55 | // Relative velocity in normal direction
56 | var rVelocityInNormal = relativeVelocity.dot(n);
57 |
58 | //if objects moving apart ignore
59 | if (rVelocityInNormal > 0) {
60 | return;
61 | }
62 |
63 | // compute and apply response impulses for each object
64 | var newRestituion = Math.min(s1.mRestitution, s2.mRestitution);
65 | var newFriction = Math.min(s1.mFriction, s2.mFriction);
66 |
67 | //R cross N
68 | var R1crossN = r1.cross(n);
69 | var R2crossN = r2.cross(n);
70 |
71 | // Calc impulse scalar
72 | // the formula of jN can be found in http://www.myphysicslab.com/collision.html
73 | var jN = -(1 + newRestituion) * rVelocityInNormal;
74 | jN = jN / (s1.mInvMass + s2.mInvMass +
75 | R1crossN * R1crossN * s1.mInertia +
76 | R2crossN * R2crossN * s2.mInertia);
77 |
78 | //impulse is in direction of normal ( from s1 to s2)
79 | var impulse = n.scale(jN);
80 | // impulse = F dt = m * ?v
81 | // ?v = impulse / m
82 | s1.mVelocity = s1.mVelocity.subtract(impulse.scale(s1.mInvMass));
83 | s2.mVelocity = s2.mVelocity.add(impulse.scale(s2.mInvMass));
84 |
85 | s1.mAngularVelocity -= R1crossN * jN * s1.mInertia;
86 | s2.mAngularVelocity += R2crossN * jN * s2.mInertia;
87 |
88 | var tangent = relativeVelocity.subtract(n.scale(relativeVelocity.dot(n)));
89 |
90 | //relativeVelocity.dot(tangent) should less than 0
91 | tangent = tangent.normalize().scale(-1);
92 |
93 | var R1crossT = r1.cross(tangent);
94 | var R2crossT = r2.cross(tangent);
95 |
96 | var jT = -(1 + newRestituion) * relativeVelocity.dot(tangent) * newFriction;
97 | jT = jT / (s1.mInvMass + s2.mInvMass + R1crossT * R1crossT * s1.mInertia + R2crossT * R2crossT * s2.mInertia);
98 |
99 | //friction should less than force in normal direction
100 | if (jT > jN) {
101 | jT = jN;
102 | }
103 |
104 | //impulse is from s1 to s2 (in opposite direction of velocity)
105 | impulse = tangent.scale(jT);
106 |
107 | s1.mVelocity = s1.mVelocity.subtract(impulse.scale(s1.mInvMass));
108 | s2.mVelocity = s2.mVelocity.add(impulse.scale(s2.mInvMass));
109 | s1.mAngularVelocity -= R1crossT * jT * s1.mInertia;
110 | s2.mAngularVelocity += R2crossT * jT * s2.mInertia;
111 | };
112 |
113 | var drawCollisionInfo = function (collisionInfo, context) {
114 | context.beginPath();
115 | context.moveTo(collisionInfo.mStart.x, collisionInfo.mStart.y);
116 | context.lineTo(collisionInfo.mEnd.x, collisionInfo.mEnd.y);
117 | context.closePath();
118 | context.strokeStyle = "orange";
119 | context.stroke();
120 | };
121 | var collision = function () {
122 | var i, j, k;
123 | var collisionInfo = new CollisionInfo();
124 | for (k = 0; k < mRelaxationCount; k++) {
125 | for (i = 0; i < gEngine.Core.mAllObjects.length; i++) {
126 | for (j = i + 1; j < gEngine.Core.mAllObjects.length; j++) {
127 | if (gEngine.Core.mAllObjects[i].boundTest(gEngine.Core.mAllObjects[j])) {
128 | if (gEngine.Core.mAllObjects[i].collisionTest(gEngine.Core.mAllObjects[j], collisionInfo)) {
129 | //make sure the normal is always from object[i] to object[j]
130 | if (collisionInfo.getNormal().dot(gEngine.Core.mAllObjects[j].mCenter.subtract(gEngine.Core.mAllObjects[i].mCenter)) < 0) {
131 | collisionInfo.changeDir();
132 | }
133 |
134 | //draw collision info (a black line that shows normal)
135 | //drawCollisionInfo(collisionInfo, gEngine.Core.mContext);
136 |
137 | resolveCollision(gEngine.Core.mAllObjects[i], gEngine.Core.mAllObjects[j], collisionInfo);
138 | }
139 | }
140 | }
141 | }
142 | }
143 | };
144 | var mPublic = {
145 | collision: collision,
146 | mPositionalCorrectionFlag: mPositionalCorrectionFlag
147 | };
148 |
149 | return mPublic;
150 | }());
151 |
152 |
--------------------------------------------------------------------------------
/book/public_html/Rectangle.js:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | /*jslint node: true, vars: true, evil: true, bitwise: true */
7 | "use strict";
8 | /* global RigidShape */
9 |
10 | var Rectangle = function (center, width, height, mass, friction, restitution) {
11 |
12 | RigidShape.call(this, center, mass, friction, restitution);
13 | this.mType = "Rectangle";
14 | this.mWidth = width;
15 | this.mHeight = height;
16 | this.mBoundRadius = Math.sqrt(width * width + height * height) / 2;
17 | this.mVertex = [];
18 | this.mFaceNormal = [];
19 |
20 | //0--TopLeft;1--TopRight;2--BottomRight;3--BottomLeft
21 | this.mVertex[0] = new Vec2(center.x - width / 2, center.y - height / 2);
22 | this.mVertex[1] = new Vec2(center.x + width / 2, center.y - height / 2);
23 | this.mVertex[2] = new Vec2(center.x + width / 2, center.y + height / 2);
24 | this.mVertex[3] = new Vec2(center.x - width / 2, center.y + height / 2);
25 |
26 | //0--Top;1--Right;2--Bottom;3--Left
27 | //mFaceNormal is normal of face toward outside of rectangle
28 | this.mFaceNormal[0] = this.mVertex[1].subtract(this.mVertex[2]);
29 | this.mFaceNormal[0] = this.mFaceNormal[0].normalize();
30 | this.mFaceNormal[1] = this.mVertex[2].subtract(this.mVertex[3]);
31 | this.mFaceNormal[1] = this.mFaceNormal[1].normalize();
32 | this.mFaceNormal[2] = this.mVertex[3].subtract(this.mVertex[0]);
33 | this.mFaceNormal[2] = this.mFaceNormal[2].normalize();
34 | this.mFaceNormal[3] = this.mVertex[0].subtract(this.mVertex[1]);
35 | this.mFaceNormal[3] = this.mFaceNormal[3].normalize();
36 |
37 | this.updateInertia();
38 | };
39 |
40 | var prototype = Object.create(RigidShape.prototype);
41 | prototype.constructor = Rectangle;
42 | Rectangle.prototype = prototype;
43 |
44 | Rectangle.prototype.rotate = function (angle) {
45 | this.mAngle += angle;
46 | var i;
47 | for (i = 0; i < this.mVertex.length; i++) {
48 | this.mVertex[i] = this.mVertex[i].rotate(this.mCenter, angle);
49 | }
50 | this.mFaceNormal[0] = this.mVertex[1].subtract(this.mVertex[2]);
51 | this.mFaceNormal[0] = this.mFaceNormal[0].normalize();
52 | this.mFaceNormal[1] = this.mVertex[2].subtract(this.mVertex[3]);
53 | this.mFaceNormal[1] = this.mFaceNormal[1].normalize();
54 | this.mFaceNormal[2] = this.mVertex[3].subtract(this.mVertex[0]);
55 | this.mFaceNormal[2] = this.mFaceNormal[2].normalize();
56 | this.mFaceNormal[3] = this.mVertex[0].subtract(this.mVertex[1]);
57 | this.mFaceNormal[3] = this.mFaceNormal[3].normalize();
58 | return this;
59 | };
60 |
61 | Rectangle.prototype.move = function (v) {
62 | var i;
63 | for (i = 0; i < this.mVertex.length; i++) {
64 | this.mVertex[i] = this.mVertex[i].add(v);
65 | }
66 | this.mCenter = this.mCenter.add(v);
67 | return this;
68 | };
69 |
70 | Rectangle.prototype.draw = function (context) {
71 | context.save();
72 |
73 | context.translate(this.mVertex[0].x, this.mVertex[0].y);
74 | context.rotate(this.mAngle);
75 | context.strokeRect(0, 0, this.mWidth, this.mHeight);
76 |
77 | context.restore();
78 | };
79 |
80 | Rectangle.prototype.updateInertia = function () {
81 | // Expect this.mInvMass to be already inverted!
82 | if (this.mInvMass === 0) {
83 | this.mInertia = 0;
84 | } else {
85 | //inertia=mass*width^2+height^2
86 | this.mInertia = (1 / this.mInvMass) * (this.mWidth * this.mWidth + this.mHeight * this.mHeight) / 12;
87 | this.mInertia = 1 / this.mInertia;
88 | }
89 | };
--------------------------------------------------------------------------------
/book/public_html/Rectangle_collision.js:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | /*jslint node: true, vars: true, evil: true, bitwise: true */
7 | "use strict";
8 | /*global Rectangle, Vec2 */
9 |
10 | Rectangle.prototype.collisionTest = function (otherShape, collisionInfo) {
11 | var status = false;
12 | if (otherShape.mType === "Circle") {
13 | status = this.collidedRectCirc(otherShape, collisionInfo);
14 | } else {
15 | status = this.collidedRectRect(this, otherShape, collisionInfo);
16 | }
17 | return status;
18 | };
19 |
20 | var SupportStruct = function () {
21 | this.mSupportPoint = null;
22 | this.mSupportPointDist = 0;
23 | };
24 | var tmpSupport = new SupportStruct();
25 |
26 | Rectangle.prototype.findSupportPoint = function (dir, ptOnEdge) {
27 | //the longest project length
28 | var vToEdge;
29 | var projection;
30 |
31 | tmpSupport.mSupportPointDist = -9999999;
32 | tmpSupport.mSupportPoint = null;
33 | //check each vector of other object
34 | for (var i = 0; i < this.mVertex.length; i++) {
35 | vToEdge = this.mVertex[i].subtract(ptOnEdge);
36 | projection = vToEdge.dot(dir);
37 |
38 | //find the longest distance with certain edge
39 | //dir is -n direction, so the distance should be positive
40 | if ((projection > 0) && (projection > tmpSupport.mSupportPointDist)) {
41 | tmpSupport.mSupportPoint = this.mVertex[i];
42 | tmpSupport.mSupportPointDist = projection;
43 | }
44 | }
45 | };
46 |
47 | /**
48 | * Find the shortest axis that overlapping
49 | * @memberOf Rectangle
50 | * @param {Rectangle} otherRect another rectangle that being tested
51 | * @param {CollisionInfo} collisionInfo record the collision information
52 | * @returns {Boolean} true if has overlap part in all four directions.
53 | * the code is convert from http://gamedevelopment.tutsplus.com/tutorials/how-to-create-a-custom-2d-physics-engine-oriented-rigid-bodies--gamedev-8032
54 | */
55 | Rectangle.prototype.findAxisLeastPenetration = function (otherRect, collisionInfo) {
56 |
57 | var n;
58 | var supportPoint;
59 |
60 | var bestDistance = 999999;
61 | var bestIndex = null;
62 |
63 | var hasSupport = true;
64 | var i = 0;
65 |
66 | while ((hasSupport) && (i < this.mFaceNormal.length)) {
67 | // Retrieve a face normal from A
68 | n = this.mFaceNormal[i];
69 |
70 | // use -n as direction and the vectex on edge i as point on edge
71 | var dir = n.scale(-1);
72 | var ptOnEdge = this.mVertex[i];
73 | // find the support on B
74 | // the point has longest distance with edge i
75 | otherRect.findSupportPoint(dir, ptOnEdge);
76 | hasSupport = (tmpSupport.mSupportPoint !== null);
77 |
78 | //get the shortest support point depth
79 | if ((hasSupport) && (tmpSupport.mSupportPointDist < bestDistance)) {
80 | bestDistance = tmpSupport.mSupportPointDist;
81 | bestIndex = i;
82 | supportPoint = tmpSupport.mSupportPoint;
83 | }
84 | i = i + 1;
85 | }
86 | if (hasSupport) {
87 | //all four directions have support point
88 | var bestVec = this.mFaceNormal[bestIndex].scale(bestDistance);
89 | collisionInfo.setInfo(bestDistance, this.mFaceNormal[bestIndex], supportPoint.add(bestVec));
90 | }
91 | return hasSupport;
92 | };
93 | /**
94 | * Check for collision between RigidRectangle and RigidRectangle
95 | * @param {Rectangle} r1 Rectangle object to check for collision status
96 | * @param {Rectangle} r2 Rectangle object to check for collision status against
97 | * @param {CollisionInfo} collisionInfo Collision info of collision
98 | * @returns {Boolean} true if collision occurs
99 | * @memberOf Rectangle
100 | */
101 | var collisionInfoR1 = new CollisionInfo();
102 | var collisionInfoR2 = new CollisionInfo();
103 | Rectangle.prototype.collidedRectRect = function (r1, r2, collisionInfo) {
104 |
105 | var status1 = false;
106 | var status2 = false;
107 |
108 | //find Axis of Separation for both rectangle
109 | status1 = r1.findAxisLeastPenetration(r2, collisionInfoR1);
110 |
111 | if (status1) {
112 | status2 = r2.findAxisLeastPenetration(r1, collisionInfoR2);
113 | if (status2) {
114 | //if both of rectangles are overlapping, choose the shorter normal as the normal
115 | if (collisionInfoR1.getDepth() < collisionInfoR2.getDepth()) {
116 | var depthVec = collisionInfoR1.getNormal().scale(collisionInfoR1.getDepth());
117 | collisionInfo.setInfo(collisionInfoR1.getDepth(), collisionInfoR1.getNormal(), collisionInfoR1.mStart.subtract(depthVec));
118 | } else {
119 | collisionInfo.setInfo(collisionInfoR2.getDepth(), collisionInfoR2.getNormal().scale(-1), collisionInfoR2.mStart);
120 | }
121 | }
122 | }
123 | return status1 && status2;
124 | };
125 |
126 | /**
127 | * Check for collision between Rectangle and Circle
128 | * @param {Circle} otherCir circle to check for collision status against
129 | * @param {CollisionInfo} collisionInfo Collision info of collision
130 | * @returns {Boolean} true if collision occurs
131 | * @memberOf Rectangle
132 | */
133 | Rectangle.prototype.collidedRectCirc = function (otherCir, collisionInfo) {
134 |
135 | var inside = true;
136 | var bestDistance = -99999;
137 | var nearestEdge = 0;
138 | var i, v;
139 | var circ2Pos, projection;
140 | for (i = 0; i < 4; i++) {
141 | //find the nearest face for center of circle
142 | circ2Pos = otherCir.mCenter;
143 | v = circ2Pos.subtract(this.mVertex[i]);
144 | projection = v.dot(this.mFaceNormal[i]);
145 | if (projection > 0) {
146 | //if the center of circle is outside of rectangle
147 | bestDistance = projection;
148 | nearestEdge = i;
149 | inside = false;
150 | break;
151 | }
152 | if (projection > bestDistance) {
153 | bestDistance = projection;
154 | nearestEdge = i;
155 | }
156 | }
157 | var dis, normal, radiusVec;
158 | if (!inside) {
159 | //the center of circle is outside of rectangle
160 |
161 | //v1 is from left vertex of face to center of circle
162 | //v2 is from left vertex of face to right vertex of face
163 | var v1 = circ2Pos.subtract(this.mVertex[nearestEdge]);
164 | var v2 = this.mVertex[(nearestEdge + 1) % 4].subtract(this.mVertex[nearestEdge]);
165 |
166 | var dot = v1.dot(v2);
167 |
168 | if (dot < 0) {
169 | //the center of circle is in corner region of mVertex[nearestEdge]
170 | dis = v1.length();
171 | //compare the distance with radium to decide collision
172 | if (dis > otherCir.mRadius) {
173 | return false;
174 | }
175 |
176 | normal = v1.normalize();
177 | radiusVec = normal.scale(-otherCir.mRadius);
178 | collisionInfo.setInfo(otherCir.mRadius - dis, normal, circ2Pos.add(radiusVec));
179 | } else {
180 | //the center of circle is in corner region of mVertex[nearestEdge+1]
181 |
182 | //v1 is from right vertex of face to center of circle
183 | //v2 is from right vertex of face to left vertex of face
184 | v1 = circ2Pos.subtract(this.mVertex[(nearestEdge + 1) % 4]);
185 | v2 = v2.scale(-1);
186 | dot = v1.dot(v2);
187 | if (dot < 0) {
188 | dis = v1.length();
189 | //compare the distance with radium to decide collision
190 | if (dis > otherCir.mRadius) {
191 | return false;
192 | }
193 | normal = v1.normalize();
194 | radiusVec = normal.scale(-otherCir.mRadius);
195 | collisionInfo.setInfo(otherCir.mRadius - dis, normal, circ2Pos.add(radiusVec));
196 | } else {
197 | //the center of circle is in face region of face[nearestEdge]
198 | if (bestDistance < otherCir.mRadius) {
199 | radiusVec = this.mFaceNormal[nearestEdge].scale(otherCir.mRadius);
200 | collisionInfo.setInfo(otherCir.mRadius - bestDistance, this.mFaceNormal[nearestEdge], circ2Pos.subtract(radiusVec));
201 | } else {
202 | return false;
203 | }
204 | }
205 | }
206 | } else {
207 | //the center of circle is inside of rectangle
208 | radiusVec = this.mFaceNormal[nearestEdge].scale(otherCir.mRadius);
209 | collisionInfo.setInfo(otherCir.mRadius - bestDistance, this.mFaceNormal[nearestEdge], circ2Pos.subtract(radiusVec));
210 | }
211 | return true;
212 | };
--------------------------------------------------------------------------------
/book/public_html/RigidShape.js:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 |
7 | /*jslint node: true, vars: true, evil: true, bitwise: true */
8 | "use strict";
9 |
10 | /* global mAllObjects, dt, gEngine */
11 |
12 | function RigidShape(center, mass, friction, restitution) {
13 |
14 | this.mCenter = center;
15 | this.mInertia = 0;
16 | if (mass !== undefined) {
17 | this.mInvMass = mass;
18 | } else {
19 | this.mInvMass = 1;
20 | }
21 |
22 | if (friction !== undefined) {
23 | this.mFriction = friction;
24 | } else {
25 | this.mFriction = 0.8;
26 | }
27 |
28 | if (restitution !== undefined) {
29 | this.mRestitution = restitution;
30 | } else {
31 | this.mRestitution = 0.2;
32 | }
33 |
34 | this.mVelocity = new Vec2(0, 0);
35 |
36 | if (this.mInvMass !== 0) {
37 | this.mInvMass = 1 / this.mInvMass;
38 | this.mAcceleration = gEngine.Core.mGravity;
39 | } else {
40 | this.mAcceleration = new Vec2(0, 0);
41 | }
42 |
43 | //angle
44 | this.mAngle = 0;
45 |
46 | //negetive-- clockwise
47 | //postive-- counterclockwise
48 | this.mAngularVelocity = 0;
49 |
50 | this.mAngularAcceleration = 0;
51 |
52 | this.mBoundRadius = 0;
53 |
54 | gEngine.Core.mAllObjects.push(this);
55 | }
56 |
57 | RigidShape.prototype.updateMass = function (delta) {
58 | var mass;
59 | if (this.mInvMass !== 0) {
60 | mass = 1 / this.mInvMass;
61 | } else {
62 | mass = 0;
63 | }
64 |
65 | mass += delta;
66 | if (mass <= 0) {
67 | this.mInvMass = 0;
68 | this.mVelocity = new Vec2(0, 0);
69 | this.mAcceleration = new Vec2(0, 0);
70 | this.mAngularVelocity = 0;
71 | this.mAngularAcceleration = 0;
72 | } else {
73 | this.mInvMass = 1 / mass;
74 | this.mAcceleration = gEngine.Core.mGravity;
75 | }
76 | this.updateInertia();
77 | };
78 |
79 | RigidShape.prototype.updateInertia = function () {
80 | // subclass must define this.
81 | // must work with inverted this.mInvMass
82 | };
83 |
84 | RigidShape.prototype.update = function () {
85 | if (gEngine.Core.mMovement) {
86 | var dt = gEngine.Core.mUpdateIntervalInSeconds;
87 | //v += a*t
88 | this.mVelocity = this.mVelocity.add(this.mAcceleration.scale(dt));
89 | //s += v*t
90 | this.move(this.mVelocity.scale(dt));
91 |
92 | this.mAngularVelocity += this.mAngularAcceleration * dt;
93 | this.rotate(this.mAngularVelocity * dt);
94 | }
95 | var width = gEngine.Core.mWidth;
96 | var height = gEngine.Core.mHeight;
97 | if (this.mCenter.x < 0 || this.mCenter.x > width || this.mCenter.y < 0 || this.mCenter.y > height) {
98 | var index = gEngine.Core.mAllObjects.indexOf(this);
99 | if (index > -1)
100 | gEngine.Core.mAllObjects.splice(index, 1);
101 | }
102 |
103 | };
104 |
105 | RigidShape.prototype.boundTest = function (otherShape) {
106 | var vFrom1to2 = otherShape.mCenter.subtract(this.mCenter);
107 | var rSum = this.mBoundRadius + otherShape.mBoundRadius;
108 | var dist = vFrom1to2.length();
109 | if (dist > rSum) {
110 | //not overlapping
111 | return false;
112 | }
113 | return true;
114 | };
115 |
--------------------------------------------------------------------------------
/book/public_html/UserControl.js:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | /*jslint node: true, vars: true, evil: true, bitwise: true */
7 | "use strict";
8 | /* global mAllObjects, gEngine */
9 |
10 | var gObjectNum = 0;
11 | function userControl(event) {
12 | var keycode;
13 |
14 | if (window.event) {
15 | //alert('ie');
16 | keycode = event.keyCode;
17 | } else if (event.which) {
18 | //alert('firefox ');
19 | keycode = event.which;
20 | }
21 | if (keycode >= 48 && keycode <= 57) {
22 | if (keycode - 48 < gEngine.Core.mAllObjects.length) {
23 | gObjectNum = keycode - 48;
24 | }
25 | }
26 | if (keycode === 38) {
27 | //up arrow
28 | if (gObjectNum > 0) {
29 | gObjectNum--;
30 | }
31 | }
32 | if (keycode === 40) {
33 | // down arrow
34 | if (gObjectNum < gEngine.Core.mAllObjects.length - 1) {
35 | gObjectNum++;
36 | }
37 | }
38 | if (keycode === 87) {
39 | //W
40 | gEngine.Core.mAllObjects[gObjectNum].move(new Vec2(0, -10));
41 | }
42 | if (keycode === 83) {
43 | // S
44 | gEngine.Core.mAllObjects[gObjectNum].move(new Vec2(0, +10));
45 | }
46 | if (keycode === 65) {
47 | //A
48 | gEngine.Core.mAllObjects[gObjectNum].move(new Vec2(-10, 0));
49 | }
50 | if (keycode === 68) {
51 | //D
52 | gEngine.Core.mAllObjects[gObjectNum].move(new Vec2(10, 0));
53 | }
54 | if (keycode === 81) {
55 | //Q
56 | gEngine.Core.mAllObjects[gObjectNum].rotate(-0.1);
57 | }
58 | if (keycode === 69) {
59 | //E
60 | gEngine.Core.mAllObjects[gObjectNum].rotate(0.1);
61 | }
62 | if (keycode === 73) {
63 | //I
64 | gEngine.Core.mAllObjects[gObjectNum].mVelocity.y -= 1;
65 | }
66 | if (keycode === 75) {
67 | // k
68 | gEngine.Core.mAllObjects[gObjectNum].mVelocity.y += 1;
69 | }
70 | if (keycode === 74) {
71 | //j
72 | gEngine.Core.mAllObjects[gObjectNum].mVelocity.x -= 1;
73 | }
74 | if (keycode === 76) {
75 | //l
76 | gEngine.Core.mAllObjects[gObjectNum].mVelocity.x += 1;
77 | }
78 | if (keycode === 85) {
79 | //U
80 | gEngine.Core.mAllObjects[gObjectNum].mAngularVelocity -= 0.1;
81 | }
82 | if (keycode === 79) {
83 | //O
84 | gEngine.Core.mAllObjects[gObjectNum].mAngularVelocity += 0.1;
85 | }
86 | if (keycode === 90) {
87 | //Z
88 | gEngine.Core.mAllObjects[gObjectNum].updateMass(-1);
89 | }
90 | if (keycode === 88) {
91 | //X
92 | gEngine.Core.mAllObjects[gObjectNum].updateMass(1);
93 | }
94 | if (keycode === 67) {
95 | //C
96 | gEngine.Core.mAllObjects[gObjectNum].mFriction -= 0.01;
97 | }
98 | if (keycode === 86) {
99 | //V
100 | gEngine.Core.mAllObjects[gObjectNum].mFriction += 0.01;
101 | }
102 | if (keycode === 66) {
103 | //B
104 | gEngine.Core.mAllObjects[gObjectNum].mRestitution -= 0.01;
105 | }
106 | if (keycode === 78) {
107 | //N
108 | gEngine.Core.mAllObjects[gObjectNum].mRestitution += 0.01;
109 | }
110 | if (keycode === 77) {
111 | //M
112 | gEngine.Physics.mPositionalCorrectionFlag = !gEngine.Physics.mPositionalCorrectionFlag;
113 | }
114 | if (keycode === 188) {
115 | //,
116 | gEngine.Core.mMovement = !gEngine.Core.mMovement;
117 | }
118 | if (keycode === 70) {
119 | //f
120 | var r1 = new Rectangle(new Vec2(gEngine.Core.mAllObjects[gObjectNum].mCenter.x, gEngine.Core.mAllObjects[gObjectNum].mCenter.y), Math.random() * 30 + 10, Math.random() * 30 + 10, Math.random() * 30, Math.random(), Math.random());
121 | r1.mVelocity = new Vec2(Math.random() * 300 - 150, Math.random() * 300 - 150);
122 | }
123 | if (keycode === 71) {
124 | //g
125 | var r1 = new Circle(new Vec2(gEngine.Core.mAllObjects[gObjectNum].mCenter.x, gEngine.Core.mAllObjects[gObjectNum].mCenter.y), Math.random() * 10 + 20, Math.random() * 30, Math.random(), Math.random());
126 | r1.mVelocity = new Vec2(Math.random() * 300 - 150, Math.random() * 300 - 150);
127 | }
128 |
129 | if (keycode === 72) {
130 | //H
131 | var i;
132 | for (i = 0; i < gEngine.Core.mAllObjects.length; i++) {
133 | if (gEngine.Core.mAllObjects[i].mInvMass !== 0) {
134 | gEngine.Core.mAllObjects[i].mVelocity = new Vec2(Math.random() * 500 - 250, Math.random() * 500 - 250);
135 | }
136 | }
137 | }
138 | if (keycode === 82) {
139 | //R
140 | gEngine.Core.mAllObjects.splice(7, gEngine.Core.mAllObjects.length);
141 | gObjectNum = 0;
142 | }
143 | }
--------------------------------------------------------------------------------
/book/public_html/Vec2.js:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | /*jslint node: true, vars: true, evil: true, bitwise: true */
7 | "use strict";
8 | var Vec2 = function (x, y) {
9 | this.x = x;
10 | this.y = y;
11 | };
12 |
13 | Vec2.prototype.length = function () {
14 | return Math.sqrt(this.x * this.x + this.y * this.y);
15 | };
16 |
17 | Vec2.prototype.add = function (vec) {
18 | return new Vec2(vec.x + this.x, vec.y + this.y);
19 | };
20 |
21 | Vec2.prototype.subtract = function (vec) {
22 | return new Vec2(this.x - vec.x, this.y - vec.y);
23 | };
24 |
25 | Vec2.prototype.scale = function (n) {
26 | return new Vec2(this.x * n, this.y * n);
27 | };
28 |
29 | Vec2.prototype.dot = function (vec) {
30 | return (this.x * vec.x + this.y * vec.y);
31 | };
32 |
33 | Vec2.prototype.cross = function (vec) {
34 | return (this.x * vec.y - this.y * vec.x);
35 | };
36 |
37 | Vec2.prototype.rotate = function (center, angle) {
38 | //rotate in counterclockwise
39 | var r = [];
40 |
41 | var x = this.x - center.x;
42 | var y = this.y - center.y;
43 |
44 | r[0] = x * Math.cos(angle) - y * Math.sin(angle);
45 | r[1] = x * Math.sin(angle) + y * Math.cos(angle);
46 |
47 | r[0] += center.x;
48 | r[1] += center.y;
49 |
50 | return new Vec2(r[0], r[1]);
51 | };
52 |
53 | Vec2.prototype.normalize = function () {
54 |
55 | var len = this.length();
56 | if (len > 0) {
57 | len = 1 / len;
58 | }
59 | return new Vec2(this.x * len, this.y * len);
60 | };
61 |
62 | Vec2.prototype.distance = function (vec) {
63 | var x = this.x - vec.x;
64 | var y = this.y - vec.y;
65 | return Math.sqrt(x * x + y * y);
66 | };
--------------------------------------------------------------------------------
/book/public_html/all.js:
--------------------------------------------------------------------------------
1 | /*
2 | * To change this license header, choose License Headers in Project Properties.
3 | * To change this template file, choose Tools | Templates
4 | * and open the template in the editor.
5 | */
6 | /*jslint node: true, vars: true, evil: true, bitwise: true */
7 | "use strict";
8 | var Vec2 = function (x, y) {
9 | this.x = x;
10 | this.y = y;
11 | };
12 |
13 | Vec2.prototype.length = function () {
14 | return Math.sqrt(this.x * this.x + this.y * this.y);
15 | };
16 |
17 | Vec2.prototype.add = function (vec) {
18 | return new Vec2(vec.x + this.x, vec.y + this.y);
19 | };
20 |
21 | Vec2.prototype.subtract = function (vec) {
22 | return new Vec2(this.x - vec.x, this.y - vec.y);
23 | };
24 |
25 | Vec2.prototype.scale = function (n) {
26 | return new Vec2(this.x * n, this.y * n);
27 | };
28 |
29 | Vec2.prototype.dot = function (vec) {
30 | return (this.x * vec.x + this.y * vec.y);
31 | };
32 |
33 | Vec2.prototype.cross = function (vec) {
34 | return (this.x * vec.y - this.y * vec.x);
35 | };
36 |
37 | Vec2.prototype.rotate = function (center, angle) {
38 | //rotate in counterclockwise
39 | var r = [];
40 |
41 | var x = this.x - center.x;
42 | var y = this.y - center.y;
43 |
44 | r[0] = x * Math.cos(angle) - y * Math.sin(angle);
45 | r[1] = x * Math.sin(angle) + y * Math.cos(angle);
46 |
47 | r[0] += center.x;
48 | r[1] += center.y;
49 |
50 | return new Vec2(r[0], r[1]);
51 | };
52 |
53 | Vec2.prototype.normalize = function () {
54 |
55 | var len = this.length();
56 | if (len > 0) {
57 | len = 1 / len;
58 | }
59 | return new Vec2(this.x * len, this.y * len);
60 | };
61 |
62 | Vec2.prototype.distance = function (vec) {
63 | var x = this.x - vec.x;
64 | var y = this.y - vec.y;
65 | return Math.sqrt(x * x + y * y);
66 | };
67 |
68 | /*
69 | * File: CollisionInfo.js
70 | * normal: vector upon which collision interpenetrates
71 | * depth: how much penetration
72 | */
73 |
74 | /*jslint node: true, vars: true, evil: true, bitwise: true */
75 | "use strict";
76 |
77 | /**
78 | * Default Constructor
79 | * @memberOf CollisionInfo
80 | * @returns {CollisionInfo} New instance of CollisionInfo
81 | */
82 | function CollisionInfo() {
83 | this.mDepth = 0;
84 | this.mNormal = new Vec2(0, 0);
85 | this.mStart = new Vec2(0, 0);
86 | this.mEnd = new Vec2(0, 0);
87 | }
88 |
89 | /**
90 | * Set the depth of the CollisionInfo
91 | * @memberOf CollisionInfo
92 | * @param {Number} s how much penetration
93 | * @returns {void}
94 | */
95 | CollisionInfo.prototype.setDepth = function (s) {
96 | this.mDepth = s;
97 | };
98 |
99 | /**
100 | * Set the normal of the CollisionInfo
101 | * @memberOf CollisionInfo
102 | * @param {vec2} s vector upon which collision interpenetrates
103 | * @returns {void}
104 | */
105 | CollisionInfo.prototype.setNormal = function (s) {
106 | this.mNormal = s;
107 | };
108 |
109 | /**
110 | * Return the depth of the CollisionInfo
111 | * @memberOf CollisionInfo
112 | * @returns {Number} how much penetration
113 | */
114 | CollisionInfo.prototype.getDepth = function () {
115 | return this.mDepth;
116 | };
117 |
118 | /**
119 | * Return the depth of the CollisionInfo
120 | * @memberOf CollisionInfo
121 | * @returns {vec2} vector upon which collision interpenetrates
122 | */
123 | CollisionInfo.prototype.getNormal = function () {
124 | return this.mNormal;
125 | };
126 |
127 | /**
128 | * Set the all value of the CollisionInfo
129 | * @memberOf CollisionInfo
130 | * @param {Number} d the depth of the CollisionInfo
131 | * @param {Vec2} n the normal of the CollisionInfo
132 | * @param {Vec2} s the startpoint of the CollisionInfo
133 | * @returns {void}
134 | */
135 | CollisionInfo.prototype.setInfo = function (d, n, s) {
136 | this.mDepth = d;
137 | this.mNormal = n;
138 | this.mStart = s;
139 | this.mEnd = s.add(n.scale(d));
140 | };
141 |
142 | /**
143 | * change the direction of normal
144 | * @memberOf CollisionInfo
145 | * @returns {void}
146 | */
147 | CollisionInfo.prototype.changeDir = function () {
148 | this.mNormal = this.mNormal.scale(-1);
149 | var n = this.mStart;
150 | this.mStart = this.mEnd;
151 | this.mEnd = n;
152 | };
153 |
154 | /*
155 | * To change this license header, choose License Headers in Project Properties.
156 | * To change this template file, choose Tools | Templates
157 | * and open the template in the editor.
158 | */
159 |
160 | /*jslint node: true, vars: true, evil: true, bitwise: true */
161 | "use strict";
162 |
163 | /* global mAllObjects, dt, gEngine */
164 |
165 | function RigidShape(center, mass, friction, restitution) {
166 |
167 | this.mCenter = center;
168 | this.mInertia = 0;
169 | if (mass !== undefined) {
170 | this.mInvMass = mass;
171 | } else {
172 | this.mInvMass = 1;
173 | }
174 |
175 | if (friction !== undefined) {
176 | this.mFriction = friction;
177 | } else {
178 | this.mFriction = 0.8;
179 | }
180 |
181 | if (restitution !== undefined) {
182 | this.mRestitution = restitution;
183 | } else {
184 | this.mRestitution = 0.2;
185 | }
186 |
187 | this.mVelocity = new Vec2(0, 0);
188 |
189 | if (this.mInvMass !== 0) {
190 | this.mInvMass = 1 / this.mInvMass;
191 | this.mAcceleration = gEngine.Core.mGravity;
192 | } else {
193 | this.mAcceleration = new Vec2(0, 0);
194 | }
195 |
196 | //angle
197 | this.mAngle = 0;
198 |
199 | //negetive-- clockwise
200 | //postive-- counterclockwise
201 | this.mAngularVelocity = 0;
202 |
203 | this.mAngularAcceleration = 0;
204 |
205 | this.mBoundRadius = 0;
206 |
207 | gEngine.Core.mAllObjects.push(this);
208 | }
209 |
210 | RigidShape.prototype.updateMass = function (delta) {
211 | var mass;
212 | if (this.mInvMass !== 0) {
213 | mass = 1 / this.mInvMass;
214 | } else {
215 | mass = 0;
216 | }
217 |
218 | mass += delta;
219 | if (mass <= 0) {
220 | this.mInvMass = 0;
221 | this.mVelocity = new Vec2(0, 0);
222 | this.mAcceleration = new Vec2(0, 0);
223 | this.mAngularVelocity = 0;
224 | this.mAngularAcceleration = 0;
225 | } else {
226 | this.mInvMass = 1 / mass;
227 | this.mAcceleration = gEngine.Core.mGravity;
228 | }
229 | this.updateInertia();
230 | };
231 |
232 | RigidShape.prototype.updateInertia = function () {
233 | // subclass must define this.
234 | // must work with inverted this.mInvMass
235 | };
236 |
237 | RigidShape.prototype.update = function () {
238 | if (gEngine.Core.mMovement) {
239 | var dt = gEngine.Core.mUpdateIntervalInSeconds;
240 | //v += a*t
241 | this.mVelocity = this.mVelocity.add(this.mAcceleration.scale(dt));
242 | //s += v*t
243 | this.move(this.mVelocity.scale(dt));
244 |
245 | this.mAngularVelocity += this.mAngularAcceleration * dt;
246 | this.rotate(this.mAngularVelocity * dt);
247 | }
248 | var width = gEngine.Core.mWidth;
249 | var height = gEngine.Core.mHeight;
250 | if (this.mCenter.x < 0 || this.mCenter.x > width || this.mCenter.y < 0 || this.mCenter.y > height) {
251 | var index = gEngine.Core.mAllObjects.indexOf(this);
252 | if (index > -1)
253 | gEngine.Core.mAllObjects.splice(index, 1);
254 | }
255 |
256 | };
257 |
258 | RigidShape.prototype.boundTest = function (otherShape) {
259 | var vFrom1to2 = otherShape.mCenter.subtract(this.mCenter);
260 | var rSum = this.mBoundRadius + otherShape.mBoundRadius;
261 | var dist = vFrom1to2.length();
262 | if (dist > rSum) {
263 | //not overlapping
264 | return false;
265 | }
266 | return true;
267 | };
268 |
269 | /*
270 | * File:Circle.js
271 | * define a circle
272 | *
273 | */
274 | /*jslint node: true, vars: true, evil: true, bitwise: true */
275 | "use strict";
276 | /* global RigidShape */
277 |
278 | var Circle = function (center, radius, mass, friction, restitution) {
279 | RigidShape.call(this, center, mass, friction, restitution);
280 | this.mType = "Circle";
281 | this.mRadius = radius;
282 | this.mBoundRadius = radius;
283 | //The start point of line in circle
284 | this.mStartpoint = new Vec2(center.x, center.y - radius);
285 | this.updateInertia();
286 | };
287 |
288 | var prototype = Object.create(RigidShape.prototype);
289 | prototype.constructor = Circle;
290 | Circle.prototype = prototype;
291 |
292 | Circle.prototype.move = function (s) {
293 | this.mStartpoint = this.mStartpoint.add(s);
294 | this.mCenter = this.mCenter.add(s);
295 | return this;
296 | };
297 |
298 | Circle.prototype.draw = function (context) {
299 | context.beginPath();
300 |
301 | //draw a circle
302 | context.arc(this.mCenter.x, this.mCenter.y, this.mRadius, 0, Math.PI * 2, true);
303 |
304 | //draw a line from start point toward center
305 | context.moveTo(this.mStartpoint.x, this.mStartpoint.y);
306 | context.lineTo(this.mCenter.x, this.mCenter.y);
307 |
308 | context.closePath();
309 | context.stroke();
310 | };
311 |
312 | //rotate angle in counterclockwise
313 | Circle.prototype.rotate = function (angle) {
314 | this.mAngle += angle;
315 | this.mStartpoint = this.mStartpoint.rotate(this.mCenter, angle);
316 | return this;
317 | };
318 |
319 | Circle.prototype.updateInertia = function () {
320 | if (this.mInvMass === 0) {
321 | this.mInertia = 0;
322 | } else {
323 | // this.mInvMass is inverted!!
324 | // Inertia=mass * radius^2
325 | // 12 is a constant value that can be changed
326 | this.mInertia = (1 / this.mInvMass) * (this.mRadius * this.mRadius) / 12;
327 | }
328 | };
329 |
330 | /*
331 | * To change this license header, choose License Headers in Project Properties.
332 | * To change this template file, choose Tools | Templates
333 | * and open the template in the editor.
334 | */
335 | /*jslint node: true, vars: true, evil: true, bitwise: true */
336 | "use strict";
337 | /* global RigidShape */
338 |
339 | var Rectangle = function (center, width, height, mass, friction, restitution) {
340 |
341 | RigidShape.call(this, center, mass, friction, restitution);
342 | this.mType = "Rectangle";
343 | this.mWidth = width;
344 | this.mHeight = height;
345 | this.mBoundRadius = Math.sqrt(width * width + height * height) / 2;
346 | this.mVertex = [];
347 | this.mFaceNormal = [];
348 |
349 | //0--TopLeft;1--TopRight;2--BottomRight;3--BottomLeft
350 | this.mVertex[0] = new Vec2(center.x - width / 2, center.y - height / 2);
351 | this.mVertex[1] = new Vec2(center.x + width / 2, center.y - height / 2);
352 | this.mVertex[2] = new Vec2(center.x + width / 2, center.y + height / 2);
353 | this.mVertex[3] = new Vec2(center.x - width / 2, center.y + height / 2);
354 |
355 | //0--Top;1--Right;2--Bottom;3--Left
356 | //mFaceNormal is normal of face toward outside of rectangle
357 | this.mFaceNormal[0] = this.mVertex[1].subtract(this.mVertex[2]);
358 | this.mFaceNormal[0] = this.mFaceNormal[0].normalize();
359 | this.mFaceNormal[1] = this.mVertex[2].subtract(this.mVertex[3]);
360 | this.mFaceNormal[1] = this.mFaceNormal[1].normalize();
361 | this.mFaceNormal[2] = this.mVertex[3].subtract(this.mVertex[0]);
362 | this.mFaceNormal[2] = this.mFaceNormal[2].normalize();
363 | this.mFaceNormal[3] = this.mVertex[0].subtract(this.mVertex[1]);
364 | this.mFaceNormal[3] = this.mFaceNormal[3].normalize();
365 |
366 | this.updateInertia();
367 | };
368 |
369 | var prototype = Object.create(RigidShape.prototype);
370 | prototype.constructor = Rectangle;
371 | Rectangle.prototype = prototype;
372 |
373 | Rectangle.prototype.rotate = function (angle) {
374 | this.mAngle += angle;
375 | var i;
376 | for (i = 0; i < this.mVertex.length; i++) {
377 | this.mVertex[i] = this.mVertex[i].rotate(this.mCenter, angle);
378 | }
379 | this.mFaceNormal[0] = this.mVertex[1].subtract(this.mVertex[2]);
380 | this.mFaceNormal[0] = this.mFaceNormal[0].normalize();
381 | this.mFaceNormal[1] = this.mVertex[2].subtract(this.mVertex[3]);
382 | this.mFaceNormal[1] = this.mFaceNormal[1].normalize();
383 | this.mFaceNormal[2] = this.mVertex[3].subtract(this.mVertex[0]);
384 | this.mFaceNormal[2] = this.mFaceNormal[2].normalize();
385 | this.mFaceNormal[3] = this.mVertex[0].subtract(this.mVertex[1]);
386 | this.mFaceNormal[3] = this.mFaceNormal[3].normalize();
387 | return this;
388 | };
389 |
390 | Rectangle.prototype.move = function (v) {
391 | var i;
392 | for (i = 0; i < this.mVertex.length; i++) {
393 | this.mVertex[i] = this.mVertex[i].add(v);
394 | }
395 | this.mCenter = this.mCenter.add(v);
396 | return this;
397 | };
398 |
399 | Rectangle.prototype.draw = function (context) {
400 | context.save();
401 |
402 | context.translate(this.mVertex[0].x, this.mVertex[0].y);
403 | context.rotate(this.mAngle);
404 | context.strokeRect(0, 0, this.mWidth, this.mHeight);
405 |
406 | context.restore();
407 | };
408 |
409 | Rectangle.prototype.updateInertia = function () {
410 | // Expect this.mInvMass to be already inverted!
411 | if (this.mInvMass === 0) {
412 | this.mInertia = 0;
413 | } else {
414 | //inertia=mass*width^2+height^2
415 | this.mInertia = (1 / this.mInvMass) * (this.mWidth * this.mWidth + this.mHeight * this.mHeight) / 12;
416 | this.mInertia = 1 / this.mInertia;
417 | }
418 | };
419 |
420 | /*
421 | * To change this license header, choose License Headers in Project Properties.
422 | * To change this template file, choose Tools | Templates
423 | * and open the template in the editor.
424 | */
425 | /*jslint node: true, vars: true, evil: true, bitwise: true */
426 | "use strict";
427 | /* global Circle */
428 |
429 | Circle.prototype.collisionTest = function (otherShape, collisionInfo) {
430 | var status = false;
431 | if (otherShape.mType === "Circle") {
432 | status = this.collidedCircCirc(this, otherShape, collisionInfo);
433 | } else {
434 | status = otherShape.collidedRectCirc(this, collisionInfo);
435 | }
436 | return status;
437 | };
438 |
439 | Circle.prototype.collidedCircCirc = function (c1, c2, collisionInfo) {
440 | var vFrom1to2 = c2.mCenter.subtract(c1.mCenter);
441 | var rSum = c1.mRadius + c2.mRadius;
442 | var dist = vFrom1to2.length();
443 | if (dist > Math.sqrt(rSum * rSum)) {
444 | //not overlapping
445 | return false;
446 | }
447 | if (dist !== 0) {
448 | // overlapping bu not same position
449 | var normalFrom2to1 = vFrom1to2.scale(-1).normalize();
450 | var radiusC2 = normalFrom2to1.scale(c2.mRadius);
451 | collisionInfo.setInfo(rSum - dist, vFrom1to2.normalize(), c2.mCenter.add(radiusC2));
452 | } else {
453 | //same position
454 | if (c1.mRadius > c2.mRadius) {
455 | collisionInfo.setInfo(rSum, new Vec2(0, -1), c1.mCenter.add(new Vec2(0, c1.mRadius)));
456 | } else {
457 | collisionInfo.setInfo(rSum, new Vec2(0, -1), c2.mCenter.add(new Vec2(0, c2.mRadius)));
458 | }
459 | }
460 | return true;
461 | };
462 |
463 | /*
464 | * To change this license header, choose License Headers in Project Properties.
465 | * To change this template file, choose Tools | Templates
466 | * and open the template in the editor.
467 | */
468 | /*jslint node: true, vars: true, evil: true, bitwise: true */
469 | "use strict";
470 | /*global Rectangle, Vec2 */
471 |
472 | Rectangle.prototype.collisionTest = function (otherShape, collisionInfo) {
473 | var status = false;
474 | if (otherShape.mType === "Circle") {
475 | status = this.collidedRectCirc(otherShape, collisionInfo);
476 | } else {
477 | status = this.collidedRectRect(this, otherShape, collisionInfo);
478 | }
479 | return status;
480 | };
481 |
482 | var SupportStruct = function () {
483 | this.mSupportPoint = null;
484 | this.mSupportPointDist = 0;
485 | };
486 | var tmpSupport = new SupportStruct();
487 |
488 | Rectangle.prototype.findSupportPoint = function (dir, ptOnEdge) {
489 | //the longest project length
490 | var vToEdge;
491 | var projection;
492 |
493 | tmpSupport.mSupportPointDist = -9999999;
494 | tmpSupport.mSupportPoint = null;
495 | //check each vector of other object
496 | for (var i = 0; i < this.mVertex.length; i++) {
497 | vToEdge = this.mVertex[i].subtract(ptOnEdge);
498 | projection = vToEdge.dot(dir);
499 |
500 | //find the longest distance with certain edge
501 | //dir is -n direction, so the distance should be positive
502 | if ((projection > 0) && (projection > tmpSupport.mSupportPointDist)) {
503 | tmpSupport.mSupportPoint = this.mVertex[i];
504 | tmpSupport.mSupportPointDist = projection;
505 | }
506 | }
507 | };
508 |
509 | /**
510 | * Find the shortest axis that overlapping
511 | * @memberOf Rectangle
512 | * @param {Rectangle} otherRect another rectangle that being tested
513 | * @param {CollisionInfo} collisionInfo record the collision information
514 | * @returns {Boolean} true if has overlap part in all four directions.
515 | * the code is convert from http://gamedevelopment.tutsplus.com/tutorials/how-to-create-a-custom-2d-physics-engine-oriented-rigid-bodies--gamedev-8032
516 | */
517 | Rectangle.prototype.findAxisLeastPenetration = function (otherRect, collisionInfo) {
518 |
519 | var n;
520 | var supportPoint;
521 |
522 | var bestDistance = 999999;
523 | var bestIndex = null;
524 |
525 | var hasSupport = true;
526 | var i = 0;
527 |
528 | while ((hasSupport) && (i < this.mFaceNormal.length)) {
529 | // Retrieve a face normal from A
530 | n = this.mFaceNormal[i];
531 |
532 | // use -n as direction and the vectex on edge i as point on edge
533 | var dir = n.scale(-1);
534 | var ptOnEdge = this.mVertex[i];
535 | // find the support on B
536 | // the point has longest distance with edge i
537 | otherRect.findSupportPoint(dir, ptOnEdge);
538 | hasSupport = (tmpSupport.mSupportPoint !== null);
539 |
540 | //get the shortest support point depth
541 | if ((hasSupport) && (tmpSupport.mSupportPointDist < bestDistance)) {
542 | bestDistance = tmpSupport.mSupportPointDist;
543 | bestIndex = i;
544 | supportPoint = tmpSupport.mSupportPoint;
545 | }
546 | i = i + 1;
547 | }
548 | if (hasSupport) {
549 | //all four directions have support point
550 | var bestVec = this.mFaceNormal[bestIndex].scale(bestDistance);
551 | collisionInfo.setInfo(bestDistance, this.mFaceNormal[bestIndex], supportPoint.add(bestVec));
552 | }
553 | return hasSupport;
554 | };
555 | /**
556 | * Check for collision between RigidRectangle and RigidRectangle
557 | * @param {Rectangle} r1 Rectangle object to check for collision status
558 | * @param {Rectangle} r2 Rectangle object to check for collision status against
559 | * @param {CollisionInfo} collisionInfo Collision info of collision
560 | * @returns {Boolean} true if collision occurs
561 | * @memberOf Rectangle
562 | */
563 | var collisionInfoR1 = new CollisionInfo();
564 | var collisionInfoR2 = new CollisionInfo();
565 | Rectangle.prototype.collidedRectRect = function (r1, r2, collisionInfo) {
566 |
567 | var status1 = false;
568 | var status2 = false;
569 |
570 | //find Axis of Separation for both rectangle
571 | status1 = r1.findAxisLeastPenetration(r2, collisionInfoR1);
572 |
573 | if (status1) {
574 | status2 = r2.findAxisLeastPenetration(r1, collisionInfoR2);
575 | if (status2) {
576 | //if both of rectangles are overlapping, choose the shorter normal as the normal
577 | if (collisionInfoR1.getDepth() < collisionInfoR2.getDepth()) {
578 | var depthVec = collisionInfoR1.getNormal().scale(collisionInfoR1.getDepth());
579 | collisionInfo.setInfo(collisionInfoR1.getDepth(), collisionInfoR1.getNormal(), collisionInfoR1.mStart.subtract(depthVec));
580 | } else {
581 | collisionInfo.setInfo(collisionInfoR2.getDepth(), collisionInfoR2.getNormal().scale(-1), collisionInfoR2.mStart);
582 | }
583 | }
584 | }
585 | return status1 && status2;
586 | };
587 |
588 | /**
589 | * Check for collision between Rectangle and Circle
590 | * @param {Circle} otherCir circle to check for collision status against
591 | * @param {CollisionInfo} collisionInfo Collision info of collision
592 | * @returns {Boolean} true if collision occurs
593 | * @memberOf Rectangle
594 | */
595 | Rectangle.prototype.collidedRectCirc = function (otherCir, collisionInfo) {
596 |
597 | var inside = true;
598 | var bestDistance = -99999;
599 | var nearestEdge = 0;
600 | var i, v;
601 | var circ2Pos, projection;
602 | for (i = 0; i < 4; i++) {
603 | //find the nearest face for center of circle
604 | circ2Pos = otherCir.mCenter;
605 | v = circ2Pos.subtract(this.mVertex[i]);
606 | projection = v.dot(this.mFaceNormal[i]);
607 | if (projection > 0) {
608 | //if the center of circle is outside of rectangle
609 | bestDistance = projection;
610 | nearestEdge = i;
611 | inside = false;
612 | break;
613 | }
614 | if (projection > bestDistance) {
615 | bestDistance = projection;
616 | nearestEdge = i;
617 | }
618 | }
619 | var dis, normal, radiusVec;
620 | if (!inside) {
621 | //the center of circle is outside of rectangle
622 |
623 | //v1 is from left vertex of face to center of circle
624 | //v2 is from left vertex of face to right vertex of face
625 | var v1 = circ2Pos.subtract(this.mVertex[nearestEdge]);
626 | var v2 = this.mVertex[(nearestEdge + 1) % 4].subtract(this.mVertex[nearestEdge]);
627 |
628 | var dot = v1.dot(v2);
629 |
630 | if (dot < 0) {
631 | //the center of circle is in corner region of mVertex[nearestEdge]
632 | dis = v1.length();
633 | //compare the distance with radium to decide collision
634 | if (dis > otherCir.mRadius) {
635 | return false;
636 | }
637 |
638 | normal = v1.normalize();
639 | radiusVec = normal.scale(-otherCir.mRadius);
640 | collisionInfo.setInfo(otherCir.mRadius - dis, normal, circ2Pos.add(radiusVec));
641 | } else {
642 | //the center of circle is in corner region of mVertex[nearestEdge+1]
643 |
644 | //v1 is from right vertex of face to center of circle
645 | //v2 is from right vertex of face to left vertex of face
646 | v1 = circ2Pos.subtract(this.mVertex[(nearestEdge + 1) % 4]);
647 | v2 = v2.scale(-1);
648 | dot = v1.dot(v2);
649 | if (dot < 0) {
650 | dis = v1.length();
651 | //compare the distance with radium to decide collision
652 | if (dis > otherCir.mRadius) {
653 | return false;
654 | }
655 | normal = v1.normalize();
656 | radiusVec = normal.scale(-otherCir.mRadius);
657 | collisionInfo.setInfo(otherCir.mRadius - dis, normal, circ2Pos.add(radiusVec));
658 | } else {
659 | //the center of circle is in face region of face[nearestEdge]
660 | if (bestDistance < otherCir.mRadius) {
661 | radiusVec = this.mFaceNormal[nearestEdge].scale(otherCir.mRadius);
662 | collisionInfo.setInfo(otherCir.mRadius - bestDistance, this.mFaceNormal[nearestEdge], circ2Pos.subtract(radiusVec));
663 | } else {
664 | return false;
665 | }
666 | }
667 | }
668 | } else {
669 | //the center of circle is inside of rectangle
670 | radiusVec = this.mFaceNormal[nearestEdge].scale(otherCir.mRadius);
671 | collisionInfo.setInfo(otherCir.mRadius - bestDistance, this.mFaceNormal[nearestEdge], circ2Pos.subtract(radiusVec));
672 | }
673 | return true;
674 | };
675 |
676 | /*
677 | The following is not free software. You may use it for educational purposes, but you may not redistribute or use it commercially.
678 | (C) Burak Kanber 2012
679 | */
680 | /* global objectNum, context, mRelaxationCount, mAllObjects, mPosCorrectionRate */
681 | /*jslint node: true, vars: true, evil: true, bitwise: true */
682 | "use strict";
683 | var gEngine = gEngine || {};
684 | // initialize the variable while ensuring it is not redefined
685 |
686 | gEngine.Physics = (function () {
687 |
688 | var mPositionalCorrectionFlag = true;
689 | var mRelaxationCount = 15; // number of relaxation iteration
690 | var mPosCorrectionRate = 0.8; // percentage of separation to project objects
691 |
692 | var positionalCorrection = function (s1, s2, collisionInfo) {
693 | var s1InvMass = s1.mInvMass;
694 | var s2InvMass = s2.mInvMass;
695 |
696 | var num = collisionInfo.getDepth() / (s1InvMass + s2InvMass) * mPosCorrectionRate;
697 | var correctionAmount = collisionInfo.getNormal().scale(num);
698 |
699 | s1.move(correctionAmount.scale(-s1InvMass));
700 | s2.move(correctionAmount.scale(s2InvMass));
701 | };
702 |
703 | var resolveCollision = function (s1, s2, collisionInfo) {
704 |
705 | if ((s1.mInvMass === 0) && (s2.mInvMass === 0)) {
706 | return;
707 | }
708 |
709 | // correct positions
710 | if (gEngine.Physics.mPositionalCorrectionFlag) {
711 | positionalCorrection(s1, s2, collisionInfo);
712 | }
713 |
714 | var n = collisionInfo.getNormal();
715 |
716 | //the direction of collisionInfo is always from s1 to s2
717 | //but the Mass is inversed, so start scale with s2 and end scale with s1
718 | var start = collisionInfo.mStart.scale(s2.mInvMass / (s1.mInvMass + s2.mInvMass));
719 | var end = collisionInfo.mEnd.scale(s1.mInvMass / (s1.mInvMass + s2.mInvMass));
720 | var p = start.add(end);
721 | //r is vector from center of object to collision point
722 | var r1 = p.subtract(s1.mCenter);
723 | var r2 = p.subtract(s2.mCenter);
724 |
725 | //newV = V + mAngularVelocity cross R
726 | var v1 = s1.mVelocity.add(new Vec2(-1 * s1.mAngularVelocity * r1.y, s1.mAngularVelocity * r1.x));
727 | var v2 = s2.mVelocity.add(new Vec2(-1 * s2.mAngularVelocity * r2.y, s2.mAngularVelocity * r2.x));
728 | var relativeVelocity = v2.subtract(v1);
729 |
730 | // Relative velocity in normal direction
731 | var rVelocityInNormal = relativeVelocity.dot(n);
732 |
733 | //if objects moving apart ignore
734 | if (rVelocityInNormal > 0) {
735 | return;
736 | }
737 |
738 | // compute and apply response impulses for each object
739 | var newRestituion = Math.min(s1.mRestitution, s2.mRestitution);
740 | var newFriction = Math.min(s1.mFriction, s2.mFriction);
741 |
742 | //R cross N
743 | var R1crossN = r1.cross(n);
744 | var R2crossN = r2.cross(n);
745 |
746 | // Calc impulse scalar
747 | // the formula of jN can be found in http://www.myphysicslab.com/collision.html
748 | var jN = -(1 + newRestituion) * rVelocityInNormal;
749 | jN = jN / (s1.mInvMass + s2.mInvMass +
750 | R1crossN * R1crossN * s1.mInertia +
751 | R2crossN * R2crossN * s2.mInertia);
752 |
753 | //impulse is in direction of normal ( from s1 to s2)
754 | var impulse = n.scale(jN);
755 | // impulse = F dt = m * ?v
756 | // ?v = impulse / m
757 | s1.mVelocity = s1.mVelocity.subtract(impulse.scale(s1.mInvMass));
758 | s2.mVelocity = s2.mVelocity.add(impulse.scale(s2.mInvMass));
759 |
760 | s1.mAngularVelocity -= R1crossN * jN * s1.mInertia;
761 | s2.mAngularVelocity += R2crossN * jN * s2.mInertia;
762 |
763 | var tangent = relativeVelocity.subtract(n.scale(relativeVelocity.dot(n)));
764 |
765 | //relativeVelocity.dot(tangent) should less than 0
766 | tangent = tangent.normalize().scale(-1);
767 |
768 | var R1crossT = r1.cross(tangent);
769 | var R2crossT = r2.cross(tangent);
770 |
771 | var jT = -(1 + newRestituion) * relativeVelocity.dot(tangent) * newFriction;
772 | jT = jT / (s1.mInvMass + s2.mInvMass + R1crossT * R1crossT * s1.mInertia + R2crossT * R2crossT * s2.mInertia);
773 |
774 | //friction should less than force in normal direction
775 | if (jT > jN) {
776 | jT = jN;
777 | }
778 |
779 | //impulse is from s1 to s2 (in opposite direction of velocity)
780 | impulse = tangent.scale(jT);
781 |
782 | s1.mVelocity = s1.mVelocity.subtract(impulse.scale(s1.mInvMass));
783 | s2.mVelocity = s2.mVelocity.add(impulse.scale(s2.mInvMass));
784 | s1.mAngularVelocity -= R1crossT * jT * s1.mInertia;
785 | s2.mAngularVelocity += R2crossT * jT * s2.mInertia;
786 | };
787 |
788 | var drawCollisionInfo = function (collisionInfo, context) {
789 | context.beginPath();
790 | context.moveTo(collisionInfo.mStart.x, collisionInfo.mStart.y);
791 | context.lineTo(collisionInfo.mEnd.x, collisionInfo.mEnd.y);
792 | context.closePath();
793 | context.strokeStyle = "orange";
794 | context.stroke();
795 | };
796 | var collision = function () {
797 | var i, j, k;
798 | var collisionInfo = new CollisionInfo();
799 | for (k = 0; k < mRelaxationCount; k++) {
800 | for (i = 0; i < gEngine.Core.mAllObjects.length; i++) {
801 | for (j = i + 1; j < gEngine.Core.mAllObjects.length; j++) {
802 | if (gEngine.Core.mAllObjects[i].boundTest(gEngine.Core.mAllObjects[j])) {
803 | if (gEngine.Core.mAllObjects[i].collisionTest(gEngine.Core.mAllObjects[j], collisionInfo)) {
804 | //make sure the normal is always from object[i] to object[j]
805 | if (collisionInfo.getNormal().dot(gEngine.Core.mAllObjects[j].mCenter.subtract(gEngine.Core.mAllObjects[i].mCenter)) < 0) {
806 | collisionInfo.changeDir();
807 | }
808 |
809 | //draw collision info (a black line that shows normal)
810 | //drawCollisionInfo(collisionInfo, gEngine.Core.mContext);
811 |
812 | resolveCollision(gEngine.Core.mAllObjects[i], gEngine.Core.mAllObjects[j], collisionInfo);
813 | }
814 | }
815 | }
816 | }
817 | }
818 | };
819 | var mPublic = {
820 | collision: collision,
821 | mPositionalCorrectionFlag: mPositionalCorrectionFlag
822 | };
823 |
824 | return mPublic;
825 | }());
826 |
827 | /*
828 | * To change this license header, choose License Headers in Project Properties.
829 | * To change this template file, choose Tools | Templates
830 | * and open the template in the editor.
831 | */
832 |
833 |
834 | /*jslint node: true, vars: true, evil: true, bitwise: true */
835 | /*global requestAnimationFrame: false */
836 | /*global document,gObjectNum */
837 | "use strict"; // Operate in Strict mode such that variables must be declared before used!
838 |
839 | /**
840 | * Static refrence to gEngine
841 | * @type gEngine
842 | */
843 | var gEngine = gEngine || {};
844 | // initialize the variable while ensuring it is not redefined
845 | gEngine.Core = (function () {
846 | var mCanvas, mContext, mWidth = 800, mHeight = 450;
847 | mCanvas = document.getElementById('canvas');
848 | mContext = mCanvas.getContext('2d');
849 | mCanvas.height = mHeight;
850 | mCanvas.width = mWidth;
851 |
852 | var mGravity = new Vec2(0, 20);
853 | var mMovement = true;
854 |
855 | var mCurrentTime, mElapsedTime, mPreviousTime = Date.now(), mLagTime = 0;
856 | var kFPS = 60; // Frames per second
857 | var kFrameTime = 1 / kFPS;
858 | var mUpdateIntervalInSeconds = kFrameTime;
859 | var kMPF = 1000 * kFrameTime; // Milliseconds per frame.
860 | var mAllObjects = [];
861 |
862 | var updateUIEcho = function () {
863 | document.getElementById("uiEchoString").innerHTML =
864 | "Selected Object::
" +
865 | "" +
866 | "- Id: " + gObjectNum + "
" +
867 | "- Center: " + mAllObjects[gObjectNum].mCenter.x.toPrecision(3) + "," + mAllObjects[gObjectNum].mCenter.y.toPrecision(3) + "
" +
868 | "- Angle: " + mAllObjects[gObjectNum].mAngle.toPrecision(3) + "
" +
869 | "- Velocity: " + mAllObjects[gObjectNum].mVelocity.x.toPrecision(3) + "," + mAllObjects[gObjectNum].mVelocity.y.toPrecision(3) + "
" +
870 | "- AngluarVelocity: " + mAllObjects[gObjectNum].mAngularVelocity.toPrecision(3) + "
" +
871 | "- Mass: " + 1 / mAllObjects[gObjectNum].mInvMass.toPrecision(3) + "
" +
872 | "- Friction: " + mAllObjects[gObjectNum].mFriction.toPrecision(3) + "
" +
873 | "- Restitution: " + mAllObjects[gObjectNum].mRestitution.toPrecision(3) + "
" +
874 | "- Positional Correction: " + gEngine.Physics.mPositionalCorrectionFlag + "
" +
875 | "- Movement: " + gEngine.Core.mMovement + "
" +
876 | "
" +
877 | "Control: of selected object
" +
878 | "" +
879 | "- Num or Up/Down Arrow: Select Object
" +
880 | "- WASD + QE: Position [Move + Rotate]
" +
881 | "- IJKL + UO: Velocities [Linear + Angular]
" +
882 | "- Z/X: Mass [Decrease/Increase]
" +
883 | "- C/V: Frictrion [Decrease/Increase]
" +
884 | "- B/N: Restitution [Decrease/Increase]
" +
885 | "- M: Positional Correction [On/Off]
" +
886 | "- ,: Movement [On/Off]
" +
887 | "
" +
888 | "F/G: Spawn [Rectangle/Circle] at selected object" +
889 | "H: Excite all objects
" +
890 | "R: Reset System
" +
891 | "
";
892 | };
893 | var draw = function () {
894 | mContext.clearRect(0, 0, mWidth, mHeight);
895 | var i;
896 | for (i = 0; i < mAllObjects.length; i++) {
897 | mContext.strokeStyle = 'blue';
898 | if (i === gObjectNum) {
899 | mContext.strokeStyle = 'red';
900 | }
901 | mAllObjects[i].draw(mContext);
902 | }
903 | };
904 | var update = function () {
905 | var i;
906 | for (i = 0; i < mAllObjects.length; i++) {
907 | mAllObjects[i].update(mContext);
908 | }
909 | };
910 | var runGameLoop = function () {
911 | requestAnimationFrame(function () {
912 | runGameLoop();
913 | });
914 |
915 | // compute how much time has elapsed since we last runGameLoop was executed
916 | mCurrentTime = Date.now();
917 | mElapsedTime = mCurrentTime - mPreviousTime;
918 | mPreviousTime = mCurrentTime;
919 | mLagTime += mElapsedTime;
920 |
921 | updateUIEcho();
922 | draw();
923 | // Make sure we update the game the appropriate number of times.
924 | // Update only every Milliseconds per frame.
925 | // If lag larger then update frames, update until caught up.
926 | while (mLagTime >= kMPF) {
927 | mLagTime -= kMPF;
928 | gEngine.Physics.collision();
929 | update();
930 | }
931 | };
932 | var initializeEngineCore = function () {
933 | runGameLoop();
934 | };
935 | var mPublic = {
936 | initializeEngineCore: initializeEngineCore,
937 | mAllObjects: mAllObjects,
938 | mWidth: mWidth,
939 | mHeight: mHeight,
940 | mContext: mContext,
941 | mGravity: mGravity,
942 | mUpdateIntervalInSeconds: mUpdateIntervalInSeconds,
943 | mMovement: mMovement
944 | };
945 | return mPublic;
946 | }());
947 |
948 | /*
949 | * To change this license header, choose License Headers in Project Properties.
950 | * To change this template file, choose Tools | Templates
951 | * and open the template in the editor.
952 | */
953 | /*jslint node: true, vars: true, evil: true, bitwise: true */
954 | "use strict";
955 | /* global height, width, gEngine */
956 | function MyGame() {
957 | var r1 = new Rectangle(new Vec2(500, 200), 400, 20, 0, 0.3, 0);
958 | r1.rotate(2.8);
959 | var r2 = new Rectangle(new Vec2(200, 400), 400, 20, 0, 1, 0.5);
960 | var r3 = new Rectangle(new Vec2(100, 200), 200, 20, 0);
961 | var r4 = new Rectangle(new Vec2(10, 360), 20, 100, 0, 0, 1);
962 |
963 | for (var i = 0; i < 10; i++) {
964 | var r1 = new Rectangle(new Vec2(Math.random() * gEngine.Core.mWidth, Math.random() * gEngine.Core.mHeight / 2), Math.random() * 50 + 10, Math.random() * 50 + 10, Math.random() * 30, Math.random(), Math.random());
965 | r1.mVelocity = new Vec2(Math.random() * 60 - 30, Math.random() * 60 - 30);
966 | var r1 = new Circle(new Vec2(Math.random() * gEngine.Core.mWidth, Math.random() * gEngine.Core.mHeight / 2), Math.random() * 20 + 10, Math.random() * 30, Math.random(), Math.random());
967 | r1.mVelocity = new Vec2(Math.random() * 60 - 30, Math.random() * 60 - 30);
968 | }
969 | }
970 |
971 | /*
972 | * To change this license header, choose License Headers in Project Properties.
973 | * To change this template file, choose Tools | Templates
974 | * and open the template in the editor.
975 | */
976 | /*jslint node: true, vars: true, evil: true, bitwise: true */
977 | "use strict";
978 | /* global mAllObjects, gEngine */
979 |
980 | var gObjectNum = 0;
981 | function userControl(event) {
982 | var keycode;
983 |
984 | if (window.event) {
985 | //alert('ie');
986 | keycode = event.keyCode;
987 | } else if (event.which) {
988 | //alert('firefox ');
989 | keycode = event.which;
990 | }
991 | if (keycode >= 48 && keycode <= 57) {
992 | if (keycode - 48 < gEngine.Core.mAllObjects.length) {
993 | gObjectNum = keycode - 48;
994 | }
995 | }
996 | if (keycode === 38) {
997 | //up arrow
998 | if (gObjectNum > 0) {
999 | gObjectNum--;
1000 | }
1001 | }
1002 | if (keycode === 40) {
1003 | // down arrow
1004 | if (gObjectNum < gEngine.Core.mAllObjects.length - 1) {
1005 | gObjectNum++;
1006 | }
1007 | }
1008 | if (keycode === 87) {
1009 | //W
1010 | gEngine.Core.mAllObjects[gObjectNum].move(new Vec2(0, -10));
1011 | }
1012 | if (keycode === 83) {
1013 | // S
1014 | gEngine.Core.mAllObjects[gObjectNum].move(new Vec2(0, +10));
1015 | }
1016 | if (keycode === 65) {
1017 | //A
1018 | gEngine.Core.mAllObjects[gObjectNum].move(new Vec2(-10, 0));
1019 | }
1020 | if (keycode === 68) {
1021 | //D
1022 | gEngine.Core.mAllObjects[gObjectNum].move(new Vec2(10, 0));
1023 | }
1024 | if (keycode === 81) {
1025 | //Q
1026 | gEngine.Core.mAllObjects[gObjectNum].rotate(-0.1);
1027 | }
1028 | if (keycode === 69) {
1029 | //E
1030 | gEngine.Core.mAllObjects[gObjectNum].rotate(0.1);
1031 | }
1032 | if (keycode === 73) {
1033 | //I
1034 | gEngine.Core.mAllObjects[gObjectNum].mVelocity.y -= 1;
1035 | }
1036 | if (keycode === 75) {
1037 | // k
1038 | gEngine.Core.mAllObjects[gObjectNum].mVelocity.y += 1;
1039 | }
1040 | if (keycode === 74) {
1041 | //j
1042 | gEngine.Core.mAllObjects[gObjectNum].mVelocity.x -= 1;
1043 | }
1044 | if (keycode === 76) {
1045 | //l
1046 | gEngine.Core.mAllObjects[gObjectNum].mVelocity.x += 1;
1047 | }
1048 | if (keycode === 85) {
1049 | //U
1050 | gEngine.Core.mAllObjects[gObjectNum].mAngularVelocity -= 0.1;
1051 | }
1052 | if (keycode === 79) {
1053 | //O
1054 | gEngine.Core.mAllObjects[gObjectNum].mAngularVelocity += 0.1;
1055 | }
1056 | if (keycode === 90) {
1057 | //Z
1058 | gEngine.Core.mAllObjects[gObjectNum].updateMass(-1);
1059 | }
1060 | if (keycode === 88) {
1061 | //X
1062 | gEngine.Core.mAllObjects[gObjectNum].updateMass(1);
1063 | }
1064 | if (keycode === 67) {
1065 | //C
1066 | gEngine.Core.mAllObjects[gObjectNum].mFriction -= 0.01;
1067 | }
1068 | if (keycode === 86) {
1069 | //V
1070 | gEngine.Core.mAllObjects[gObjectNum].mFriction += 0.01;
1071 | }
1072 | if (keycode === 66) {
1073 | //B
1074 | gEngine.Core.mAllObjects[gObjectNum].mRestitution -= 0.01;
1075 | }
1076 | if (keycode === 78) {
1077 | //N
1078 | gEngine.Core.mAllObjects[gObjectNum].mRestitution += 0.01;
1079 | }
1080 | if (keycode === 77) {
1081 | //M
1082 | gEngine.Physics.mPositionalCorrectionFlag = !gEngine.Physics.mPositionalCorrectionFlag;
1083 | }
1084 | if (keycode === 188) {
1085 | //,
1086 | gEngine.Core.mMovement = !gEngine.Core.mMovement;
1087 | }
1088 | if (keycode === 70) {
1089 | //f
1090 | var r1 = new Rectangle(new Vec2(gEngine.Core.mAllObjects[gObjectNum].mCenter.x, gEngine.Core.mAllObjects[gObjectNum].mCenter.y), Math.random() * 30 + 10, Math.random() * 30 + 10, Math.random() * 30, Math.random(), Math.random());
1091 | r1.mVelocity = new Vec2(Math.random() * 300 - 150, Math.random() * 300 - 150);
1092 | }
1093 | if (keycode === 71) {
1094 | //g
1095 | var r1 = new Circle(new Vec2(gEngine.Core.mAllObjects[gObjectNum].mCenter.x, gEngine.Core.mAllObjects[gObjectNum].mCenter.y), Math.random() * 10 + 20, Math.random() * 30, Math.random(), Math.random());
1096 | r1.mVelocity = new Vec2(Math.random() * 300 - 150, Math.random() * 300 - 150);
1097 | }
1098 |
1099 | if (keycode === 72) {
1100 | //H
1101 | var i;
1102 | for (i = 0; i < gEngine.Core.mAllObjects.length; i++) {
1103 | if (gEngine.Core.mAllObjects[i].mInvMass !== 0) {
1104 | gEngine.Core.mAllObjects[i].mVelocity = new Vec2(Math.random() * 500 - 250, Math.random() * 500 - 250);
1105 | }
1106 | }
1107 | }
1108 | if (keycode === 82) {
1109 | //R
1110 | gEngine.Core.mAllObjects.splice(7, gEngine.Core.mAllObjects.length);
1111 | gObjectNum = 0;
1112 | }
1113 | }
--------------------------------------------------------------------------------
/book/public_html/all.min.js:
--------------------------------------------------------------------------------
1 | "use strict";var t=function(t,e){this.x=t,this.y=e};function e(){this.mDepth=0,this.mNormal=new t(0,0),this.mStart=new t(0,0),this.mEnd=new t(0,0)}function i(e,i,s,r){this.mCenter=e,this.mInertia=0,this.mInvMass=void 0!==i?i:1,this.mFriction=void 0!==s?s:.8,this.mRestitution=void 0!==r?r:.2,this.mVelocity=new t(0,0),0!==this.mInvMass?(this.mInvMass=1/this.mInvMass,this.mAcceleration=m.Core.mGravity):this.mAcceleration=new t(0,0),this.mAngle=0,this.mAngularVelocity=0,this.mAngularAcceleration=0,this.mBoundRadius=0,m.Core.mAllObjects.push(this)}t.prototype.length=function(){return Math.sqrt(this.x*this.x+this.y*this.y)},t.prototype.add=function(e){return new t(e.x+this.x,e.y+this.y)},t.prototype.subtract=function(e){return new t(this.x-e.x,this.y-e.y)},t.prototype.scale=function(e){return new t(this.x*e,this.y*e)},t.prototype.dot=function(t){return this.x*t.x+this.y*t.y},t.prototype.cross=function(t){return this.x*t.y-this.y*t.x},t.prototype.rotate=function(e,i){var s=[],r=this.x-e.x,o=this.y-e.y;return s[0]=r*Math.cos(i)-o*Math.sin(i),s[1]=r*Math.sin(i)+o*Math.cos(i),s[0]+=e.x,s[1]+=e.y,new t(s[0],s[1])},t.prototype.normalize=function(){var e=this.length();return e>0&&(e=1/e),new t(this.x*e,this.y*e)},t.prototype.distance=function(t){var e=this.x-t.x,i=this.y-t.y;return Math.sqrt(e*e+i*i)},e.prototype.setDepth=function(t){this.mDepth=t},e.prototype.setNormal=function(t){this.mNormal=t},e.prototype.getDepth=function(){return this.mDepth},e.prototype.getNormal=function(){return this.mNormal},e.prototype.setInfo=function(t,e,i){this.mDepth=t,this.mNormal=e,this.mStart=i,this.mEnd=i.add(e.scale(t))},e.prototype.changeDir=function(){this.mNormal=this.mNormal.scale(-1);var t=this.mStart;this.mStart=this.mEnd,this.mEnd=t},i.prototype.updateMass=function(e){var i;i=0!==this.mInvMass?1/this.mInvMass:0,(i+=e)<=0?(this.mInvMass=0,this.mVelocity=new t(0,0),this.mAcceleration=new t(0,0),this.mAngularVelocity=0,this.mAngularAcceleration=0):(this.mInvMass=1/i,this.mAcceleration=m.Core.mGravity),this.updateInertia()},i.prototype.updateInertia=function(){},i.prototype.update=function(){if(m.Core.mMovement){var t=m.Core.mUpdateIntervalInSeconds;this.mVelocity=this.mVelocity.add(this.mAcceleration.scale(t)),this.move(this.mVelocity.scale(t)),this.mAngularVelocity+=this.mAngularAcceleration*t,this.rotate(this.mAngularVelocity*t)}var e=m.Core.mWidth,i=m.Core.mHeight;if(this.mCenter.x<0||this.mCenter.x>e||this.mCenter.y<0||this.mCenter.y>i){var s=m.Core.mAllObjects.indexOf(this);s>-1&&m.Core.mAllObjects.splice(s,1)}},i.prototype.boundTest=function(t){var e=t.mCenter.subtract(this.mCenter),i=this.mBoundRadius+t.mBoundRadius;return!(e.length()>i)};var s=function(e,s,r,o,n){i.call(this,e,r,o,n),this.mType="Circle",this.mRadius=s,this.mBoundRadius=s,this.mStartpoint=new t(e.x,e.y-s),this.updateInertia()};(r=Object.create(i.prototype)).constructor=s,s.prototype=r,s.prototype.move=function(t){return this.mStartpoint=this.mStartpoint.add(t),this.mCenter=this.mCenter.add(t),this},s.prototype.draw=function(t){t.beginPath(),t.arc(this.mCenter.x,this.mCenter.y,this.mRadius,0,2*Math.PI,!0),t.moveTo(this.mStartpoint.x,this.mStartpoint.y),t.lineTo(this.mCenter.x,this.mCenter.y),t.closePath(),t.stroke()},s.prototype.rotate=function(t){return this.mAngle+=t,this.mStartpoint=this.mStartpoint.rotate(this.mCenter,t),this},s.prototype.updateInertia=function(){0===this.mInvMass?this.mInertia=0:this.mInertia=1/this.mInvMass*(this.mRadius*this.mRadius)/12};var r,o=function(e,s,r,o,n,a){i.call(this,e,o,n,a),this.mType="Rectangle",this.mWidth=s,this.mHeight=r,this.mBoundRadius=Math.sqrt(s*s+r*r)/2,this.mVertex=[],this.mFaceNormal=[],this.mVertex[0]=new t(e.x-s/2,e.y-r/2),this.mVertex[1]=new t(e.x+s/2,e.y-r/2),this.mVertex[2]=new t(e.x+s/2,e.y+r/2),this.mVertex[3]=new t(e.x-s/2,e.y+r/2),this.mFaceNormal[0]=this.mVertex[1].subtract(this.mVertex[2]),this.mFaceNormal[0]=this.mFaceNormal[0].normalize(),this.mFaceNormal[1]=this.mVertex[2].subtract(this.mVertex[3]),this.mFaceNormal[1]=this.mFaceNormal[1].normalize(),this.mFaceNormal[2]=this.mVertex[3].subtract(this.mVertex[0]),this.mFaceNormal[2]=this.mFaceNormal[2].normalize(),this.mFaceNormal[3]=this.mVertex[0].subtract(this.mVertex[1]),this.mFaceNormal[3]=this.mFaceNormal[3].normalize(),this.updateInertia()};(r=Object.create(i.prototype)).constructor=o,o.prototype=r,o.prototype.rotate=function(t){var e;for(this.mAngle+=t,e=0;eMath.sqrt(o*o))return!1;if(0!==n){var a=r.scale(-1).normalize().scale(i.mRadius);s.setInfo(o-n,r.normalize(),i.mCenter.add(a))}else e.mRadius>i.mRadius?s.setInfo(o,new t(0,-1),e.mCenter.add(new t(0,e.mRadius))):s.setInfo(o,new t(0,-1),i.mCenter.add(new t(0,i.mRadius)));return!0},o.prototype.collisionTest=function(t,e){return"Circle"===t.mType?this.collidedRectCirc(t,e):this.collidedRectRect(this,t,e)};var n=new function(){this.mSupportPoint=null,this.mSupportPointDist=0};o.prototype.findSupportPoint=function(t,e){var i;n.mSupportPointDist=-9999999,n.mSupportPoint=null;for(var s=0;s0&&i>n.mSupportPointDist&&(n.mSupportPoint=this.mVertex[s],n.mSupportPointDist=i)},o.prototype.findAxisLeastPenetration=function(t,e){for(var i,s=999999,r=null,o=!0,a=0;o&&a0){l=r,c=i,m=!1;break}r>l&&(l=r,c=i)}if(m)a=this.mFaceNormal[c].scale(t.mRadius),e.setInfo(t.mRadius-l,this.mFaceNormal[c],s.subtract(a));else{var h=s.subtract(this.mVertex[c]),u=this.mVertex[(c+1)%4].subtract(this.mVertex[c]),p=h.dot(u);if(p<0){if((o=h.length())>t.mRadius)return!1;a=(n=h.normalize()).scale(-t.mRadius),e.setInfo(t.mRadius-o,n,s.add(a))}else if(h=s.subtract(this.mVertex[(c+1)%4]),u=u.scale(-1),(p=h.dot(u))<0){if((o=h.length())>t.mRadius)return!1;a=(n=h.normalize()).scale(-t.mRadius),e.setInfo(t.mRadius-o,n,s.add(a))}else{if(!(l0)){var d=Math.min(e.mRestitution,i.mRestitution),y=Math.min(e.mFriction,i.mFriction),f=l.cross(r),v=c.cross(r),b=-(1+d)*p;b/=e.mInvMass+i.mInvMass+f*f*e.mInertia+v*v*i.mInertia;var x=r.scale(b);e.mVelocity=e.mVelocity.subtract(x.scale(e.mInvMass)),i.mVelocity=i.mVelocity.add(x.scale(i.mInvMass)),e.mAngularVelocity-=f*b*e.mInertia,i.mAngularVelocity+=v*b*i.mInertia;var I=u.subtract(r.scale(u.dot(r)));I=I.normalize().scale(-1);var g=l.cross(I),C=c.cross(I),V=-(1+d)*u.dot(I)*y;(V/=e.mInvMass+i.mInvMass+g*g*e.mInertia+C*C*i.mInertia)>b&&(V=b),x=I.scale(V),e.mVelocity=e.mVelocity.subtract(x.scale(e.mInvMass)),i.mVelocity=i.mVelocity.add(x.scale(i.mInvMass)),e.mAngularVelocity-=g*V*e.mInertia,i.mAngularVelocity+=C*V*i.mInertia}}},{collision:function(){var t,i,s,r=new e;for(s=0;s<15;s++)for(t=0;tSelected Object::- Id: '+h+"
- Center: "+l[h].mCenter.x.toPrecision(3)+","+l[h].mCenter.y.toPrecision(3)+"
- Angle: "+l[h].mAngle.toPrecision(3)+"
- Velocity: "+l[h].mVelocity.x.toPrecision(3)+","+l[h].mVelocity.y.toPrecision(3)+"
- AngluarVelocity: "+l[h].mAngularVelocity.toPrecision(3)+"
- Mass: "+1/l[h].mInvMass.toPrecision(3)+"
- Friction: "+l[h].mFriction.toPrecision(3)+"
- Restitution: "+l[h].mRestitution.toPrecision(3)+"
- Positional Correction: "+m.Physics.mPositionalCorrectionFlag+"
- Movement: "+m.Core.mMovement+'
Control: of selected object
- Num or Up/Down Arrow: Select Object
- WASD + QE: Position [Move + Rotate]
- IJKL + UO: Velocities [Linear + Angular]
- Z/X: Mass [Decrease/Increase]
- C/V: Frictrion [Decrease/Increase]
- B/N: Restitution [Decrease/Increase]
- M: Positional Correction [On/Off]
- ,: Movement [On/Off]
F/G: Spawn [Rectangle/Circle] at selected objectH: Excite all objects
R: Reset System
',function(){var t;for(i.clearRect(0,0,800,450),t=0;t=1/60*1e3;)a-=1/60*1e3,m.Physics.collision(),c()};return{initializeEngineCore:function(){u()},mAllObjects:l,mWidth:800,mHeight:450,mContext:i,mGravity:o,mUpdateIntervalInSeconds:1/60,mMovement:!0}}();var h=0
--------------------------------------------------------------------------------
/book/public_html/all.min.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xem/mini2Dphysics/d3d986643641662e302f431459707b2c972a9fd0/book/public_html/all.min.zip
--------------------------------------------------------------------------------
/book/public_html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | PhysicsEngine
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | |
24 |
25 |
26 | |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
510 |
--------------------------------------------------------------------------------
/index.min.js:
--------------------------------------------------------------------------------
1 | t=(r,a)=>({x:r,y:a}),M=r=>h(r,r)**.5,o=(r,a)=>t(r.x+a.x,r.y+a.y),n=(r,a)=>o(r,e(a,-1)),e=(r,a)=>t(r.x*a,r.y*a),h=(r,a)=>r.x*a.x+r.y*a.y,f=(r,a)=>r.x*a.y-r.y*a.x,v=(r,a,M,o=r.x-a.x,n=r.y-a.y)=>t(o*Math.cos(M)-n*Math.sin(M)+a.x,o*Math.sin(M)+n*Math.cos(M)+a.y),i=r=>e(r,1/(M(r)||1)),y=t(0,100),N=[],x={},B={},d={},m=(r,a,t,M)=>{r.D=a,r.N=t,r.S=M,r.E=o(M,e(t,a))},s=(r,a,M,o,n,e,h,f,v)=>(v={T:n,C:r,F:M,R:o,M:a?1/a:0,V:t(0,0),A:a?y:t(0,0),G:0,v:0,a:0,B:e,W:h,H:f,I:n?(Math.hypot(h,f),a>0?1/(a*(h**2+f**2)/12):0):a>0?a*e**2/12:0,N:[],X:[t(r.x-h/2,r.y-f/2),t(r.x+h/2,r.y-f/2),t(r.x+h/2,r.y+f/2),t(r.x-h/2,r.y+f/2)]},n&&T(v),N.push(v),v),C=(r,a,t)=>{if(r.C=o(r.C,a),r.T)for(t=4;t--;)r.X[t]=o(r.X[t],a)},X=(r,a,t)=>{if(r.G+=a,r.T){for(t=4;t--;)r.X[t]=v(r.X[t],r.C,a);T(r)}},V=(r,a)=>M(n(a.C,r.C))<=r.B+a.B,T=(r,a)=>{for(a=4;a--;)r.N[a]=i(n(r.X[(a+1)%4],r.X[(a+2)%4]))},l=(r,a,t)=>{var M,f,v,i,y,N,x=1e9,B=-1,d=1;for(f=4;d&&f--;){M=r.N[f];var c,s,C=e(M,-1),X=r.X[f];for(N=-1e9,y=-1,v=4;v--;)c=n(a.X[v],X),(s=h(c,C))>0&&s>N&&(y=a.X[v],N=s);(d=-1!==y)&&N{if(!r.T&&!a.T){var f=n(a.C,r.C),v=r.B+a.B,y=M(f);if(y<=Math.sqrt(v*v)){var N=i(e(f,-1)),c=e(N,a.B);m(x,v-y,i(f),o(a.C,c))}return 1}if(r.T&&a.T){var s,C=0;return(s=l(r,a,B))&&(C=l(a,r,d))&&(B.D0){g=I,E=X,S=0;break}I>g&&(g=I,E=X)}if(S)m(x,a.B-g,r.N[E],n(T,e(r.N[E],a.B)));else{var R=n(T,r.X[E]),k=n(r.X[(E+1)%4],r.X[E]),p=h(R,k);if(p<0){if((u=M(R))>a.B)return;D=i(R),m(x,a.B-u,D,o(T,e(D,-a.B)))}else if(R=n(T,r.X[(E+1)%4]),k=e(k,-1),(p=h(R,k))<0){if((u=M(R))>a.B)return;D=i(R),m(x,a.B-u,D,o(T,e(D,-a.B)))}else{if(!(g{if(r.M||a.M){var v=M.D/(r.M+a.M)*.8,y=e(M.N,v),N=M.N;C(r,e(y,-r.M)),C(a,e(y,a.M));var x=e(M.S,a.M/(r.M+a.M)),B=e(M.E,r.M/(r.M+a.M)),d=o(x,B),m=n(d,r.C),c=n(d,a.C),s=o(r.V,t(-1*r.v*m.y,r.v*m.x)),X=o(a.V,t(-1*a.v*c.y,a.v*c.x)),V=n(X,s),T=h(V,N);if(!(T>0)){var l=Math.min(r.R,a.R),I=Math.min(r.F,a.F),u=f(m,N),D=f(c,N),S=-(1+l)*T/(r.M+a.M+u*u*r.I+D*D*a.I),g=e(N,S);r.V=n(r.V,e(g,r.M)),a.V=o(a.V,e(g,a.M)),r.v-=u*S*r.I,a.v+=D*S*a.I;var E=e(i(n(V,e(N,h(V,N)))),-1),R=f(m,E),k=f(c,E),p=-(1+l)*h(V,E)*I/(r.M+a.M+R*R*r.I+k*k*a.I);p>S&&(p=S),g=e(E,p),r.V=n(r.V,e(g,r.M)),a.V=o(a.V,e(g,a.M)),r.v-=R*p*r.I,a.v+=k*p*a.I}}};setInterval((r,t,M)=>{for(a.width^=0,M=9;M--;)for(r=N.length;r--;)for(t=N.length;t-->r;)V(N[r],N[t])&&I(N[r],N[t])&&(h(x.N,n(N[t].C,N[r].C))<0&&(x={D:x.D,N:e(x.N,-1),S:x.E,E:x.S}),u(N[r],N[t],x));for(r=N.length;r--;)c.save(),c.translate(N[r].C.x,N[r].C.y),c.rotate(N[r].G),N[r].T?c.strokeRect(-N[r].W/2,-N[r].H/2,N[r].W,N[r].H):(c.beginPath(),c.arc(0,0,N[r].B,0,7),c.lineTo(0,0),c.closePath(),c.stroke()),c.restore(),N[r].V=o(N[r].V,e(N[r].A,1/60)),C(N[r],e(N[r].V,1/60)),N[r].v+=1*N[r].a/60,X(N[r],1*N[r].v/60)},16);var D=(r,a,t,M,o)=>s(r,t,M,o,0,a),S=(r,a,t,M,o,n)=>s(r,M,o,n,1,Math.hypot(a,t)/2,a,t)
--------------------------------------------------------------------------------
/index.min.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xem/mini2Dphysics/d3d986643641662e302f431459707b2c972a9fd0/index.min.zip
--------------------------------------------------------------------------------
/js1k/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
160 |
--------------------------------------------------------------------------------
/js1k/index.min.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/js1k/index.min.pack.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/min/engine.min.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------