Skip to content

Instantly share code, notes, and snippets.

@gene-ressler
Last active January 2, 2016 11:19
Show Gist options
  • Save gene-ressler/8295865 to your computer and use it in GitHub Desktop.
Save gene-ressler/8295865 to your computer and use it in GitHub Desktop.
My first HTML 5 graphics program.
<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