Last active
January 2, 2016 11:19
-
-
Save gene-ressler/8295865 to your computer and use it in GitHub Desktop.
My first HTML 5 graphics program.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html><head> | |
<script> | |
function run(elt) { (function(ctx) { | |
var render = { | |
clear: function() { | |
ctx.fillStyle = 'white' | |
ctx.clearRect(0, 0, elt.width, elt.height) | |
}, | |
setAttributes: function(obj) { | |
ctx.strokeStyle = obj.color || 'black' | |
ctx.lineWidth = obj.lineWidth || 1 | |
}, | |
moveTo: function(p) { | |
ctx.moveTo(p.x, p.y) | |
}, | |
lineTo: function(p) { | |
ctx.lineTo(p.x, p.y) | |
}, | |
point: function(pt) { | |
if (pt == null) return | |
this.setAttributes(pt) | |
ctx.beginPath() | |
var h = 4 | |
ctx.moveTo(pt.x - h, pt.y - h) | |
ctx.lineTo(pt.x + h, pt.y + h) | |
ctx.moveTo(pt.x - h, pt.y + h) | |
ctx.lineTo(pt.x + h, pt.y - h) | |
ctx.stroke() | |
}, | |
segment: function(segment) { | |
this.setAttributes(segment) | |
ctx.beginPath() | |
this.moveTo(segment.p0) | |
this.lineTo(segment.p1) | |
ctx.stroke() | |
}, | |
circle: function(circle) { | |
this.setAttributes(circle) | |
ctx.beginPath() | |
ctx.arc(circle.center.x, circle.center.y, circle.radius, 0, 2 * Math.PI) | |
ctx.closePath() | |
ctx.stroke() | |
}, | |
polygon: function(polygon) { | |
this.setAttributes(polygon) | |
ctx.beginPath() | |
var pts = polygon.points | |
var size = pts.length | |
this.moveTo(pts[size - 1]) | |
for (var i = 0; i < size; ++i) | |
this.lineTo(pts[i]) | |
ctx.closePath() | |
ctx.stroke() | |
}, | |
camera: function(camera) { | |
var p0 = camera.position | |
this.circle({ color: 'red', lineWidth: 2, center: p0, radius: 8 }) | |
var rateMag = 8 | |
c = camera.direction.x | |
s = camera.direction.y | |
var p1 = { x: p0.x + rateMag * c, y: p0.y + rateMag * s } | |
this.segment({ color: 'gray', lineWidth: 1, p0: p0, p1: p1}) | |
p1 = { x: p0.x + rateMag * camera.rate * c, y: p0.y + rateMag * camera.rate * s } | |
this.segment({ color: 'red', lineWidth: 2, p0: p0, p1: p1}) | |
} | |
} | |
var lib = { | |
// Find parameters of intersection of segments a->b and c->d or null for parallel lines | |
intersectionParams: function(a, b, c, d) { | |
var dx_ab = b.x - a.x | |
var dy_ab = b.y - a.y | |
var dx_dc = c.x - d.x | |
var dy_dc = c.y - d.y | |
var eps = 0.00001 | |
var det = dx_ab * dy_dc - dx_dc * dy_ab | |
if (-eps < det && det < eps) return null | |
var dx_ac = c.x - a.x | |
var dy_ac = c.y - a.y | |
var t_ab = (dx_ac * dy_dc - dx_dc * dy_ac) / det | |
var t_cd = (dx_ab * dy_ac - dx_ac * dy_ab) / det | |
return { t_ab: t_ab, t_cd: t_cd } | |
}, | |
// Determine if intersection parameters represent an intersection. | |
// This always counts parallel lines as non-intersecting even if they're touching. | |
areIntersecting: function(ip) { | |
return ip != null && 0 <= ip.t_ab && ip.t_ab <= 1 && 0 <= ip.t_cd && ip.t_cd <= 1 | |
}, | |
// Find the intersection from its parameters and two points. | |
intersection: function(ip, a, b) { | |
return { x: a.x + ip.t_ab * (b.x - a.x), y: a.y + ip.t_ab * (b.y - a.y) } | |
} | |
} | |
var boundary = { | |
color: 'blue', | |
lineWidth: 3, | |
points: [ | |
{ x: 180, y: 120}, | |
{ x: 240, y: 60}, | |
{ x: 360, y: 40}, | |
{ x: 420, y: 120}, | |
{ x: 360, y: 220}, | |
{ x: 350, y: 240}, | |
{ x: 360, y: 265}, | |
{ x: 470, y: 360}, | |
{ x: 450, y: 480}, | |
{ x: 360, y: 540}, | |
{ x: 240, y: 550}, | |
{ x: 140, y: 480}, | |
{ x: 120, y: 470}, | |
{ x: 100, y: 360}, | |
{ x: 120, y: 300}, | |
{ x: 220, y: 240}, | |
{ x: 240, y: 220} | |
] | |
} | |
var newCamera = function (p0) { | |
return { | |
lastPosition: { x: p0.x, y: p0.y }, | |
position: { x: p0.x, y: p0.y }, | |
direction: { x: 1, y: 0 }, | |
ray: { x: p0.x + 1, y: p0.y + 0 }, | |
azimuth: 0, | |
rate: 0, | |
maxRate: 5, | |
updateRay: function() { | |
this.ray.x = this.position.x + this.direction.x | |
this.ray.y = this.position.y + this.direction.y | |
}, | |
step: function() { | |
this.lastPosition.x = this.position.x | |
this.lastPosition.y = this.position.y | |
this.position.x += this.rate * this.direction.x | |
this.position.y += this.rate * this.direction.y | |
this.updateRay() | |
}, | |
stepBack: function() { | |
this.position.x = this.lastPosition.x | |
this.position.y = this.lastPosition.y | |
this.updateRay() | |
}, | |
turn: function(delta) { | |
this.azimuth += delta | |
while (this.azimuth <= -Math.PI) this.azimuth += 2 * Math.PI | |
while (this.azimuth > Math.PI) this.azimuth -= 2 * Math.PI | |
this.direction.x = Math.cos(this.azimuth) | |
this.direction.y = Math.sin(this.azimuth) | |
this.updateRay() | |
}, | |
accelerate: function(delta) { | |
this.rate += delta | |
if (this.rate > this.maxRate) this.rate = this.maxRate | |
if (this.rate < 0) this.rate = 0 | |
}, | |
stop: function() { | |
this.rate = 0 | |
}, | |
reset: function() { | |
this.lastPosition.x = this.position.x = p0.x | |
this.lastPosition.y = this.position.y = p0.y | |
}, | |
checkCollision: function(boundary) { | |
var pts = boundary.points | |
var size = pts.length | |
var j = size - 1 | |
for (var i = 0; i < size; j = i++) { | |
var ip = lib.intersectionParams(this.lastPosition, this.position, pts[j], pts[i]) | |
if (lib.areIntersecting(ip)) | |
this.stepBack(); | |
} | |
}, | |
getHit: function(boundary) { | |
var pts = boundary.points | |
var size = pts.length | |
var j = size - 1 | |
var ipNear = null | |
for (var i = 0; i < size; j = i++) { | |
var ip = lib.intersectionParams(this.position, this.ray, pts[j], pts[i]) | |
if (ip != null && | |
0 <= ip.t_cd && ip.t_cd <= 1 && | |
0 <= ip.t_ab && (ipNear == null || ip.t_ab < ipNear.t_ab)) | |
ipNear = ip | |
} | |
return ipNear == null ? ipNear : lib.intersection(ipNear, this.position, this.ray) | |
} | |
} | |
}; | |
var accelerationDelta = 0.2 | |
var turnDelta = 2 * Math.PI * 0.01 | |
var setKeyHandlers = function(state) { | |
down = {} | |
document.onkeydown = function(e) { | |
var code = (e || window.event).keyCode | |
// Ignore key repeat | |
if (down[code]) return | |
down[code] = true | |
switch (code) { | |
case 37: // left | |
state.turn -= turnDelta | |
break | |
case 38: // up | |
state.accelerate += accelerationDelta | |
break | |
case 39: // right | |
state.turn += turnDelta | |
break | |
case 40: // down | |
state.accelerate -= accelerationDelta | |
break | |
} | |
} | |
document.onkeyup = function(e) { | |
var code = (e || window.event).keyCode | |
down[code] = false | |
switch (code) { | |
case 37: // left | |
state.turn += turnDelta | |
break | |
case 38: // up | |
state.accelerate -= accelerationDelta | |
break | |
case 39: // right | |
state.turn -= turnDelta | |
break | |
case 40: // down | |
state.accelerate += accelerationDelta | |
break | |
} | |
} | |
// Return a function that tests for stop key | |
return function() { | |
for (var code = 37; code <= 40; code++) | |
if (down[code]) return false | |
return down[32] | |
} | |
}; | |
// main | |
(function (camera) { | |
var keyState = { turn: 0, accelerate: 0 } | |
stop = setKeyHandlers(keyState) | |
var tick = function() { | |
if (stop()) | |
camera.stop() | |
else { | |
camera.accelerate(keyState.accelerate) | |
camera.turn(keyState.turn) | |
} | |
camera.step() | |
camera.checkCollision(boundary) | |
render.clear() | |
render.segment({ color: 'lightGray', p0: camera.position, p1: camera.getHit(boundary) }) | |
render.polygon(boundary) | |
render.camera(camera) | |
} | |
setInterval(tick, 33) | |
}(newCamera({ x: 300, y: 90 }))) | |
})(elt.getContext('2d'))} | |
</script> | |
<style> | |
#world { | |
width: 600px; | |
height: 600px; | |
} | |
</style> | |
</head> | |
<body onLoad="run(document.getElementById('world'))"> | |
<canvas id='world' height='600' width='600'/> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment