├── .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 | "
" + 51 | "

Control: of selected object

" + 52 | "
" + 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 | "
" + 877 | "

Control: of selected object

" + 878 | "
" + 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 object

H: 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 | 24 | 27 | 28 |
20 |
21 | 22 |
23 |
25 |
26 |
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 | --------------------------------------------------------------------------------