├── 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 | 
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 |
--------------------------------------------------------------------------------