Skip to content

Commit df77d23

Browse files
committed
Add some examples
1 parent 33e2497 commit df77d23

File tree

5 files changed

+315
-1
lines changed

5 files changed

+315
-1
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Changelog
2+
3+
## 0.2 (Dec 8, 2013)
4+
5+
- Reformat comments so that they can be run through `docco` to create an annotated source file.
6+
- Fix/optimize compilation with the Closure Compiler in advanced mode (previously it was mangling some important properties)
7+
- Wrap the code in a UMD declaration so that it works:
8+
- Just inserting it as a `<script>`
9+
- Using an AMD loader
10+
- In Node.js
11+
- Add `rotate` method to `Vector` and `Polygon`.
12+
- Add `translate` method to `Polygon`
13+
- Add some examples (using Raphael.js for display)
14+
15+
## 0.1
16+
17+
Initial release

SAT.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//
55
// A simple library for determining intersections of circles and
66
// polygons using the Separating Axis Theorem.
7-
/** @preserve Copyright 2013 - Jim Riecken <jimr@jimr.ca> - released under the MIT License. */
7+
/** @preserve SAT.js - Version 0.2 - Copyright 2013 - Jim Riecken <jimr@jimr.ca> - released under the MIT License. https://github.com/jriecken/sat-js */
88

99
/*global define: false, module: false*/
1010
/*jshint shadow:true, sub:true, forin:true, noarg:true, noempty:true,

examples/dynamic_collision.html

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>SAT.js - Simple Collision Examples</title>
5+
<script src="../SAT.js"></script>
6+
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
7+
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js"></script>
8+
<script src="examples.js"></script>
9+
</head>
10+
<body style="margin: 50px">
11+
<h1>Dynamic Collision Examples</h1>
12+
13+
<h2>Dynamic response</h2>
14+
<p>Drag the shapes around. If they collide they will be moved so that they don't collide. The circle is "heavy" - it will not be moved by other items (but will move other items)</p>
15+
<div id="example1" style="border: 1px solid #CCC; width: 640px; height: 480px;"></div>
16+
<script>
17+
(function () {
18+
var canvas = Raphael('example1', 640, 480);
19+
var world = new World(canvas);
20+
var poly = world.addEntity(P(V(160,120), [V(0,0), V(60, 0), V(100, 40), V(60, 80), V(0, 80)]).translate(-50, -40), { solid: true, draggable: true });
21+
var poly2 = world.addEntity(P(V(10,10), [V(0,0), V(30, 0), V(30, 30), V(0, 30)]), { solid: true, draggable: true });
22+
var circle1 = world.addEntity(C(V(50, 200), 30), { solid: true, heavy: true, draggable: true });
23+
function doRotate() {
24+
poly.data.rotate(Math.PI / 60); // Assuming 60fps this will take ~2 seconds for a full rotation
25+
world.simulate();
26+
window.requestAnimationFrame(doRotate);
27+
}
28+
window.requestAnimationFrame(doRotate);
29+
}());
30+
</script>
31+
32+
<h2>Lots of circles, all collidable with each other.</h2>
33+
<p>Drag any of the circles and watch them react.</p>
34+
<div id="example2" style="border: 1px solid #CCC; width: 640px; height: 640px;"></div>
35+
<script>
36+
(function () {
37+
var canvas = Raphael('example2', 640, 640);
38+
var world = new World(canvas, {
39+
loopCount: 5
40+
});
41+
for (var i = 0; i < 16; i++) {
42+
for (var j = 0; j < 16; j++) {
43+
var displayAttrs = {
44+
fill: 'rgba(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ')'
45+
}
46+
var c = world.addEntity(C(V((40 * i) + 20, (40 * j) + 20), 18), { solid: true, draggable: true, displayAttrs: displayAttrs });
47+
}
48+
}
49+
}());
50+
</script>
51+
52+
</body>
53+
</html>

examples/examples.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// Alias a few things in SAT.js to make the code shorter
2+
var V = function (x, y) { return new SAT.Vector(x, y); };
3+
var P = function (pos, points) { return new SAT.Polygon(pos, points); };
4+
var C = function (pos, r) { return new SAT.Circle(pos, r); };
5+
var B = function (pos, w, h) { return new SAT.Box(pos, w, h); };
6+
7+
// Converts a SAT.Polygon into a SVG path string.
8+
function poly2path(polygon) {
9+
var pos = polygon.pos;
10+
var points = polygon.points;
11+
var result = 'M' + pos.x + ' ' + pos.y;
12+
result += 'M' + (pos.x + points[0].x) + ' ' + (pos.y + points[0].y);
13+
for (var i = 1; i < points.length; i++) {
14+
var point = points[i];
15+
result += 'L' + (pos.x + point.x) + ' ' + (pos.y + point.y);
16+
}
17+
result += 'Z';
18+
return result;
19+
}
20+
21+
// Create a Raphael start drag handler for specified entity
22+
function startDrag(entity) {
23+
return function () {
24+
this.ox = entity.data.pos.x;
25+
this.oy = entity.data.pos.y;
26+
};
27+
}
28+
// Create a Raphael move drag handler for specified entity
29+
function moveDrag(entity, world) {
30+
return function (dx, dy) {
31+
// This position updating is fairly naive - it lets objects tunnel through each other, but it suffices for these examples.
32+
entity.data.pos.x = this.ox + dx;
33+
entity.data.pos.y = this.oy + dy;
34+
world.simulate();
35+
};
36+
}
37+
// Create a Raphael end drag handler for specified entity
38+
function endDrag(entity) {
39+
return function () {
40+
entity.updateDisplay();
41+
};
42+
}
43+
44+
var idCounter = 0;
45+
46+
function noop() {}
47+
48+
function Entity(data, display, options) {
49+
options = _.defaults(options || {}, {
50+
solid: false, // Whether this object is "solid" and therefore should participate in responses.
51+
heavy: false, // Whether this object is "heavy" and can't be moved by other objects.
52+
displayAttrs: {}, // Raphael attrs to set on the display object
53+
onCollide: noop, // Function to execute when this entity collides with another - arguments are (otherEntity, response)
54+
onTick: noop // Function called at the start of every simulation tick - no arguments
55+
});
56+
this.id = idCounter++;
57+
this.data = data;
58+
this.display = display;
59+
this.displayAttrs = _.extend({
60+
fill: '#CCC',
61+
stroke: '#000'
62+
}, options.displayAttrs);
63+
this.isSolid = options.solid;
64+
this.isHeavy = options.heavy;
65+
this.onCollide = options.onCollide;
66+
this.onTick = options.onTick;
67+
}
68+
Entity.prototype = {
69+
remove: function () {
70+
this.display.remove();
71+
},
72+
// Call this to update the display after changing the underlying data.
73+
updateDisplay: function () {
74+
if (this.data instanceof SAT.Circle) {
75+
this.displayAttrs.cx = this.data.pos.x;
76+
this.displayAttrs.cy = this.data.pos.y;
77+
this.displayAttrs.r = this.data.r;
78+
} else {
79+
this.displayAttrs.path = poly2path(this.data);
80+
}
81+
this.display.attr(this.displayAttrs);
82+
},
83+
tick: function () {
84+
this.onTick();
85+
},
86+
respondToCollision: function (other, response) {
87+
this.onCollide(other, response);
88+
// Collisions between "ghostly" objects don't matter, and
89+
// two "heavy" objects will just remain where they are.
90+
if (this.isSolid && other.isSolid &&
91+
!(this.isHeavy && other.isHeavy)) {
92+
if (this.isHeavy) {
93+
// Move the other object out of us
94+
other.data.pos.add(response.overlapV);
95+
} else if (other.isHeavy) {
96+
// Move us out of the other object
97+
this.data.pos.sub(response.overlapV);
98+
} else {
99+
// Move equally out of each other
100+
response.overlapV.scale(0.5);
101+
this.data.pos.sub(response.overlapV);
102+
other.data.pos.add(response.overlapV);
103+
}
104+
}
105+
}
106+
};
107+
108+
function World(canvas, options) {
109+
options = _.defaults(options || {}, {
110+
loopCount: 1 // number of loops to do each time simulation is called. The higher the more accurate the simulation, but slowers.
111+
});
112+
this.canvas = canvas; // A raphael.js canvas
113+
this.response = new SAT.Response(); // Response reused for collisions
114+
this.loopCount = options.loopCount;
115+
this.entities = {};
116+
}
117+
World.prototype = {
118+
addEntity: function(data, options) {
119+
var entity = new Entity(
120+
data,
121+
data instanceof SAT.Circle ? this.canvas.circle() : this.canvas.path(),
122+
options
123+
);
124+
// Make the display item draggable if requested.
125+
if (options.draggable) {
126+
entity.display.drag(moveDrag(entity, this), startDrag(entity), endDrag(entity));
127+
}
128+
entity.updateDisplay();
129+
this.entities[entity.id] = entity;
130+
return entity;
131+
},
132+
removeEntity: function (entity) {
133+
entity.remove();
134+
delete this.entities[entity.id];
135+
},
136+
simulate: function () {
137+
var entities = _.values(this.entities);
138+
var entitiesLen = entities.length;
139+
// Let the entity do something every simulation tick
140+
_.each(entities, function (entity) {
141+
entity.tick();
142+
});
143+
// Handle collisions - loop a configurable number of times to let things "settle"
144+
var loopCount = this.loopCount;
145+
for (var i = 0; i < loopCount; i++) {
146+
// Naively check for collision between all pairs of entities
147+
// E.g if there are 4 entities: (0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)
148+
for (var aCount = 0; aCount < entitiesLen; aCount++) {
149+
var a = entities[aCount];
150+
for (var bCount = aCount + 1; bCount < entitiesLen; bCount++) {
151+
var b = entities[bCount];
152+
this.response.clear();
153+
var collided;
154+
var aData = a.data;
155+
var bData = b.data;
156+
if (aData instanceof SAT.Circle) {
157+
if (bData instanceof SAT.Circle) {
158+
collided = SAT.testCircleCircle(aData, bData, this.response);
159+
} else {
160+
collided = SAT.testCirclePolygon(aData, bData, this.response);
161+
}
162+
} else {
163+
if (bData instanceof SAT.Circle) {
164+
collided = SAT.testPolygonCircle(aData, bData, this.response);
165+
} else {
166+
collided = SAT.testPolygonPolygon(aData, bData, this.response);
167+
}
168+
}
169+
if (collided) {
170+
a.respondToCollision(b, this.response);
171+
}
172+
}
173+
}
174+
}
175+
// Finally, update the display of each entity now that the simulation step is done.
176+
_.each(entities, function (entity) {
177+
entity.updateDisplay();
178+
});
179+
}
180+
};

examples/simple_collision.html

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>SAT.js - Simple Collision Examples</title>
5+
<script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min.js"></script>
6+
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js"></script>
7+
<script src="../SAT.js"></script>
8+
<script src="examples.js"></script>
9+
<script>
10+
// Simple onCollide handler that changes colors of displayed items.
11+
function displayCollision(other, response) {
12+
this.displayAttrs.fill = response.aInB ? '#0F0' : '#F00';
13+
other.displayAttrs.fill = response.bInA ? '#0F0' : '#F00';
14+
}
15+
// onTick handler that restores "non collided" color
16+
function restoreNoCollisionColors() {
17+
this.displayAttrs.fill = '#CCC';
18+
}
19+
// Default entity options that will just highlight entities when they overlap
20+
var defaultEntityOptions = {
21+
solid: false,
22+
draggable: true,
23+
onCollide: displayCollision,
24+
onTick: restoreNoCollisionColors
25+
};
26+
</script>
27+
</head>
28+
<body style="margin: 50px">
29+
<h1>Simple Collision Examples</h1>
30+
<p>Drag the shapes around. The shapes will turn red when they are colliding. The smaller shape will turn green if it is completely inside the larger one.</p>
31+
<h2>Circle-Circle collision</h2>
32+
<div id="example1" style="border: 1px solid #CCC; width: 320px; height: 240px;"></div>
33+
<script>
34+
(function () {
35+
var canvas = Raphael('example1', 320, 240);
36+
var world = new World(canvas);
37+
var circle1 = world.addEntity(C(V(160, 120), 30), defaultEntityOptions);
38+
var circle2 = world.addEntity(C(V(30,30), 10), defaultEntityOptions);
39+
}());
40+
</script>
41+
42+
<h2>Circle-Polygon Collision</h2>
43+
<div id="example2" style="border: 1px solid #CCC; width: 320px; height: 240px;"></div>
44+
<script>
45+
(function () {
46+
var canvas = Raphael('example2', 320, 240);
47+
var world = new World(canvas);
48+
var poly = world.addEntity(P(V(160,120), [V(0,0), V(60, 0), V(100, 40), V(60, 80), V(0, 80)]), defaultEntityOptions);
49+
var circle = world.addEntity(C(V(30,30), 20), defaultEntityOptions);
50+
}());
51+
</script>
52+
53+
<h2>Polygon-Polygon Collision</h2>
54+
<div id="example3" style="border: 1px solid #CCC; width: 320px; height: 240px;"></div>
55+
<script>
56+
(function () {
57+
var canvas = Raphael('example3', 320, 240);
58+
var world = new World(canvas);
59+
var poly = world.addEntity(P(V(160,120), [V(0,0), V(60, 0), V(100, 40), V(60, 80), V(0, 80)]), defaultEntityOptions);
60+
var poly2 = world.addEntity(P(V(10,10), [V(0,0), V(30, 0), V(30, 30), V(0, 30)]), defaultEntityOptions);
61+
}());
62+
</script>
63+
</body>
64+
</html>

0 commit comments

Comments
 (0)