Skip to content

Instantly share code, notes, and snippets.

@powerc9000
Last active August 29, 2015 13:56
Show Gist options
  • Save powerc9000/9127180 to your computer and use it in GitHub Desktop.
Save powerc9000/9127180 to your computer and use it in GitHub Desktop.
raycaster it's slow will speed it up
(function(window, undefined){
"use strict";
var headOn = (function(){
var vectorProto;
var headOn = {
groups: {},
_images: {},
fps: 50,
imagesLoaded: true,
gameTime: 0,
_update:"",
_render:"",
_ticks: 0,
randInt: function(min, max) {
return Math.floor(Math.random() * (max +1 - min)) + min;
},
randFloat: function(min, max) {
return Math.random() * (max - min) + min;
},
events: {
events: {},
listen: function(eventName, callback){
if(!this.events[eventName]){
this.events[eventName] = [];
}
this.events[eventName].push(callback);
},
trigger: function(eventName){
var args = [].splice.call(arguments, 1),
e = this.events[eventName],
l,
i;
if(e){
l = e.length;
for(i = 0; i < l; i++){
e[i].apply(headOn, args);
}
}
}
},
Camera: function(width, height, x, y, zoom){
this.width = width;
this.height = height;
x = x || 0;
y = y || 0;
this.position = new headOn.Vector(x, y);
this.dimensions = new headOn.Vector(width, height);
this.center = new headOn.Vector(x+width/2, y+height/2);
this.zoomAmt = zoom || 1;
return this;
},
animate: function(object,keyFrames,callback){
var that, interval, currentFrame = 0;
if(!object.animating){
object.animating = true;
object.image = keyFrames[0];
that = this;
interval = setInterval(function(){
if(keyFrames.length === currentFrame){
callback();
object.animating = false;
object.image = "";
clearInterval(interval);
}
else{
currentFrame += 1;
object.image = keyFrames[currentFrame];
}
},1000/this.fps);
}
},
update: function(cb){this._update = cb;},
render: function(cb){this._render = cb;},
entity: function(values, parent){
var i, o, base;
if (parent && typeof parent === "object") {
o = Object.create(parent);
}
else{
o = {};
}
for(i in values){
if(values.hasOwnProperty(i)){
o[i] = values[i];
}
}
return o;
},
inherit: function (base, sub) {
// Avoid instantiating the base class just to setup inheritance
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
// for a polyfill
sub.prototype = Object.create(base.prototype);
// Remember the constructor property was set wrong, let's fix it
sub.prototype.constructor = sub;
// In ECMAScript5+ (all modern browsers), you can make the constructor property
// non-enumerable if you define it like this instead
Object.defineProperty(sub.prototype, 'constructor', {
enumerable: false,
value: sub
});
},
extend: function(base, values){
var i;
for(i in values){
if(values.hasOwnProperty(i)){
base[i] = values[i];
}
}
},
distance: function(obj1, obj2){
return Math.sqrt(Math.pow(obj1.position.x - obj2.position.x, 2) + Math.pow(obj1.position.y - obj2.position.y, 2));
},
collides: function(poly1, poly2) {
var points1 = this.getPoints(poly1),
points2 = this.getPoints(poly2),
i = 0,
l = points1.length,
j, k = points2.length,
normal = {
x: 0,
y: 0
},
length,
min1, min2,
max1, max2,
interval,
MTV = null,
MTV2 = null,
MN = null,
dot,
nextPoint,
currentPoint;
if(poly1.type === "circle" && poly2.type ==="circle"){
return circleCircle(poly1, poly2);
}else if(poly1.type === "circle"){
return circleRect(poly1, poly2);
}else if(poly2.type === "circle"){
return circleRect(poly2, poly1);
}
//loop through the edges of Polygon 1
for (; i < l; i++) {
nextPoint = points1[(i == l - 1 ? 0 : i + 1)];
currentPoint = points1[i];
//generate the normal for the current edge
normal.x = -(nextPoint[1] - currentPoint[1]);
normal.y = (nextPoint[0] - currentPoint[0]);
//normalize the vector
length = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
normal.x /= length;
normal.y /= length;
//default min max
min1 = min2 = -1;
max1 = max2 = -1;
//project all vertices from poly1 onto axis
for (j = 0; j < l; ++j) {
dot = points1[j][0] * normal.x + points1[j][1] * normal.y;
if (dot > max1 || max1 === -1) max1 = dot;
if (dot < min1 || min1 === -1) min1 = dot;
}
//project all vertices from poly2 onto axis
for (j = 0; j < k; ++j) {
dot = points2[j][0] * normal.x + points2[j][1] * normal.y;
if (dot > max2 || max2 === -1) max2 = dot;
if (dot < min2 || min2 === -1) min2 = dot;
}
//calculate the minimum translation vector should be negative
if (min1 < min2) {
interval = min2 - max1;
normal.x = -normal.x;
normal.y = -normal.y;
} else {
interval = min1 - max2;
}
//exit early if positive
if (interval >= 0) {
return false;
}
if (MTV === null || interval > MTV) {
MTV = interval;
MN = {
x: normal.x,
y: normal.y
};
}
}
//loop through the edges of Polygon 2
for (i = 0; i < k; i++) {
nextPoint = points2[(i == k - 1 ? 0 : i + 1)];
currentPoint = points2[i];
//generate the normal for the current edge
normal.x = -(nextPoint[1] - currentPoint[1]);
normal.y = (nextPoint[0] - currentPoint[0]);
//normalize the vector
length = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
normal.x /= length;
normal.y /= length;
//default min max
min1 = min2 = -1;
max1 = max2 = -1;
//project all vertices from poly1 onto axis
for (j = 0; j < l; ++j) {
dot = points1[j][0] * normal.x + points1[j][1] * normal.y;
if (dot > max1 || max1 === -1) max1 = dot;
if (dot < min1 || min1 === -1) min1 = dot;
}
//project all vertices from poly2 onto axis
for (j = 0; j < k; ++j) {
dot = points2[j][0] * normal.x + points2[j][1] * normal.y;
if (dot > max2 || max2 === -1) max2 = dot;
if (dot < min2 || min2 === -1) min2 = dot;
}
//calculate the minimum translation vector should be negative
if (min1 < min2) {
interval = min2 - max1;
normal.x = -normal.x;
normal.y = -normal.y;
} else {
interval = min1 - max2;
}
//exit early if positive
if (interval >= 0) {
return false;
}
if (MTV === null || interval > MTV) MTV = interval;
if (interval > MTV2 || MTV2 === null) {
MTV2 = interval;
MN = {
x: normal.x,
y: normal.y
};
}
}
return {
overlap: MTV2,
normal: MN
};
function circleRect(circle, rect){
var newX = circle.position.x * Math.cos(-rect.angle);
var newY = circle.position.y * Math.sin(-rect.angle);
var circleDistance = {x:newX, y:newY};
var cornerDistance_sq;
circleDistance.x = Math.abs(circle.position.x - rect.position.x);
circleDistance.y = Math.abs(circle.position.y - rect.position.y);
if (circleDistance.x > (rect.width/2 + circle.radius)) { return false; }
if (circleDistance.y > (rect.height/2 + circle.radius)) { return false; }
if (circleDistance.x <= (rect.width/2)) { return true; }
if (circleDistance.y <= (rect.height/2)) { return true; }
cornerDistance_sq = Math.pow(circleDistance.x - rect.width/2,2) +
Math.pow(circleDistance.y - rect.height/2, 2);
return (cornerDistance_sq <= Math.pow(circle.radius,2));
}
function pointInCircle(point, circle){
return Math.pow(point.x - circle.position.x ,2) + Math.pow(point.y - circle.position.y, 2) < Math.pow(circle.radius,2);
}
function circleCircle(ob1, ob2){
return square(ob2.position.x - ob1.position.x) + square(ob2.position.y - ob1.position.y) <= square(ob1.radius + ob2.radius);
}
},
getPoints: function (obj){
if(obj.type === "circle"){
return [];
}
var x = obj.position.x,
y = obj.position.y,
width = obj.width,
height = obj.height,
angle = obj.angle,
that = this,
points = [];
points[0] = [x,y];
points[1] = [];
points[1].push(Math.sin(-angle) * height + x);
points[1].push(Math.cos(-angle) * height + y);
points[2] = [];
points[2].push(Math.cos(angle) * width + points[1][0]);
points[2].push(Math.sin(angle) * width + points[1][1]);
points[3] = [];
points[3].push(Math.cos(angle) * width + x);
points[3].push(Math.sin(angle) * width + y);
//console.log(points);
return points;
},
Timer: function(){
this.jobs = [];
},
pause: function(){
this.paused = true;
this.events.trigger("pause");
},
unpause: function(){
this.events.trigger("unpause");
this.paused = false;
},
isPaused: function(){
return this.paused;
},
group: function(groupName, entity){
if(this.groups[groupName]){
if(entity){
this.groups[groupName].push(entity);
}
}
else{
this.groups[groupName] = [];
if(entity){
this.groups[groupName].push(entity);
}
}
return this.groups[groupName];
},
loadImages: function(imageArray, imgCallback, allCallback){
var args, img, total, loaded, timeout, interval, that, cb, imgOnload;
that = this;
this.imagesLoaded = false;
total = imageArray.length;
if(!total){
this.imagesLoaded = true;
}
loaded = 0;
imgOnload = function(){
console.log("heyo");
loaded += 1;
imgCallback && imgCallback(image.name);
if(loaded === total){
allCallback && allCallback();
that.imagesLoaded = true;
}
};
imageArray.forEach(function(image){
img = new Image();
img.src = image.src;
img.onload = imgOnload;
that._images[image.name] = img;
});
},
images: function(image){
if(this._images[image]){
return this._images[image];
}
else{
return new Image();
}
},
onTick: function(then){
var now = Date.now(),
modifier = now - then;
this.trueFps = 1/(modifier/1000);
this._ticks+=1;
this._update(modifier, this._ticks);
this._render(modifier, this._ticks);
this.gameTime += modifier;
},
timeout: function(cb, time, scope){
setTimeout(function(){
cb.call(scope);
}, time);
},
interval: function(cb, time, scope){
return setInterval(function(){
cb.call(scope);
}, time);
},
canvas: function(name){
if(this === headOn){
return new this.canvas(name);
}
this.canvas = this.canvases[name];
this.width = this.canvas.width;
this.height = this.canvas.height;
return this;
},
Vector: function(x, y){
this.x = x;
this.y = y;
//return this;
},
run: function(){
var that = this;
var then = Date.now();
window.requestAnimationFrame(aniframe);
function aniframe(){
if(that.imagesLoaded){
that.onTick(then);
then = Date.now();
}
window.requestAnimationFrame(aniframe);
}
},
exception: function(message){
this.message = message;
this.name = "Head-on Exception";
this.toString = function(){
return this.name + ": " + this.message;
};
}
};
headOn.canvas.create = function(name, width, height, camera){
var canvas, ctx;
if(!camera || !(camera instanceof headOn.Camera)){
throw new headOn.exception("Canvas must be intialized with a camera");
}
canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
ctx = canvas.getContext("2d");
this.prototype.canvases[name] = {
canvas: canvas,
ctx: ctx,
width: canvas.width,
height: canvas.height,
camera: camera
};
return headOn.canvas(name);
};
headOn.canvas.prototype = {
canvases: {},
stroke: function(stroke){
var ctx = this.canvas.ctx;
ctx.save();
if(stroke){
ctx.lineWith = stroke.width;
ctx.strokeStyle = stroke.color;
ctx.stroke();
}
ctx.restore();
},
drawRect: function(width, height, x, y, color, stroke, rotation, center_of_rotation){
var ctx = this.canvas.ctx, mod = 1, camera = this.canvas.camera;
ctx.save();
ctx.beginPath();
if(rotation){
center_of_rotation = center_of_rotation || {x:0,y:0};
ctx.translate((x + center_of_rotation.x- camera.position.x)/camera.zoomAmt ,(y + center_of_rotation.y- camera.position.y)/camera.zoomAmt);
ctx.rotate(rotation);
ctx.rect(0 - center_of_rotation.x, 0 - center_of_rotation.y , width / camera.zoomAmt, height / camera.zoomAmt);
}
else{
//console.log(camera.position.x)
ctx.rect((x - camera.position.x)/camera.zoomAmt , (y - camera.position.y)/camera.zoomAmt , width / camera.zoomAmt, height / camera.zoomAmt);
}
if(color){
ctx.fillStyle = color;
}
ctx.fill();
if(typeof stroke === "object" && !isEmpty(stroke)){
this.stroke(stroke);
}
ctx.closePath();
ctx.restore();
return this;
},
drawCircle: function(x, y, radius, color, stroke){
var ctx = this.canvas.ctx, camera = this.canvas.camera;
ctx.save();
ctx.beginPath();
ctx.arc((x - camera.position.x)/camera.zoomAmt, (y - camera.position.y)/camera.zoomAmt, radius / camera.zoomAmt, 0, 2*Math.PI, false);
ctx.fillStyle = color || "black";
ctx.fill();
this.stroke(stroke);
ctx.restore();
ctx.closePath();
return this;
},
drawImage: function(image,x,y){
var ctx = this.canvas.ctx, camera = this.canvas.camera;
try{
ctx.drawImage(image,(x - camera.position.x)/camera.zoomAmt , (y - camera.position.y)/camera.zoomAmt , image.width / camera.zoomAmt, image.height / camera.zoomAmt);
}
catch(e){
console.log(image);
}
return this;
},
drawImageRotated: function(image, rotation, x,y){
var ctx = this.canvas.ctx;
var radians = rotation * Math.PI / 180;
ctx.save();
ctx.translate(x, y);
ctx.rotate(radians);
ctx.drawImage(image, 0-image.width, 0-image.height);
ctx.restore();
return this;
},
drawText: function(textString, x, y, fontStyle, color, alignment, baseline){
var ctx = this.canvas.ctx;
ctx.save();
if(fontStyle){
ctx.font = fontStyle + " sans-serif";
}
if(color){
ctx.fillStyle = color;
}
if(alignment){
ctx.textAlign = alignment;
}
if(baseline){
ctx.textBaseline = baseline;
}
ctx.fillText(textString,x,y);
ctx.restore();
return this;
},
append: function(element){
element = document.querySelector(element);
if(element){
element.appendChild(this.canvas.canvas);
}
else{
document.body.appendChild(this.canvas.canvas);
}
return this;
},
clear: function(){
var ctx = this.canvas.ctx;
ctx.clearRect(0,0, this.canvas.width, this.canvas.height);
},
setCamera: function(cam){
this.canvas.camera = cam;
}
};
headOn.Timer.prototype = {
job: function(time, start){
var jiff = {
TTL: time,
remaining: start || time
};
this.jobs.push(jiff);
return {
ready: function(){
return jiff.remaining <= 0;
},
reset: function(){
jiff.remaining = jiff.TTL;
},
timeLeft: function(){
return jiff.remaining;
}
};
},
update: function(time){
this.jobs.forEach(function(j){
j.remaining -= time;
});
}
};
headOn.Camera.prototype = {
zoomIn: function(amt){
this.zoomAmt /= amt;
this.position = this.center.sub(this.dimensions.mul(this.zoomAmt / 2));
return this;
},
zoomOut: function(amt){
this.zoomAmt *= amt;
this.position = this.center.sub(this.dimensions.mul(this.zoomAmt / 2));
return this;
},
move: function(vec){
this.position = this.position.add(vec);
this.center = this.position.add(headOn.Vector(this.width, this.height).mul(0.5));
return this;
},
moveTo: function(vec){
this.position = vec.sub(this.dimensions.mul(0.5).mul(this.zoomAmt));
this.center = vec;
},
unproject: function(vec){
return vec.sub(this.position).mul(1/this.zoomAmt);
}
};
headOn.Vector.prototype = {
normalize: function(){
var len = this.length();
if(len === 0) return headOn.Vector(0,0);
return new headOn.Vector(this.x/len, this.y/len);
},
normalizeInPlace: function(){
var len = this.length();
this.x /= len;
this.y /= len;
},
truncate: function(max){
var i;
i = max / this.length();
i = i < 1 ? i : 1;
this.mul(i);
},
dot: function(vec2){
return vec2.x * this.x + vec2.y * this.y;
},
length: function(){
return Math.sqrt(this.x*this.x + this.y*this.y);
},
sub: function(vec2){
return new headOn.Vector(this.x - vec2.x, this.y - vec2.y);
},
add: function(vec2){
return new headOn.Vector(this.x + vec2.x, this.y + vec2.y);
},
mul: function(scalar){
return new headOn.Vector(this.x * scalar, this.y * scalar);
}
};
function sign(num){
if(num < 0){
return -1;
}else{
return 1;
}
}
return headOn;
function square(num){
return num * num;
}
function isEmpty(obj){
return Object.keys(obj).length === 0;
}
}());
window.headOn = headOn;
return headOn;
}(window));
function line(p1,p2){
//console.log("in here");
this.p1 = p1;
this.p2 = p2;
return this;
}
line.prototype = {length:function(){
return Math.sqrt(Math.pow(this.p2.x - this.p1.x, 2), Math.pow(this.p2.y - this.p1.y,2))
}};
(function($h){
var squares = [];
var canvas;
var mask;
var cam = new $h.Camera(500,500);
var mcam = new $h.Camera(500,500);
var light = {position:new $h.Vector(250,250)};
var reverse = 1;
var v = new $h.Vector(4,20);
fillSquares(squares,15);
$h.canvas.create("main",500,500,cam);
$h.canvas.create("mask",500,500,mcam);
canvas = $h.canvas("main");
mask = $h.canvas("mask");
canvas.append("body");
mask.append("body");
canvas.canvas.canvas.style.margin = 0;
mask.canvas.canvas.style.position = "absolute"
mask.canvas.canvas.style.margin= 0
mask.canvas.canvas.style.top = 0;
mask.canvas.canvas.style.left = 0;
mask.canvas.ctx.lineWidth = 0
document.addEventListener("mousemove", function(e){
light.position = new $h.Vector(e.x,e.y);
});
$h.update(function(delta){
});
$h.render(function(){
canvas.canvas.canvas.width = canvas.width;
renderSquares(squares, canvas);
doMask(mask, squares, light);
//canvas.drawImage(mask.canvas.canvas, 0,0);
});
$h.run();
}(headOn));
function doMask(mask, sq, light){
mask.canvas.canvas.width = mask.width;
mask.canvas.ctx.save();
mask.drawRect(500,500,0,0,"rgba(0,0,0,.9)");
mask.canvas.ctx.globalCompositeOperation = "destination-out";
doLight(mask, sq, light);
mask.canvas.ctx.restore();
}
function doLight(mask, sq, light){
var NUM_OF_RAYS = 500;
var ctx = mask.canvas.ctx;
var i;
var s;
var a = 0;
var amt = (Math.PI * 2)/NUM_OF_RAYS;
var ray;
var p;
var end;
var t;
ctx.beginPath();
ctx.moveTo(light.position.x, light.position.y);
ray = new line(light.position, 0);
for(i=0; i<NUM_OF_RAYS; i++, a+=amt){
ray.p2 = new headOn.Vector(light.position.x + Math.cos(a) * 200, light.position.y + Math.sin(a) * 200);
sq.forEach(function(s){
if(i === 0) s.touched = false;
s.ls.forEach(function(l){
t = get_line_intersection(l,ray);
if(t.collided){
var q = new line(light.position, t.position);
//console.log(rays[i]);
if(q.length() < ray.length()){
ray.p2 = t.position;
}
s.touched = true;
}
});
});
//if(i==0) continue;
//ctx.beginPath();
//ctx.lineTo(rays[i-1].p2.x,rays[i-1].p2.y);
ctx.lineTo(ray.p2.x,ray.p2.y);
//ctx.lineTo(rays[i].p1.x, rays[i].p1.y);
//ctx.fill();
if(i===0) end = ray.p2;
}
ctx.lineTo(end.x,end.y);
ctx.fill()
//ctx.fill()
//mask.drawRect(30,30,light.position.x,light.position.y,"white");
sq.forEach(function(s){
if(s.touched){
mask.drawRect(s.width, s.height, s.position.x, s.position.y, "white", {}, s.angle);
}
})
}
function renderSquares(arr, canvas){
arr.forEach(function(s){
canvas.drawRect(s.width,s.height,s.position.x,s.position.y,"blue",{},s.angle);
})
}
function fillSquares(arr,amt){
var i;
for(i=0; i<amt; i++){
var ls =[];
arr.push({
position:new headOn.Vector(headOn.randInt(0,500), headOn.randInt(0,500)),
width:headOn.randInt(5,20),
height:headOn.randInt(5,20),
angle:headOn.randFloat(0, 2*Math.PI)
})
p = headOn.getPoints(arr[i]);
ls.push(new line(new headOn.Vector(p[0][0],p[0][1]), new headOn.Vector(p[1][0],p[1][1])))
ls.push(new line(new headOn.Vector(p[1][0],p[1][1]), new headOn.Vector(p[2][0],p[2][1])))
ls.push(new line(new headOn.Vector(p[2][0],p[2][1]), new headOn.Vector(p[3][0],p[3][1])))
ls.push(new line(new headOn.Vector(p[3][0],p[3][1]), new headOn.Vector(p[0][0],p[0][1])))
arr[i].ls = ls;
}
}
function get_line_intersection(line1, line2)
{
//console.log(line1.p1, line2.p1);
var s1, s2;
s1 = line1.p2.sub(line1.p1);
s2 = line2.p2.sub(line2.p1);
var s, t;
//console.log(line1.p1, line2.p1);
s = (-s1.y * (line1.p1.x - line2.p1.x) + s1.x * (line1.p1.y - line2.p1.y)) / (-s2.x * s1.y + s1.x * s2.y);
t = ( s2.x * (line1.p1.y - line2.p1.y) - s2.y * (line1.p1.x - line2.p1.x)) / (-s2.x * s1.y + s1.x * s2.y);
if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
{
// Collision detected
return{
collided:true,
position:new headOn.Vector(line1.p1.x + (t * s1.x),line1.p1.y + (t * s1.y))
}
}
return {collided:false}; // No collision
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment