├── LICENSE ├── README.md ├── examples ├── gravity.framer │ ├── .gitignore │ ├── app.coffee │ ├── framer │ │ ├── .bookmark │ │ ├── coffee-script.js │ │ ├── config.json │ │ ├── framer.generated.js │ │ ├── framer.init.js │ │ ├── framer.js │ │ ├── framer.js.map │ │ ├── framer.modules.js │ │ ├── images │ │ │ ├── cursor-active.png │ │ │ ├── cursor-active@2x.png │ │ │ ├── cursor.png │ │ │ ├── cursor@2x.png │ │ │ ├── icon-120.png │ │ │ ├── icon-152.png │ │ │ ├── icon-180.png │ │ │ ├── icon-192.png │ │ │ └── icon-76.png │ │ ├── social-800x600.png │ │ ├── social-80x80.png │ │ ├── style.css │ │ └── version │ ├── images │ │ └── .gitkeep │ ├── index.html │ └── modules │ │ ├── coffeePhysics.coffee │ │ └── coffeePhysics │ │ ├── base.coffee │ │ ├── behaviour │ │ ├── Attraction.coffee │ │ ├── Behaviour.coffee │ │ ├── Collision.coffee │ │ ├── ConstantForce.coffee │ │ ├── EdgeBounce.coffee │ │ ├── EdgeWrap.coffee │ │ ├── Gravity.coffee │ │ └── Wander.coffee │ │ ├── demos │ │ ├── AttractionDemo.coffee │ │ ├── BalloonDemo.coffee │ │ ├── BoundsDemo.coffee │ │ ├── ChainDemo.coffee │ │ ├── ClothDemo.coffee │ │ ├── CollisionDemo.coffee │ │ ├── Demo.coffee │ │ └── renderer │ │ │ ├── CanvasRenderer.coffee │ │ │ ├── DOMRenderer.coffee │ │ │ ├── Renderer.coffee │ │ │ └── WebGLRenderer.coffee │ │ ├── engine │ │ ├── Particle.coffee │ │ ├── Physics.coffee │ │ ├── Spring.coffee │ │ └── integrator │ │ │ ├── Euler.coffee │ │ │ ├── ImprovedEuler.coffee │ │ │ ├── Integrator.coffee │ │ │ └── Verlet.coffee │ │ └── math │ │ ├── Random.coffee │ │ └── Vector.coffee ├── physics.framer │ ├── .gitignore │ ├── app.coffee │ ├── framer │ │ ├── .bookmark │ │ ├── coffee-script.js │ │ ├── config.json │ │ ├── framer.generated.js │ │ ├── framer.init.js │ │ ├── framer.js │ │ ├── framer.js.map │ │ ├── framer.modules.js │ │ ├── images │ │ │ ├── cursor-active.png │ │ │ ├── cursor-active@2x.png │ │ │ ├── cursor.png │ │ │ ├── cursor@2x.png │ │ │ ├── icon-120.png │ │ │ ├── icon-152.png │ │ │ ├── icon-180.png │ │ │ ├── icon-192.png │ │ │ └── icon-76.png │ │ ├── social-800x600.png │ │ ├── social-80x80.png │ │ ├── style.css │ │ └── version │ ├── images │ │ └── .gitkeep │ ├── index.html │ └── modules │ │ ├── coffeePhysics.coffee │ │ └── coffeePhysics │ │ ├── base.coffee │ │ ├── behaviour │ │ ├── Attraction.coffee │ │ ├── Behaviour.coffee │ │ ├── Collision.coffee │ │ ├── ConstantForce.coffee │ │ ├── EdgeBounce.coffee │ │ ├── EdgeWrap.coffee │ │ ├── Gravity.coffee │ │ └── Wander.coffee │ │ ├── demos │ │ ├── AttractionDemo.coffee │ │ ├── BalloonDemo.coffee │ │ ├── BoundsDemo.coffee │ │ ├── ChainDemo.coffee │ │ ├── ClothDemo.coffee │ │ ├── CollisionDemo.coffee │ │ ├── Demo.coffee │ │ └── renderer │ │ │ ├── CanvasRenderer.coffee │ │ │ ├── DOMRenderer.coffee │ │ │ ├── Renderer.coffee │ │ │ └── WebGLRenderer.coffee │ │ ├── engine │ │ ├── Particle.coffee │ │ ├── Physics.coffee │ │ ├── Spring.coffee │ │ └── integrator │ │ │ ├── Euler.coffee │ │ │ ├── ImprovedEuler.coffee │ │ │ ├── Integrator.coffee │ │ │ └── Verlet.coffee │ │ └── math │ │ ├── Random.coffee │ │ └── Vector.coffee └── wander.framer │ ├── .gitignore │ ├── app.coffee │ ├── framer │ ├── .bookmark │ ├── coffee-script.js │ ├── config.json │ ├── framer.generated.js │ ├── framer.init.js │ ├── framer.js │ ├── framer.js.map │ ├── framer.modules.js │ ├── images │ │ ├── cursor-active.png │ │ ├── cursor-active@2x.png │ │ ├── cursor.png │ │ ├── cursor@2x.png │ │ ├── icon-120.png │ │ ├── icon-152.png │ │ ├── icon-180.png │ │ ├── icon-192.png │ │ └── icon-76.png │ ├── social-800x600.png │ ├── social-80x80.png │ ├── style.css │ └── version │ ├── images │ └── .gitkeep │ ├── index.html │ └── modules │ ├── coffeePhysics.coffee │ └── coffeePhysics │ ├── base.coffee │ ├── behaviour │ ├── Attraction.coffee │ ├── Behaviour.coffee │ ├── Collision.coffee │ ├── ConstantForce.coffee │ ├── EdgeBounce.coffee │ ├── EdgeWrap.coffee │ ├── Gravity.coffee │ └── Wander.coffee │ ├── demos │ ├── AttractionDemo.coffee │ ├── BalloonDemo.coffee │ ├── BoundsDemo.coffee │ ├── ChainDemo.coffee │ ├── ClothDemo.coffee │ ├── CollisionDemo.coffee │ ├── Demo.coffee │ └── renderer │ │ ├── CanvasRenderer.coffee │ │ ├── DOMRenderer.coffee │ │ ├── Renderer.coffee │ │ └── WebGLRenderer.coffee │ ├── engine │ ├── Particle.coffee │ ├── Physics.coffee │ ├── Spring.coffee │ └── integrator │ │ ├── Euler.coffee │ │ ├── ImprovedEuler.coffee │ │ ├── Integrator.coffee │ │ └── Verlet.coffee │ └── math │ ├── Random.coffee │ └── Vector.coffee └── module files ├── coffeePhysics.coffee └── coffeePhysics ├── base.coffee ├── behaviour ├── Attraction.coffee ├── Behaviour.coffee ├── Collision.coffee ├── ConstantForce.coffee ├── EdgeBounce.coffee ├── EdgeWrap.coffee ├── Gravity.coffee └── Wander.coffee ├── demos ├── AttractionDemo.coffee ├── BalloonDemo.coffee ├── BoundsDemo.coffee ├── ChainDemo.coffee ├── ClothDemo.coffee ├── CollisionDemo.coffee ├── Demo.coffee └── renderer │ ├── CanvasRenderer.coffee │ ├── DOMRenderer.coffee │ ├── Renderer.coffee │ └── WebGLRenderer.coffee ├── engine ├── Particle.coffee ├── Physics.coffee ├── Spring.coffee └── integrator │ ├── Euler.coffee │ ├── ImprovedEuler.coffee │ ├── Integrator.coffee │ └── Verlet.coffee └── math ├── Random.coffee └── Vector.coffee /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Giles Perry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # framer-physics 2 | A module for adding 2D physics simulations to your Framer prototypes. 3 | 4 | Based on [Coffee-Physics](https://github.com/soulwire/Coffee-Physics/) by Justin Windle (a.k.a. soulwire) ‘A simple, lightweight physics engine written in CoffeeScript’ 5 | 6 | [Read the article on Medium](https://blog.framer.com/its-particle-time-how-to-use-a-physics-engine-with-framer-e66af34ec859) 7 | 8 | ![physics-gif](https://cdn-images-1.medium.com/max/800/1*mg76rbpBwpGpxZHdes70eQ.gif) 9 | 10 | ## Examples 11 | 12 | [Attraction](https://framer.cloud/KXQHl/) 13 | 14 | [Gravity](https://framer.cloud/VOHjW/) 15 | 16 | ## Installation 17 | 18 | Drag the contents of the `module files` folder to the `modules` folder of your Framer project. 19 | 20 | ## Syntax 21 | 22 | Import module: 23 | 24 | ``` 25 | {Integrator, Euler, ImprovedEuler, Verlet, Particle, Physics, Vector, Spring, Behaviour, Attraction, Collision, ConstantForce, EdgeBounce, EdgeWrap, Wander, Gravity} = require 'coffeePhysics' 26 | ``` 27 | 28 | --- 29 | 30 | **If you are using this module, please 'star' the project**. It's a simple way to help me see how many people are using it. 31 | 32 | If you ***love*** this module, why not shout me a coffee ☕️ via [PayPal](https://www.paypal.me/perrysmotors/5) to share the love! 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/gravity.framer/.gitignore: -------------------------------------------------------------------------------- 1 | # Framer Git Ignore 2 | 3 | # General OSX 4 | 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | # Framer Specific 31 | .*.html 32 | .app.js 33 | framer/*.old* 34 | framer/.*.hash 35 | framer/backup.coffee 36 | framer/backups/* 37 | framer/manifest.txt 38 | framer/metadata.json 39 | framer/preview.png 40 | framer/social-880x460.png 41 | framer/social-1200x630.png 42 | -------------------------------------------------------------------------------- /examples/gravity.framer/app.coffee: -------------------------------------------------------------------------------- 1 | # Project setup 2 | ################################################################################ 3 | {Integrator, Euler, ImprovedEuler, Verlet, Particle, Physics, Vector, Spring, Behaviour, Attraction, Collision, ConstantForce, EdgeBounce, EdgeWrap, Wander, Gravity} = require 'coffeePhysics' 4 | 5 | # Colours 6 | ################################################################################ 7 | 8 | red = new Color("rgba(224,32,36,1)") 9 | orange = new Color("rgba(255,128,21,1)") 10 | yellow = new Color("rgba(255,224,0,1)") 11 | green = new Color("rgba(55,191,0,1)") 12 | blue = new Color("rgba(0,150,212,1)") 13 | pink = new Color("rgba(213,45,177,1)") 14 | lightGrey = new Color("rgba(239,239,239,1)") 15 | 16 | colours = [red, orange, yellow, green, blue, pink] 17 | colourCycler = Utils.cycle(colours) 18 | 19 | background = new BackgroundLayer 20 | backgroundColor: lightGrey 21 | 22 | ################################################################################ 23 | 24 | # Create a physics instance which uses the Verlet integration method 25 | physics = new Physics() 26 | physics.integrator = new Verlet() 27 | 28 | # Allow particle collisions to make things interesting 29 | collision = new Collision() 30 | 31 | # Design some behaviours for particles 32 | topLeft = new Vector(0,0) 33 | bottomRight = new Vector(Screen.width,Screen.height) 34 | edges = new EdgeBounce(topLeft, bottomRight) 35 | gravity = new Gravity() 36 | 37 | ################################################################################ 38 | 39 | balls = [] 40 | 41 | # Render the particles 42 | for i in [0..200] 43 | 44 | # Create a particle 45 | particle = new Particle( Utils.randomNumber(.1,1) ) 46 | position = new Vector( Utils.randomNumber( 0, Screen.width ), Utils.randomNumber( 0, Screen.height ) ) 47 | particle.setRadius( particle.mass * 10 ) 48 | particle.moveTo( position ) 49 | 50 | # Apply behaviours to the particle 51 | particle.behaviours.push( gravity, edges, collision ) 52 | 53 | # Make it collidable 54 | collision.pool.push( particle ) 55 | 56 | # Add to the simulation 57 | physics.particles.push( particle ) 58 | 59 | # Create a layer to show the particle on the screen 60 | ball = new Layer 61 | x: particle.pos.x - particle.radius 62 | y: particle.pos.y - particle.radius 63 | size: particle.radius * 2 64 | borderRadius: particle.radius 65 | backgroundColor: colourCycler() 66 | 67 | # Add the particle instance to the layer 68 | ball.particle = particle 69 | 70 | balls.push(ball) 71 | 72 | # Set everything in motion 73 | ################################################################################ 74 | 75 | frameRate = 1 / 60 76 | 77 | Utils.interval frameRate, -> 78 | 79 | # Step the simulation 80 | physics.step() 81 | 82 | # Update the position of the balls 83 | for ball, i in balls 84 | ball.x = ball.particle.pos.x - ball.particle.radius 85 | ball.y = ball.particle.pos.y - ball.particle.radius -------------------------------------------------------------------------------- /examples/gravity.framer/framer/.bookmark: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/gravity.framer/framer/.bookmark -------------------------------------------------------------------------------- /examples/gravity.framer/framer/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "orientation" : 0, 3 | "updateDelay" : 0.3, 4 | "cachedDeviceHeight" : 1334, 5 | "contentScale" : 1, 6 | "fullScreen" : false, 7 | "cachedDeviceWidth" : 750, 8 | "sharedPrototype" : 0, 9 | "propertyPanelToggleStates" : { 10 | "Filters" : false 11 | }, 12 | "deviceType" : "apple-iphone-7-silver", 13 | "projectId" : "A0BAE4C0-A481-4D63-A993-D18B154C58A7", 14 | "deviceOrientation" : 0, 15 | "selectedHand" : "", 16 | "foldedCodeRanges" : [ 17 | 18 | ], 19 | "deviceScale" : "fit" 20 | } -------------------------------------------------------------------------------- /examples/gravity.framer/framer/framer.generated.js: -------------------------------------------------------------------------------- 1 | // This is autogenerated by Framer 2 | 3 | 4 | if (!window.Framer && window._bridge) {window._bridge('runtime.error', {message:'[framer.js] Framer library missing or corrupt. Select File → Update Framer Library.'})} 5 | if (DeviceComponent) {DeviceComponent.Devices["iphone-6-silver"].deviceImageJP2 = false}; 6 | if (window.Framer) {window.Framer.Defaults.DeviceView = {"deviceScale":"fit","selectedHand":"","deviceType":"apple-iphone-7-silver","contentScale":1,"hideBezel":true,"orientation":0}; 7 | } 8 | if (window.Framer) {window.Framer.Defaults.DeviceComponent = {"deviceScale":"fit","selectedHand":"","deviceType":"apple-iphone-7-silver","contentScale":1,"hideBezel":true,"orientation":0}; 9 | } 10 | window.FramerStudioInfo = {"deviceImagesUrl":"\/_server\/resources\/DeviceImages","documentTitle":"gravity.framer"}; 11 | 12 | Framer.Device = new Framer.DeviceView(); 13 | Framer.Device.setupContext(); -------------------------------------------------------------------------------- /examples/gravity.framer/framer/framer.init.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | function isFileLoadingAllowed() { 4 | return (window.location.protocol.indexOf("file") == -1) 5 | } 6 | 7 | function isHomeScreened() { 8 | return ("standalone" in window.navigator) && window.navigator.standalone == true 9 | } 10 | 11 | function isCompatibleBrowser() { 12 | return Utils.isWebKit() 13 | } 14 | 15 | var alertNode; 16 | 17 | function dismissAlert() { 18 | alertNode.parentElement.removeChild(alertNode) 19 | loadProject() 20 | } 21 | 22 | function showAlert(html) { 23 | 24 | alertNode = document.createElement("div") 25 | 26 | alertNode.classList.add("framerAlertBackground") 27 | alertNode.innerHTML = html 28 | 29 | document.addEventListener("DOMContentLoaded", function(event) { 30 | document.body.appendChild(alertNode) 31 | }) 32 | 33 | window.dismissAlert = dismissAlert; 34 | } 35 | 36 | function showBrowserAlert() { 37 | var html = "" 38 | html += "
" 39 | html += "Error: Not A WebKit Browser" 40 | html += "Your browser is not supported.
Please use Safari or Chrome.
" 41 | html += "Try anyway" 42 | html += "
" 43 | 44 | showAlert(html) 45 | } 46 | 47 | function showFileLoadingAlert() { 48 | var html = "" 49 | html += "
" 50 | html += "Error: Local File Restrictions" 51 | html += "Preview this prototype with Framer Mirror or learn more about " 52 | html += "file restrictions.
" 53 | html += "Try anyway" 54 | html += "
" 55 | 56 | showAlert(html) 57 | } 58 | 59 | function loadProject() { 60 | CoffeeScript.load("app.coffee") 61 | } 62 | 63 | function setDefaultPageTitle() { 64 | // If no title was set we set it to the project folder name so 65 | // you get a nice name on iOS if you bookmark to desktop. 66 | document.addEventListener("DOMContentLoaded", function() { 67 | if (document.title == "") { 68 | if (window.FramerStudioInfo && window.FramerStudioInfo.documentTitle) { 69 | document.title = window.FramerStudioInfo.documentTitle 70 | } else { 71 | document.title = window.location.pathname.replace(/\//g, "") 72 | } 73 | } 74 | }) 75 | } 76 | 77 | function init() { 78 | 79 | if (Utils.isFramerStudio()) { 80 | return 81 | } 82 | 83 | setDefaultPageTitle() 84 | 85 | if (!isCompatibleBrowser()) { 86 | return showBrowserAlert() 87 | } 88 | 89 | if (!isFileLoadingAllowed()) { 90 | return showFileLoadingAlert() 91 | } 92 | 93 | loadProject() 94 | 95 | } 96 | 97 | init() 98 | 99 | })() 100 | -------------------------------------------------------------------------------- /examples/gravity.framer/framer/images/cursor-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/gravity.framer/framer/images/cursor-active.png -------------------------------------------------------------------------------- /examples/gravity.framer/framer/images/cursor-active@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/gravity.framer/framer/images/cursor-active@2x.png -------------------------------------------------------------------------------- /examples/gravity.framer/framer/images/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/gravity.framer/framer/images/cursor.png -------------------------------------------------------------------------------- /examples/gravity.framer/framer/images/cursor@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/gravity.framer/framer/images/cursor@2x.png -------------------------------------------------------------------------------- /examples/gravity.framer/framer/images/icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/gravity.framer/framer/images/icon-120.png -------------------------------------------------------------------------------- /examples/gravity.framer/framer/images/icon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/gravity.framer/framer/images/icon-152.png -------------------------------------------------------------------------------- /examples/gravity.framer/framer/images/icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/gravity.framer/framer/images/icon-180.png -------------------------------------------------------------------------------- /examples/gravity.framer/framer/images/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/gravity.framer/framer/images/icon-192.png -------------------------------------------------------------------------------- /examples/gravity.framer/framer/images/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/gravity.framer/framer/images/icon-76.png -------------------------------------------------------------------------------- /examples/gravity.framer/framer/social-800x600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/gravity.framer/framer/social-800x600.png -------------------------------------------------------------------------------- /examples/gravity.framer/framer/social-80x80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/gravity.framer/framer/social-80x80.png -------------------------------------------------------------------------------- /examples/gravity.framer/framer/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | border: none; 5 | -webkit-user-select: none; 6 | -webkit-tap-highlight-color: rgba(0,0,0,0); 7 | } 8 | 9 | body { 10 | background-color: #fff; 11 | font: 28px/1em "Helvetica"; 12 | color: gray; 13 | overflow: hidden; 14 | } 15 | 16 | a { 17 | color: gray; 18 | } 19 | 20 | body { 21 | cursor: url('images/cursor.png') 32 32, auto; 22 | cursor: -webkit-image-set( 23 | url('images/cursor.png') 1x, 24 | url('images/cursor@2x.png') 2x 25 | ) 32 32, auto; 26 | } 27 | 28 | body:active { 29 | cursor: url('images/cursor-active.png') 32 32, auto; 30 | cursor: -webkit-image-set( 31 | url('images/cursor-active.png') 1x, 32 | url('images/cursor-active@2x.png') 2x 33 | ) 32 32, auto; 34 | } 35 | 36 | .framerAlertBackground { 37 | position: absolute; top:0px; left:0px; right:0px; bottom:0px; 38 | z-index: 1000; 39 | background-color: #fff; 40 | } 41 | 42 | .framerAlert { 43 | font:400 14px/1.4 "Helvetica Neue", Helvetica, Arial, sans-serif; 44 | -webkit-font-smoothing:antialiased; 45 | color:#616367; text-align:center; 46 | position: absolute; top:40%; left:50%; width:260px; margin-left:-130px; 47 | } 48 | .framerAlert strong { font-weight:500; color:#000; margin-bottom:8px; display:block; } 49 | .framerAlert a { color:#28AFFA; } 50 | .framerAlert .btn { 51 | font-weight:500; text-decoration:none; line-height:1; 52 | display:inline-block; padding:6px 12px 7px 12px; 53 | border-radius:3px; margin-top:12px; 54 | background:#28AFFA; color:#fff; 55 | } 56 | 57 | ::-webkit-scrollbar { 58 | display: none; 59 | } -------------------------------------------------------------------------------- /examples/gravity.framer/framer/version: -------------------------------------------------------------------------------- 1 | 6 -------------------------------------------------------------------------------- /examples/gravity.framer/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/gravity.framer/images/.gitkeep -------------------------------------------------------------------------------- /examples/gravity.framer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics.coffee: -------------------------------------------------------------------------------- 1 | # Add the following line to your project in Framer Studio. 2 | # myModule = require "myModule" 3 | # Reference the contents by name, like myModule.myFunction() or myModule.myVar 4 | 5 | 6 | # Import integrator framework 7 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 8 | {Euler} = require 'coffeePhysics/engine/integrator/Euler' 9 | {ImprovedEuler} = require 'coffeePhysics/engine/integrator/ImprovedEuler' 10 | {Verlet} = require 'coffeePhysics/engine/integrator/Verlet' 11 | 12 | exports.Integrator = Integrator 13 | exports.Euler = Euler 14 | exports.ImprovedEuler = ImprovedEuler 15 | exports.Verlet = Verlet 16 | 17 | # Import physics framework 18 | {Particle} = require 'coffeePhysics/engine/Particle' 19 | {Physics} = require 'coffeePhysics/engine/Physics' 20 | {Spring} = require 'coffeePhysics/engine/Spring' 21 | 22 | exports.Particle = Particle 23 | exports.Physics = Physics 24 | exports.Spring = Spring 25 | 26 | # Import math framework 27 | # {Random} = require 'coffeePhysics/math/Random' 28 | {Vector} = require 'coffeePhysics/math/Vector' 29 | 30 | # exports.Random = Random 31 | exports.Vector = Vector 32 | 33 | # Import behaviour framework 34 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 35 | {Attraction} = require 'coffeePhysics/behaviour/Attraction' 36 | {Collision} = require 'coffeePhysics/behaviour/Collision' 37 | {ConstantForce} = require 'coffeePhysics/behaviour/ConstantForce' 38 | {EdgeBounce} = require 'coffeePhysics/behaviour/EdgeBounce' 39 | {EdgeWrap} = require 'coffeePhysics/behaviour/EdgeWrap' 40 | {Wander} = require 'coffeePhysics/behaviour/Wander' 41 | {Gravity} = require 'coffeePhysics/behaviour/Gravity' 42 | 43 | exports.Behaviour = Behaviour 44 | exports.Attraction = Attraction 45 | exports.Collision = Collision 46 | exports.ConstantForce = ConstantForce 47 | exports.EdgeBounce = EdgeBounce 48 | exports.EdgeWrap = EdgeWrap 49 | exports.Wander = Wander 50 | exports.Gravity = Gravity 51 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/base.coffee: -------------------------------------------------------------------------------- 1 | ### Allows safe, dyamic creation of namespaces. ### 2 | 3 | namespace = (id) -> 4 | root = self 5 | root = root[path] ?= {} for path in id.split '.' 6 | 7 | ### RequestAnimationFrame shim. ### 8 | do -> 9 | 10 | time = 0 11 | vendors = ['ms', 'moz', 'webkit', 'o'] 12 | 13 | for vendor in vendors when not window.requestAnimationFrame 14 | window.requestAnimationFrame = window[ vendor + 'RequestAnimationFrame'] 15 | window.cancelAnimationFrame = window[ vendor + 'CancelAnimationFrame'] 16 | 17 | if not window.requestAnimationFrame 18 | 19 | window.requestAnimationFrame = (callback, element) -> 20 | now = new Date().getTime() 21 | delta = Math.max 0, 16 - (now - old) 22 | setTimeout (-> callback(time + delta)), delta 23 | old = now + delta 24 | 25 | if not window.cancelAnimationFrame 26 | 27 | window.cancelAnimationFrame = (id) -> 28 | clearTimeout id 29 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/behaviour/Attraction.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Attraction Behaviour ### 6 | 7 | class exports.Attraction extends Behaviour 8 | 9 | constructor: (@target = new Vector(), @radius = 1000, @strength = 100.0) -> 10 | 11 | @_delta = new Vector() 12 | @setRadius @radius 13 | 14 | super 15 | 16 | ### Sets the effective radius of the bahavious. ### 17 | setRadius: (radius) -> 18 | 19 | @radius = radius 20 | @radiusSq = radius * radius 21 | 22 | apply: (p, dt, index) -> 23 | 24 | #super p, dt, index 25 | 26 | # Vector pointing from particle to target. 27 | (@_delta.copy @target).sub p.pos 28 | 29 | # Squared distance to target. 30 | distSq = @_delta.magSq() 31 | 32 | # Limit force to behaviour radius. 33 | if distSq < @radiusSq and distSq > 0.000001 34 | 35 | # Calculate force vector. 36 | @_delta.norm().scale (1.0 - distSq / @radiusSq) 37 | 38 | #Apply force. 39 | p.acc.add @_delta.scale @strength 40 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/behaviour/Behaviour.coffee: -------------------------------------------------------------------------------- 1 | ### Behaviour ### 2 | 3 | class exports.Behaviour 4 | 5 | # Each behaviour has a unique id 6 | @GUID = 0 7 | 8 | constructor: -> 9 | 10 | @GUID = Behaviour.GUID++ 11 | @interval = 1 12 | 13 | ## console.log @, @GUID 14 | 15 | apply: (p, dt, index) -> 16 | 17 | # Store some data in each particle. 18 | (p['__behaviour' + @GUID] ?= {counter: 0}).counter++ 19 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/behaviour/Collision.coffee: -------------------------------------------------------------------------------- 1 | ### Import Behaviour ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Collision Behaviour ### 6 | 7 | # TODO: Collision response for non Verlet integrators. 8 | 9 | class exports.Collision extends Behaviour 10 | 11 | constructor: (@useMass = yes, @callback = null) -> 12 | 13 | # Pool of collidable particles. 14 | @pool = [] 15 | 16 | # Delta between particle positions. 17 | @_delta = new Vector() 18 | 19 | super 20 | 21 | apply: (p, dt, index) -> 22 | 23 | #super p, dt, index 24 | 25 | # Check pool for collisions. 26 | for o in @pool[index..] when o isnt p 27 | 28 | # Delta between particles positions. 29 | (@_delta.copy o.pos).sub p.pos 30 | 31 | # Squared distance between particles. 32 | distSq = @_delta.magSq() 33 | 34 | # Sum of both radii. 35 | radii = p.radius + o.radius 36 | 37 | # Check if particles collide. 38 | if distSq <= radii * radii 39 | 40 | # Compute real distance. 41 | dist = Math.sqrt distSq 42 | 43 | # Determine overlap. 44 | overlap = radii - dist 45 | overlap += 0.5 46 | 47 | # Total mass. 48 | mt = p.mass + o.mass 49 | 50 | # Distribute collision responses. 51 | r1 = if @useMass then o.mass / mt else 0.5 52 | r2 = if @useMass then p.mass / mt else 0.5 53 | 54 | # Move particles so they no longer overlap. 55 | p.pos.add (@_delta.clone().norm().scale overlap * -r1) 56 | o.pos.add (@_delta.norm().scale overlap * r2) 57 | 58 | # Fire callback if defined. 59 | @callback?(p, o, overlap) 60 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/behaviour/ConstantForce.coffee: -------------------------------------------------------------------------------- 1 | ### Import Behaviour ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Constant Force Behaviour ### 6 | 7 | class exports.ConstantForce extends Behaviour 8 | 9 | constructor: (@force = new Vector()) -> 10 | 11 | super 12 | 13 | apply: (p, dt,index) -> 14 | 15 | #super p, dt, index 16 | 17 | p.acc.add @force 18 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/behaviour/EdgeBounce.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Edge Bounce Behaviour ### 6 | 7 | class exports.EdgeBounce extends Behaviour 8 | 9 | constructor: (@min = new Vector(), @max = new Vector()) -> 10 | 11 | super 12 | 13 | apply: (p, dt, index) -> 14 | 15 | #super p, dt, index 16 | 17 | if p.pos.x - p.radius < @min.x 18 | 19 | p.pos.x = @min.x + p.radius 20 | 21 | else if p.pos.x + p.radius > @max.x 22 | 23 | p.pos.x = @max.x - p.radius 24 | 25 | if p.pos.y - p.radius < @min.y 26 | 27 | p.pos.y = @min.y + p.radius 28 | 29 | else if p.pos.y + p.radius > @max.y 30 | 31 | p.pos.y = @max.y - p.radius 32 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/behaviour/EdgeWrap.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Edge Wrap Behaviour ### 6 | 7 | class exports.EdgeWrap extends Behaviour 8 | 9 | constructor: (@min = new Vector(), @max = new Vector()) -> 10 | 11 | super 12 | 13 | apply: (p, dt, index) -> 14 | 15 | #super p, dt, index 16 | 17 | if p.pos.x + p.radius < @min.x 18 | 19 | p.pos.x = @max.x + p.radius 20 | p.old.pos.x = p.pos.x 21 | 22 | else if p.pos.x - p.radius > @max.x 23 | 24 | p.pos.x = @min.x - p.radius 25 | p.old.pos.x = p.pos.x 26 | 27 | if p.pos.y + p.radius < @min.y 28 | 29 | p.pos.y = @max.y + p.radius 30 | p.old.pos.y = p.pos.y 31 | 32 | else if p.pos.y - p.radius > @max.y 33 | 34 | p.pos.y = @min.y - p.radius 35 | p.old.pos.y = p.pos.y 36 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/behaviour/Gravity.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {ConstantForce} = require 'coffeePhysics/behaviour/ConstantForce' 3 | 4 | ### Gravity Behaviour ### 5 | 6 | class exports.Gravity extends ConstantForce 7 | 8 | constructor: (@scale = 1000) -> 9 | 10 | super() 11 | 12 | force = @force 13 | scale = @scale 14 | 15 | window.addEventListener "devicemotion", -> 16 | accX = event.accelerationIncludingGravity.x 17 | accY = event.accelerationIncludingGravity.y * -1 18 | 19 | force.x = accX * scale / 10 20 | force.y = accY * scale / 10 21 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/behaviour/Wander.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | 4 | ### Wander Behaviour ### 5 | 6 | class exports.Wander extends Behaviour 7 | 8 | constructor: (@jitter = 0.5, @radius = 100, @strength = 1.0) -> 9 | 10 | @theta = Math.random() * Math.PI * 2 11 | 12 | super 13 | 14 | apply: (p, dt, index) -> 15 | 16 | #super p, dt, index 17 | 18 | @theta += (Math.random() - 0.5) * @jitter * Math.PI * 2 19 | 20 | p.acc.x += Math.cos(@theta) * @radius * @strength 21 | p.acc.y += Math.sin(@theta) * @radius * @strength 22 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/demos/AttractionDemo.coffee: -------------------------------------------------------------------------------- 1 | class AttractionDemo extends Demo 2 | 3 | setup: (full = yes) -> 4 | 5 | super full 6 | 7 | min = new Vector 0.0, 0.0 8 | max = new Vector @width, @height 9 | 10 | bounds = new EdgeBounce min, max 11 | 12 | @physics.integrator = new Verlet() 13 | 14 | attraction = new Attraction @mouse.pos, 1200, 1200 15 | repulsion = new Attraction @mouse.pos, 200, -2000 16 | collide = new Collision() 17 | 18 | max = if full then 400 else 200 19 | 20 | for i in [0..max] 21 | 22 | p = new Particle (Random 0.1, 3.0) 23 | p.setRadius p.mass * 4 24 | 25 | p.moveTo new Vector (Random @width), (Random @height) 26 | 27 | p.behaviours.push attraction 28 | p.behaviours.push repulsion 29 | p.behaviours.push bounds 30 | p.behaviours.push collide 31 | 32 | collide.pool.push p 33 | 34 | @physics.particles.push p -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/demos/BalloonDemo.coffee: -------------------------------------------------------------------------------- 1 | ### BalloonDemo ### 2 | class BalloonDemo extends Demo 3 | 4 | setup: (full = yes) -> 5 | 6 | super 7 | 8 | @physics.integrator = new ImprovedEuler() 9 | attraction = new Attraction @mouse.pos 10 | 11 | max = if full then 400 else 200 12 | 13 | for i in [0..max] 14 | 15 | p = new Particle (Random 0.25, 4.0) 16 | p.setRadius p.mass * 8 17 | 18 | p.behaviours.push new Wander 0.2 19 | p.behaviours.push attraction 20 | 21 | p.moveTo new Vector (Random @width), (Random @height) 22 | 23 | s = new Spring @mouse, p, (Random 30, 300), 1.0 24 | 25 | @physics.particles.push p 26 | @physics.springs.push s 27 | 28 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/demos/BoundsDemo.coffee: -------------------------------------------------------------------------------- 1 | ### BoundsDemo ### 2 | class BoundsDemo extends Demo 3 | 4 | setup: -> 5 | 6 | super 7 | 8 | min = new Vector 0.0, 0.0 9 | max = new Vector @width, @height 10 | 11 | edge = new EdgeWrap min, max 12 | 13 | for i in [0..200] 14 | 15 | p = new Particle (Random 0.5, 4.0) 16 | p.setRadius p.mass * 5 17 | 18 | p.moveTo new Vector (Random @width), (Random @height) 19 | 20 | p.behaviours.push new Wander 0.2, 120, Random 1.0, 2.0 21 | p.behaviours.push edge 22 | 23 | @physics.particles.push p 24 | 25 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/demos/ChainDemo.coffee: -------------------------------------------------------------------------------- 1 | class ChainDemo extends Demo 2 | 3 | setup: (full = yes) -> 4 | 5 | super 6 | 7 | @stiffness = 1.0 8 | @spacing = 2.0 9 | 10 | @physics.integrator = new Verlet() 11 | @physics.viscosity = 0.0001 12 | @mouse.setMass 1000 13 | 14 | gap = 50.0 15 | min = new Vector -gap, -gap 16 | max = new Vector @width + gap, @height + gap 17 | 18 | edge = new EdgeBounce min, max 19 | 20 | center = new Vector @width * 0.5, @height * 0.5 21 | 22 | #@renderer.renderParticles = no 23 | 24 | wander = new Wander 0.05, 100.0, 80.0 25 | 26 | max = if full then 2000 else 600 27 | 28 | for i in [0..max] 29 | 30 | p = new Particle 6.0 31 | p.colour = '#FFFFFF' 32 | p.moveTo center 33 | p.setRadius 1.0 34 | 35 | p.behaviours.push wander 36 | p.behaviours.push edge 37 | 38 | @physics.particles.push p 39 | 40 | if op? then s = new Spring op, p, @spacing, @stiffness 41 | else s = new Spring @mouse, p, @spacing, @stiffness 42 | 43 | @physics.springs.push s 44 | 45 | op = p 46 | 47 | @physics.springs.push new Spring @mouse, p, @spacing, @stiffness -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/demos/ClothDemo.coffee: -------------------------------------------------------------------------------- 1 | class ClothDemo extends Demo 2 | 3 | setup: (full = yes) -> 4 | 5 | super 6 | 7 | # Only render springs. 8 | @renderer.renderParticles = false 9 | 10 | @physics.integrator = new Verlet() 11 | @physics.timestep = 1.0 / 200 12 | @mouse.setMass 10 13 | 14 | # Add gravity to the simulation. 15 | @gravity = new ConstantForce new Vector 0.0, 80.0 16 | @physics.behaviours.push @gravity 17 | 18 | stiffness = 0.5 19 | size = if full then 8 else 10 20 | rows = if full then 30 else 25 21 | cols = if full then 55 else 40 22 | cell = [] 23 | 24 | sx = @width * 0.5 - cols * size * 0.5 25 | sy = @height * 0.5 - rows * size * 0.5 26 | 27 | for x in [0..cols] 28 | 29 | cell[x] = [] 30 | 31 | for y in [0..rows] 32 | 33 | p = new Particle(0.1) 34 | 35 | p.fixed = (y is 0) 36 | 37 | # Always set initial position using moveTo for Verlet 38 | p.moveTo new Vector (sx + x * size), (sy + y * size) 39 | 40 | if x > 0 41 | s = new Spring p, cell[x-1][y], size, stiffness 42 | @physics.springs.push s 43 | 44 | if y > 0 45 | s = new Spring p, cell[x][y - 1], size, stiffness 46 | @physics.springs.push s 47 | 48 | @physics.particles.push p 49 | cell[x][y] = p 50 | 51 | p = cell[Math.floor cols / 2][Math.floor rows / 2] 52 | s = new Spring @mouse, p, 10, 1.0 53 | @physics.springs.push s 54 | 55 | cell[0][0].fixed = true 56 | cell[cols - 1][0].fixed = true 57 | 58 | step: -> 59 | 60 | super 61 | 62 | @gravity.force.x = 50 * Math.sin new Date().getTime() * 0.0005 63 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/demos/CollisionDemo.coffee: -------------------------------------------------------------------------------- 1 | ### CollisionDemo ### 2 | class CollisionDemo extends Demo 3 | 4 | setup: (full = yes) -> 5 | 6 | super 7 | 8 | # Verlet gives us collision responce for free! 9 | @physics.integrator = new Verlet() 10 | 11 | min = new Vector 0.0, 0.0 12 | max = new Vector @width, @height 13 | 14 | bounds = new EdgeBounce min, max 15 | collide = new Collision 16 | attraction = new Attraction @mouse.pos, 2000, 1400 17 | 18 | max = if full then 350 else 150 19 | prob = if full then 0.35 else 0.5 20 | 21 | for i in [0..max] 22 | 23 | p = new Particle (Random 0.5, 4.0) 24 | p.setRadius p.mass * 4 25 | 26 | p.moveTo new Vector (Random @width), (Random @height) 27 | 28 | # Connect to spring or move free. 29 | if Random.bool prob 30 | s = new Spring @mouse, p, (Random 120, 180), 0.8 31 | @physics.springs.push s 32 | else 33 | p.behaviours.push attraction 34 | 35 | # Add particle to collision pool. 36 | collide.pool.push p 37 | 38 | # Allow particle to collide. 39 | p.behaviours.push collide 40 | p.behaviours.push bounds 41 | 42 | @physics.particles.push p 43 | 44 | onCollision: (p1, p2) => 45 | 46 | # Respond to collision. 47 | 48 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/demos/Demo.coffee: -------------------------------------------------------------------------------- 1 | ### Demo ### 2 | class Demo 3 | 4 | @COLOURS = ['DC0048', 'F14646', '4AE6A9', '7CFF3F', '4EC9D9', 'E4272E'] 5 | 6 | constructor: -> 7 | 8 | @physics = new Physics() 9 | @mouse = new Particle() 10 | @mouse.fixed = true 11 | @height = window.innerHeight 12 | @width = window.innerWidth 13 | 14 | @renderTime = 0 15 | @counter = 0 16 | 17 | setup: (full = yes) -> 18 | 19 | ### Override and add paticles / springs here ### 20 | 21 | ### Initialise the demo (override). ### 22 | init: (@container, @renderer = new WebGLRenderer()) -> 23 | 24 | # Build the scene. 25 | @setup renderer.gl? 26 | 27 | # Give the particles random colours. 28 | for particle in @physics.particles 29 | particle.colour ?= Random.item Demo.COLOURS 30 | 31 | # Add event handlers. 32 | document.addEventListener 'touchmove', @mousemove, false 33 | document.addEventListener 'mousemove', @mousemove, false 34 | document.addEventListener 'resize', @resize, false 35 | 36 | # Add to render output to the DOM. 37 | @container.appendChild @renderer.domElement 38 | 39 | # Prepare the renderer. 40 | @renderer.mouse = @mouse 41 | @renderer.init @physics 42 | 43 | # Resize for the sake of the renderer. 44 | do @resize 45 | 46 | ### Handler for window resize event. ### 47 | resize: (event) => 48 | 49 | @width = window.innerWidth 50 | @height = window.innerHeight 51 | @renderer.setSize @width, @height 52 | 53 | ### Update loop. ### 54 | step: -> 55 | 56 | #console.profile 'physics' 57 | 58 | # Step physics. 59 | do @physics.step 60 | 61 | #console.profileEnd() 62 | 63 | #console.profile 'render' 64 | 65 | # Render. 66 | 67 | # Render every frame for WebGL, or every 3 frames for canvas. 68 | @renderer.render @physics if @renderer.gl? or ++@counter % 3 is 0 69 | 70 | #console.profileEnd() 71 | 72 | ### Clean up after yourself. ### 73 | destroy: -> 74 | 75 | ## console.log @, 'destroy' 76 | 77 | # Remove event handlers. 78 | document.removeEventListener 'touchmove', @mousemove, false 79 | document.removeEventListener 'mousemove', @mousemove, false 80 | document.removeEventListener 'resize', @resize, false 81 | 82 | # Remove the render output from the DOM. 83 | try container.removeChild @renderer.domElement 84 | catch error 85 | 86 | do @renderer.destroy 87 | do @physics.destroy 88 | 89 | @renderer = null 90 | @physics = null 91 | @mouse = null 92 | 93 | ### Handler for window mousemove event. ### 94 | mousemove: (event) => 95 | 96 | do event.preventDefault 97 | 98 | if event.touches and !!event.touches.length 99 | 100 | touch = event.touches[0] 101 | @mouse.pos.set touch.pageX, touch.pageY 102 | 103 | else 104 | 105 | @mouse.pos.set event.clientX, event.clientY 106 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/demos/renderer/CanvasRenderer.coffee: -------------------------------------------------------------------------------- 1 | ### Canvas Renderer ### 2 | class CanvasRenderer extends Renderer 3 | 4 | constructor: -> 5 | 6 | super 7 | 8 | @canvas = document.createElement 'canvas' 9 | @ctx = @canvas.getContext '2d' 10 | 11 | # Set the DOM element. 12 | @domElement = @canvas 13 | 14 | init: (physics) -> 15 | 16 | super physics 17 | 18 | render: (physics) -> 19 | 20 | super physics 21 | 22 | time = new Date().getTime() 23 | 24 | # Draw velocity. 25 | vel = new Vector() 26 | 27 | # Draw heading. 28 | dir = new Vector() 29 | 30 | # Clear canvas. 31 | @canvas.width = @canvas.width 32 | 33 | @ctx.globalCompositeOperation = 'lighter' 34 | @ctx.lineWidth = 1 35 | 36 | # Draw particles. 37 | if @renderParticles 38 | 39 | TWO_PI = Math.PI * 2 40 | 41 | for p in physics.particles 42 | 43 | @ctx.beginPath() 44 | @ctx.arc(p.pos.x, p.pos.y, p.radius, 0, TWO_PI, no) 45 | 46 | @ctx.fillStyle = '#' + (p.colour or 'FFFFFF') 47 | @ctx.fill() 48 | 49 | if @renderSprings 50 | 51 | @ctx.strokeStyle = 'rgba(255,255,255,0.1)' 52 | @ctx.beginPath() 53 | 54 | for s in physics.springs 55 | @ctx.moveTo(s.p1.pos.x, s.p1.pos.y) 56 | @ctx.lineTo(s.p2.pos.x, s.p2.pos.y) 57 | 58 | @ctx.stroke() 59 | 60 | if @renderMouse 61 | 62 | # Draw mouse. 63 | @ctx.fillStyle = 'rgba(255,255,255,0.1)' 64 | @ctx.beginPath() 65 | @ctx.arc(@mouse.pos.x, @mouse.pos.y, 20, 0, TWO_PI) 66 | @ctx.fill() 67 | 68 | @renderTime = new Date().getTime() - time 69 | 70 | setSize: (@width, @height) => 71 | 72 | super @width, @height 73 | 74 | @canvas.width = @width 75 | @canvas.height = @height 76 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/demos/renderer/DOMRenderer.coffee: -------------------------------------------------------------------------------- 1 | ### DOM Renderer ### 2 | ### 3 | 4 | Updating styles: 5 | 6 | Nodes 7 | 8 | ### 9 | class DOMRenderer extends Renderer 10 | 11 | constructor: -> 12 | 13 | super 14 | 15 | @useGPU = yes 16 | 17 | @domElement = document.createElement 'div' 18 | @canvas = document.createElement 'canvas' 19 | @ctx = @canvas.getContext '2d' 20 | 21 | @canvas.style.position = 'absolute' 22 | @canvas.style.left = 0 23 | @canvas.style.top = 0 24 | 25 | @domElement.style.pointerEvents = 'none' 26 | @domElement.appendChild @canvas 27 | 28 | init: (physics) -> 29 | 30 | super physics 31 | 32 | # Set up particle DOM elements 33 | for p in physics.particles 34 | 35 | el = document.createElement 'span' 36 | st = el.style 37 | 38 | st.backgroundColor = p.colour 39 | st.borderRadius = p.radius 40 | st.marginLeft = -p.radius 41 | st.marginTop = -p.radius 42 | st.position = 'absolute' 43 | st.display = 'block' 44 | st.opacity = 0.85 45 | st.height = p.radius * 2 46 | st.width = p.radius * 2 47 | 48 | @domElement.appendChild el 49 | p.domElement = el 50 | 51 | # Set up mouse DOM element 52 | el = document.createElement 'span' 53 | st = el.style 54 | mr = 20 55 | 56 | st.backgroundColor = '#ffffff' 57 | st.borderRadius = mr 58 | st.marginLeft = -mr 59 | st.marginTop = -mr 60 | st.position = 'absolute' 61 | st.display = 'block' 62 | st.opacity = 0.1 63 | st.height = mr * 2 64 | st.width = mr * 2 65 | 66 | @domElement.appendChild el 67 | @mouse.domElement = el 68 | 69 | render: (physics) -> 70 | 71 | super physics 72 | 73 | time = new Date().getTime() 74 | 75 | if @renderParticles 76 | 77 | for p in physics.particles 78 | 79 | if @useGPU 80 | 81 | p.domElement.style.WebkitTransform = """ 82 | translate3d(#{p.pos.x|0}px,#{p.pos.y|0}px,0px) 83 | """ 84 | else 85 | 86 | p.domElement.style.left = p.pos.x 87 | p.domElement.style.top = p.pos.y 88 | 89 | if @renderSprings 90 | 91 | @canvas.width = @canvas.width 92 | 93 | @ctx.strokeStyle = 'rgba(255,255,255,0.1)' 94 | @ctx.beginPath() 95 | 96 | for s in physics.springs 97 | @ctx.moveTo(s.p1.pos.x, s.p1.pos.y) 98 | @ctx.lineTo(s.p2.pos.x, s.p2.pos.y) 99 | 100 | @ctx.stroke() 101 | 102 | if @renderMouse 103 | 104 | if @useGPU 105 | 106 | @mouse.domElement.style.WebkitTransform = """ 107 | translate3d(#{@mouse.pos.x|0}px,#{@mouse.pos.y|0}px,0px) 108 | """ 109 | else 110 | 111 | @mouse.domElement.style.left = @mouse.pos.x 112 | @mouse.domElement.style.top = @mouse.pos.y 113 | 114 | @renderTime = new Date().getTime() - time 115 | 116 | setSize: (@width, @height) => 117 | 118 | super @width, @height 119 | 120 | @canvas.width = @width 121 | @canvas.height = @height 122 | 123 | destroy: -> 124 | 125 | while @domElement.hasChildNodes() 126 | @domElement.removeChild @domElement.lastChild 127 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/demos/renderer/Renderer.coffee: -------------------------------------------------------------------------------- 1 | ### Base Renderer ### 2 | class Renderer 3 | 4 | constructor: -> 5 | 6 | @width = 0 7 | @height = 0 8 | 9 | @renderParticles = true 10 | @renderSprings = true 11 | @renderMouse = true 12 | @initialized = false 13 | @renderTime = 0 14 | 15 | init: (physics) -> 16 | 17 | @initialized = true 18 | 19 | render: (physics) -> 20 | 21 | if not @initialized then @init physics 22 | 23 | setSize: (@width, @height) => 24 | 25 | destroy: -> 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/engine/Particle.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Vector} = require 'coffeePhysics/math/Vector' 3 | 4 | ### Particle ### 5 | 6 | class exports.Particle 7 | 8 | @GUID = 0 9 | 10 | constructor: (@mass = 1.0) -> 11 | 12 | # Set a unique id. 13 | @id = 'p' + Particle.GUID++ 14 | 15 | # Set initial mass. 16 | @setMass @mass 17 | 18 | # Set initial radius. 19 | @setRadius 1.0 20 | 21 | # Apply forces. 22 | @fixed = false 23 | 24 | # Behaviours to be applied. 25 | @behaviours = [] 26 | 27 | # Current position. 28 | @pos = new Vector() 29 | 30 | # Current velocity. 31 | @vel = new Vector() 32 | 33 | # Current force. 34 | @acc = new Vector() 35 | 36 | # Previous state. 37 | @old = 38 | pos: new Vector() 39 | vel: new Vector() 40 | acc: new Vector() 41 | 42 | ### Moves the particle to a given location vector. ### 43 | moveTo: (pos) -> 44 | 45 | @pos.copy pos 46 | @old.pos.copy pos 47 | 48 | ### Sets the mass of the particle. ### 49 | setMass: (@mass = 1.0) -> 50 | 51 | # The inverse mass. 52 | @massInv = 1.0 / @mass 53 | 54 | ### Sets the radius of the particle. ### 55 | setRadius: (@radius = 1.0) -> 56 | 57 | @radiusSq = @radius * @radius 58 | 59 | ### Applies all behaviours to derive new force. ### 60 | update: (dt, index) -> 61 | 62 | # Apply all behaviours. 63 | 64 | if not @fixed 65 | 66 | for behaviour in @behaviours 67 | 68 | behaviour.apply @, dt, index 69 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/engine/Physics.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Euler} = require 'coffeePhysics/engine/integrator/Euler' 3 | 4 | ### Physics Engine ### 5 | 6 | class exports.Physics 7 | 8 | constructor: (@integrator = new Euler()) -> 9 | 10 | # Fixed timestep. 11 | @timestep = 1.0 / 60 12 | 13 | # Friction within the system. 14 | @viscosity = 0.005 15 | 16 | # Global behaviours. 17 | @behaviours = [] 18 | 19 | # Time in seconds. 20 | @_time = 0.0 21 | 22 | # Last step duration. 23 | @_step = 0.0 24 | 25 | # Current time. 26 | @_clock = null 27 | 28 | # Time buffer. 29 | @_buffer = 0.0 30 | 31 | # Max iterations per step. 32 | @_maxSteps = 4 33 | 34 | # Particles in system. 35 | @particles = [] 36 | 37 | # Springs in system. 38 | @springs = [] 39 | 40 | ### Performs a numerical integration step. ### 41 | integrate: (dt) -> 42 | 43 | # Drag is inversely proportional to viscosity. 44 | drag = 1.0 - @viscosity 45 | 46 | # Update particles / apply behaviours. 47 | 48 | for particle, index in @particles 49 | 50 | for behaviour in @behaviours 51 | 52 | behaviour.apply particle, dt, index 53 | 54 | particle.update dt, index 55 | 56 | # Integrate motion. 57 | 58 | @integrator.integrate @particles, dt, drag 59 | 60 | # Compute all springs. 61 | 62 | for spring in @springs 63 | 64 | spring.apply() 65 | 66 | ### Steps the system. ### 67 | step: -> 68 | 69 | # Initialise the clock on first step. 70 | @_clock ?= new Date().getTime() 71 | 72 | # Compute delta time since last step. 73 | time = new Date().getTime() 74 | delta = time - @_clock 75 | 76 | # No sufficient change. 77 | return if delta <= 0.0 78 | 79 | # Convert time to seconds. 80 | delta *= 0.001 81 | 82 | # Update the clock. 83 | @_clock = time 84 | 85 | # Increment time buffer. 86 | @_buffer += delta 87 | 88 | # Integrate until the buffer is empty or until the 89 | # maximum amount of iterations per step is reached. 90 | 91 | i = 0 92 | 93 | while @_buffer >= @timestep and ++i < @_maxSteps 94 | 95 | # Integrate motion by fixed timestep. 96 | @integrate @timestep 97 | 98 | # Reduce buffer by one timestep. 99 | @_buffer -= @timestep 100 | 101 | # Increment running time. 102 | @_time += @timestep 103 | 104 | # Store step time for debugging. 105 | @_step = new Date().getTime() - time 106 | 107 | ### Clean up after yourself. ### 108 | destroy: -> 109 | 110 | @integrator = null 111 | @particles = null 112 | @springs = null 113 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/engine/Spring.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Vector} = require 'coffeePhysics/math/Vector' 3 | 4 | ### Spring ### 5 | 6 | class exports.Spring 7 | 8 | constructor: (@p1, @p2, @restLength = 100, @stiffness = 1.0) -> 9 | 10 | @_delta = new Vector() 11 | 12 | # F = -kx 13 | 14 | apply: -> 15 | 16 | (@_delta.copy @p2.pos).sub @p1.pos 17 | 18 | dist = @_delta.mag() + 0.000001 19 | force = (dist - @restLength) / (dist * (@p1.massInv + @p2.massInv)) * @stiffness 20 | 21 | if not @p1.fixed 22 | 23 | @p1.pos.add (@_delta.clone().scale force * @p1.massInv) 24 | 25 | if not @p2.fixed 26 | 27 | @p2.pos.add (@_delta.scale -force * @p2.massInv) 28 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/engine/integrator/Euler.coffee: -------------------------------------------------------------------------------- 1 | ### Import Integrator ### 2 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 3 | 4 | ### Euler Integrator ### 5 | class exports.Euler extends Integrator 6 | 7 | # v += a * dt 8 | # x += v * dt 9 | 10 | integrate: (particles, dt, drag) -> 11 | 12 | vel = new Vector() 13 | 14 | for p in particles when not p.fixed 15 | 16 | # Store previous location. 17 | p.old.pos.copy p.pos 18 | 19 | # Scale force to mass. 20 | p.acc.scale p.massInv 21 | 22 | # Duplicate velocity to preserve momentum. 23 | vel.copy p.vel 24 | 25 | # Add force to velocity. 26 | p.vel.add p.acc.scale dt 27 | 28 | # Add velocity to position. 29 | p.pos.add vel.scale dt 30 | 31 | # Apply friction. 32 | if drag then p.vel.scale drag 33 | 34 | # Reset forces. 35 | p.acc.clear() 36 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/engine/integrator/ImprovedEuler.coffee: -------------------------------------------------------------------------------- 1 | ### Import Integrator ### 2 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 3 | 4 | ### Improved Euler Integrator ### 5 | 6 | class exports.ImprovedEuler extends Integrator 7 | 8 | # x += (v * dt) + (a * 0.5 * dt * dt) 9 | # v += a * dt 10 | 11 | integrate: (particles, dt, drag) -> 12 | 13 | acc = new Vector() 14 | vel = new Vector() 15 | 16 | dtSq = dt * dt 17 | 18 | for p in particles when not p.fixed 19 | 20 | # Store previous location. 21 | p.old.pos.copy p.pos 22 | 23 | # Scale force to mass. 24 | p.acc.scale p.massInv 25 | 26 | # Duplicate velocity to preserve momentum. 27 | vel.copy p.vel 28 | 29 | # Duplicate force. 30 | acc.copy p.acc 31 | 32 | # Update position. 33 | p.pos.add (vel.scale dt).add (acc.scale 0.5 * dtSq) 34 | 35 | # Update velocity. 36 | p.vel.add p.acc.scale dt 37 | 38 | # Apply friction. 39 | if drag then p.vel.scale drag 40 | 41 | # Reset forces. 42 | p.acc.clear() 43 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/engine/integrator/Integrator.coffee: -------------------------------------------------------------------------------- 1 | ### Integrator ### 2 | 3 | class exports.Integrator 4 | 5 | integrate: (particles, dt) -> 6 | 7 | # Override. 8 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/engine/integrator/Verlet.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | 6 | ### Velocity Verlet Integrator ### 7 | 8 | class exports.Verlet extends Integrator 9 | 10 | # v = x - ox 11 | # x = x + (v + a * dt * dt) 12 | 13 | integrate: (particles, dt, drag) -> 14 | 15 | pos = new Vector() 16 | 17 | dtSq = dt * dt 18 | 19 | for p in particles when not p.fixed 20 | 21 | # Scale force to mass. 22 | p.acc.scale p.massInv 23 | 24 | # Derive velocity. 25 | (p.vel.copy p.pos).sub p.old.pos 26 | 27 | # Apply friction. 28 | if drag then p.vel.scale drag 29 | 30 | # Apply forces to new position. 31 | (pos.copy p.pos).add (p.vel.add p.acc.scale dtSq) 32 | 33 | # Store old position. 34 | p.old.pos.copy p.pos 35 | 36 | # update position. 37 | p.pos.copy pos 38 | 39 | # Reset forces. 40 | p.acc.clear() 41 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/math/Random.coffee: -------------------------------------------------------------------------------- 1 | ### Random ### 2 | 3 | exports.Random = (min, max) -> 4 | 5 | if not max? 6 | max = min 7 | min = 0 8 | 9 | min + Math.random() * (max - min) 10 | 11 | Random.int = (min, max) -> 12 | 13 | if not max? 14 | max = min 15 | min = 0 16 | 17 | Math.floor min + Math.random() * (max - min) 18 | 19 | Random.sign = (prob = 0.5) -> 20 | 21 | if do Math.random < prob then 1 else -1 22 | 23 | Random.bool = (prob = 0.5) -> 24 | 25 | do Math.random < prob 26 | 27 | Random.item = (list) -> 28 | 29 | list[ Math.floor Math.random() * list.length ] 30 | -------------------------------------------------------------------------------- /examples/gravity.framer/modules/coffeePhysics/math/Vector.coffee: -------------------------------------------------------------------------------- 1 | ### 2D Vector ### 2 | 3 | class exports.Vector 4 | 5 | ### Adds two vectors and returns the product. ### 6 | @add: (v1, v2) -> 7 | new Vector v1.x + v2.x, v1.y + v2.y 8 | 9 | ### Subtracts v2 from v1 and returns the product. ### 10 | @sub: (v1, v2) -> 11 | new Vector v1.x - v2.x, v1.y - v2.y 12 | 13 | ### Projects one vector (v1) onto another (v2) ### 14 | @project: (v1, v2) -> 15 | v1.clone().scale ((v1.dot v2) / v1.magSq()) 16 | 17 | ### Creates a new Vector instance. ### 18 | constructor: (@x = 0.0, @y = 0.0) -> 19 | 20 | ### Sets the components of this vector. ### 21 | set: (@x, @y) -> 22 | @ 23 | 24 | ### Add a vector to this one. ### 25 | add: (v) -> 26 | @x += v.x; @y += v.y; @ 27 | 28 | ### Subtracts a vector from this one. ### 29 | sub: (v) -> 30 | @x -= v.x; @y -= v.y; @ 31 | 32 | ### Scales this vector by a value. ### 33 | scale: (f) -> 34 | @x *= f; @y *= f; @ 35 | 36 | ### Computes the dot product between vectors. ### 37 | dot: (v) -> 38 | @x * v.x + @y * v.y 39 | 40 | ### Computes the cross product between vectors. ### 41 | cross: (v) -> 42 | (@x * v.y) - (@y * v.x) 43 | 44 | ### Computes the magnitude (length). ### 45 | mag: -> 46 | Math.sqrt @x*@x + @y*@y 47 | 48 | ### Computes the squared magnitude (length). ### 49 | magSq: -> 50 | @x*@x + @y*@y 51 | 52 | ### Computes the distance to another vector. ### 53 | dist: (v) -> 54 | dx = v.x - @x; dy = v.y - @y 55 | Math.sqrt dx*dx + dy*dy 56 | 57 | ### Computes the squared distance to another vector. ### 58 | distSq: (v) -> 59 | dx = v.x - @x; dy = v.y - @y 60 | dx*dx + dy*dy 61 | 62 | ### Normalises the vector, making it a unit vector (of length 1). ### 63 | norm: -> 64 | m = Math.sqrt @x*@x + @y*@y 65 | @x /= m 66 | @y /= m 67 | @ 68 | 69 | ### Limits the vector length to a given amount. ### 70 | limit: (l) -> 71 | mSq = @x*@x + @y*@y 72 | if mSq > l*l 73 | m = Math.sqrt mSq 74 | @x /= m; @y /= m 75 | @x *= l; @y *= l 76 | @ 77 | 78 | ### Copies components from another vector. ### 79 | copy: (v) -> 80 | @x = v.x; @y = v.y; @ 81 | 82 | ### Clones this vector to a new itentical one. ### 83 | clone: -> 84 | new Vector @x, @y 85 | 86 | ### Resets the vector to zero. ### 87 | clear: -> 88 | @x = 0.0; @y = 0.0; @ 89 | -------------------------------------------------------------------------------- /examples/physics.framer/.gitignore: -------------------------------------------------------------------------------- 1 | # Framer Git Ignore 2 | 3 | # General OSX 4 | 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | # Framer Specific 31 | .*.html 32 | .app.js 33 | framer/*.old* 34 | framer/.*.hash 35 | framer/backup.coffee 36 | framer/backups/* 37 | framer/manifest.txt 38 | framer/metadata.json 39 | framer/preview.png 40 | framer/social-880x460.png 41 | framer/social-1200x630.png 42 | -------------------------------------------------------------------------------- /examples/physics.framer/app.coffee: -------------------------------------------------------------------------------- 1 | # Project setup 2 | ################################################################################ 3 | {Integrator, Euler, ImprovedEuler, Verlet, Particle, Physics, Vector, Spring, Behaviour, Attraction, Collision, ConstantForce, EdgeBounce, EdgeWrap, Wander, Gravity} = require 'coffeePhysics' 4 | 5 | # Colours 6 | ################################################################################ 7 | 8 | red = new Color("rgba(224,32,36,1)") 9 | orange = new Color("rgba(255,128,21,1)") 10 | yellow = new Color("rgba(255,224,0,1)") 11 | green = new Color("rgba(55,191,0,1)") 12 | blue = new Color("rgba(0,150,212,1)") 13 | pink = new Color("rgba(213,45,177,1)") 14 | lightGrey = new Color("rgba(239,239,239,1)") 15 | 16 | colours = [red, orange, yellow, green, blue, pink] 17 | colourCycler = Utils.cycle(colours) 18 | 19 | background = new BackgroundLayer 20 | backgroundColor: lightGrey 21 | 22 | ################################################################################ 23 | 24 | # Create a physics instance which uses the Verlet integration method 25 | physics = new Physics() 26 | physics.integrator = new Verlet() 27 | 28 | # Allow particle collisions to make things interesting 29 | collision = new Collision() 30 | 31 | # Design some behaviours for particles 32 | avoid = new Attraction() 33 | pullToCenter = new Attraction() 34 | 35 | pullToCenter.target.x = Screen.width / 2 36 | pullToCenter.target.y = Screen.height / 2 37 | pullToCenter.strength = 120 38 | 39 | avoid.setRadius( 100 ) 40 | avoid.strength = -1000 41 | 42 | ################################################################################ 43 | 44 | avoidLayer = new Layer 45 | borderRadius: 100 46 | size: 100 47 | opacity: 0.30 48 | 49 | avoidLayer.center() 50 | avoidLayer.draggable.enabled = true 51 | avoidLayer.draggable.constraints = 52 | x: 0 53 | y: 0 54 | width: Screen.width 55 | height: Screen.height 56 | 57 | ################################################################################ 58 | 59 | balls = [] 60 | 61 | # Render the particles 62 | for i in [0..200] 63 | 64 | # Create a particle 65 | particle = new Particle( Utils.randomNumber(.1,1) ) 66 | position = new Vector( Utils.randomNumber( 0, Screen.width ), Utils.randomNumber( 0, Screen.height ) ) 67 | particle.setRadius( particle.mass * 10 ) 68 | particle.moveTo( position ) 69 | 70 | # Apply behaviours to the particle 71 | particle.behaviours.push( avoid, pullToCenter, collision ) 72 | 73 | # Make it collidable 74 | collision.pool.push( particle ) 75 | 76 | # Add to the simulation 77 | physics.particles.push( particle ) 78 | 79 | # Create a layer to show the particle on the screen 80 | ball = new Layer 81 | x: particle.pos.x - particle.radius 82 | y: particle.pos.y - particle.radius 83 | size: particle.radius * 2 84 | borderRadius: particle.radius 85 | backgroundColor: colourCycler() 86 | 87 | # Add the particle instance to the layer 88 | ball.particle = particle 89 | 90 | balls.push(ball) 91 | 92 | # Set everything in motion 93 | ################################################################################ 94 | 95 | frameRate = 1 / 60 96 | 97 | Utils.interval frameRate, -> 98 | # Update the position of the avoid target 99 | avoid.target.x = avoidLayer.x + 50 100 | avoid.target.y = avoidLayer.y + 50 101 | 102 | # Step the simulation 103 | physics.step() 104 | 105 | # Update the position of the balls 106 | for ball, i in balls 107 | ball.x = ball.particle.pos.x - ball.particle.radius 108 | ball.y = ball.particle.pos.y - ball.particle.radius 109 | -------------------------------------------------------------------------------- /examples/physics.framer/framer/.bookmark: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/physics.framer/framer/.bookmark -------------------------------------------------------------------------------- /examples/physics.framer/framer/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "orientation" : 0, 3 | "updateDelay" : 0.3, 4 | "cachedDeviceHeight" : 1334, 5 | "contentScale" : 1, 6 | "fullScreen" : false, 7 | "cachedDeviceWidth" : 750, 8 | "sharedPrototype" : 0, 9 | "propertyPanelToggleStates" : { 10 | "Filters" : false 11 | }, 12 | "deviceType" : "apple-iphone-7-silver", 13 | "projectId" : "263AE842-3609-46D8-8BB7-8F3E8135BCD3", 14 | "deviceOrientation" : 0, 15 | "selectedHand" : "", 16 | "foldedCodeRanges" : [ 17 | 18 | ], 19 | "deviceScale" : "fit" 20 | } -------------------------------------------------------------------------------- /examples/physics.framer/framer/framer.generated.js: -------------------------------------------------------------------------------- 1 | // This is autogenerated by Framer 2 | 3 | 4 | if (!window.Framer && window._bridge) {window._bridge('runtime.error', {message:'[framer.js] Framer library missing or corrupt. Select File → Update Framer Library.'})} 5 | if (DeviceComponent) {DeviceComponent.Devices["iphone-6-silver"].deviceImageJP2 = false}; 6 | if (window.Framer) {window.Framer.Defaults.DeviceView = {"deviceScale":"fit","selectedHand":"","deviceType":"apple-iphone-7-silver","contentScale":1,"hideBezel":true,"orientation":0}; 7 | } 8 | if (window.Framer) {window.Framer.Defaults.DeviceComponent = {"deviceScale":"fit","selectedHand":"","deviceType":"apple-iphone-7-silver","contentScale":1,"hideBezel":true,"orientation":0}; 9 | } 10 | window.FramerStudioInfo = {"deviceImagesUrl":"\/_server\/resources\/DeviceImages","documentTitle":"physics.framer"}; 11 | 12 | Framer.Device = new Framer.DeviceView(); 13 | Framer.Device.setupContext(); -------------------------------------------------------------------------------- /examples/physics.framer/framer/framer.init.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | function isFileLoadingAllowed() { 4 | return (window.location.protocol.indexOf("file") == -1) 5 | } 6 | 7 | function isHomeScreened() { 8 | return ("standalone" in window.navigator) && window.navigator.standalone == true 9 | } 10 | 11 | function isCompatibleBrowser() { 12 | return Utils.isWebKit() 13 | } 14 | 15 | var alertNode; 16 | 17 | function dismissAlert() { 18 | alertNode.parentElement.removeChild(alertNode) 19 | loadProject() 20 | } 21 | 22 | function showAlert(html) { 23 | 24 | alertNode = document.createElement("div") 25 | 26 | alertNode.classList.add("framerAlertBackground") 27 | alertNode.innerHTML = html 28 | 29 | document.addEventListener("DOMContentLoaded", function(event) { 30 | document.body.appendChild(alertNode) 31 | }) 32 | 33 | window.dismissAlert = dismissAlert; 34 | } 35 | 36 | function showBrowserAlert() { 37 | var html = "" 38 | html += "
" 39 | html += "Error: Not A WebKit Browser" 40 | html += "Your browser is not supported.
Please use Safari or Chrome.
" 41 | html += "Try anyway" 42 | html += "
" 43 | 44 | showAlert(html) 45 | } 46 | 47 | function showFileLoadingAlert() { 48 | var html = "" 49 | html += "
" 50 | html += "Error: Local File Restrictions" 51 | html += "Preview this prototype with Framer Mirror or learn more about " 52 | html += "file restrictions.
" 53 | html += "Try anyway" 54 | html += "
" 55 | 56 | showAlert(html) 57 | } 58 | 59 | function loadProject() { 60 | CoffeeScript.load("app.coffee") 61 | } 62 | 63 | function setDefaultPageTitle() { 64 | // If no title was set we set it to the project folder name so 65 | // you get a nice name on iOS if you bookmark to desktop. 66 | document.addEventListener("DOMContentLoaded", function() { 67 | if (document.title == "") { 68 | if (window.FramerStudioInfo && window.FramerStudioInfo.documentTitle) { 69 | document.title = window.FramerStudioInfo.documentTitle 70 | } else { 71 | document.title = window.location.pathname.replace(/\//g, "") 72 | } 73 | } 74 | }) 75 | } 76 | 77 | function init() { 78 | 79 | if (Utils.isFramerStudio()) { 80 | return 81 | } 82 | 83 | setDefaultPageTitle() 84 | 85 | if (!isCompatibleBrowser()) { 86 | return showBrowserAlert() 87 | } 88 | 89 | if (!isFileLoadingAllowed()) { 90 | return showFileLoadingAlert() 91 | } 92 | 93 | loadProject() 94 | 95 | } 96 | 97 | init() 98 | 99 | })() 100 | -------------------------------------------------------------------------------- /examples/physics.framer/framer/images/cursor-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/physics.framer/framer/images/cursor-active.png -------------------------------------------------------------------------------- /examples/physics.framer/framer/images/cursor-active@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/physics.framer/framer/images/cursor-active@2x.png -------------------------------------------------------------------------------- /examples/physics.framer/framer/images/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/physics.framer/framer/images/cursor.png -------------------------------------------------------------------------------- /examples/physics.framer/framer/images/cursor@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/physics.framer/framer/images/cursor@2x.png -------------------------------------------------------------------------------- /examples/physics.framer/framer/images/icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/physics.framer/framer/images/icon-120.png -------------------------------------------------------------------------------- /examples/physics.framer/framer/images/icon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/physics.framer/framer/images/icon-152.png -------------------------------------------------------------------------------- /examples/physics.framer/framer/images/icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/physics.framer/framer/images/icon-180.png -------------------------------------------------------------------------------- /examples/physics.framer/framer/images/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/physics.framer/framer/images/icon-192.png -------------------------------------------------------------------------------- /examples/physics.framer/framer/images/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/physics.framer/framer/images/icon-76.png -------------------------------------------------------------------------------- /examples/physics.framer/framer/social-800x600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/physics.framer/framer/social-800x600.png -------------------------------------------------------------------------------- /examples/physics.framer/framer/social-80x80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/physics.framer/framer/social-80x80.png -------------------------------------------------------------------------------- /examples/physics.framer/framer/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | border: none; 5 | -webkit-user-select: none; 6 | -webkit-tap-highlight-color: rgba(0,0,0,0); 7 | } 8 | 9 | body { 10 | background-color: #fff; 11 | font: 28px/1em "Helvetica"; 12 | color: gray; 13 | overflow: hidden; 14 | } 15 | 16 | a { 17 | color: gray; 18 | } 19 | 20 | body { 21 | cursor: url('images/cursor.png') 32 32, auto; 22 | cursor: -webkit-image-set( 23 | url('images/cursor.png') 1x, 24 | url('images/cursor@2x.png') 2x 25 | ) 32 32, auto; 26 | } 27 | 28 | body:active { 29 | cursor: url('images/cursor-active.png') 32 32, auto; 30 | cursor: -webkit-image-set( 31 | url('images/cursor-active.png') 1x, 32 | url('images/cursor-active@2x.png') 2x 33 | ) 32 32, auto; 34 | } 35 | 36 | .framerAlertBackground { 37 | position: absolute; top:0px; left:0px; right:0px; bottom:0px; 38 | z-index: 1000; 39 | background-color: #fff; 40 | } 41 | 42 | .framerAlert { 43 | font:400 14px/1.4 "Helvetica Neue", Helvetica, Arial, sans-serif; 44 | -webkit-font-smoothing:antialiased; 45 | color:#616367; text-align:center; 46 | position: absolute; top:40%; left:50%; width:260px; margin-left:-130px; 47 | } 48 | .framerAlert strong { font-weight:500; color:#000; margin-bottom:8px; display:block; } 49 | .framerAlert a { color:#28AFFA; } 50 | .framerAlert .btn { 51 | font-weight:500; text-decoration:none; line-height:1; 52 | display:inline-block; padding:6px 12px 7px 12px; 53 | border-radius:3px; margin-top:12px; 54 | background:#28AFFA; color:#fff; 55 | } 56 | 57 | ::-webkit-scrollbar { 58 | display: none; 59 | } -------------------------------------------------------------------------------- /examples/physics.framer/framer/version: -------------------------------------------------------------------------------- 1 | 6 -------------------------------------------------------------------------------- /examples/physics.framer/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/physics.framer/images/.gitkeep -------------------------------------------------------------------------------- /examples/physics.framer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics.coffee: -------------------------------------------------------------------------------- 1 | # Add the following line to your project in Framer Studio. 2 | # myModule = require "myModule" 3 | # Reference the contents by name, like myModule.myFunction() or myModule.myVar 4 | 5 | 6 | # Import integrator framework 7 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 8 | {Euler} = require 'coffeePhysics/engine/integrator/Euler' 9 | {ImprovedEuler} = require 'coffeePhysics/engine/integrator/ImprovedEuler' 10 | {Verlet} = require 'coffeePhysics/engine/integrator/Verlet' 11 | 12 | exports.Integrator = Integrator 13 | exports.Euler = Euler 14 | exports.ImprovedEuler = ImprovedEuler 15 | exports.Verlet = Verlet 16 | 17 | # Import physics framework 18 | {Particle} = require 'coffeePhysics/engine/Particle' 19 | {Physics} = require 'coffeePhysics/engine/Physics' 20 | {Spring} = require 'coffeePhysics/engine/Spring' 21 | 22 | exports.Particle = Particle 23 | exports.Physics = Physics 24 | exports.Spring = Spring 25 | 26 | # Import math framework 27 | # {Random} = require 'coffeePhysics/math/Random' 28 | {Vector} = require 'coffeePhysics/math/Vector' 29 | 30 | # exports.Random = Random 31 | exports.Vector = Vector 32 | 33 | # Import behaviour framework 34 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 35 | {Attraction} = require 'coffeePhysics/behaviour/Attraction' 36 | {Collision} = require 'coffeePhysics/behaviour/Collision' 37 | {ConstantForce} = require 'coffeePhysics/behaviour/ConstantForce' 38 | {EdgeBounce} = require 'coffeePhysics/behaviour/EdgeBounce' 39 | {EdgeWrap} = require 'coffeePhysics/behaviour/EdgeWrap' 40 | {Wander} = require 'coffeePhysics/behaviour/Wander' 41 | {Gravity} = require 'coffeePhysics/behaviour/Gravity' 42 | 43 | exports.Behaviour = Behaviour 44 | exports.Attraction = Attraction 45 | exports.Collision = Collision 46 | exports.ConstantForce = ConstantForce 47 | exports.EdgeBounce = EdgeBounce 48 | exports.EdgeWrap = EdgeWrap 49 | exports.Wander = Wander 50 | exports.Gravity = Gravity 51 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/base.coffee: -------------------------------------------------------------------------------- 1 | ### Allows safe, dyamic creation of namespaces. ### 2 | 3 | namespace = (id) -> 4 | root = self 5 | root = root[path] ?= {} for path in id.split '.' 6 | 7 | ### RequestAnimationFrame shim. ### 8 | do -> 9 | 10 | time = 0 11 | vendors = ['ms', 'moz', 'webkit', 'o'] 12 | 13 | for vendor in vendors when not window.requestAnimationFrame 14 | window.requestAnimationFrame = window[ vendor + 'RequestAnimationFrame'] 15 | window.cancelAnimationFrame = window[ vendor + 'CancelAnimationFrame'] 16 | 17 | if not window.requestAnimationFrame 18 | 19 | window.requestAnimationFrame = (callback, element) -> 20 | now = new Date().getTime() 21 | delta = Math.max 0, 16 - (now - old) 22 | setTimeout (-> callback(time + delta)), delta 23 | old = now + delta 24 | 25 | if not window.cancelAnimationFrame 26 | 27 | window.cancelAnimationFrame = (id) -> 28 | clearTimeout id 29 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/behaviour/Attraction.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Attraction Behaviour ### 6 | 7 | class exports.Attraction extends Behaviour 8 | 9 | constructor: (@target = new Vector(), @radius = 1000, @strength = 100.0) -> 10 | 11 | @_delta = new Vector() 12 | @setRadius @radius 13 | 14 | super 15 | 16 | ### Sets the effective radius of the bahavious. ### 17 | setRadius: (radius) -> 18 | 19 | @radius = radius 20 | @radiusSq = radius * radius 21 | 22 | apply: (p, dt, index) -> 23 | 24 | #super p, dt, index 25 | 26 | # Vector pointing from particle to target. 27 | (@_delta.copy @target).sub p.pos 28 | 29 | # Squared distance to target. 30 | distSq = @_delta.magSq() 31 | 32 | # Limit force to behaviour radius. 33 | if distSq < @radiusSq and distSq > 0.000001 34 | 35 | # Calculate force vector. 36 | @_delta.norm().scale (1.0 - distSq / @radiusSq) 37 | 38 | #Apply force. 39 | p.acc.add @_delta.scale @strength 40 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/behaviour/Behaviour.coffee: -------------------------------------------------------------------------------- 1 | ### Behaviour ### 2 | 3 | class exports.Behaviour 4 | 5 | # Each behaviour has a unique id 6 | @GUID = 0 7 | 8 | constructor: -> 9 | 10 | @GUID = Behaviour.GUID++ 11 | @interval = 1 12 | 13 | ## console.log @, @GUID 14 | 15 | apply: (p, dt, index) -> 16 | 17 | # Store some data in each particle. 18 | (p['__behaviour' + @GUID] ?= {counter: 0}).counter++ 19 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/behaviour/Collision.coffee: -------------------------------------------------------------------------------- 1 | ### Import Behaviour ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Collision Behaviour ### 6 | 7 | # TODO: Collision response for non Verlet integrators. 8 | 9 | class exports.Collision extends Behaviour 10 | 11 | constructor: (@useMass = yes, @callback = null) -> 12 | 13 | # Pool of collidable particles. 14 | @pool = [] 15 | 16 | # Delta between particle positions. 17 | @_delta = new Vector() 18 | 19 | super 20 | 21 | apply: (p, dt, index) -> 22 | 23 | #super p, dt, index 24 | 25 | # Check pool for collisions. 26 | for o in @pool[index..] when o isnt p 27 | 28 | # Delta between particles positions. 29 | (@_delta.copy o.pos).sub p.pos 30 | 31 | # Squared distance between particles. 32 | distSq = @_delta.magSq() 33 | 34 | # Sum of both radii. 35 | radii = p.radius + o.radius 36 | 37 | # Check if particles collide. 38 | if distSq <= radii * radii 39 | 40 | # Compute real distance. 41 | dist = Math.sqrt distSq 42 | 43 | # Determine overlap. 44 | overlap = radii - dist 45 | overlap += 0.5 46 | 47 | # Total mass. 48 | mt = p.mass + o.mass 49 | 50 | # Distribute collision responses. 51 | r1 = if @useMass then o.mass / mt else 0.5 52 | r2 = if @useMass then p.mass / mt else 0.5 53 | 54 | # Move particles so they no longer overlap. 55 | p.pos.add (@_delta.clone().norm().scale overlap * -r1) 56 | o.pos.add (@_delta.norm().scale overlap * r2) 57 | 58 | # Fire callback if defined. 59 | @callback?(p, o, overlap) 60 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/behaviour/ConstantForce.coffee: -------------------------------------------------------------------------------- 1 | ### Import Behaviour ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Constant Force Behaviour ### 6 | 7 | class exports.ConstantForce extends Behaviour 8 | 9 | constructor: (@force = new Vector()) -> 10 | 11 | super 12 | 13 | apply: (p, dt,index) -> 14 | 15 | #super p, dt, index 16 | 17 | p.acc.add @force 18 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/behaviour/EdgeBounce.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Edge Bounce Behaviour ### 6 | 7 | class exports.EdgeBounce extends Behaviour 8 | 9 | constructor: (@min = new Vector(), @max = new Vector()) -> 10 | 11 | super 12 | 13 | apply: (p, dt, index) -> 14 | 15 | #super p, dt, index 16 | 17 | if p.pos.x - p.radius < @min.x 18 | 19 | p.pos.x = @min.x + p.radius 20 | 21 | else if p.pos.x + p.radius > @max.x 22 | 23 | p.pos.x = @max.x - p.radius 24 | 25 | if p.pos.y - p.radius < @min.y 26 | 27 | p.pos.y = @min.y + p.radius 28 | 29 | else if p.pos.y + p.radius > @max.y 30 | 31 | p.pos.y = @max.y - p.radius 32 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/behaviour/EdgeWrap.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Edge Wrap Behaviour ### 6 | 7 | class exports.EdgeWrap extends Behaviour 8 | 9 | constructor: (@min = new Vector(), @max = new Vector()) -> 10 | 11 | super 12 | 13 | apply: (p, dt, index) -> 14 | 15 | #super p, dt, index 16 | 17 | if p.pos.x + p.radius < @min.x 18 | 19 | p.pos.x = @max.x + p.radius 20 | p.old.pos.x = p.pos.x 21 | 22 | else if p.pos.x - p.radius > @max.x 23 | 24 | p.pos.x = @min.x - p.radius 25 | p.old.pos.x = p.pos.x 26 | 27 | if p.pos.y + p.radius < @min.y 28 | 29 | p.pos.y = @max.y + p.radius 30 | p.old.pos.y = p.pos.y 31 | 32 | else if p.pos.y - p.radius > @max.y 33 | 34 | p.pos.y = @min.y - p.radius 35 | p.old.pos.y = p.pos.y 36 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/behaviour/Gravity.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {ConstantForce} = require 'coffeePhysics/behaviour/ConstantForce' 3 | 4 | ### Gravity Behaviour ### 5 | 6 | class exports.Gravity extends ConstantForce 7 | 8 | constructor: (@scale = 1000) -> 9 | 10 | super() 11 | 12 | force = @force 13 | scale = @scale 14 | 15 | window.addEventListener "devicemotion", -> 16 | accX = event.accelerationIncludingGravity.x 17 | accY = event.accelerationIncludingGravity.y * -1 18 | 19 | force.x = accX * scale / 10 20 | force.y = accY * scale / 10 21 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/behaviour/Wander.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | 4 | ### Wander Behaviour ### 5 | 6 | class exports.Wander extends Behaviour 7 | 8 | constructor: (@jitter = 0.5, @radius = 100, @strength = 1.0) -> 9 | 10 | @theta = Math.random() * Math.PI * 2 11 | 12 | super 13 | 14 | apply: (p, dt, index) -> 15 | 16 | #super p, dt, index 17 | 18 | @theta += (Math.random() - 0.5) * @jitter * Math.PI * 2 19 | 20 | p.acc.x += Math.cos(@theta) * @radius * @strength 21 | p.acc.y += Math.sin(@theta) * @radius * @strength 22 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/demos/AttractionDemo.coffee: -------------------------------------------------------------------------------- 1 | class AttractionDemo extends Demo 2 | 3 | setup: (full = yes) -> 4 | 5 | super full 6 | 7 | min = new Vector 0.0, 0.0 8 | max = new Vector @width, @height 9 | 10 | bounds = new EdgeBounce min, max 11 | 12 | @physics.integrator = new Verlet() 13 | 14 | attraction = new Attraction @mouse.pos, 1200, 1200 15 | repulsion = new Attraction @mouse.pos, 200, -2000 16 | collide = new Collision() 17 | 18 | max = if full then 400 else 200 19 | 20 | for i in [0..max] 21 | 22 | p = new Particle (Random 0.1, 3.0) 23 | p.setRadius p.mass * 4 24 | 25 | p.moveTo new Vector (Random @width), (Random @height) 26 | 27 | p.behaviours.push attraction 28 | p.behaviours.push repulsion 29 | p.behaviours.push bounds 30 | p.behaviours.push collide 31 | 32 | collide.pool.push p 33 | 34 | @physics.particles.push p -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/demos/BalloonDemo.coffee: -------------------------------------------------------------------------------- 1 | ### BalloonDemo ### 2 | class BalloonDemo extends Demo 3 | 4 | setup: (full = yes) -> 5 | 6 | super 7 | 8 | @physics.integrator = new ImprovedEuler() 9 | attraction = new Attraction @mouse.pos 10 | 11 | max = if full then 400 else 200 12 | 13 | for i in [0..max] 14 | 15 | p = new Particle (Random 0.25, 4.0) 16 | p.setRadius p.mass * 8 17 | 18 | p.behaviours.push new Wander 0.2 19 | p.behaviours.push attraction 20 | 21 | p.moveTo new Vector (Random @width), (Random @height) 22 | 23 | s = new Spring @mouse, p, (Random 30, 300), 1.0 24 | 25 | @physics.particles.push p 26 | @physics.springs.push s 27 | 28 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/demos/BoundsDemo.coffee: -------------------------------------------------------------------------------- 1 | ### BoundsDemo ### 2 | class BoundsDemo extends Demo 3 | 4 | setup: -> 5 | 6 | super 7 | 8 | min = new Vector 0.0, 0.0 9 | max = new Vector @width, @height 10 | 11 | edge = new EdgeWrap min, max 12 | 13 | for i in [0..200] 14 | 15 | p = new Particle (Random 0.5, 4.0) 16 | p.setRadius p.mass * 5 17 | 18 | p.moveTo new Vector (Random @width), (Random @height) 19 | 20 | p.behaviours.push new Wander 0.2, 120, Random 1.0, 2.0 21 | p.behaviours.push edge 22 | 23 | @physics.particles.push p 24 | 25 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/demos/ChainDemo.coffee: -------------------------------------------------------------------------------- 1 | class ChainDemo extends Demo 2 | 3 | setup: (full = yes) -> 4 | 5 | super 6 | 7 | @stiffness = 1.0 8 | @spacing = 2.0 9 | 10 | @physics.integrator = new Verlet() 11 | @physics.viscosity = 0.0001 12 | @mouse.setMass 1000 13 | 14 | gap = 50.0 15 | min = new Vector -gap, -gap 16 | max = new Vector @width + gap, @height + gap 17 | 18 | edge = new EdgeBounce min, max 19 | 20 | center = new Vector @width * 0.5, @height * 0.5 21 | 22 | #@renderer.renderParticles = no 23 | 24 | wander = new Wander 0.05, 100.0, 80.0 25 | 26 | max = if full then 2000 else 600 27 | 28 | for i in [0..max] 29 | 30 | p = new Particle 6.0 31 | p.colour = '#FFFFFF' 32 | p.moveTo center 33 | p.setRadius 1.0 34 | 35 | p.behaviours.push wander 36 | p.behaviours.push edge 37 | 38 | @physics.particles.push p 39 | 40 | if op? then s = new Spring op, p, @spacing, @stiffness 41 | else s = new Spring @mouse, p, @spacing, @stiffness 42 | 43 | @physics.springs.push s 44 | 45 | op = p 46 | 47 | @physics.springs.push new Spring @mouse, p, @spacing, @stiffness -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/demos/ClothDemo.coffee: -------------------------------------------------------------------------------- 1 | class ClothDemo extends Demo 2 | 3 | setup: (full = yes) -> 4 | 5 | super 6 | 7 | # Only render springs. 8 | @renderer.renderParticles = false 9 | 10 | @physics.integrator = new Verlet() 11 | @physics.timestep = 1.0 / 200 12 | @mouse.setMass 10 13 | 14 | # Add gravity to the simulation. 15 | @gravity = new ConstantForce new Vector 0.0, 80.0 16 | @physics.behaviours.push @gravity 17 | 18 | stiffness = 0.5 19 | size = if full then 8 else 10 20 | rows = if full then 30 else 25 21 | cols = if full then 55 else 40 22 | cell = [] 23 | 24 | sx = @width * 0.5 - cols * size * 0.5 25 | sy = @height * 0.5 - rows * size * 0.5 26 | 27 | for x in [0..cols] 28 | 29 | cell[x] = [] 30 | 31 | for y in [0..rows] 32 | 33 | p = new Particle(0.1) 34 | 35 | p.fixed = (y is 0) 36 | 37 | # Always set initial position using moveTo for Verlet 38 | p.moveTo new Vector (sx + x * size), (sy + y * size) 39 | 40 | if x > 0 41 | s = new Spring p, cell[x-1][y], size, stiffness 42 | @physics.springs.push s 43 | 44 | if y > 0 45 | s = new Spring p, cell[x][y - 1], size, stiffness 46 | @physics.springs.push s 47 | 48 | @physics.particles.push p 49 | cell[x][y] = p 50 | 51 | p = cell[Math.floor cols / 2][Math.floor rows / 2] 52 | s = new Spring @mouse, p, 10, 1.0 53 | @physics.springs.push s 54 | 55 | cell[0][0].fixed = true 56 | cell[cols - 1][0].fixed = true 57 | 58 | step: -> 59 | 60 | super 61 | 62 | @gravity.force.x = 50 * Math.sin new Date().getTime() * 0.0005 63 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/demos/CollisionDemo.coffee: -------------------------------------------------------------------------------- 1 | ### CollisionDemo ### 2 | class CollisionDemo extends Demo 3 | 4 | setup: (full = yes) -> 5 | 6 | super 7 | 8 | # Verlet gives us collision responce for free! 9 | @physics.integrator = new Verlet() 10 | 11 | min = new Vector 0.0, 0.0 12 | max = new Vector @width, @height 13 | 14 | bounds = new EdgeBounce min, max 15 | collide = new Collision 16 | attraction = new Attraction @mouse.pos, 2000, 1400 17 | 18 | max = if full then 350 else 150 19 | prob = if full then 0.35 else 0.5 20 | 21 | for i in [0..max] 22 | 23 | p = new Particle (Random 0.5, 4.0) 24 | p.setRadius p.mass * 4 25 | 26 | p.moveTo new Vector (Random @width), (Random @height) 27 | 28 | # Connect to spring or move free. 29 | if Random.bool prob 30 | s = new Spring @mouse, p, (Random 120, 180), 0.8 31 | @physics.springs.push s 32 | else 33 | p.behaviours.push attraction 34 | 35 | # Add particle to collision pool. 36 | collide.pool.push p 37 | 38 | # Allow particle to collide. 39 | p.behaviours.push collide 40 | p.behaviours.push bounds 41 | 42 | @physics.particles.push p 43 | 44 | onCollision: (p1, p2) => 45 | 46 | # Respond to collision. 47 | 48 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/demos/Demo.coffee: -------------------------------------------------------------------------------- 1 | ### Demo ### 2 | class Demo 3 | 4 | @COLOURS = ['DC0048', 'F14646', '4AE6A9', '7CFF3F', '4EC9D9', 'E4272E'] 5 | 6 | constructor: -> 7 | 8 | @physics = new Physics() 9 | @mouse = new Particle() 10 | @mouse.fixed = true 11 | @height = window.innerHeight 12 | @width = window.innerWidth 13 | 14 | @renderTime = 0 15 | @counter = 0 16 | 17 | setup: (full = yes) -> 18 | 19 | ### Override and add paticles / springs here ### 20 | 21 | ### Initialise the demo (override). ### 22 | init: (@container, @renderer = new WebGLRenderer()) -> 23 | 24 | # Build the scene. 25 | @setup renderer.gl? 26 | 27 | # Give the particles random colours. 28 | for particle in @physics.particles 29 | particle.colour ?= Random.item Demo.COLOURS 30 | 31 | # Add event handlers. 32 | document.addEventListener 'touchmove', @mousemove, false 33 | document.addEventListener 'mousemove', @mousemove, false 34 | document.addEventListener 'resize', @resize, false 35 | 36 | # Add to render output to the DOM. 37 | @container.appendChild @renderer.domElement 38 | 39 | # Prepare the renderer. 40 | @renderer.mouse = @mouse 41 | @renderer.init @physics 42 | 43 | # Resize for the sake of the renderer. 44 | do @resize 45 | 46 | ### Handler for window resize event. ### 47 | resize: (event) => 48 | 49 | @width = window.innerWidth 50 | @height = window.innerHeight 51 | @renderer.setSize @width, @height 52 | 53 | ### Update loop. ### 54 | step: -> 55 | 56 | #console.profile 'physics' 57 | 58 | # Step physics. 59 | do @physics.step 60 | 61 | #console.profileEnd() 62 | 63 | #console.profile 'render' 64 | 65 | # Render. 66 | 67 | # Render every frame for WebGL, or every 3 frames for canvas. 68 | @renderer.render @physics if @renderer.gl? or ++@counter % 3 is 0 69 | 70 | #console.profileEnd() 71 | 72 | ### Clean up after yourself. ### 73 | destroy: -> 74 | 75 | ## console.log @, 'destroy' 76 | 77 | # Remove event handlers. 78 | document.removeEventListener 'touchmove', @mousemove, false 79 | document.removeEventListener 'mousemove', @mousemove, false 80 | document.removeEventListener 'resize', @resize, false 81 | 82 | # Remove the render output from the DOM. 83 | try container.removeChild @renderer.domElement 84 | catch error 85 | 86 | do @renderer.destroy 87 | do @physics.destroy 88 | 89 | @renderer = null 90 | @physics = null 91 | @mouse = null 92 | 93 | ### Handler for window mousemove event. ### 94 | mousemove: (event) => 95 | 96 | do event.preventDefault 97 | 98 | if event.touches and !!event.touches.length 99 | 100 | touch = event.touches[0] 101 | @mouse.pos.set touch.pageX, touch.pageY 102 | 103 | else 104 | 105 | @mouse.pos.set event.clientX, event.clientY 106 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/demos/renderer/CanvasRenderer.coffee: -------------------------------------------------------------------------------- 1 | ### Canvas Renderer ### 2 | class CanvasRenderer extends Renderer 3 | 4 | constructor: -> 5 | 6 | super 7 | 8 | @canvas = document.createElement 'canvas' 9 | @ctx = @canvas.getContext '2d' 10 | 11 | # Set the DOM element. 12 | @domElement = @canvas 13 | 14 | init: (physics) -> 15 | 16 | super physics 17 | 18 | render: (physics) -> 19 | 20 | super physics 21 | 22 | time = new Date().getTime() 23 | 24 | # Draw velocity. 25 | vel = new Vector() 26 | 27 | # Draw heading. 28 | dir = new Vector() 29 | 30 | # Clear canvas. 31 | @canvas.width = @canvas.width 32 | 33 | @ctx.globalCompositeOperation = 'lighter' 34 | @ctx.lineWidth = 1 35 | 36 | # Draw particles. 37 | if @renderParticles 38 | 39 | TWO_PI = Math.PI * 2 40 | 41 | for p in physics.particles 42 | 43 | @ctx.beginPath() 44 | @ctx.arc(p.pos.x, p.pos.y, p.radius, 0, TWO_PI, no) 45 | 46 | @ctx.fillStyle = '#' + (p.colour or 'FFFFFF') 47 | @ctx.fill() 48 | 49 | if @renderSprings 50 | 51 | @ctx.strokeStyle = 'rgba(255,255,255,0.1)' 52 | @ctx.beginPath() 53 | 54 | for s in physics.springs 55 | @ctx.moveTo(s.p1.pos.x, s.p1.pos.y) 56 | @ctx.lineTo(s.p2.pos.x, s.p2.pos.y) 57 | 58 | @ctx.stroke() 59 | 60 | if @renderMouse 61 | 62 | # Draw mouse. 63 | @ctx.fillStyle = 'rgba(255,255,255,0.1)' 64 | @ctx.beginPath() 65 | @ctx.arc(@mouse.pos.x, @mouse.pos.y, 20, 0, TWO_PI) 66 | @ctx.fill() 67 | 68 | @renderTime = new Date().getTime() - time 69 | 70 | setSize: (@width, @height) => 71 | 72 | super @width, @height 73 | 74 | @canvas.width = @width 75 | @canvas.height = @height 76 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/demos/renderer/DOMRenderer.coffee: -------------------------------------------------------------------------------- 1 | ### DOM Renderer ### 2 | ### 3 | 4 | Updating styles: 5 | 6 | Nodes 7 | 8 | ### 9 | class DOMRenderer extends Renderer 10 | 11 | constructor: -> 12 | 13 | super 14 | 15 | @useGPU = yes 16 | 17 | @domElement = document.createElement 'div' 18 | @canvas = document.createElement 'canvas' 19 | @ctx = @canvas.getContext '2d' 20 | 21 | @canvas.style.position = 'absolute' 22 | @canvas.style.left = 0 23 | @canvas.style.top = 0 24 | 25 | @domElement.style.pointerEvents = 'none' 26 | @domElement.appendChild @canvas 27 | 28 | init: (physics) -> 29 | 30 | super physics 31 | 32 | # Set up particle DOM elements 33 | for p in physics.particles 34 | 35 | el = document.createElement 'span' 36 | st = el.style 37 | 38 | st.backgroundColor = p.colour 39 | st.borderRadius = p.radius 40 | st.marginLeft = -p.radius 41 | st.marginTop = -p.radius 42 | st.position = 'absolute' 43 | st.display = 'block' 44 | st.opacity = 0.85 45 | st.height = p.radius * 2 46 | st.width = p.radius * 2 47 | 48 | @domElement.appendChild el 49 | p.domElement = el 50 | 51 | # Set up mouse DOM element 52 | el = document.createElement 'span' 53 | st = el.style 54 | mr = 20 55 | 56 | st.backgroundColor = '#ffffff' 57 | st.borderRadius = mr 58 | st.marginLeft = -mr 59 | st.marginTop = -mr 60 | st.position = 'absolute' 61 | st.display = 'block' 62 | st.opacity = 0.1 63 | st.height = mr * 2 64 | st.width = mr * 2 65 | 66 | @domElement.appendChild el 67 | @mouse.domElement = el 68 | 69 | render: (physics) -> 70 | 71 | super physics 72 | 73 | time = new Date().getTime() 74 | 75 | if @renderParticles 76 | 77 | for p in physics.particles 78 | 79 | if @useGPU 80 | 81 | p.domElement.style.WebkitTransform = """ 82 | translate3d(#{p.pos.x|0}px,#{p.pos.y|0}px,0px) 83 | """ 84 | else 85 | 86 | p.domElement.style.left = p.pos.x 87 | p.domElement.style.top = p.pos.y 88 | 89 | if @renderSprings 90 | 91 | @canvas.width = @canvas.width 92 | 93 | @ctx.strokeStyle = 'rgba(255,255,255,0.1)' 94 | @ctx.beginPath() 95 | 96 | for s in physics.springs 97 | @ctx.moveTo(s.p1.pos.x, s.p1.pos.y) 98 | @ctx.lineTo(s.p2.pos.x, s.p2.pos.y) 99 | 100 | @ctx.stroke() 101 | 102 | if @renderMouse 103 | 104 | if @useGPU 105 | 106 | @mouse.domElement.style.WebkitTransform = """ 107 | translate3d(#{@mouse.pos.x|0}px,#{@mouse.pos.y|0}px,0px) 108 | """ 109 | else 110 | 111 | @mouse.domElement.style.left = @mouse.pos.x 112 | @mouse.domElement.style.top = @mouse.pos.y 113 | 114 | @renderTime = new Date().getTime() - time 115 | 116 | setSize: (@width, @height) => 117 | 118 | super @width, @height 119 | 120 | @canvas.width = @width 121 | @canvas.height = @height 122 | 123 | destroy: -> 124 | 125 | while @domElement.hasChildNodes() 126 | @domElement.removeChild @domElement.lastChild 127 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/demos/renderer/Renderer.coffee: -------------------------------------------------------------------------------- 1 | ### Base Renderer ### 2 | class Renderer 3 | 4 | constructor: -> 5 | 6 | @width = 0 7 | @height = 0 8 | 9 | @renderParticles = true 10 | @renderSprings = true 11 | @renderMouse = true 12 | @initialized = false 13 | @renderTime = 0 14 | 15 | init: (physics) -> 16 | 17 | @initialized = true 18 | 19 | render: (physics) -> 20 | 21 | if not @initialized then @init physics 22 | 23 | setSize: (@width, @height) => 24 | 25 | destroy: -> 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/engine/Particle.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Vector} = require 'coffeePhysics/math/Vector' 3 | 4 | ### Particle ### 5 | 6 | class exports.Particle 7 | 8 | @GUID = 0 9 | 10 | constructor: (@mass = 1.0) -> 11 | 12 | # Set a unique id. 13 | @id = 'p' + Particle.GUID++ 14 | 15 | # Set initial mass. 16 | @setMass @mass 17 | 18 | # Set initial radius. 19 | @setRadius 1.0 20 | 21 | # Apply forces. 22 | @fixed = false 23 | 24 | # Behaviours to be applied. 25 | @behaviours = [] 26 | 27 | # Current position. 28 | @pos = new Vector() 29 | 30 | # Current velocity. 31 | @vel = new Vector() 32 | 33 | # Current force. 34 | @acc = new Vector() 35 | 36 | # Previous state. 37 | @old = 38 | pos: new Vector() 39 | vel: new Vector() 40 | acc: new Vector() 41 | 42 | ### Moves the particle to a given location vector. ### 43 | moveTo: (pos) -> 44 | 45 | @pos.copy pos 46 | @old.pos.copy pos 47 | 48 | ### Sets the mass of the particle. ### 49 | setMass: (@mass = 1.0) -> 50 | 51 | # The inverse mass. 52 | @massInv = 1.0 / @mass 53 | 54 | ### Sets the radius of the particle. ### 55 | setRadius: (@radius = 1.0) -> 56 | 57 | @radiusSq = @radius * @radius 58 | 59 | ### Applies all behaviours to derive new force. ### 60 | update: (dt, index) -> 61 | 62 | # Apply all behaviours. 63 | 64 | if not @fixed 65 | 66 | for behaviour in @behaviours 67 | 68 | behaviour.apply @, dt, index 69 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/engine/Physics.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Euler} = require 'coffeePhysics/engine/integrator/Euler' 3 | 4 | ### Physics Engine ### 5 | 6 | class exports.Physics 7 | 8 | constructor: (@integrator = new Euler()) -> 9 | 10 | # Fixed timestep. 11 | @timestep = 1.0 / 60 12 | 13 | # Friction within the system. 14 | @viscosity = 0.005 15 | 16 | # Global behaviours. 17 | @behaviours = [] 18 | 19 | # Time in seconds. 20 | @_time = 0.0 21 | 22 | # Last step duration. 23 | @_step = 0.0 24 | 25 | # Current time. 26 | @_clock = null 27 | 28 | # Time buffer. 29 | @_buffer = 0.0 30 | 31 | # Max iterations per step. 32 | @_maxSteps = 4 33 | 34 | # Particles in system. 35 | @particles = [] 36 | 37 | # Springs in system. 38 | @springs = [] 39 | 40 | ### Performs a numerical integration step. ### 41 | integrate: (dt) -> 42 | 43 | # Drag is inversely proportional to viscosity. 44 | drag = 1.0 - @viscosity 45 | 46 | # Update particles / apply behaviours. 47 | 48 | for particle, index in @particles 49 | 50 | for behaviour in @behaviours 51 | 52 | behaviour.apply particle, dt, index 53 | 54 | particle.update dt, index 55 | 56 | # Integrate motion. 57 | 58 | @integrator.integrate @particles, dt, drag 59 | 60 | # Compute all springs. 61 | 62 | for spring in @springs 63 | 64 | spring.apply() 65 | 66 | ### Steps the system. ### 67 | step: -> 68 | 69 | # Initialise the clock on first step. 70 | @_clock ?= new Date().getTime() 71 | 72 | # Compute delta time since last step. 73 | time = new Date().getTime() 74 | delta = time - @_clock 75 | 76 | # No sufficient change. 77 | return if delta <= 0.0 78 | 79 | # Convert time to seconds. 80 | delta *= 0.001 81 | 82 | # Update the clock. 83 | @_clock = time 84 | 85 | # Increment time buffer. 86 | @_buffer += delta 87 | 88 | # Integrate until the buffer is empty or until the 89 | # maximum amount of iterations per step is reached. 90 | 91 | i = 0 92 | 93 | while @_buffer >= @timestep and ++i < @_maxSteps 94 | 95 | # Integrate motion by fixed timestep. 96 | @integrate @timestep 97 | 98 | # Reduce buffer by one timestep. 99 | @_buffer -= @timestep 100 | 101 | # Increment running time. 102 | @_time += @timestep 103 | 104 | # Store step time for debugging. 105 | @_step = new Date().getTime() - time 106 | 107 | ### Clean up after yourself. ### 108 | destroy: -> 109 | 110 | @integrator = null 111 | @particles = null 112 | @springs = null 113 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/engine/Spring.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Vector} = require 'coffeePhysics/math/Vector' 3 | 4 | ### Spring ### 5 | 6 | class exports.Spring 7 | 8 | constructor: (@p1, @p2, @restLength = 100, @stiffness = 1.0) -> 9 | 10 | @_delta = new Vector() 11 | 12 | # F = -kx 13 | 14 | apply: -> 15 | 16 | (@_delta.copy @p2.pos).sub @p1.pos 17 | 18 | dist = @_delta.mag() + 0.000001 19 | force = (dist - @restLength) / (dist * (@p1.massInv + @p2.massInv)) * @stiffness 20 | 21 | if not @p1.fixed 22 | 23 | @p1.pos.add (@_delta.clone().scale force * @p1.massInv) 24 | 25 | if not @p2.fixed 26 | 27 | @p2.pos.add (@_delta.scale -force * @p2.massInv) 28 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/engine/integrator/Euler.coffee: -------------------------------------------------------------------------------- 1 | ### Import Integrator ### 2 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 3 | 4 | ### Euler Integrator ### 5 | class exports.Euler extends Integrator 6 | 7 | # v += a * dt 8 | # x += v * dt 9 | 10 | integrate: (particles, dt, drag) -> 11 | 12 | vel = new Vector() 13 | 14 | for p in particles when not p.fixed 15 | 16 | # Store previous location. 17 | p.old.pos.copy p.pos 18 | 19 | # Scale force to mass. 20 | p.acc.scale p.massInv 21 | 22 | # Duplicate velocity to preserve momentum. 23 | vel.copy p.vel 24 | 25 | # Add force to velocity. 26 | p.vel.add p.acc.scale dt 27 | 28 | # Add velocity to position. 29 | p.pos.add vel.scale dt 30 | 31 | # Apply friction. 32 | if drag then p.vel.scale drag 33 | 34 | # Reset forces. 35 | p.acc.clear() 36 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/engine/integrator/ImprovedEuler.coffee: -------------------------------------------------------------------------------- 1 | ### Import Integrator ### 2 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 3 | 4 | ### Improved Euler Integrator ### 5 | 6 | class exports.ImprovedEuler extends Integrator 7 | 8 | # x += (v * dt) + (a * 0.5 * dt * dt) 9 | # v += a * dt 10 | 11 | integrate: (particles, dt, drag) -> 12 | 13 | acc = new Vector() 14 | vel = new Vector() 15 | 16 | dtSq = dt * dt 17 | 18 | for p in particles when not p.fixed 19 | 20 | # Store previous location. 21 | p.old.pos.copy p.pos 22 | 23 | # Scale force to mass. 24 | p.acc.scale p.massInv 25 | 26 | # Duplicate velocity to preserve momentum. 27 | vel.copy p.vel 28 | 29 | # Duplicate force. 30 | acc.copy p.acc 31 | 32 | # Update position. 33 | p.pos.add (vel.scale dt).add (acc.scale 0.5 * dtSq) 34 | 35 | # Update velocity. 36 | p.vel.add p.acc.scale dt 37 | 38 | # Apply friction. 39 | if drag then p.vel.scale drag 40 | 41 | # Reset forces. 42 | p.acc.clear() 43 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/engine/integrator/Integrator.coffee: -------------------------------------------------------------------------------- 1 | ### Integrator ### 2 | 3 | class exports.Integrator 4 | 5 | integrate: (particles, dt) -> 6 | 7 | # Override. 8 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/engine/integrator/Verlet.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | 6 | ### Velocity Verlet Integrator ### 7 | 8 | class exports.Verlet extends Integrator 9 | 10 | # v = x - ox 11 | # x = x + (v + a * dt * dt) 12 | 13 | integrate: (particles, dt, drag) -> 14 | 15 | pos = new Vector() 16 | 17 | dtSq = dt * dt 18 | 19 | for p in particles when not p.fixed 20 | 21 | # Scale force to mass. 22 | p.acc.scale p.massInv 23 | 24 | # Derive velocity. 25 | (p.vel.copy p.pos).sub p.old.pos 26 | 27 | # Apply friction. 28 | if drag then p.vel.scale drag 29 | 30 | # Apply forces to new position. 31 | (pos.copy p.pos).add (p.vel.add p.acc.scale dtSq) 32 | 33 | # Store old position. 34 | p.old.pos.copy p.pos 35 | 36 | # update position. 37 | p.pos.copy pos 38 | 39 | # Reset forces. 40 | p.acc.clear() 41 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/math/Random.coffee: -------------------------------------------------------------------------------- 1 | ### Random ### 2 | 3 | exports.Random = (min, max) -> 4 | 5 | if not max? 6 | max = min 7 | min = 0 8 | 9 | min + Math.random() * (max - min) 10 | 11 | Random.int = (min, max) -> 12 | 13 | if not max? 14 | max = min 15 | min = 0 16 | 17 | Math.floor min + Math.random() * (max - min) 18 | 19 | Random.sign = (prob = 0.5) -> 20 | 21 | if do Math.random < prob then 1 else -1 22 | 23 | Random.bool = (prob = 0.5) -> 24 | 25 | do Math.random < prob 26 | 27 | Random.item = (list) -> 28 | 29 | list[ Math.floor Math.random() * list.length ] 30 | -------------------------------------------------------------------------------- /examples/physics.framer/modules/coffeePhysics/math/Vector.coffee: -------------------------------------------------------------------------------- 1 | ### 2D Vector ### 2 | 3 | class exports.Vector 4 | 5 | ### Adds two vectors and returns the product. ### 6 | @add: (v1, v2) -> 7 | new Vector v1.x + v2.x, v1.y + v2.y 8 | 9 | ### Subtracts v2 from v1 and returns the product. ### 10 | @sub: (v1, v2) -> 11 | new Vector v1.x - v2.x, v1.y - v2.y 12 | 13 | ### Projects one vector (v1) onto another (v2) ### 14 | @project: (v1, v2) -> 15 | v1.clone().scale ((v1.dot v2) / v1.magSq()) 16 | 17 | ### Creates a new Vector instance. ### 18 | constructor: (@x = 0.0, @y = 0.0) -> 19 | 20 | ### Sets the components of this vector. ### 21 | set: (@x, @y) -> 22 | @ 23 | 24 | ### Add a vector to this one. ### 25 | add: (v) -> 26 | @x += v.x; @y += v.y; @ 27 | 28 | ### Subtracts a vector from this one. ### 29 | sub: (v) -> 30 | @x -= v.x; @y -= v.y; @ 31 | 32 | ### Scales this vector by a value. ### 33 | scale: (f) -> 34 | @x *= f; @y *= f; @ 35 | 36 | ### Computes the dot product between vectors. ### 37 | dot: (v) -> 38 | @x * v.x + @y * v.y 39 | 40 | ### Computes the cross product between vectors. ### 41 | cross: (v) -> 42 | (@x * v.y) - (@y * v.x) 43 | 44 | ### Computes the magnitude (length). ### 45 | mag: -> 46 | Math.sqrt @x*@x + @y*@y 47 | 48 | ### Computes the squared magnitude (length). ### 49 | magSq: -> 50 | @x*@x + @y*@y 51 | 52 | ### Computes the distance to another vector. ### 53 | dist: (v) -> 54 | dx = v.x - @x; dy = v.y - @y 55 | Math.sqrt dx*dx + dy*dy 56 | 57 | ### Computes the squared distance to another vector. ### 58 | distSq: (v) -> 59 | dx = v.x - @x; dy = v.y - @y 60 | dx*dx + dy*dy 61 | 62 | ### Normalises the vector, making it a unit vector (of length 1). ### 63 | norm: -> 64 | m = Math.sqrt @x*@x + @y*@y 65 | @x /= m 66 | @y /= m 67 | @ 68 | 69 | ### Limits the vector length to a given amount. ### 70 | limit: (l) -> 71 | mSq = @x*@x + @y*@y 72 | if mSq > l*l 73 | m = Math.sqrt mSq 74 | @x /= m; @y /= m 75 | @x *= l; @y *= l 76 | @ 77 | 78 | ### Copies components from another vector. ### 79 | copy: (v) -> 80 | @x = v.x; @y = v.y; @ 81 | 82 | ### Clones this vector to a new itentical one. ### 83 | clone: -> 84 | new Vector @x, @y 85 | 86 | ### Resets the vector to zero. ### 87 | clear: -> 88 | @x = 0.0; @y = 0.0; @ 89 | -------------------------------------------------------------------------------- /examples/wander.framer/.gitignore: -------------------------------------------------------------------------------- 1 | # Framer Git Ignore 2 | 3 | # General OSX 4 | 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | # Framer Specific 31 | .*.html 32 | .app.js 33 | framer/*.old* 34 | framer/.*.hash 35 | framer/backup.coffee 36 | framer/backups/* 37 | framer/manifest.txt 38 | framer/metadata.json 39 | framer/preview.png 40 | framer/social-880x460.png 41 | framer/social-1200x630.png 42 | -------------------------------------------------------------------------------- /examples/wander.framer/app.coffee: -------------------------------------------------------------------------------- 1 | # Project setup 2 | ################################################################################ 3 | {Integrator, Euler, ImprovedEuler, Verlet, Particle, Physics, Vector, Spring, Behaviour, Attraction, Collision, ConstantForce, EdgeBounce, EdgeWrap, Wander, Gravity} = require 'coffeePhysics' 4 | 5 | # Colours 6 | ################################################################################ 7 | 8 | red = new Color("rgba(224,32,36,1)") 9 | orange = new Color("rgba(255,128,21,1)") 10 | yellow = new Color("rgba(255,224,0,1)") 11 | green = new Color("rgba(55,191,0,1)") 12 | blue = new Color("rgba(0,150,212,1)") 13 | pink = new Color("rgba(213,45,177,1)") 14 | lightGrey = new Color("rgba(239,239,239,1)") 15 | 16 | colours = [red, orange, yellow, green, blue, pink] 17 | colourCycler = Utils.cycle(colours) 18 | 19 | background = new BackgroundLayer 20 | backgroundColor: lightGrey 21 | 22 | ################################################################################ 23 | 24 | # Create a physics instance which uses the Verlet integration method 25 | physics = new Physics() 26 | physics.integrator = new Verlet() 27 | 28 | # Allow particle collisions to make things interesting 29 | collision = new Collision() 30 | 31 | # Design some behaviours for particles 32 | topLeft = new Vector(0,0) 33 | bottomRight = new Vector(Screen.width,Screen.height) 34 | edges = new EdgeBounce(topLeft, bottomRight) 35 | wander = new Wander(.5,200,4) 36 | 37 | ################################################################################ 38 | 39 | balls = [] 40 | 41 | # Render the particles 42 | for i in [0..200] 43 | 44 | # Create a particle 45 | particle = new Particle( Utils.randomNumber(.1,1) ) 46 | position = new Vector( Utils.randomNumber( 0, Screen.width ), Utils.randomNumber( 0, Screen.height ) ) 47 | particle.setRadius( particle.mass * 10 ) 48 | particle.moveTo( position ) 49 | 50 | # Apply behaviours to the particle 51 | particle.behaviours.push( wander, edges, collision ) 52 | 53 | # Make it collidable 54 | collision.pool.push( particle ) 55 | 56 | # Add to the simulation 57 | physics.particles.push( particle ) 58 | 59 | # Create a layer to show the particle on the screen 60 | ball = new Layer 61 | x: particle.pos.x - particle.radius 62 | y: particle.pos.y - particle.radius 63 | size: particle.radius * 2 64 | borderRadius: particle.radius 65 | backgroundColor: colourCycler() 66 | 67 | # Add the particle instance to the layer 68 | ball.particle = particle 69 | 70 | balls.push(ball) 71 | 72 | # Set everything in motion 73 | ################################################################################ 74 | 75 | frameRate = 1 / 60 76 | 77 | Utils.interval frameRate, -> 78 | 79 | # Step the simulation 80 | physics.step() 81 | 82 | # Update the position of the balls 83 | for ball, i in balls 84 | ball.x = ball.particle.pos.x - ball.particle.radius 85 | ball.y = ball.particle.pos.y - ball.particle.radius -------------------------------------------------------------------------------- /examples/wander.framer/framer/.bookmark: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/wander.framer/framer/.bookmark -------------------------------------------------------------------------------- /examples/wander.framer/framer/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "orientation" : 0, 3 | "updateDelay" : 0.3, 4 | "cachedDeviceHeight" : 1334, 5 | "contentScale" : 1, 6 | "fullScreen" : false, 7 | "cachedDeviceWidth" : 750, 8 | "sharedPrototype" : 0, 9 | "propertyPanelToggleStates" : { 10 | 11 | }, 12 | "deviceType" : "apple-iphone-7-silver", 13 | "projectId" : "CC44CC9B-3D82-4E91-95C3-DD4732F07254", 14 | "deviceOrientation" : 0, 15 | "selectedHand" : "", 16 | "foldedCodeRanges" : [ 17 | 18 | ], 19 | "deviceScale" : "fit" 20 | } -------------------------------------------------------------------------------- /examples/wander.framer/framer/framer.generated.js: -------------------------------------------------------------------------------- 1 | // This is autogenerated by Framer 2 | 3 | 4 | if (!window.Framer && window._bridge) {window._bridge('runtime.error', {message:'[framer.js] Framer library missing or corrupt. Select File → Update Framer Library.'})} 5 | if (DeviceComponent) {DeviceComponent.Devices["iphone-6-silver"].deviceImageJP2 = false}; 6 | if (window.Framer) {window.Framer.Defaults.DeviceView = {"deviceScale":"fit","selectedHand":"","deviceType":"apple-iphone-7-silver","contentScale":1,"hideBezel":true,"orientation":0}; 7 | } 8 | if (window.Framer) {window.Framer.Defaults.DeviceComponent = {"deviceScale":"fit","selectedHand":"","deviceType":"apple-iphone-7-silver","contentScale":1,"hideBezel":true,"orientation":0}; 9 | } 10 | window.FramerStudioInfo = {"deviceImagesUrl":"\/_server\/resources\/DeviceImages","documentTitle":"wander.framer"}; 11 | 12 | Framer.Device = new Framer.DeviceView(); 13 | Framer.Device.setupContext(); -------------------------------------------------------------------------------- /examples/wander.framer/framer/framer.init.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | function isFileLoadingAllowed() { 4 | return (window.location.protocol.indexOf("file") == -1) 5 | } 6 | 7 | function isHomeScreened() { 8 | return ("standalone" in window.navigator) && window.navigator.standalone == true 9 | } 10 | 11 | function isCompatibleBrowser() { 12 | return Utils.isWebKit() 13 | } 14 | 15 | var alertNode; 16 | 17 | function dismissAlert() { 18 | alertNode.parentElement.removeChild(alertNode) 19 | loadProject() 20 | } 21 | 22 | function showAlert(html) { 23 | 24 | alertNode = document.createElement("div") 25 | 26 | alertNode.classList.add("framerAlertBackground") 27 | alertNode.innerHTML = html 28 | 29 | document.addEventListener("DOMContentLoaded", function(event) { 30 | document.body.appendChild(alertNode) 31 | }) 32 | 33 | window.dismissAlert = dismissAlert; 34 | } 35 | 36 | function showBrowserAlert() { 37 | var html = "" 38 | html += "
" 39 | html += "Error: Not A WebKit Browser" 40 | html += "Your browser is not supported.
Please use Safari or Chrome.
" 41 | html += "Try anyway" 42 | html += "
" 43 | 44 | showAlert(html) 45 | } 46 | 47 | function showFileLoadingAlert() { 48 | var html = "" 49 | html += "
" 50 | html += "Error: Local File Restrictions" 51 | html += "Preview this prototype with Framer Mirror or learn more about " 52 | html += "file restrictions.
" 53 | html += "Try anyway" 54 | html += "
" 55 | 56 | showAlert(html) 57 | } 58 | 59 | function loadProject() { 60 | CoffeeScript.load("app.coffee") 61 | } 62 | 63 | function setDefaultPageTitle() { 64 | // If no title was set we set it to the project folder name so 65 | // you get a nice name on iOS if you bookmark to desktop. 66 | document.addEventListener("DOMContentLoaded", function() { 67 | if (document.title == "") { 68 | if (window.FramerStudioInfo && window.FramerStudioInfo.documentTitle) { 69 | document.title = window.FramerStudioInfo.documentTitle 70 | } else { 71 | document.title = window.location.pathname.replace(/\//g, "") 72 | } 73 | } 74 | }) 75 | } 76 | 77 | function init() { 78 | 79 | if (Utils.isFramerStudio()) { 80 | return 81 | } 82 | 83 | setDefaultPageTitle() 84 | 85 | if (!isCompatibleBrowser()) { 86 | return showBrowserAlert() 87 | } 88 | 89 | if (!isFileLoadingAllowed()) { 90 | return showFileLoadingAlert() 91 | } 92 | 93 | loadProject() 94 | 95 | } 96 | 97 | init() 98 | 99 | })() 100 | -------------------------------------------------------------------------------- /examples/wander.framer/framer/images/cursor-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/wander.framer/framer/images/cursor-active.png -------------------------------------------------------------------------------- /examples/wander.framer/framer/images/cursor-active@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/wander.framer/framer/images/cursor-active@2x.png -------------------------------------------------------------------------------- /examples/wander.framer/framer/images/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/wander.framer/framer/images/cursor.png -------------------------------------------------------------------------------- /examples/wander.framer/framer/images/cursor@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/wander.framer/framer/images/cursor@2x.png -------------------------------------------------------------------------------- /examples/wander.framer/framer/images/icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/wander.framer/framer/images/icon-120.png -------------------------------------------------------------------------------- /examples/wander.framer/framer/images/icon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/wander.framer/framer/images/icon-152.png -------------------------------------------------------------------------------- /examples/wander.framer/framer/images/icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/wander.framer/framer/images/icon-180.png -------------------------------------------------------------------------------- /examples/wander.framer/framer/images/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/wander.framer/framer/images/icon-192.png -------------------------------------------------------------------------------- /examples/wander.framer/framer/images/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/wander.framer/framer/images/icon-76.png -------------------------------------------------------------------------------- /examples/wander.framer/framer/social-800x600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/wander.framer/framer/social-800x600.png -------------------------------------------------------------------------------- /examples/wander.framer/framer/social-80x80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/wander.framer/framer/social-80x80.png -------------------------------------------------------------------------------- /examples/wander.framer/framer/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | border: none; 5 | -webkit-user-select: none; 6 | -webkit-tap-highlight-color: rgba(0,0,0,0); 7 | } 8 | 9 | body { 10 | background-color: #fff; 11 | font: 28px/1em "Helvetica"; 12 | color: gray; 13 | overflow: hidden; 14 | } 15 | 16 | a { 17 | color: gray; 18 | } 19 | 20 | body { 21 | cursor: url('images/cursor.png') 32 32, auto; 22 | cursor: -webkit-image-set( 23 | url('images/cursor.png') 1x, 24 | url('images/cursor@2x.png') 2x 25 | ) 32 32, auto; 26 | } 27 | 28 | body:active { 29 | cursor: url('images/cursor-active.png') 32 32, auto; 30 | cursor: -webkit-image-set( 31 | url('images/cursor-active.png') 1x, 32 | url('images/cursor-active@2x.png') 2x 33 | ) 32 32, auto; 34 | } 35 | 36 | .framerAlertBackground { 37 | position: absolute; top:0px; left:0px; right:0px; bottom:0px; 38 | z-index: 1000; 39 | background-color: #fff; 40 | } 41 | 42 | .framerAlert { 43 | font:400 14px/1.4 "Helvetica Neue", Helvetica, Arial, sans-serif; 44 | -webkit-font-smoothing:antialiased; 45 | color:#616367; text-align:center; 46 | position: absolute; top:40%; left:50%; width:260px; margin-left:-130px; 47 | } 48 | .framerAlert strong { font-weight:500; color:#000; margin-bottom:8px; display:block; } 49 | .framerAlert a { color:#28AFFA; } 50 | .framerAlert .btn { 51 | font-weight:500; text-decoration:none; line-height:1; 52 | display:inline-block; padding:6px 12px 7px 12px; 53 | border-radius:3px; margin-top:12px; 54 | background:#28AFFA; color:#fff; 55 | } 56 | 57 | ::-webkit-scrollbar { 58 | display: none; 59 | } -------------------------------------------------------------------------------- /examples/wander.framer/framer/version: -------------------------------------------------------------------------------- 1 | 6 -------------------------------------------------------------------------------- /examples/wander.framer/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/perrysmotors/framer-physics/5bd2178464e380e9588b7140b71d655a8e5eda00/examples/wander.framer/images/.gitkeep -------------------------------------------------------------------------------- /examples/wander.framer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics.coffee: -------------------------------------------------------------------------------- 1 | # Add the following line to your project in Framer Studio. 2 | # myModule = require "myModule" 3 | # Reference the contents by name, like myModule.myFunction() or myModule.myVar 4 | 5 | 6 | # Import integrator framework 7 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 8 | {Euler} = require 'coffeePhysics/engine/integrator/Euler' 9 | {ImprovedEuler} = require 'coffeePhysics/engine/integrator/ImprovedEuler' 10 | {Verlet} = require 'coffeePhysics/engine/integrator/Verlet' 11 | 12 | exports.Integrator = Integrator 13 | exports.Euler = Euler 14 | exports.ImprovedEuler = ImprovedEuler 15 | exports.Verlet = Verlet 16 | 17 | # Import physics framework 18 | {Particle} = require 'coffeePhysics/engine/Particle' 19 | {Physics} = require 'coffeePhysics/engine/Physics' 20 | {Spring} = require 'coffeePhysics/engine/Spring' 21 | 22 | exports.Particle = Particle 23 | exports.Physics = Physics 24 | exports.Spring = Spring 25 | 26 | # Import math framework 27 | # {Random} = require 'coffeePhysics/math/Random' 28 | {Vector} = require 'coffeePhysics/math/Vector' 29 | 30 | # exports.Random = Random 31 | exports.Vector = Vector 32 | 33 | # Import behaviour framework 34 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 35 | {Attraction} = require 'coffeePhysics/behaviour/Attraction' 36 | {Collision} = require 'coffeePhysics/behaviour/Collision' 37 | {ConstantForce} = require 'coffeePhysics/behaviour/ConstantForce' 38 | {EdgeBounce} = require 'coffeePhysics/behaviour/EdgeBounce' 39 | {EdgeWrap} = require 'coffeePhysics/behaviour/EdgeWrap' 40 | {Wander} = require 'coffeePhysics/behaviour/Wander' 41 | {Gravity} = require 'coffeePhysics/behaviour/Gravity' 42 | 43 | exports.Behaviour = Behaviour 44 | exports.Attraction = Attraction 45 | exports.Collision = Collision 46 | exports.ConstantForce = ConstantForce 47 | exports.EdgeBounce = EdgeBounce 48 | exports.EdgeWrap = EdgeWrap 49 | exports.Wander = Wander 50 | exports.Gravity = Gravity 51 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/base.coffee: -------------------------------------------------------------------------------- 1 | ### Allows safe, dyamic creation of namespaces. ### 2 | 3 | namespace = (id) -> 4 | root = self 5 | root = root[path] ?= {} for path in id.split '.' 6 | 7 | ### RequestAnimationFrame shim. ### 8 | do -> 9 | 10 | time = 0 11 | vendors = ['ms', 'moz', 'webkit', 'o'] 12 | 13 | for vendor in vendors when not window.requestAnimationFrame 14 | window.requestAnimationFrame = window[ vendor + 'RequestAnimationFrame'] 15 | window.cancelAnimationFrame = window[ vendor + 'CancelAnimationFrame'] 16 | 17 | if not window.requestAnimationFrame 18 | 19 | window.requestAnimationFrame = (callback, element) -> 20 | now = new Date().getTime() 21 | delta = Math.max 0, 16 - (now - old) 22 | setTimeout (-> callback(time + delta)), delta 23 | old = now + delta 24 | 25 | if not window.cancelAnimationFrame 26 | 27 | window.cancelAnimationFrame = (id) -> 28 | clearTimeout id 29 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/behaviour/Attraction.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Attraction Behaviour ### 6 | 7 | class exports.Attraction extends Behaviour 8 | 9 | constructor: (@target = new Vector(), @radius = 1000, @strength = 100.0) -> 10 | 11 | @_delta = new Vector() 12 | @setRadius @radius 13 | 14 | super 15 | 16 | ### Sets the effective radius of the bahavious. ### 17 | setRadius: (radius) -> 18 | 19 | @radius = radius 20 | @radiusSq = radius * radius 21 | 22 | apply: (p, dt, index) -> 23 | 24 | #super p, dt, index 25 | 26 | # Vector pointing from particle to target. 27 | (@_delta.copy @target).sub p.pos 28 | 29 | # Squared distance to target. 30 | distSq = @_delta.magSq() 31 | 32 | # Limit force to behaviour radius. 33 | if distSq < @radiusSq and distSq > 0.000001 34 | 35 | # Calculate force vector. 36 | @_delta.norm().scale (1.0 - distSq / @radiusSq) 37 | 38 | #Apply force. 39 | p.acc.add @_delta.scale @strength 40 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/behaviour/Behaviour.coffee: -------------------------------------------------------------------------------- 1 | ### Behaviour ### 2 | 3 | class exports.Behaviour 4 | 5 | # Each behaviour has a unique id 6 | @GUID = 0 7 | 8 | constructor: -> 9 | 10 | @GUID = Behaviour.GUID++ 11 | @interval = 1 12 | 13 | ## console.log @, @GUID 14 | 15 | apply: (p, dt, index) -> 16 | 17 | # Store some data in each particle. 18 | (p['__behaviour' + @GUID] ?= {counter: 0}).counter++ 19 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/behaviour/Collision.coffee: -------------------------------------------------------------------------------- 1 | ### Import Behaviour ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Collision Behaviour ### 6 | 7 | # TODO: Collision response for non Verlet integrators. 8 | 9 | class exports.Collision extends Behaviour 10 | 11 | constructor: (@useMass = yes, @callback = null) -> 12 | 13 | # Pool of collidable particles. 14 | @pool = [] 15 | 16 | # Delta between particle positions. 17 | @_delta = new Vector() 18 | 19 | super 20 | 21 | apply: (p, dt, index) -> 22 | 23 | #super p, dt, index 24 | 25 | # Check pool for collisions. 26 | for o in @pool[index..] when o isnt p 27 | 28 | # Delta between particles positions. 29 | (@_delta.copy o.pos).sub p.pos 30 | 31 | # Squared distance between particles. 32 | distSq = @_delta.magSq() 33 | 34 | # Sum of both radii. 35 | radii = p.radius + o.radius 36 | 37 | # Check if particles collide. 38 | if distSq <= radii * radii 39 | 40 | # Compute real distance. 41 | dist = Math.sqrt distSq 42 | 43 | # Determine overlap. 44 | overlap = radii - dist 45 | overlap += 0.5 46 | 47 | # Total mass. 48 | mt = p.mass + o.mass 49 | 50 | # Distribute collision responses. 51 | r1 = if @useMass then o.mass / mt else 0.5 52 | r2 = if @useMass then p.mass / mt else 0.5 53 | 54 | # Move particles so they no longer overlap. 55 | p.pos.add (@_delta.clone().norm().scale overlap * -r1) 56 | o.pos.add (@_delta.norm().scale overlap * r2) 57 | 58 | # Fire callback if defined. 59 | @callback?(p, o, overlap) 60 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/behaviour/ConstantForce.coffee: -------------------------------------------------------------------------------- 1 | ### Import Behaviour ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Constant Force Behaviour ### 6 | 7 | class exports.ConstantForce extends Behaviour 8 | 9 | constructor: (@force = new Vector()) -> 10 | 11 | super 12 | 13 | apply: (p, dt,index) -> 14 | 15 | #super p, dt, index 16 | 17 | p.acc.add @force 18 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/behaviour/EdgeBounce.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Edge Bounce Behaviour ### 6 | 7 | class exports.EdgeBounce extends Behaviour 8 | 9 | constructor: (@min = new Vector(), @max = new Vector()) -> 10 | 11 | super 12 | 13 | apply: (p, dt, index) -> 14 | 15 | #super p, dt, index 16 | 17 | if p.pos.x - p.radius < @min.x 18 | 19 | p.pos.x = @min.x + p.radius 20 | 21 | else if p.pos.x + p.radius > @max.x 22 | 23 | p.pos.x = @max.x - p.radius 24 | 25 | if p.pos.y - p.radius < @min.y 26 | 27 | p.pos.y = @min.y + p.radius 28 | 29 | else if p.pos.y + p.radius > @max.y 30 | 31 | p.pos.y = @max.y - p.radius 32 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/behaviour/EdgeWrap.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Edge Wrap Behaviour ### 6 | 7 | class exports.EdgeWrap extends Behaviour 8 | 9 | constructor: (@min = new Vector(), @max = new Vector()) -> 10 | 11 | super 12 | 13 | apply: (p, dt, index) -> 14 | 15 | #super p, dt, index 16 | 17 | if p.pos.x + p.radius < @min.x 18 | 19 | p.pos.x = @max.x + p.radius 20 | p.old.pos.x = p.pos.x 21 | 22 | else if p.pos.x - p.radius > @max.x 23 | 24 | p.pos.x = @min.x - p.radius 25 | p.old.pos.x = p.pos.x 26 | 27 | if p.pos.y + p.radius < @min.y 28 | 29 | p.pos.y = @max.y + p.radius 30 | p.old.pos.y = p.pos.y 31 | 32 | else if p.pos.y - p.radius > @max.y 33 | 34 | p.pos.y = @min.y - p.radius 35 | p.old.pos.y = p.pos.y 36 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/behaviour/Gravity.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {ConstantForce} = require 'coffeePhysics/behaviour/ConstantForce' 3 | 4 | ### Gravity Behaviour ### 5 | 6 | class exports.Gravity extends ConstantForce 7 | 8 | constructor: (@scale = 1000) -> 9 | 10 | super() 11 | 12 | force = @force 13 | scale = @scale 14 | 15 | window.addEventListener "devicemotion", -> 16 | accX = event.accelerationIncludingGravity.x 17 | accY = event.accelerationIncludingGravity.y * -1 18 | 19 | force.x = accX * scale / 10 20 | force.y = accY * scale / 10 21 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/behaviour/Wander.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | 4 | ### Wander Behaviour ### 5 | 6 | class exports.Wander extends Behaviour 7 | 8 | constructor: (@jitter = 0.5, @radius = 100, @strength = 1.0) -> 9 | 10 | @theta = Math.random() * Math.PI * 2 11 | 12 | super 13 | 14 | apply: (p, dt, index) -> 15 | 16 | #super p, dt, index 17 | 18 | @theta += (Math.random() - 0.5) * @jitter * Math.PI * 2 19 | 20 | p.acc.x += Math.cos(@theta) * @radius * @strength 21 | p.acc.y += Math.sin(@theta) * @radius * @strength 22 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/demos/AttractionDemo.coffee: -------------------------------------------------------------------------------- 1 | class AttractionDemo extends Demo 2 | 3 | setup: (full = yes) -> 4 | 5 | super full 6 | 7 | min = new Vector 0.0, 0.0 8 | max = new Vector @width, @height 9 | 10 | bounds = new EdgeBounce min, max 11 | 12 | @physics.integrator = new Verlet() 13 | 14 | attraction = new Attraction @mouse.pos, 1200, 1200 15 | repulsion = new Attraction @mouse.pos, 200, -2000 16 | collide = new Collision() 17 | 18 | max = if full then 400 else 200 19 | 20 | for i in [0..max] 21 | 22 | p = new Particle (Random 0.1, 3.0) 23 | p.setRadius p.mass * 4 24 | 25 | p.moveTo new Vector (Random @width), (Random @height) 26 | 27 | p.behaviours.push attraction 28 | p.behaviours.push repulsion 29 | p.behaviours.push bounds 30 | p.behaviours.push collide 31 | 32 | collide.pool.push p 33 | 34 | @physics.particles.push p -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/demos/BalloonDemo.coffee: -------------------------------------------------------------------------------- 1 | ### BalloonDemo ### 2 | class BalloonDemo extends Demo 3 | 4 | setup: (full = yes) -> 5 | 6 | super 7 | 8 | @physics.integrator = new ImprovedEuler() 9 | attraction = new Attraction @mouse.pos 10 | 11 | max = if full then 400 else 200 12 | 13 | for i in [0..max] 14 | 15 | p = new Particle (Random 0.25, 4.0) 16 | p.setRadius p.mass * 8 17 | 18 | p.behaviours.push new Wander 0.2 19 | p.behaviours.push attraction 20 | 21 | p.moveTo new Vector (Random @width), (Random @height) 22 | 23 | s = new Spring @mouse, p, (Random 30, 300), 1.0 24 | 25 | @physics.particles.push p 26 | @physics.springs.push s 27 | 28 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/demos/BoundsDemo.coffee: -------------------------------------------------------------------------------- 1 | ### BoundsDemo ### 2 | class BoundsDemo extends Demo 3 | 4 | setup: -> 5 | 6 | super 7 | 8 | min = new Vector 0.0, 0.0 9 | max = new Vector @width, @height 10 | 11 | edge = new EdgeWrap min, max 12 | 13 | for i in [0..200] 14 | 15 | p = new Particle (Random 0.5, 4.0) 16 | p.setRadius p.mass * 5 17 | 18 | p.moveTo new Vector (Random @width), (Random @height) 19 | 20 | p.behaviours.push new Wander 0.2, 120, Random 1.0, 2.0 21 | p.behaviours.push edge 22 | 23 | @physics.particles.push p 24 | 25 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/demos/ChainDemo.coffee: -------------------------------------------------------------------------------- 1 | class ChainDemo extends Demo 2 | 3 | setup: (full = yes) -> 4 | 5 | super 6 | 7 | @stiffness = 1.0 8 | @spacing = 2.0 9 | 10 | @physics.integrator = new Verlet() 11 | @physics.viscosity = 0.0001 12 | @mouse.setMass 1000 13 | 14 | gap = 50.0 15 | min = new Vector -gap, -gap 16 | max = new Vector @width + gap, @height + gap 17 | 18 | edge = new EdgeBounce min, max 19 | 20 | center = new Vector @width * 0.5, @height * 0.5 21 | 22 | #@renderer.renderParticles = no 23 | 24 | wander = new Wander 0.05, 100.0, 80.0 25 | 26 | max = if full then 2000 else 600 27 | 28 | for i in [0..max] 29 | 30 | p = new Particle 6.0 31 | p.colour = '#FFFFFF' 32 | p.moveTo center 33 | p.setRadius 1.0 34 | 35 | p.behaviours.push wander 36 | p.behaviours.push edge 37 | 38 | @physics.particles.push p 39 | 40 | if op? then s = new Spring op, p, @spacing, @stiffness 41 | else s = new Spring @mouse, p, @spacing, @stiffness 42 | 43 | @physics.springs.push s 44 | 45 | op = p 46 | 47 | @physics.springs.push new Spring @mouse, p, @spacing, @stiffness -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/demos/ClothDemo.coffee: -------------------------------------------------------------------------------- 1 | class ClothDemo extends Demo 2 | 3 | setup: (full = yes) -> 4 | 5 | super 6 | 7 | # Only render springs. 8 | @renderer.renderParticles = false 9 | 10 | @physics.integrator = new Verlet() 11 | @physics.timestep = 1.0 / 200 12 | @mouse.setMass 10 13 | 14 | # Add gravity to the simulation. 15 | @gravity = new ConstantForce new Vector 0.0, 80.0 16 | @physics.behaviours.push @gravity 17 | 18 | stiffness = 0.5 19 | size = if full then 8 else 10 20 | rows = if full then 30 else 25 21 | cols = if full then 55 else 40 22 | cell = [] 23 | 24 | sx = @width * 0.5 - cols * size * 0.5 25 | sy = @height * 0.5 - rows * size * 0.5 26 | 27 | for x in [0..cols] 28 | 29 | cell[x] = [] 30 | 31 | for y in [0..rows] 32 | 33 | p = new Particle(0.1) 34 | 35 | p.fixed = (y is 0) 36 | 37 | # Always set initial position using moveTo for Verlet 38 | p.moveTo new Vector (sx + x * size), (sy + y * size) 39 | 40 | if x > 0 41 | s = new Spring p, cell[x-1][y], size, stiffness 42 | @physics.springs.push s 43 | 44 | if y > 0 45 | s = new Spring p, cell[x][y - 1], size, stiffness 46 | @physics.springs.push s 47 | 48 | @physics.particles.push p 49 | cell[x][y] = p 50 | 51 | p = cell[Math.floor cols / 2][Math.floor rows / 2] 52 | s = new Spring @mouse, p, 10, 1.0 53 | @physics.springs.push s 54 | 55 | cell[0][0].fixed = true 56 | cell[cols - 1][0].fixed = true 57 | 58 | step: -> 59 | 60 | super 61 | 62 | @gravity.force.x = 50 * Math.sin new Date().getTime() * 0.0005 63 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/demos/CollisionDemo.coffee: -------------------------------------------------------------------------------- 1 | ### CollisionDemo ### 2 | class CollisionDemo extends Demo 3 | 4 | setup: (full = yes) -> 5 | 6 | super 7 | 8 | # Verlet gives us collision responce for free! 9 | @physics.integrator = new Verlet() 10 | 11 | min = new Vector 0.0, 0.0 12 | max = new Vector @width, @height 13 | 14 | bounds = new EdgeBounce min, max 15 | collide = new Collision 16 | attraction = new Attraction @mouse.pos, 2000, 1400 17 | 18 | max = if full then 350 else 150 19 | prob = if full then 0.35 else 0.5 20 | 21 | for i in [0..max] 22 | 23 | p = new Particle (Random 0.5, 4.0) 24 | p.setRadius p.mass * 4 25 | 26 | p.moveTo new Vector (Random @width), (Random @height) 27 | 28 | # Connect to spring or move free. 29 | if Random.bool prob 30 | s = new Spring @mouse, p, (Random 120, 180), 0.8 31 | @physics.springs.push s 32 | else 33 | p.behaviours.push attraction 34 | 35 | # Add particle to collision pool. 36 | collide.pool.push p 37 | 38 | # Allow particle to collide. 39 | p.behaviours.push collide 40 | p.behaviours.push bounds 41 | 42 | @physics.particles.push p 43 | 44 | onCollision: (p1, p2) => 45 | 46 | # Respond to collision. 47 | 48 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/demos/Demo.coffee: -------------------------------------------------------------------------------- 1 | ### Demo ### 2 | class Demo 3 | 4 | @COLOURS = ['DC0048', 'F14646', '4AE6A9', '7CFF3F', '4EC9D9', 'E4272E'] 5 | 6 | constructor: -> 7 | 8 | @physics = new Physics() 9 | @mouse = new Particle() 10 | @mouse.fixed = true 11 | @height = window.innerHeight 12 | @width = window.innerWidth 13 | 14 | @renderTime = 0 15 | @counter = 0 16 | 17 | setup: (full = yes) -> 18 | 19 | ### Override and add paticles / springs here ### 20 | 21 | ### Initialise the demo (override). ### 22 | init: (@container, @renderer = new WebGLRenderer()) -> 23 | 24 | # Build the scene. 25 | @setup renderer.gl? 26 | 27 | # Give the particles random colours. 28 | for particle in @physics.particles 29 | particle.colour ?= Random.item Demo.COLOURS 30 | 31 | # Add event handlers. 32 | document.addEventListener 'touchmove', @mousemove, false 33 | document.addEventListener 'mousemove', @mousemove, false 34 | document.addEventListener 'resize', @resize, false 35 | 36 | # Add to render output to the DOM. 37 | @container.appendChild @renderer.domElement 38 | 39 | # Prepare the renderer. 40 | @renderer.mouse = @mouse 41 | @renderer.init @physics 42 | 43 | # Resize for the sake of the renderer. 44 | do @resize 45 | 46 | ### Handler for window resize event. ### 47 | resize: (event) => 48 | 49 | @width = window.innerWidth 50 | @height = window.innerHeight 51 | @renderer.setSize @width, @height 52 | 53 | ### Update loop. ### 54 | step: -> 55 | 56 | #console.profile 'physics' 57 | 58 | # Step physics. 59 | do @physics.step 60 | 61 | #console.profileEnd() 62 | 63 | #console.profile 'render' 64 | 65 | # Render. 66 | 67 | # Render every frame for WebGL, or every 3 frames for canvas. 68 | @renderer.render @physics if @renderer.gl? or ++@counter % 3 is 0 69 | 70 | #console.profileEnd() 71 | 72 | ### Clean up after yourself. ### 73 | destroy: -> 74 | 75 | ## console.log @, 'destroy' 76 | 77 | # Remove event handlers. 78 | document.removeEventListener 'touchmove', @mousemove, false 79 | document.removeEventListener 'mousemove', @mousemove, false 80 | document.removeEventListener 'resize', @resize, false 81 | 82 | # Remove the render output from the DOM. 83 | try container.removeChild @renderer.domElement 84 | catch error 85 | 86 | do @renderer.destroy 87 | do @physics.destroy 88 | 89 | @renderer = null 90 | @physics = null 91 | @mouse = null 92 | 93 | ### Handler for window mousemove event. ### 94 | mousemove: (event) => 95 | 96 | do event.preventDefault 97 | 98 | if event.touches and !!event.touches.length 99 | 100 | touch = event.touches[0] 101 | @mouse.pos.set touch.pageX, touch.pageY 102 | 103 | else 104 | 105 | @mouse.pos.set event.clientX, event.clientY 106 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/demos/renderer/CanvasRenderer.coffee: -------------------------------------------------------------------------------- 1 | ### Canvas Renderer ### 2 | class CanvasRenderer extends Renderer 3 | 4 | constructor: -> 5 | 6 | super 7 | 8 | @canvas = document.createElement 'canvas' 9 | @ctx = @canvas.getContext '2d' 10 | 11 | # Set the DOM element. 12 | @domElement = @canvas 13 | 14 | init: (physics) -> 15 | 16 | super physics 17 | 18 | render: (physics) -> 19 | 20 | super physics 21 | 22 | time = new Date().getTime() 23 | 24 | # Draw velocity. 25 | vel = new Vector() 26 | 27 | # Draw heading. 28 | dir = new Vector() 29 | 30 | # Clear canvas. 31 | @canvas.width = @canvas.width 32 | 33 | @ctx.globalCompositeOperation = 'lighter' 34 | @ctx.lineWidth = 1 35 | 36 | # Draw particles. 37 | if @renderParticles 38 | 39 | TWO_PI = Math.PI * 2 40 | 41 | for p in physics.particles 42 | 43 | @ctx.beginPath() 44 | @ctx.arc(p.pos.x, p.pos.y, p.radius, 0, TWO_PI, no) 45 | 46 | @ctx.fillStyle = '#' + (p.colour or 'FFFFFF') 47 | @ctx.fill() 48 | 49 | if @renderSprings 50 | 51 | @ctx.strokeStyle = 'rgba(255,255,255,0.1)' 52 | @ctx.beginPath() 53 | 54 | for s in physics.springs 55 | @ctx.moveTo(s.p1.pos.x, s.p1.pos.y) 56 | @ctx.lineTo(s.p2.pos.x, s.p2.pos.y) 57 | 58 | @ctx.stroke() 59 | 60 | if @renderMouse 61 | 62 | # Draw mouse. 63 | @ctx.fillStyle = 'rgba(255,255,255,0.1)' 64 | @ctx.beginPath() 65 | @ctx.arc(@mouse.pos.x, @mouse.pos.y, 20, 0, TWO_PI) 66 | @ctx.fill() 67 | 68 | @renderTime = new Date().getTime() - time 69 | 70 | setSize: (@width, @height) => 71 | 72 | super @width, @height 73 | 74 | @canvas.width = @width 75 | @canvas.height = @height 76 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/demos/renderer/DOMRenderer.coffee: -------------------------------------------------------------------------------- 1 | ### DOM Renderer ### 2 | ### 3 | 4 | Updating styles: 5 | 6 | Nodes 7 | 8 | ### 9 | class DOMRenderer extends Renderer 10 | 11 | constructor: -> 12 | 13 | super 14 | 15 | @useGPU = yes 16 | 17 | @domElement = document.createElement 'div' 18 | @canvas = document.createElement 'canvas' 19 | @ctx = @canvas.getContext '2d' 20 | 21 | @canvas.style.position = 'absolute' 22 | @canvas.style.left = 0 23 | @canvas.style.top = 0 24 | 25 | @domElement.style.pointerEvents = 'none' 26 | @domElement.appendChild @canvas 27 | 28 | init: (physics) -> 29 | 30 | super physics 31 | 32 | # Set up particle DOM elements 33 | for p in physics.particles 34 | 35 | el = document.createElement 'span' 36 | st = el.style 37 | 38 | st.backgroundColor = p.colour 39 | st.borderRadius = p.radius 40 | st.marginLeft = -p.radius 41 | st.marginTop = -p.radius 42 | st.position = 'absolute' 43 | st.display = 'block' 44 | st.opacity = 0.85 45 | st.height = p.radius * 2 46 | st.width = p.radius * 2 47 | 48 | @domElement.appendChild el 49 | p.domElement = el 50 | 51 | # Set up mouse DOM element 52 | el = document.createElement 'span' 53 | st = el.style 54 | mr = 20 55 | 56 | st.backgroundColor = '#ffffff' 57 | st.borderRadius = mr 58 | st.marginLeft = -mr 59 | st.marginTop = -mr 60 | st.position = 'absolute' 61 | st.display = 'block' 62 | st.opacity = 0.1 63 | st.height = mr * 2 64 | st.width = mr * 2 65 | 66 | @domElement.appendChild el 67 | @mouse.domElement = el 68 | 69 | render: (physics) -> 70 | 71 | super physics 72 | 73 | time = new Date().getTime() 74 | 75 | if @renderParticles 76 | 77 | for p in physics.particles 78 | 79 | if @useGPU 80 | 81 | p.domElement.style.WebkitTransform = """ 82 | translate3d(#{p.pos.x|0}px,#{p.pos.y|0}px,0px) 83 | """ 84 | else 85 | 86 | p.domElement.style.left = p.pos.x 87 | p.domElement.style.top = p.pos.y 88 | 89 | if @renderSprings 90 | 91 | @canvas.width = @canvas.width 92 | 93 | @ctx.strokeStyle = 'rgba(255,255,255,0.1)' 94 | @ctx.beginPath() 95 | 96 | for s in physics.springs 97 | @ctx.moveTo(s.p1.pos.x, s.p1.pos.y) 98 | @ctx.lineTo(s.p2.pos.x, s.p2.pos.y) 99 | 100 | @ctx.stroke() 101 | 102 | if @renderMouse 103 | 104 | if @useGPU 105 | 106 | @mouse.domElement.style.WebkitTransform = """ 107 | translate3d(#{@mouse.pos.x|0}px,#{@mouse.pos.y|0}px,0px) 108 | """ 109 | else 110 | 111 | @mouse.domElement.style.left = @mouse.pos.x 112 | @mouse.domElement.style.top = @mouse.pos.y 113 | 114 | @renderTime = new Date().getTime() - time 115 | 116 | setSize: (@width, @height) => 117 | 118 | super @width, @height 119 | 120 | @canvas.width = @width 121 | @canvas.height = @height 122 | 123 | destroy: -> 124 | 125 | while @domElement.hasChildNodes() 126 | @domElement.removeChild @domElement.lastChild 127 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/demos/renderer/Renderer.coffee: -------------------------------------------------------------------------------- 1 | ### Base Renderer ### 2 | class Renderer 3 | 4 | constructor: -> 5 | 6 | @width = 0 7 | @height = 0 8 | 9 | @renderParticles = true 10 | @renderSprings = true 11 | @renderMouse = true 12 | @initialized = false 13 | @renderTime = 0 14 | 15 | init: (physics) -> 16 | 17 | @initialized = true 18 | 19 | render: (physics) -> 20 | 21 | if not @initialized then @init physics 22 | 23 | setSize: (@width, @height) => 24 | 25 | destroy: -> 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/engine/Particle.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Vector} = require 'coffeePhysics/math/Vector' 3 | 4 | ### Particle ### 5 | 6 | class exports.Particle 7 | 8 | @GUID = 0 9 | 10 | constructor: (@mass = 1.0) -> 11 | 12 | # Set a unique id. 13 | @id = 'p' + Particle.GUID++ 14 | 15 | # Set initial mass. 16 | @setMass @mass 17 | 18 | # Set initial radius. 19 | @setRadius 1.0 20 | 21 | # Apply forces. 22 | @fixed = false 23 | 24 | # Behaviours to be applied. 25 | @behaviours = [] 26 | 27 | # Current position. 28 | @pos = new Vector() 29 | 30 | # Current velocity. 31 | @vel = new Vector() 32 | 33 | # Current force. 34 | @acc = new Vector() 35 | 36 | # Previous state. 37 | @old = 38 | pos: new Vector() 39 | vel: new Vector() 40 | acc: new Vector() 41 | 42 | ### Moves the particle to a given location vector. ### 43 | moveTo: (pos) -> 44 | 45 | @pos.copy pos 46 | @old.pos.copy pos 47 | 48 | ### Sets the mass of the particle. ### 49 | setMass: (@mass = 1.0) -> 50 | 51 | # The inverse mass. 52 | @massInv = 1.0 / @mass 53 | 54 | ### Sets the radius of the particle. ### 55 | setRadius: (@radius = 1.0) -> 56 | 57 | @radiusSq = @radius * @radius 58 | 59 | ### Applies all behaviours to derive new force. ### 60 | update: (dt, index) -> 61 | 62 | # Apply all behaviours. 63 | 64 | if not @fixed 65 | 66 | for behaviour in @behaviours 67 | 68 | behaviour.apply @, dt, index 69 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/engine/Physics.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Euler} = require 'coffeePhysics/engine/integrator/Euler' 3 | 4 | ### Physics Engine ### 5 | 6 | class exports.Physics 7 | 8 | constructor: (@integrator = new Euler()) -> 9 | 10 | # Fixed timestep. 11 | @timestep = 1.0 / 60 12 | 13 | # Friction within the system. 14 | @viscosity = 0.005 15 | 16 | # Global behaviours. 17 | @behaviours = [] 18 | 19 | # Time in seconds. 20 | @_time = 0.0 21 | 22 | # Last step duration. 23 | @_step = 0.0 24 | 25 | # Current time. 26 | @_clock = null 27 | 28 | # Time buffer. 29 | @_buffer = 0.0 30 | 31 | # Max iterations per step. 32 | @_maxSteps = 4 33 | 34 | # Particles in system. 35 | @particles = [] 36 | 37 | # Springs in system. 38 | @springs = [] 39 | 40 | ### Performs a numerical integration step. ### 41 | integrate: (dt) -> 42 | 43 | # Drag is inversely proportional to viscosity. 44 | drag = 1.0 - @viscosity 45 | 46 | # Update particles / apply behaviours. 47 | 48 | for particle, index in @particles 49 | 50 | for behaviour in @behaviours 51 | 52 | behaviour.apply particle, dt, index 53 | 54 | particle.update dt, index 55 | 56 | # Integrate motion. 57 | 58 | @integrator.integrate @particles, dt, drag 59 | 60 | # Compute all springs. 61 | 62 | for spring in @springs 63 | 64 | spring.apply() 65 | 66 | ### Steps the system. ### 67 | step: -> 68 | 69 | # Initialise the clock on first step. 70 | @_clock ?= new Date().getTime() 71 | 72 | # Compute delta time since last step. 73 | time = new Date().getTime() 74 | delta = time - @_clock 75 | 76 | # No sufficient change. 77 | return if delta <= 0.0 78 | 79 | # Convert time to seconds. 80 | delta *= 0.001 81 | 82 | # Update the clock. 83 | @_clock = time 84 | 85 | # Increment time buffer. 86 | @_buffer += delta 87 | 88 | # Integrate until the buffer is empty or until the 89 | # maximum amount of iterations per step is reached. 90 | 91 | i = 0 92 | 93 | while @_buffer >= @timestep and ++i < @_maxSteps 94 | 95 | # Integrate motion by fixed timestep. 96 | @integrate @timestep 97 | 98 | # Reduce buffer by one timestep. 99 | @_buffer -= @timestep 100 | 101 | # Increment running time. 102 | @_time += @timestep 103 | 104 | # Store step time for debugging. 105 | @_step = new Date().getTime() - time 106 | 107 | ### Clean up after yourself. ### 108 | destroy: -> 109 | 110 | @integrator = null 111 | @particles = null 112 | @springs = null 113 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/engine/Spring.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Vector} = require 'coffeePhysics/math/Vector' 3 | 4 | ### Spring ### 5 | 6 | class exports.Spring 7 | 8 | constructor: (@p1, @p2, @restLength = 100, @stiffness = 1.0) -> 9 | 10 | @_delta = new Vector() 11 | 12 | # F = -kx 13 | 14 | apply: -> 15 | 16 | (@_delta.copy @p2.pos).sub @p1.pos 17 | 18 | dist = @_delta.mag() + 0.000001 19 | force = (dist - @restLength) / (dist * (@p1.massInv + @p2.massInv)) * @stiffness 20 | 21 | if not @p1.fixed 22 | 23 | @p1.pos.add (@_delta.clone().scale force * @p1.massInv) 24 | 25 | if not @p2.fixed 26 | 27 | @p2.pos.add (@_delta.scale -force * @p2.massInv) 28 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/engine/integrator/Euler.coffee: -------------------------------------------------------------------------------- 1 | ### Import Integrator ### 2 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 3 | 4 | ### Euler Integrator ### 5 | class exports.Euler extends Integrator 6 | 7 | # v += a * dt 8 | # x += v * dt 9 | 10 | integrate: (particles, dt, drag) -> 11 | 12 | vel = new Vector() 13 | 14 | for p in particles when not p.fixed 15 | 16 | # Store previous location. 17 | p.old.pos.copy p.pos 18 | 19 | # Scale force to mass. 20 | p.acc.scale p.massInv 21 | 22 | # Duplicate velocity to preserve momentum. 23 | vel.copy p.vel 24 | 25 | # Add force to velocity. 26 | p.vel.add p.acc.scale dt 27 | 28 | # Add velocity to position. 29 | p.pos.add vel.scale dt 30 | 31 | # Apply friction. 32 | if drag then p.vel.scale drag 33 | 34 | # Reset forces. 35 | p.acc.clear() 36 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/engine/integrator/ImprovedEuler.coffee: -------------------------------------------------------------------------------- 1 | ### Import Integrator ### 2 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 3 | 4 | ### Improved Euler Integrator ### 5 | 6 | class exports.ImprovedEuler extends Integrator 7 | 8 | # x += (v * dt) + (a * 0.5 * dt * dt) 9 | # v += a * dt 10 | 11 | integrate: (particles, dt, drag) -> 12 | 13 | acc = new Vector() 14 | vel = new Vector() 15 | 16 | dtSq = dt * dt 17 | 18 | for p in particles when not p.fixed 19 | 20 | # Store previous location. 21 | p.old.pos.copy p.pos 22 | 23 | # Scale force to mass. 24 | p.acc.scale p.massInv 25 | 26 | # Duplicate velocity to preserve momentum. 27 | vel.copy p.vel 28 | 29 | # Duplicate force. 30 | acc.copy p.acc 31 | 32 | # Update position. 33 | p.pos.add (vel.scale dt).add (acc.scale 0.5 * dtSq) 34 | 35 | # Update velocity. 36 | p.vel.add p.acc.scale dt 37 | 38 | # Apply friction. 39 | if drag then p.vel.scale drag 40 | 41 | # Reset forces. 42 | p.acc.clear() 43 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/engine/integrator/Integrator.coffee: -------------------------------------------------------------------------------- 1 | ### Integrator ### 2 | 3 | class exports.Integrator 4 | 5 | integrate: (particles, dt) -> 6 | 7 | # Override. 8 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/engine/integrator/Verlet.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | 6 | ### Velocity Verlet Integrator ### 7 | 8 | class exports.Verlet extends Integrator 9 | 10 | # v = x - ox 11 | # x = x + (v + a * dt * dt) 12 | 13 | integrate: (particles, dt, drag) -> 14 | 15 | pos = new Vector() 16 | 17 | dtSq = dt * dt 18 | 19 | for p in particles when not p.fixed 20 | 21 | # Scale force to mass. 22 | p.acc.scale p.massInv 23 | 24 | # Derive velocity. 25 | (p.vel.copy p.pos).sub p.old.pos 26 | 27 | # Apply friction. 28 | if drag then p.vel.scale drag 29 | 30 | # Apply forces to new position. 31 | (pos.copy p.pos).add (p.vel.add p.acc.scale dtSq) 32 | 33 | # Store old position. 34 | p.old.pos.copy p.pos 35 | 36 | # update position. 37 | p.pos.copy pos 38 | 39 | # Reset forces. 40 | p.acc.clear() 41 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/math/Random.coffee: -------------------------------------------------------------------------------- 1 | ### Random ### 2 | 3 | exports.Random = (min, max) -> 4 | 5 | if not max? 6 | max = min 7 | min = 0 8 | 9 | min + Math.random() * (max - min) 10 | 11 | Random.int = (min, max) -> 12 | 13 | if not max? 14 | max = min 15 | min = 0 16 | 17 | Math.floor min + Math.random() * (max - min) 18 | 19 | Random.sign = (prob = 0.5) -> 20 | 21 | if do Math.random < prob then 1 else -1 22 | 23 | Random.bool = (prob = 0.5) -> 24 | 25 | do Math.random < prob 26 | 27 | Random.item = (list) -> 28 | 29 | list[ Math.floor Math.random() * list.length ] 30 | -------------------------------------------------------------------------------- /examples/wander.framer/modules/coffeePhysics/math/Vector.coffee: -------------------------------------------------------------------------------- 1 | ### 2D Vector ### 2 | 3 | class exports.Vector 4 | 5 | ### Adds two vectors and returns the product. ### 6 | @add: (v1, v2) -> 7 | new Vector v1.x + v2.x, v1.y + v2.y 8 | 9 | ### Subtracts v2 from v1 and returns the product. ### 10 | @sub: (v1, v2) -> 11 | new Vector v1.x - v2.x, v1.y - v2.y 12 | 13 | ### Projects one vector (v1) onto another (v2) ### 14 | @project: (v1, v2) -> 15 | v1.clone().scale ((v1.dot v2) / v1.magSq()) 16 | 17 | ### Creates a new Vector instance. ### 18 | constructor: (@x = 0.0, @y = 0.0) -> 19 | 20 | ### Sets the components of this vector. ### 21 | set: (@x, @y) -> 22 | @ 23 | 24 | ### Add a vector to this one. ### 25 | add: (v) -> 26 | @x += v.x; @y += v.y; @ 27 | 28 | ### Subtracts a vector from this one. ### 29 | sub: (v) -> 30 | @x -= v.x; @y -= v.y; @ 31 | 32 | ### Scales this vector by a value. ### 33 | scale: (f) -> 34 | @x *= f; @y *= f; @ 35 | 36 | ### Computes the dot product between vectors. ### 37 | dot: (v) -> 38 | @x * v.x + @y * v.y 39 | 40 | ### Computes the cross product between vectors. ### 41 | cross: (v) -> 42 | (@x * v.y) - (@y * v.x) 43 | 44 | ### Computes the magnitude (length). ### 45 | mag: -> 46 | Math.sqrt @x*@x + @y*@y 47 | 48 | ### Computes the squared magnitude (length). ### 49 | magSq: -> 50 | @x*@x + @y*@y 51 | 52 | ### Computes the distance to another vector. ### 53 | dist: (v) -> 54 | dx = v.x - @x; dy = v.y - @y 55 | Math.sqrt dx*dx + dy*dy 56 | 57 | ### Computes the squared distance to another vector. ### 58 | distSq: (v) -> 59 | dx = v.x - @x; dy = v.y - @y 60 | dx*dx + dy*dy 61 | 62 | ### Normalises the vector, making it a unit vector (of length 1). ### 63 | norm: -> 64 | m = Math.sqrt @x*@x + @y*@y 65 | @x /= m 66 | @y /= m 67 | @ 68 | 69 | ### Limits the vector length to a given amount. ### 70 | limit: (l) -> 71 | mSq = @x*@x + @y*@y 72 | if mSq > l*l 73 | m = Math.sqrt mSq 74 | @x /= m; @y /= m 75 | @x *= l; @y *= l 76 | @ 77 | 78 | ### Copies components from another vector. ### 79 | copy: (v) -> 80 | @x = v.x; @y = v.y; @ 81 | 82 | ### Clones this vector to a new itentical one. ### 83 | clone: -> 84 | new Vector @x, @y 85 | 86 | ### Resets the vector to zero. ### 87 | clear: -> 88 | @x = 0.0; @y = 0.0; @ 89 | -------------------------------------------------------------------------------- /module files/coffeePhysics.coffee: -------------------------------------------------------------------------------- 1 | # Add the following line to your project in Framer Studio. 2 | # myModule = require "myModule" 3 | # Reference the contents by name, like myModule.myFunction() or myModule.myVar 4 | 5 | 6 | # Import integrator framework 7 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 8 | {Euler} = require 'coffeePhysics/engine/integrator/Euler' 9 | {ImprovedEuler} = require 'coffeePhysics/engine/integrator/ImprovedEuler' 10 | {Verlet} = require 'coffeePhysics/engine/integrator/Verlet' 11 | 12 | exports.Integrator = Integrator 13 | exports.Euler = Euler 14 | exports.ImprovedEuler = ImprovedEuler 15 | exports.Verlet = Verlet 16 | 17 | # Import physics framework 18 | {Particle} = require 'coffeePhysics/engine/Particle' 19 | {Physics} = require 'coffeePhysics/engine/Physics' 20 | {Spring} = require 'coffeePhysics/engine/Spring' 21 | 22 | exports.Particle = Particle 23 | exports.Physics = Physics 24 | exports.Spring = Spring 25 | 26 | # Import math framework 27 | # {Random} = require 'coffeePhysics/math/Random' 28 | {Vector} = require 'coffeePhysics/math/Vector' 29 | 30 | # exports.Random = Random 31 | exports.Vector = Vector 32 | 33 | # Import behaviour framework 34 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 35 | {Attraction} = require 'coffeePhysics/behaviour/Attraction' 36 | {Collision} = require 'coffeePhysics/behaviour/Collision' 37 | {ConstantForce} = require 'coffeePhysics/behaviour/ConstantForce' 38 | {EdgeBounce} = require 'coffeePhysics/behaviour/EdgeBounce' 39 | {EdgeWrap} = require 'coffeePhysics/behaviour/EdgeWrap' 40 | {Wander} = require 'coffeePhysics/behaviour/Wander' 41 | {Gravity} = require 'coffeePhysics/behaviour/Gravity' 42 | 43 | exports.Behaviour = Behaviour 44 | exports.Attraction = Attraction 45 | exports.Collision = Collision 46 | exports.ConstantForce = ConstantForce 47 | exports.EdgeBounce = EdgeBounce 48 | exports.EdgeWrap = EdgeWrap 49 | exports.Wander = Wander 50 | exports.Gravity = Gravity 51 | -------------------------------------------------------------------------------- /module files/coffeePhysics/base.coffee: -------------------------------------------------------------------------------- 1 | ### Allows safe, dyamic creation of namespaces. ### 2 | 3 | namespace = (id) -> 4 | root = self 5 | root = root[path] ?= {} for path in id.split '.' 6 | 7 | ### RequestAnimationFrame shim. ### 8 | do -> 9 | 10 | time = 0 11 | vendors = ['ms', 'moz', 'webkit', 'o'] 12 | 13 | for vendor in vendors when not window.requestAnimationFrame 14 | window.requestAnimationFrame = window[ vendor + 'RequestAnimationFrame'] 15 | window.cancelAnimationFrame = window[ vendor + 'CancelAnimationFrame'] 16 | 17 | if not window.requestAnimationFrame 18 | 19 | window.requestAnimationFrame = (callback, element) -> 20 | now = new Date().getTime() 21 | delta = Math.max 0, 16 - (now - old) 22 | setTimeout (-> callback(time + delta)), delta 23 | old = now + delta 24 | 25 | if not window.cancelAnimationFrame 26 | 27 | window.cancelAnimationFrame = (id) -> 28 | clearTimeout id 29 | -------------------------------------------------------------------------------- /module files/coffeePhysics/behaviour/Attraction.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Attraction Behaviour ### 6 | 7 | class exports.Attraction extends Behaviour 8 | 9 | constructor: (@target = new Vector(), @radius = 1000, @strength = 100.0) -> 10 | 11 | @_delta = new Vector() 12 | @setRadius @radius 13 | 14 | super 15 | 16 | ### Sets the effective radius of the bahavious. ### 17 | setRadius: (radius) -> 18 | 19 | @radius = radius 20 | @radiusSq = radius * radius 21 | 22 | apply: (p, dt, index) -> 23 | 24 | #super p, dt, index 25 | 26 | # Vector pointing from particle to target. 27 | (@_delta.copy @target).sub p.pos 28 | 29 | # Squared distance to target. 30 | distSq = @_delta.magSq() 31 | 32 | # Limit force to behaviour radius. 33 | if distSq < @radiusSq and distSq > 0.000001 34 | 35 | # Calculate force vector. 36 | @_delta.norm().scale (1.0 - distSq / @radiusSq) 37 | 38 | #Apply force. 39 | p.acc.add @_delta.scale @strength 40 | -------------------------------------------------------------------------------- /module files/coffeePhysics/behaviour/Behaviour.coffee: -------------------------------------------------------------------------------- 1 | ### Behaviour ### 2 | 3 | class exports.Behaviour 4 | 5 | # Each behaviour has a unique id 6 | @GUID = 0 7 | 8 | constructor: -> 9 | 10 | @GUID = Behaviour.GUID++ 11 | @interval = 1 12 | 13 | ## console.log @, @GUID 14 | 15 | apply: (p, dt, index) -> 16 | 17 | # Store some data in each particle. 18 | (p['__behaviour' + @GUID] ?= {counter: 0}).counter++ 19 | -------------------------------------------------------------------------------- /module files/coffeePhysics/behaviour/Collision.coffee: -------------------------------------------------------------------------------- 1 | ### Import Behaviour ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Collision Behaviour ### 6 | 7 | # TODO: Collision response for non Verlet integrators. 8 | 9 | class exports.Collision extends Behaviour 10 | 11 | constructor: (@useMass = yes, @callback = null) -> 12 | 13 | # Pool of collidable particles. 14 | @pool = [] 15 | 16 | # Delta between particle positions. 17 | @_delta = new Vector() 18 | 19 | super 20 | 21 | apply: (p, dt, index) -> 22 | 23 | #super p, dt, index 24 | 25 | # Check pool for collisions. 26 | for o in @pool[index..] when o isnt p 27 | 28 | # Delta between particles positions. 29 | (@_delta.copy o.pos).sub p.pos 30 | 31 | # Squared distance between particles. 32 | distSq = @_delta.magSq() 33 | 34 | # Sum of both radii. 35 | radii = p.radius + o.radius 36 | 37 | # Check if particles collide. 38 | if distSq <= radii * radii 39 | 40 | # Compute real distance. 41 | dist = Math.sqrt distSq 42 | 43 | # Determine overlap. 44 | overlap = radii - dist 45 | overlap += 0.5 46 | 47 | # Total mass. 48 | mt = p.mass + o.mass 49 | 50 | # Distribute collision responses. 51 | r1 = if @useMass then o.mass / mt else 0.5 52 | r2 = if @useMass then p.mass / mt else 0.5 53 | 54 | # Move particles so they no longer overlap. 55 | p.pos.add (@_delta.clone().norm().scale overlap * -r1) 56 | o.pos.add (@_delta.norm().scale overlap * r2) 57 | 58 | # Fire callback if defined. 59 | @callback?(p, o, overlap) 60 | -------------------------------------------------------------------------------- /module files/coffeePhysics/behaviour/ConstantForce.coffee: -------------------------------------------------------------------------------- 1 | ### Import Behaviour ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Constant Force Behaviour ### 6 | 7 | class exports.ConstantForce extends Behaviour 8 | 9 | constructor: (@force = new Vector()) -> 10 | 11 | super 12 | 13 | apply: (p, dt,index) -> 14 | 15 | #super p, dt, index 16 | 17 | p.acc.add @force 18 | -------------------------------------------------------------------------------- /module files/coffeePhysics/behaviour/EdgeBounce.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Edge Bounce Behaviour ### 6 | 7 | class exports.EdgeBounce extends Behaviour 8 | 9 | constructor: (@min = new Vector(), @max = new Vector()) -> 10 | 11 | super 12 | 13 | apply: (p, dt, index) -> 14 | 15 | #super p, dt, index 16 | 17 | if p.pos.x - p.radius < @min.x 18 | 19 | p.pos.x = @min.x + p.radius 20 | 21 | else if p.pos.x + p.radius > @max.x 22 | 23 | p.pos.x = @max.x - p.radius 24 | 25 | if p.pos.y - p.radius < @min.y 26 | 27 | p.pos.y = @min.y + p.radius 28 | 29 | else if p.pos.y + p.radius > @max.y 30 | 31 | p.pos.y = @max.y - p.radius 32 | -------------------------------------------------------------------------------- /module files/coffeePhysics/behaviour/EdgeWrap.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | ### Edge Wrap Behaviour ### 6 | 7 | class exports.EdgeWrap extends Behaviour 8 | 9 | constructor: (@min = new Vector(), @max = new Vector()) -> 10 | 11 | super 12 | 13 | apply: (p, dt, index) -> 14 | 15 | #super p, dt, index 16 | 17 | if p.pos.x + p.radius < @min.x 18 | 19 | p.pos.x = @max.x + p.radius 20 | p.old.pos.x = p.pos.x 21 | 22 | else if p.pos.x - p.radius > @max.x 23 | 24 | p.pos.x = @min.x - p.radius 25 | p.old.pos.x = p.pos.x 26 | 27 | if p.pos.y + p.radius < @min.y 28 | 29 | p.pos.y = @max.y + p.radius 30 | p.old.pos.y = p.pos.y 31 | 32 | else if p.pos.y - p.radius > @max.y 33 | 34 | p.pos.y = @min.y - p.radius 35 | p.old.pos.y = p.pos.y 36 | -------------------------------------------------------------------------------- /module files/coffeePhysics/behaviour/Gravity.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {ConstantForce} = require 'coffeePhysics/behaviour/ConstantForce' 3 | 4 | ### Gravity Behaviour ### 5 | 6 | class exports.Gravity extends ConstantForce 7 | 8 | constructor: (@scale = 1000) -> 9 | 10 | super() 11 | 12 | force = @force 13 | scale = @scale 14 | 15 | window.addEventListener "devicemotion", -> 16 | accX = event.accelerationIncludingGravity.x 17 | accY = event.accelerationIncludingGravity.y * -1 18 | 19 | force.x = accX * scale / 10 20 | force.y = accY * scale / 10 21 | -------------------------------------------------------------------------------- /module files/coffeePhysics/behaviour/Wander.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Behaviour} = require 'coffeePhysics/behaviour/Behaviour' 3 | 4 | ### Wander Behaviour ### 5 | 6 | class exports.Wander extends Behaviour 7 | 8 | constructor: (@jitter = 0.5, @radius = 100, @strength = 1.0) -> 9 | 10 | @theta = Math.random() * Math.PI * 2 11 | 12 | super 13 | 14 | apply: (p, dt, index) -> 15 | 16 | #super p, dt, index 17 | 18 | @theta += (Math.random() - 0.5) * @jitter * Math.PI * 2 19 | 20 | p.acc.x += Math.cos(@theta) * @radius * @strength 21 | p.acc.y += Math.sin(@theta) * @radius * @strength 22 | -------------------------------------------------------------------------------- /module files/coffeePhysics/demos/AttractionDemo.coffee: -------------------------------------------------------------------------------- 1 | class AttractionDemo extends Demo 2 | 3 | setup: (full = yes) -> 4 | 5 | super full 6 | 7 | min = new Vector 0.0, 0.0 8 | max = new Vector @width, @height 9 | 10 | bounds = new EdgeBounce min, max 11 | 12 | @physics.integrator = new Verlet() 13 | 14 | attraction = new Attraction @mouse.pos, 1200, 1200 15 | repulsion = new Attraction @mouse.pos, 200, -2000 16 | collide = new Collision() 17 | 18 | max = if full then 400 else 200 19 | 20 | for i in [0..max] 21 | 22 | p = new Particle (Random 0.1, 3.0) 23 | p.setRadius p.mass * 4 24 | 25 | p.moveTo new Vector (Random @width), (Random @height) 26 | 27 | p.behaviours.push attraction 28 | p.behaviours.push repulsion 29 | p.behaviours.push bounds 30 | p.behaviours.push collide 31 | 32 | collide.pool.push p 33 | 34 | @physics.particles.push p -------------------------------------------------------------------------------- /module files/coffeePhysics/demos/BalloonDemo.coffee: -------------------------------------------------------------------------------- 1 | ### BalloonDemo ### 2 | class BalloonDemo extends Demo 3 | 4 | setup: (full = yes) -> 5 | 6 | super 7 | 8 | @physics.integrator = new ImprovedEuler() 9 | attraction = new Attraction @mouse.pos 10 | 11 | max = if full then 400 else 200 12 | 13 | for i in [0..max] 14 | 15 | p = new Particle (Random 0.25, 4.0) 16 | p.setRadius p.mass * 8 17 | 18 | p.behaviours.push new Wander 0.2 19 | p.behaviours.push attraction 20 | 21 | p.moveTo new Vector (Random @width), (Random @height) 22 | 23 | s = new Spring @mouse, p, (Random 30, 300), 1.0 24 | 25 | @physics.particles.push p 26 | @physics.springs.push s 27 | 28 | -------------------------------------------------------------------------------- /module files/coffeePhysics/demos/BoundsDemo.coffee: -------------------------------------------------------------------------------- 1 | ### BoundsDemo ### 2 | class BoundsDemo extends Demo 3 | 4 | setup: -> 5 | 6 | super 7 | 8 | min = new Vector 0.0, 0.0 9 | max = new Vector @width, @height 10 | 11 | edge = new EdgeWrap min, max 12 | 13 | for i in [0..200] 14 | 15 | p = new Particle (Random 0.5, 4.0) 16 | p.setRadius p.mass * 5 17 | 18 | p.moveTo new Vector (Random @width), (Random @height) 19 | 20 | p.behaviours.push new Wander 0.2, 120, Random 1.0, 2.0 21 | p.behaviours.push edge 22 | 23 | @physics.particles.push p 24 | 25 | -------------------------------------------------------------------------------- /module files/coffeePhysics/demos/ChainDemo.coffee: -------------------------------------------------------------------------------- 1 | class ChainDemo extends Demo 2 | 3 | setup: (full = yes) -> 4 | 5 | super 6 | 7 | @stiffness = 1.0 8 | @spacing = 2.0 9 | 10 | @physics.integrator = new Verlet() 11 | @physics.viscosity = 0.0001 12 | @mouse.setMass 1000 13 | 14 | gap = 50.0 15 | min = new Vector -gap, -gap 16 | max = new Vector @width + gap, @height + gap 17 | 18 | edge = new EdgeBounce min, max 19 | 20 | center = new Vector @width * 0.5, @height * 0.5 21 | 22 | #@renderer.renderParticles = no 23 | 24 | wander = new Wander 0.05, 100.0, 80.0 25 | 26 | max = if full then 2000 else 600 27 | 28 | for i in [0..max] 29 | 30 | p = new Particle 6.0 31 | p.colour = '#FFFFFF' 32 | p.moveTo center 33 | p.setRadius 1.0 34 | 35 | p.behaviours.push wander 36 | p.behaviours.push edge 37 | 38 | @physics.particles.push p 39 | 40 | if op? then s = new Spring op, p, @spacing, @stiffness 41 | else s = new Spring @mouse, p, @spacing, @stiffness 42 | 43 | @physics.springs.push s 44 | 45 | op = p 46 | 47 | @physics.springs.push new Spring @mouse, p, @spacing, @stiffness -------------------------------------------------------------------------------- /module files/coffeePhysics/demos/ClothDemo.coffee: -------------------------------------------------------------------------------- 1 | class ClothDemo extends Demo 2 | 3 | setup: (full = yes) -> 4 | 5 | super 6 | 7 | # Only render springs. 8 | @renderer.renderParticles = false 9 | 10 | @physics.integrator = new Verlet() 11 | @physics.timestep = 1.0 / 200 12 | @mouse.setMass 10 13 | 14 | # Add gravity to the simulation. 15 | @gravity = new ConstantForce new Vector 0.0, 80.0 16 | @physics.behaviours.push @gravity 17 | 18 | stiffness = 0.5 19 | size = if full then 8 else 10 20 | rows = if full then 30 else 25 21 | cols = if full then 55 else 40 22 | cell = [] 23 | 24 | sx = @width * 0.5 - cols * size * 0.5 25 | sy = @height * 0.5 - rows * size * 0.5 26 | 27 | for x in [0..cols] 28 | 29 | cell[x] = [] 30 | 31 | for y in [0..rows] 32 | 33 | p = new Particle(0.1) 34 | 35 | p.fixed = (y is 0) 36 | 37 | # Always set initial position using moveTo for Verlet 38 | p.moveTo new Vector (sx + x * size), (sy + y * size) 39 | 40 | if x > 0 41 | s = new Spring p, cell[x-1][y], size, stiffness 42 | @physics.springs.push s 43 | 44 | if y > 0 45 | s = new Spring p, cell[x][y - 1], size, stiffness 46 | @physics.springs.push s 47 | 48 | @physics.particles.push p 49 | cell[x][y] = p 50 | 51 | p = cell[Math.floor cols / 2][Math.floor rows / 2] 52 | s = new Spring @mouse, p, 10, 1.0 53 | @physics.springs.push s 54 | 55 | cell[0][0].fixed = true 56 | cell[cols - 1][0].fixed = true 57 | 58 | step: -> 59 | 60 | super 61 | 62 | @gravity.force.x = 50 * Math.sin new Date().getTime() * 0.0005 63 | -------------------------------------------------------------------------------- /module files/coffeePhysics/demos/CollisionDemo.coffee: -------------------------------------------------------------------------------- 1 | ### CollisionDemo ### 2 | class CollisionDemo extends Demo 3 | 4 | setup: (full = yes) -> 5 | 6 | super 7 | 8 | # Verlet gives us collision responce for free! 9 | @physics.integrator = new Verlet() 10 | 11 | min = new Vector 0.0, 0.0 12 | max = new Vector @width, @height 13 | 14 | bounds = new EdgeBounce min, max 15 | collide = new Collision 16 | attraction = new Attraction @mouse.pos, 2000, 1400 17 | 18 | max = if full then 350 else 150 19 | prob = if full then 0.35 else 0.5 20 | 21 | for i in [0..max] 22 | 23 | p = new Particle (Random 0.5, 4.0) 24 | p.setRadius p.mass * 4 25 | 26 | p.moveTo new Vector (Random @width), (Random @height) 27 | 28 | # Connect to spring or move free. 29 | if Random.bool prob 30 | s = new Spring @mouse, p, (Random 120, 180), 0.8 31 | @physics.springs.push s 32 | else 33 | p.behaviours.push attraction 34 | 35 | # Add particle to collision pool. 36 | collide.pool.push p 37 | 38 | # Allow particle to collide. 39 | p.behaviours.push collide 40 | p.behaviours.push bounds 41 | 42 | @physics.particles.push p 43 | 44 | onCollision: (p1, p2) => 45 | 46 | # Respond to collision. 47 | 48 | -------------------------------------------------------------------------------- /module files/coffeePhysics/demos/Demo.coffee: -------------------------------------------------------------------------------- 1 | ### Demo ### 2 | class Demo 3 | 4 | @COLOURS = ['DC0048', 'F14646', '4AE6A9', '7CFF3F', '4EC9D9', 'E4272E'] 5 | 6 | constructor: -> 7 | 8 | @physics = new Physics() 9 | @mouse = new Particle() 10 | @mouse.fixed = true 11 | @height = window.innerHeight 12 | @width = window.innerWidth 13 | 14 | @renderTime = 0 15 | @counter = 0 16 | 17 | setup: (full = yes) -> 18 | 19 | ### Override and add paticles / springs here ### 20 | 21 | ### Initialise the demo (override). ### 22 | init: (@container, @renderer = new WebGLRenderer()) -> 23 | 24 | # Build the scene. 25 | @setup renderer.gl? 26 | 27 | # Give the particles random colours. 28 | for particle in @physics.particles 29 | particle.colour ?= Random.item Demo.COLOURS 30 | 31 | # Add event handlers. 32 | document.addEventListener 'touchmove', @mousemove, false 33 | document.addEventListener 'mousemove', @mousemove, false 34 | document.addEventListener 'resize', @resize, false 35 | 36 | # Add to render output to the DOM. 37 | @container.appendChild @renderer.domElement 38 | 39 | # Prepare the renderer. 40 | @renderer.mouse = @mouse 41 | @renderer.init @physics 42 | 43 | # Resize for the sake of the renderer. 44 | do @resize 45 | 46 | ### Handler for window resize event. ### 47 | resize: (event) => 48 | 49 | @width = window.innerWidth 50 | @height = window.innerHeight 51 | @renderer.setSize @width, @height 52 | 53 | ### Update loop. ### 54 | step: -> 55 | 56 | #console.profile 'physics' 57 | 58 | # Step physics. 59 | do @physics.step 60 | 61 | #console.profileEnd() 62 | 63 | #console.profile 'render' 64 | 65 | # Render. 66 | 67 | # Render every frame for WebGL, or every 3 frames for canvas. 68 | @renderer.render @physics if @renderer.gl? or ++@counter % 3 is 0 69 | 70 | #console.profileEnd() 71 | 72 | ### Clean up after yourself. ### 73 | destroy: -> 74 | 75 | ## console.log @, 'destroy' 76 | 77 | # Remove event handlers. 78 | document.removeEventListener 'touchmove', @mousemove, false 79 | document.removeEventListener 'mousemove', @mousemove, false 80 | document.removeEventListener 'resize', @resize, false 81 | 82 | # Remove the render output from the DOM. 83 | try container.removeChild @renderer.domElement 84 | catch error 85 | 86 | do @renderer.destroy 87 | do @physics.destroy 88 | 89 | @renderer = null 90 | @physics = null 91 | @mouse = null 92 | 93 | ### Handler for window mousemove event. ### 94 | mousemove: (event) => 95 | 96 | do event.preventDefault 97 | 98 | if event.touches and !!event.touches.length 99 | 100 | touch = event.touches[0] 101 | @mouse.pos.set touch.pageX, touch.pageY 102 | 103 | else 104 | 105 | @mouse.pos.set event.clientX, event.clientY 106 | -------------------------------------------------------------------------------- /module files/coffeePhysics/demos/renderer/CanvasRenderer.coffee: -------------------------------------------------------------------------------- 1 | ### Canvas Renderer ### 2 | class CanvasRenderer extends Renderer 3 | 4 | constructor: -> 5 | 6 | super 7 | 8 | @canvas = document.createElement 'canvas' 9 | @ctx = @canvas.getContext '2d' 10 | 11 | # Set the DOM element. 12 | @domElement = @canvas 13 | 14 | init: (physics) -> 15 | 16 | super physics 17 | 18 | render: (physics) -> 19 | 20 | super physics 21 | 22 | time = new Date().getTime() 23 | 24 | # Draw velocity. 25 | vel = new Vector() 26 | 27 | # Draw heading. 28 | dir = new Vector() 29 | 30 | # Clear canvas. 31 | @canvas.width = @canvas.width 32 | 33 | @ctx.globalCompositeOperation = 'lighter' 34 | @ctx.lineWidth = 1 35 | 36 | # Draw particles. 37 | if @renderParticles 38 | 39 | TWO_PI = Math.PI * 2 40 | 41 | for p in physics.particles 42 | 43 | @ctx.beginPath() 44 | @ctx.arc(p.pos.x, p.pos.y, p.radius, 0, TWO_PI, no) 45 | 46 | @ctx.fillStyle = '#' + (p.colour or 'FFFFFF') 47 | @ctx.fill() 48 | 49 | if @renderSprings 50 | 51 | @ctx.strokeStyle = 'rgba(255,255,255,0.1)' 52 | @ctx.beginPath() 53 | 54 | for s in physics.springs 55 | @ctx.moveTo(s.p1.pos.x, s.p1.pos.y) 56 | @ctx.lineTo(s.p2.pos.x, s.p2.pos.y) 57 | 58 | @ctx.stroke() 59 | 60 | if @renderMouse 61 | 62 | # Draw mouse. 63 | @ctx.fillStyle = 'rgba(255,255,255,0.1)' 64 | @ctx.beginPath() 65 | @ctx.arc(@mouse.pos.x, @mouse.pos.y, 20, 0, TWO_PI) 66 | @ctx.fill() 67 | 68 | @renderTime = new Date().getTime() - time 69 | 70 | setSize: (@width, @height) => 71 | 72 | super @width, @height 73 | 74 | @canvas.width = @width 75 | @canvas.height = @height 76 | -------------------------------------------------------------------------------- /module files/coffeePhysics/demos/renderer/DOMRenderer.coffee: -------------------------------------------------------------------------------- 1 | ### DOM Renderer ### 2 | ### 3 | 4 | Updating styles: 5 | 6 | Nodes 7 | 8 | ### 9 | class DOMRenderer extends Renderer 10 | 11 | constructor: -> 12 | 13 | super 14 | 15 | @useGPU = yes 16 | 17 | @domElement = document.createElement 'div' 18 | @canvas = document.createElement 'canvas' 19 | @ctx = @canvas.getContext '2d' 20 | 21 | @canvas.style.position = 'absolute' 22 | @canvas.style.left = 0 23 | @canvas.style.top = 0 24 | 25 | @domElement.style.pointerEvents = 'none' 26 | @domElement.appendChild @canvas 27 | 28 | init: (physics) -> 29 | 30 | super physics 31 | 32 | # Set up particle DOM elements 33 | for p in physics.particles 34 | 35 | el = document.createElement 'span' 36 | st = el.style 37 | 38 | st.backgroundColor = p.colour 39 | st.borderRadius = p.radius 40 | st.marginLeft = -p.radius 41 | st.marginTop = -p.radius 42 | st.position = 'absolute' 43 | st.display = 'block' 44 | st.opacity = 0.85 45 | st.height = p.radius * 2 46 | st.width = p.radius * 2 47 | 48 | @domElement.appendChild el 49 | p.domElement = el 50 | 51 | # Set up mouse DOM element 52 | el = document.createElement 'span' 53 | st = el.style 54 | mr = 20 55 | 56 | st.backgroundColor = '#ffffff' 57 | st.borderRadius = mr 58 | st.marginLeft = -mr 59 | st.marginTop = -mr 60 | st.position = 'absolute' 61 | st.display = 'block' 62 | st.opacity = 0.1 63 | st.height = mr * 2 64 | st.width = mr * 2 65 | 66 | @domElement.appendChild el 67 | @mouse.domElement = el 68 | 69 | render: (physics) -> 70 | 71 | super physics 72 | 73 | time = new Date().getTime() 74 | 75 | if @renderParticles 76 | 77 | for p in physics.particles 78 | 79 | if @useGPU 80 | 81 | p.domElement.style.WebkitTransform = """ 82 | translate3d(#{p.pos.x|0}px,#{p.pos.y|0}px,0px) 83 | """ 84 | else 85 | 86 | p.domElement.style.left = p.pos.x 87 | p.domElement.style.top = p.pos.y 88 | 89 | if @renderSprings 90 | 91 | @canvas.width = @canvas.width 92 | 93 | @ctx.strokeStyle = 'rgba(255,255,255,0.1)' 94 | @ctx.beginPath() 95 | 96 | for s in physics.springs 97 | @ctx.moveTo(s.p1.pos.x, s.p1.pos.y) 98 | @ctx.lineTo(s.p2.pos.x, s.p2.pos.y) 99 | 100 | @ctx.stroke() 101 | 102 | if @renderMouse 103 | 104 | if @useGPU 105 | 106 | @mouse.domElement.style.WebkitTransform = """ 107 | translate3d(#{@mouse.pos.x|0}px,#{@mouse.pos.y|0}px,0px) 108 | """ 109 | else 110 | 111 | @mouse.domElement.style.left = @mouse.pos.x 112 | @mouse.domElement.style.top = @mouse.pos.y 113 | 114 | @renderTime = new Date().getTime() - time 115 | 116 | setSize: (@width, @height) => 117 | 118 | super @width, @height 119 | 120 | @canvas.width = @width 121 | @canvas.height = @height 122 | 123 | destroy: -> 124 | 125 | while @domElement.hasChildNodes() 126 | @domElement.removeChild @domElement.lastChild 127 | -------------------------------------------------------------------------------- /module files/coffeePhysics/demos/renderer/Renderer.coffee: -------------------------------------------------------------------------------- 1 | ### Base Renderer ### 2 | class Renderer 3 | 4 | constructor: -> 5 | 6 | @width = 0 7 | @height = 0 8 | 9 | @renderParticles = true 10 | @renderSprings = true 11 | @renderMouse = true 12 | @initialized = false 13 | @renderTime = 0 14 | 15 | init: (physics) -> 16 | 17 | @initialized = true 18 | 19 | render: (physics) -> 20 | 21 | if not @initialized then @init physics 22 | 23 | setSize: (@width, @height) => 24 | 25 | destroy: -> 26 | 27 | 28 | -------------------------------------------------------------------------------- /module files/coffeePhysics/demos/renderer/WebGLRenderer.coffee: -------------------------------------------------------------------------------- 1 | ### WebGL Renderer ### 2 | 3 | class WebGLRenderer extends Renderer 4 | 5 | # Particle vertex shader source. 6 | @PARTICLE_VS = ''' 7 | 8 | uniform vec2 viewport; 9 | attribute vec3 position; 10 | attribute float radius; 11 | attribute vec4 colour; 12 | varying vec4 tint; 13 | 14 | void main() { 15 | 16 | // convert the rectangle from pixels to 0.0 to 1.0 17 | vec2 zeroToOne = position.xy / viewport; 18 | zeroToOne.y = 1.0 - zeroToOne.y; 19 | 20 | // convert from 0->1 to 0->2 21 | vec2 zeroToTwo = zeroToOne * 2.0; 22 | 23 | // convert from 0->2 to -1->+1 (clipspace) 24 | vec2 clipSpace = zeroToTwo - 1.0; 25 | 26 | tint = colour; 27 | 28 | gl_Position = vec4(clipSpace, 0, 1); 29 | gl_PointSize = radius * 2.0; 30 | } 31 | ''' 32 | 33 | # Particle fragent shader source. 34 | @PARTICLE_FS = ''' 35 | 36 | precision mediump float; 37 | 38 | uniform sampler2D texture; 39 | varying vec4 tint; 40 | 41 | void main() { 42 | gl_FragColor = texture2D(texture, gl_PointCoord) * tint; 43 | } 44 | ''' 45 | 46 | # Spring vertex shader source. 47 | @SPRING_VS = ''' 48 | 49 | uniform vec2 viewport; 50 | attribute vec3 position; 51 | 52 | void main() { 53 | 54 | // convert the rectangle from pixels to 0.0 to 1.0 55 | vec2 zeroToOne = position.xy / viewport; 56 | zeroToOne.y = 1.0 - zeroToOne.y; 57 | 58 | // convert from 0->1 to 0->2 59 | vec2 zeroToTwo = zeroToOne * 2.0; 60 | 61 | // convert from 0->2 to -1->+1 (clipspace) 62 | vec2 clipSpace = zeroToTwo - 1.0; 63 | 64 | gl_Position = vec4(clipSpace, 0, 1); 65 | } 66 | ''' 67 | 68 | # Spring fragent shader source. 69 | @SPRING_FS = ''' 70 | 71 | void main() { 72 | gl_FragColor = vec4(1.0, 1.0, 1.0, 0.1); 73 | } 74 | ''' 75 | 76 | constructor: (@usePointSprites = true) -> 77 | 78 | super 79 | 80 | @particlePositionBuffer = null 81 | @particleRadiusBuffer = null 82 | @particleColourBuffer = null 83 | @particleTexture = null 84 | @particleShader = null 85 | 86 | @springPositionBuffer = null 87 | @springShader = null 88 | 89 | @canvas = document.createElement 'canvas' 90 | 91 | # Init WebGL. 92 | try @gl = @canvas.getContext 'experimental-webgl' catch error 93 | finally return new CanvasRenderer() if not @gl 94 | 95 | # Set the DOM element. 96 | @domElement = @canvas 97 | 98 | init: (physics) -> 99 | 100 | super physics 101 | 102 | @initShaders() 103 | @initBuffers physics 104 | 105 | # Create particle texture from canvas. 106 | @particleTexture = do @createParticleTextureData 107 | 108 | # Use additive blending. 109 | @gl.blendFunc @gl.SRC_ALPHA, @gl.ONE 110 | 111 | # Enable the other shit we need from WebGL. 112 | #@gl.enable @gl.VERTEX_PROGRAM_POINT_SIZE 113 | #@gl.enable @gl.TEXTURE_2D 114 | @gl.enable @gl.BLEND 115 | 116 | initShaders: -> 117 | 118 | # Create shaders. 119 | @particleShader = @createShaderProgram WebGLRenderer.PARTICLE_VS, WebGLRenderer.PARTICLE_FS 120 | @springShader = @createShaderProgram WebGLRenderer.SPRING_VS, WebGLRenderer.SPRING_FS 121 | 122 | # Store particle shader uniform locations. 123 | @particleShader.uniforms = 124 | viewport: @gl.getUniformLocation @particleShader, 'viewport' 125 | 126 | # Store spring shader uniform locations. 127 | @springShader.uniforms = 128 | viewport: @gl.getUniformLocation @springShader, 'viewport' 129 | 130 | # Store particle shader attribute locations. 131 | @particleShader.attributes = 132 | position: @gl.getAttribLocation @particleShader, 'position' 133 | radius: @gl.getAttribLocation @particleShader, 'radius' 134 | colour: @gl.getAttribLocation @particleShader, 'colour' 135 | 136 | # Store spring shader attribute locations. 137 | @springShader.attributes = 138 | position: @gl.getAttribLocation @springShader, 'position' 139 | 140 | console.log @particleShader 141 | 142 | initBuffers: (physics) -> 143 | 144 | colours = [] 145 | radii = [] 146 | 147 | # Create buffers. 148 | @particlePositionBuffer = do @gl.createBuffer 149 | @springPositionBuffer = do @gl.createBuffer 150 | @particleColourBuffer = do @gl.createBuffer 151 | @particleRadiusBuffer = do @gl.createBuffer 152 | 153 | # Create attribute arrays. 154 | for particle in physics.particles 155 | 156 | # Break the colour string into RGBA components. 157 | rgba = (particle.colour or '#FFFFFF').match(/[\dA-F]{2}/gi) 158 | 159 | # Parse into integers. 160 | r = (parseInt rgba[0], 16) or 255 161 | g = (parseInt rgba[1], 16) or 255 162 | b = (parseInt rgba[2], 16) or 255 163 | a = (parseInt rgba[3], 16) or 255 164 | 165 | # Prepare for adding to the colour buffer. 166 | colours.push r / 255, g / 255, b / 255, a / 255 167 | 168 | # Prepare for adding to the radius buffer. 169 | radii.push particle.radius or 32 170 | 171 | # Init Particle colour buffer. 172 | @gl.bindBuffer @gl.ARRAY_BUFFER, @particleColourBuffer 173 | @gl.bufferData @gl.ARRAY_BUFFER, new Float32Array(colours), @gl.STATIC_DRAW 174 | 175 | # Init Particle radius buffer. 176 | @gl.bindBuffer @gl.ARRAY_BUFFER, @particleRadiusBuffer 177 | @gl.bufferData @gl.ARRAY_BUFFER, new Float32Array(radii), @gl.STATIC_DRAW 178 | 179 | ## console.log @particleColourBuffer 180 | 181 | # Creates a generic texture for particles. 182 | createParticleTextureData: (size = 128) -> 183 | 184 | canvas = document.createElement 'canvas' 185 | canvas.width = canvas.height = size 186 | ctx = canvas.getContext '2d' 187 | rad = size * 0.5 188 | 189 | ctx.beginPath() 190 | ctx.arc rad, rad, rad, 0, Math.PI * 2, false 191 | ctx.closePath() 192 | 193 | ctx.fillStyle = '#FFF' 194 | ctx.fill() 195 | 196 | texture = @gl.createTexture() 197 | @setupTexture texture, canvas 198 | 199 | texture 200 | 201 | # Creates a WebGL texture from an image path or data. 202 | loadTexture: (source) -> 203 | 204 | texture = @gl.createTexture() 205 | texture.image = new Image() 206 | 207 | texture.image.onload = => 208 | 209 | @setupTexture texture, texture.image 210 | 211 | texture.image.src = source 212 | texture 213 | 214 | setupTexture: (texture, data) -> 215 | 216 | @gl.bindTexture @gl.TEXTURE_2D, texture 217 | @gl.texImage2D @gl.TEXTURE_2D, 0, @gl.RGBA, @gl.RGBA, @gl.UNSIGNED_BYTE, data 218 | @gl.texParameteri @gl.TEXTURE_2D, @gl.TEXTURE_MIN_FILTER, @gl.LINEAR 219 | @gl.texParameteri @gl.TEXTURE_2D, @gl.TEXTURE_MAG_FILTER, @gl.LINEAR 220 | @gl.texParameteri @gl.TEXTURE_2D, @gl.TEXTURE_WRAP_S, @gl.CLAMP_TO_EDGE 221 | @gl.texParameteri @gl.TEXTURE_2D, @gl.TEXTURE_WRAP_T, @gl.CLAMP_TO_EDGE 222 | @gl.generateMipmap @gl.TEXTURE_2D 223 | @gl.bindTexture @gl.TEXTURE_2D, null 224 | 225 | texture 226 | 227 | # Creates a shader program from vertex and fragment shader sources. 228 | createShaderProgram: (_vs, _fs) -> 229 | 230 | vs = @gl.createShader @gl.VERTEX_SHADER 231 | fs = @gl.createShader @gl.FRAGMENT_SHADER 232 | 233 | @gl.shaderSource vs, _vs 234 | @gl.shaderSource fs, _fs 235 | 236 | @gl.compileShader vs 237 | @gl.compileShader fs 238 | 239 | if not @gl.getShaderParameter vs, @gl.COMPILE_STATUS 240 | alert @gl.getShaderInfoLog vs 241 | null 242 | 243 | if not @gl.getShaderParameter fs, @gl.COMPILE_STATUS 244 | alert @gl.getShaderInfoLog fs 245 | null 246 | 247 | prog = do @gl.createProgram 248 | 249 | @gl.attachShader prog, vs 250 | @gl.attachShader prog, fs 251 | @gl.linkProgram prog 252 | 253 | ## console.log 'Vertex Shader Compiled', @gl.getShaderParameter vs, @gl.COMPILE_STATUS 254 | ## console.log 'Fragment Shader Compiled', @gl.getShaderParameter fs, @gl.COMPILE_STATUS 255 | ## console.log 'Program Linked', @gl.getProgramParameter prog, @gl.LINK_STATUS 256 | 257 | prog 258 | 259 | # Sets the size of the viewport. 260 | setSize: (@width, @height) => 261 | 262 | ## console.log 'resize', @width, @height 263 | 264 | super @width, @height 265 | 266 | @canvas.width = @width 267 | @canvas.height = @height 268 | @gl.viewport 0, 0, @width, @height 269 | 270 | # Update shader uniforms. 271 | @gl.useProgram @particleShader 272 | @gl.uniform2fv @particleShader.uniforms.viewport, new Float32Array [@width, @height] 273 | 274 | # Update shader uniforms. 275 | @gl.useProgram @springShader 276 | @gl.uniform2fv @springShader.uniforms.viewport, new Float32Array [@width, @height] 277 | 278 | # Renders the current physics state. 279 | render: (physics) -> 280 | 281 | super 282 | 283 | # Clear the viewport. 284 | @gl.clear @gl.COLOR_BUFFER_BIT | @gl.DEPTH_BUFFER_BIT 285 | 286 | # Draw particles. 287 | if @renderParticles 288 | 289 | vertices = [] 290 | 291 | # Update particle positions. 292 | for p in physics.particles 293 | vertices.push p.pos.x, p.pos.y, 0.0 294 | 295 | # Bind the particle texture. 296 | @gl.activeTexture @gl.TEXTURE0 297 | @gl.bindTexture @gl.TEXTURE_2D, @particleTexture 298 | 299 | # Use the particle program. 300 | @gl.useProgram @particleShader 301 | 302 | # Setup vertices. 303 | @gl.bindBuffer @gl.ARRAY_BUFFER, @particlePositionBuffer 304 | @gl.bufferData @gl.ARRAY_BUFFER, new Float32Array(vertices), @gl.STATIC_DRAW 305 | @gl.vertexAttribPointer @particleShader.attributes.position, 3, @gl.FLOAT, false, 0, 0 306 | @gl.enableVertexAttribArray @particleShader.attributes.position 307 | 308 | # Setup colours. 309 | @gl.bindBuffer @gl.ARRAY_BUFFER, @particleColourBuffer 310 | @gl.enableVertexAttribArray @particleShader.attributes.colour 311 | @gl.vertexAttribPointer @particleShader.attributes.colour, 4, @gl.FLOAT, false, 0, 0 312 | 313 | # Setup radii. 314 | @gl.bindBuffer @gl.ARRAY_BUFFER, @particleRadiusBuffer 315 | @gl.enableVertexAttribArray @particleShader.attributes.radius 316 | @gl.vertexAttribPointer @particleShader.attributes.radius, 1, @gl.FLOAT, false, 0, 0 317 | 318 | # Draw particles. 319 | @gl.drawArrays @gl.POINTS, 0, vertices.length / 3 320 | 321 | # Draw springs. 322 | if @renderSprings and physics.springs.length > 0 323 | 324 | vertices = [] 325 | 326 | # Update spring positions. 327 | for s in physics.springs 328 | vertices.push s.p1.pos.x, s.p1.pos.y, 0.0 329 | vertices.push s.p2.pos.x, s.p2.pos.y, 0.0 330 | 331 | # Use the spring program. 332 | @gl.useProgram @springShader 333 | 334 | # Setup vertices. 335 | @gl.bindBuffer @gl.ARRAY_BUFFER, @springPositionBuffer 336 | @gl.bufferData @gl.ARRAY_BUFFER, new Float32Array(vertices), @gl.STATIC_DRAW 337 | @gl.vertexAttribPointer @springShader.attributes.position, 3, @gl.FLOAT, false, 0, 0 338 | @gl.enableVertexAttribArray @springShader.attributes.position 339 | 340 | # Draw springs. 341 | @gl.drawArrays @gl.LINES, 0, vertices.length / 3 342 | 343 | destroy: -> 344 | 345 | ## console.log 'Destroy' 346 | -------------------------------------------------------------------------------- /module files/coffeePhysics/engine/Particle.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Vector} = require 'coffeePhysics/math/Vector' 3 | 4 | ### Particle ### 5 | 6 | class exports.Particle 7 | 8 | @GUID = 0 9 | 10 | constructor: (@mass = 1.0) -> 11 | 12 | # Set a unique id. 13 | @id = 'p' + Particle.GUID++ 14 | 15 | # Set initial mass. 16 | @setMass @mass 17 | 18 | # Set initial radius. 19 | @setRadius 1.0 20 | 21 | # Apply forces. 22 | @fixed = false 23 | 24 | # Behaviours to be applied. 25 | @behaviours = [] 26 | 27 | # Current position. 28 | @pos = new Vector() 29 | 30 | # Current velocity. 31 | @vel = new Vector() 32 | 33 | # Current force. 34 | @acc = new Vector() 35 | 36 | # Previous state. 37 | @old = 38 | pos: new Vector() 39 | vel: new Vector() 40 | acc: new Vector() 41 | 42 | ### Moves the particle to a given location vector. ### 43 | moveTo: (pos) -> 44 | 45 | @pos.copy pos 46 | @old.pos.copy pos 47 | 48 | ### Sets the mass of the particle. ### 49 | setMass: (@mass = 1.0) -> 50 | 51 | # The inverse mass. 52 | @massInv = 1.0 / @mass 53 | 54 | ### Sets the radius of the particle. ### 55 | setRadius: (@radius = 1.0) -> 56 | 57 | @radiusSq = @radius * @radius 58 | 59 | ### Applies all behaviours to derive new force. ### 60 | update: (dt, index) -> 61 | 62 | # Apply all behaviours. 63 | 64 | if not @fixed 65 | 66 | for behaviour in @behaviours 67 | 68 | behaviour.apply @, dt, index 69 | -------------------------------------------------------------------------------- /module files/coffeePhysics/engine/Physics.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Euler} = require 'coffeePhysics/engine/integrator/Euler' 3 | 4 | ### Physics Engine ### 5 | 6 | class exports.Physics 7 | 8 | constructor: (@integrator = new Euler()) -> 9 | 10 | # Fixed timestep. 11 | @timestep = 1.0 / 60 12 | 13 | # Friction within the system. 14 | @viscosity = 0.005 15 | 16 | # Global behaviours. 17 | @behaviours = [] 18 | 19 | # Time in seconds. 20 | @_time = 0.0 21 | 22 | # Last step duration. 23 | @_step = 0.0 24 | 25 | # Current time. 26 | @_clock = null 27 | 28 | # Time buffer. 29 | @_buffer = 0.0 30 | 31 | # Max iterations per step. 32 | @_maxSteps = 4 33 | 34 | # Particles in system. 35 | @particles = [] 36 | 37 | # Springs in system. 38 | @springs = [] 39 | 40 | ### Performs a numerical integration step. ### 41 | integrate: (dt) -> 42 | 43 | # Drag is inversely proportional to viscosity. 44 | drag = 1.0 - @viscosity 45 | 46 | # Update particles / apply behaviours. 47 | 48 | for particle, index in @particles 49 | 50 | for behaviour in @behaviours 51 | 52 | behaviour.apply particle, dt, index 53 | 54 | particle.update dt, index 55 | 56 | # Integrate motion. 57 | 58 | @integrator.integrate @particles, dt, drag 59 | 60 | # Compute all springs. 61 | 62 | for spring in @springs 63 | 64 | spring.apply() 65 | 66 | ### Steps the system. ### 67 | step: -> 68 | 69 | # Initialise the clock on first step. 70 | @_clock ?= new Date().getTime() 71 | 72 | # Compute delta time since last step. 73 | time = new Date().getTime() 74 | delta = time - @_clock 75 | 76 | # No sufficient change. 77 | return if delta <= 0.0 78 | 79 | # Convert time to seconds. 80 | delta *= 0.001 81 | 82 | # Update the clock. 83 | @_clock = time 84 | 85 | # Increment time buffer. 86 | @_buffer += delta 87 | 88 | # Integrate until the buffer is empty or until the 89 | # maximum amount of iterations per step is reached. 90 | 91 | i = 0 92 | 93 | while @_buffer >= @timestep and ++i < @_maxSteps 94 | 95 | # Integrate motion by fixed timestep. 96 | @integrate @timestep 97 | 98 | # Reduce buffer by one timestep. 99 | @_buffer -= @timestep 100 | 101 | # Increment running time. 102 | @_time += @timestep 103 | 104 | # Store step time for debugging. 105 | @_step = new Date().getTime() - time 106 | 107 | ### Clean up after yourself. ### 108 | destroy: -> 109 | 110 | @integrator = null 111 | @particles = null 112 | @springs = null 113 | -------------------------------------------------------------------------------- /module files/coffeePhysics/engine/Spring.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Vector} = require 'coffeePhysics/math/Vector' 3 | 4 | ### Spring ### 5 | 6 | class exports.Spring 7 | 8 | constructor: (@p1, @p2, @restLength = 100, @stiffness = 1.0) -> 9 | 10 | @_delta = new Vector() 11 | 12 | # F = -kx 13 | 14 | apply: -> 15 | 16 | (@_delta.copy @p2.pos).sub @p1.pos 17 | 18 | dist = @_delta.mag() + 0.000001 19 | force = (dist - @restLength) / (dist * (@p1.massInv + @p2.massInv)) * @stiffness 20 | 21 | if not @p1.fixed 22 | 23 | @p1.pos.add (@_delta.clone().scale force * @p1.massInv) 24 | 25 | if not @p2.fixed 26 | 27 | @p2.pos.add (@_delta.scale -force * @p2.massInv) 28 | -------------------------------------------------------------------------------- /module files/coffeePhysics/engine/integrator/Euler.coffee: -------------------------------------------------------------------------------- 1 | ### Import Integrator ### 2 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 3 | 4 | ### Euler Integrator ### 5 | class exports.Euler extends Integrator 6 | 7 | # v += a * dt 8 | # x += v * dt 9 | 10 | integrate: (particles, dt, drag) -> 11 | 12 | vel = new Vector() 13 | 14 | for p in particles when not p.fixed 15 | 16 | # Store previous location. 17 | p.old.pos.copy p.pos 18 | 19 | # Scale force to mass. 20 | p.acc.scale p.massInv 21 | 22 | # Duplicate velocity to preserve momentum. 23 | vel.copy p.vel 24 | 25 | # Add force to velocity. 26 | p.vel.add p.acc.scale dt 27 | 28 | # Add velocity to position. 29 | p.pos.add vel.scale dt 30 | 31 | # Apply friction. 32 | if drag then p.vel.scale drag 33 | 34 | # Reset forces. 35 | p.acc.clear() 36 | -------------------------------------------------------------------------------- /module files/coffeePhysics/engine/integrator/ImprovedEuler.coffee: -------------------------------------------------------------------------------- 1 | ### Import Integrator ### 2 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 3 | 4 | ### Improved Euler Integrator ### 5 | 6 | class exports.ImprovedEuler extends Integrator 7 | 8 | # x += (v * dt) + (a * 0.5 * dt * dt) 9 | # v += a * dt 10 | 11 | integrate: (particles, dt, drag) -> 12 | 13 | acc = new Vector() 14 | vel = new Vector() 15 | 16 | dtSq = dt * dt 17 | 18 | for p in particles when not p.fixed 19 | 20 | # Store previous location. 21 | p.old.pos.copy p.pos 22 | 23 | # Scale force to mass. 24 | p.acc.scale p.massInv 25 | 26 | # Duplicate velocity to preserve momentum. 27 | vel.copy p.vel 28 | 29 | # Duplicate force. 30 | acc.copy p.acc 31 | 32 | # Update position. 33 | p.pos.add (vel.scale dt).add (acc.scale 0.5 * dtSq) 34 | 35 | # Update velocity. 36 | p.vel.add p.acc.scale dt 37 | 38 | # Apply friction. 39 | if drag then p.vel.scale drag 40 | 41 | # Reset forces. 42 | p.acc.clear() 43 | -------------------------------------------------------------------------------- /module files/coffeePhysics/engine/integrator/Integrator.coffee: -------------------------------------------------------------------------------- 1 | ### Integrator ### 2 | 3 | class exports.Integrator 4 | 5 | integrate: (particles, dt) -> 6 | 7 | # Override. 8 | -------------------------------------------------------------------------------- /module files/coffeePhysics/engine/integrator/Verlet.coffee: -------------------------------------------------------------------------------- 1 | ### Imports ### 2 | {Integrator} = require 'coffeePhysics/engine/integrator/Integrator' 3 | {Vector} = require 'coffeePhysics/math/Vector' 4 | 5 | 6 | ### Velocity Verlet Integrator ### 7 | 8 | class exports.Verlet extends Integrator 9 | 10 | # v = x - ox 11 | # x = x + (v + a * dt * dt) 12 | 13 | integrate: (particles, dt, drag) -> 14 | 15 | pos = new Vector() 16 | 17 | dtSq = dt * dt 18 | 19 | for p in particles when not p.fixed 20 | 21 | # Scale force to mass. 22 | p.acc.scale p.massInv 23 | 24 | # Derive velocity. 25 | (p.vel.copy p.pos).sub p.old.pos 26 | 27 | # Apply friction. 28 | if drag then p.vel.scale drag 29 | 30 | # Apply forces to new position. 31 | (pos.copy p.pos).add (p.vel.add p.acc.scale dtSq) 32 | 33 | # Store old position. 34 | p.old.pos.copy p.pos 35 | 36 | # update position. 37 | p.pos.copy pos 38 | 39 | # Reset forces. 40 | p.acc.clear() 41 | -------------------------------------------------------------------------------- /module files/coffeePhysics/math/Random.coffee: -------------------------------------------------------------------------------- 1 | ### Random ### 2 | 3 | exports.Random = (min, max) -> 4 | 5 | if not max? 6 | max = min 7 | min = 0 8 | 9 | min + Math.random() * (max - min) 10 | 11 | Random.int = (min, max) -> 12 | 13 | if not max? 14 | max = min 15 | min = 0 16 | 17 | Math.floor min + Math.random() * (max - min) 18 | 19 | Random.sign = (prob = 0.5) -> 20 | 21 | if do Math.random < prob then 1 else -1 22 | 23 | Random.bool = (prob = 0.5) -> 24 | 25 | do Math.random < prob 26 | 27 | Random.item = (list) -> 28 | 29 | list[ Math.floor Math.random() * list.length ] 30 | -------------------------------------------------------------------------------- /module files/coffeePhysics/math/Vector.coffee: -------------------------------------------------------------------------------- 1 | ### 2D Vector ### 2 | 3 | class exports.Vector 4 | 5 | ### Adds two vectors and returns the product. ### 6 | @add: (v1, v2) -> 7 | new Vector v1.x + v2.x, v1.y + v2.y 8 | 9 | ### Subtracts v2 from v1 and returns the product. ### 10 | @sub: (v1, v2) -> 11 | new Vector v1.x - v2.x, v1.y - v2.y 12 | 13 | ### Projects one vector (v1) onto another (v2) ### 14 | @project: (v1, v2) -> 15 | v1.clone().scale ((v1.dot v2) / v1.magSq()) 16 | 17 | ### Creates a new Vector instance. ### 18 | constructor: (@x = 0.0, @y = 0.0) -> 19 | 20 | ### Sets the components of this vector. ### 21 | set: (@x, @y) -> 22 | @ 23 | 24 | ### Add a vector to this one. ### 25 | add: (v) -> 26 | @x += v.x; @y += v.y; @ 27 | 28 | ### Subtracts a vector from this one. ### 29 | sub: (v) -> 30 | @x -= v.x; @y -= v.y; @ 31 | 32 | ### Scales this vector by a value. ### 33 | scale: (f) -> 34 | @x *= f; @y *= f; @ 35 | 36 | ### Computes the dot product between vectors. ### 37 | dot: (v) -> 38 | @x * v.x + @y * v.y 39 | 40 | ### Computes the cross product between vectors. ### 41 | cross: (v) -> 42 | (@x * v.y) - (@y * v.x) 43 | 44 | ### Computes the magnitude (length). ### 45 | mag: -> 46 | Math.sqrt @x*@x + @y*@y 47 | 48 | ### Computes the squared magnitude (length). ### 49 | magSq: -> 50 | @x*@x + @y*@y 51 | 52 | ### Computes the distance to another vector. ### 53 | dist: (v) -> 54 | dx = v.x - @x; dy = v.y - @y 55 | Math.sqrt dx*dx + dy*dy 56 | 57 | ### Computes the squared distance to another vector. ### 58 | distSq: (v) -> 59 | dx = v.x - @x; dy = v.y - @y 60 | dx*dx + dy*dy 61 | 62 | ### Normalises the vector, making it a unit vector (of length 1). ### 63 | norm: -> 64 | m = Math.sqrt @x*@x + @y*@y 65 | @x /= m 66 | @y /= m 67 | @ 68 | 69 | ### Limits the vector length to a given amount. ### 70 | limit: (l) -> 71 | mSq = @x*@x + @y*@y 72 | if mSq > l*l 73 | m = Math.sqrt mSq 74 | @x /= m; @y /= m 75 | @x *= l; @y *= l 76 | @ 77 | 78 | ### Copies components from another vector. ### 79 | copy: (v) -> 80 | @x = v.x; @y = v.y; @ 81 | 82 | ### Clones this vector to a new itentical one. ### 83 | clone: -> 84 | new Vector @x, @y 85 | 86 | ### Resets the vector to zero. ### 87 | clear: -> 88 | @x = 0.0; @y = 0.0; @ 89 | --------------------------------------------------------------------------------