Last active
December 28, 2015 07:19
-
-
Save 6174/7463137 to your computer and use it in GitHub Desktop.
fisheye
This file contains hidden or 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
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
<title>FishEye</title> | |
<style> | |
html { | |
overflow: hidden; | |
-ms-touch-action: none; | |
-ms-content-zooming: none; | |
} | |
body { | |
position: absolute; | |
margin: 0px; | |
padding: 0px; | |
background: #000; | |
width: 100%; | |
height: 100%; | |
} | |
#screen { | |
position: absolute; | |
background: #000; | |
width: 100%; | |
height: 100%; | |
cursor:pointer; | |
} | |
</style> | |
<script> | |
// Tools | |
var CT = CT || { | |
json: null, | |
screen: null, | |
pointer: null, | |
camera: null, | |
loadJS: function (url, callback, data) { | |
if (typeof url == "string") url = [url]; | |
var load = function (src) { | |
var script = document.createElement("script"); | |
if (callback) { | |
if (script.readyState){ | |
script.onreadystatechange = function () { | |
if (script.readyState == "loaded" || script.readyState == "complete"){ | |
script.onreadystatechange = null; | |
if (--n === 0) callback(data || false); | |
} | |
} | |
} else { | |
script.onload = function() { | |
if (--n === 0) callback(data || false); | |
} | |
} | |
} | |
script.src = src; | |
document.getElementsByTagName("head")[0].appendChild(script); | |
} | |
for (var i = 0, n = url.length; i < n; i++) load(url[i]); | |
} | |
} | |
// ===== html/canvas container ===== | |
CT.Screen = function (setup) { | |
CT.screen = this; | |
this.elem = document.getElementById(setup.container) || setup.container; | |
this.ctx = this.elem.tagName == "CANVAS" ? this.elem.getContext("2d") : false; | |
this.style = this.elem.style; | |
this.left = 0; | |
this.top = 0; | |
this.width = 0; | |
this.height = 0; | |
this.cursor = "default"; | |
this.setup = setup; | |
this.resize = function () { | |
var o = this.elem; | |
this.width = o.offsetWidth; | |
this.height = o.offsetHeight; | |
for (this.left = 0, this.top = 0; o != null; o = o.offsetParent) { | |
this.left += o.offsetLeft; | |
this.top += o.offsetTop; | |
} | |
if (this.ctx) { | |
this.elem.width = this.width; | |
this.elem.height = this.height; | |
} | |
this.setup.resize && this.setup.resize(); | |
} | |
this.setCursor = function (type) { | |
if (type !== this.cursor && 'ontouchstart' in window === false) { | |
this.cursor = type; | |
this.style.cursor = type; | |
} | |
} | |
window.addEventListener('resize', function () { | |
CT.screen.resize(); | |
}, false); | |
!this.setup.resize && this.resize(); | |
} | |
// ==== unified touch events handler ==== | |
CT.Pointer = function (setup) { | |
CT.pointer = this; | |
var self = this; | |
var body = document.body; | |
var html = document.documentElement; | |
this.setup = setup; | |
this.screen = CT.screen; | |
this.elem = this.screen.elem; | |
this.X = 0; | |
this.Y = 0; | |
this.Xi = 0; | |
this.Yi = 0; | |
this.bXi = 0; | |
this.bYi = 0; | |
this.Xr = 0; | |
this.Yr = 0; | |
this.startX = 0; | |
this.startY = 0; | |
this.isDraging = false; | |
this.hasMoved = false; | |
this.isDown = false; | |
this.evt = false; | |
var sX = 0; | |
var sY = 0; | |
if (setup.tap) this.elem.onclick = function () { return false; } | |
if (!setup.documentMove) { | |
document.ontouchmove = function(e) { e.preventDefault(); } | |
} | |
this.pointerDown = function (e) { | |
if (!this.isDown) { | |
if (this.elem.setCapture) this.elem.setCapture(); | |
this.isDraging = false; | |
this.hasMoved = false; | |
this.isDown = true; | |
this.evt = e; | |
this.Xr = (e.clientX !== undefined ? e.clientX : e.touches[0].clientX); | |
this.Yr = (e.clientY !== undefined ? e.clientY : e.touches[0].clientY); | |
this.X = sX = this.Xr - this.screen.left; | |
this.Y = sY = this.Yr - this.screen.top + ((html && html.scrollTop) || body.scrollTop); | |
this.setup.down && this.setup.down(e); | |
} | |
} | |
this.pointerMove = function(e) { | |
this.Xr = (e.clientX !== undefined ? e.clientX : e.touches[0].clientX); | |
this.Yr = (e.clientY !== undefined ? e.clientY : e.touches[0].clientY); | |
this.X = this.Xr - this.screen.left; | |
this.Y = this.Yr - this.screen.top + ((html && html.scrollTop) || body.scrollTop); | |
if (this.isDown) { | |
this.Xi = this.bXi + (this.X - sX); | |
this.Yi = this.bYi - (this.Y - sY); | |
} | |
if (Math.abs(this.X - sX) > 11 || Math.abs(this.Y - sY) > 11) { | |
this.hasMoved = true; | |
if (this.isDown) { | |
if (!this.isDraging) { | |
this.startX = sX; | |
this.startY = sY; | |
this.setup.startDrag && this.setup.startDrag(e); | |
this.isDraging = true; | |
} else { | |
this.setup.drag && this.setup.drag(e); | |
} | |
} else { | |
sX = this.X; | |
sY = this.Y; | |
} | |
} | |
this.setup.move && this.setup.move(e); | |
} | |
this.pointerUp = function(e) { | |
this.bXi = this.Xi; | |
this.bYi = this.Yi; | |
if (!this.hasMoved) { | |
this.X = sX; | |
this.Y = sY; | |
this.setup.tap && this.setup.tap(this.evt); | |
} else { | |
this.setup.up && this.setup.up(this.evt); | |
} | |
this.isDraging = false; | |
this.isDown = false; | |
this.hasMoved = false; | |
this.setup.up && this.setup.up(this.evt); | |
if (this.elem.releaseCapture) this.elem.releaseCapture(); | |
this.evt = false; | |
} | |
this.pointerCancel = function(e) { | |
if (this.elem.releaseCapture) this.elem.releaseCapture(); | |
this.isDraging = false; | |
this.hasMoved = false; | |
this.isDown = false; | |
this.bXi = this.Xi; | |
this.bYi = this.Yi; | |
sX = 0; | |
sY = 0; | |
} | |
if ('ontouchstart' in window) { | |
this.elem.ontouchstart = function (e) { self.pointerDown(e); return false; } | |
this.elem.ontouchmove = function (e) { self.pointerMove(e); return false; } | |
this.elem.ontouchend = function (e) { self.pointerUp(e); return false; } | |
this.elem.ontouchcancel = function (e) { self.pointerCancel(e); return false;} | |
} | |
document.addEventListener("mousedown", function (e) { | |
if ( | |
e.target === self.elem || | |
(e.target.parentNode && e.target.parentNode === self.elem) || | |
(e.target.parentNode.parentNode && e.target.parentNode.parentNode === self.elem) | |
) { | |
if (typeof e.stopPropagation != "undefined") { | |
e.stopPropagation(); | |
} else { | |
e.cancelBubble = true; | |
} | |
e.preventDefault(); | |
self.pointerDown(e); | |
} | |
}, false); | |
document.addEventListener("mousemove", function (e) { self.pointerMove(e); }, false); | |
document.addEventListener("mouseup", function (e) { | |
self.pointerUp(e); | |
}, false); | |
} | |
// ===== smooth animation ===== | |
window.requestAnimFrame = (function(){ | |
return window.requestAnimationFrame || | |
window.webkitRequestAnimationFrame || | |
window.mozRequestAnimationFrame || | |
window.oRequestAnimationFrame || | |
window.msRequestAnimationFrame || | |
function( run ){ | |
window.setTimeout(run, 16); | |
}; | |
})(); | |
// fish eye | |
;(function () { | |
var scr, ctx, pointer, points, planes, over, | |
size, radius, force = -1.5, spring = 0.05, friction = 0.9, ngrid = 10, | |
npoints = (ngrid + 1) * (ngrid + 1), nplanes = ngrid * ngrid; | |
// ==== Points constructor ==== | |
var Points = function (x, y) { | |
this.x = Math.round(0.5 * (scr.width - (size * ngrid)) + x); | |
this.y = Math.round(0.5 * (scr.height - (size * ngrid)) + y); | |
this.X = this.x; | |
this.Y = this.y; | |
this.vx = 0; | |
this.vy = 0; | |
this.d = 0; | |
} | |
Points.prototype.update = function () { | |
var z, a, x, y, | |
dx = pointer.X - this.x, | |
dy = pointer.Y - this.y; | |
this.d = Math.sqrt(dx * dx + dy * dy); | |
if (this.d < radius) { | |
z = this.d * force * (radius - this.d) / radius, | |
a = Math.atan2(dy, dx); | |
x = this.x + Math.cos(a) * z; | |
y = this.y + Math.sin(a) * z; | |
} else { | |
x = this.x; | |
y = this.y; | |
} | |
this.vx = (this.vx + (x - this.X) * spring) * friction; | |
this.vy = (this.vy + (y - this.Y) * spring) * friction; | |
this.X += this.vx; | |
this.Y += this.vy; | |
} | |
// ==== Planes constructor ==== | |
var Plane = function (p0, p1, p2, p3) { | |
this.p0 = p0; | |
this.p1 = p1; | |
this.p2 = p2; | |
this.p3 = p3; | |
this.d = 0; | |
} | |
// ==== compute z-index ==== | |
Plane.prototype.update = function () { | |
this.zIndex = Math.max(this.p0.d, this.p1.d, this.p2.d, this.p3.d); | |
} | |
// ==== draw Plane ==== | |
Plane.prototype.draw = function () { | |
// ---- path ---- | |
ctx.beginPath(); | |
ctx.moveTo(this.p0.X, this.p0.Y); | |
ctx.lineTo(this.p1.X, this.p1.Y); | |
ctx.lineTo(this.p2.X, this.p2.Y); | |
ctx.lineTo(this.p3.X, this.p3.Y); | |
ctx.closePath(); | |
// ---- fill color ---- | |
if (ctx.isPointInPath(pointer.X, pointer.Y)) { | |
if (this.d < 260) this.d += 20; | |
over = true; | |
} else { | |
if (this.d > 0) this.d -= 10; | |
} | |
var c = 255 - Math.round(this.zIndex / scr.minSize * 255); | |
ctx.fillStyle = "RGB(" + c + "," + this.d + "," + this.d + ")"; | |
ctx.fill(); | |
} | |
/* ==== build grid ==== */ | |
var creatGrid = function () { | |
// ---- create points ---- | |
points = []; | |
for (var i = 0; i <= ngrid; i++) { | |
for (var j = 0; j <= ngrid; j++) { | |
points.push( | |
new Points( | |
size * j, | |
size * i | |
) | |
); | |
} | |
} | |
// ---- create grid ---- | |
planes = []; | |
for (var i = 0; i < ngrid; i++) { | |
for (var j = 0; j < ngrid; j++) { | |
planes.push( | |
new Plane( | |
points[i + (ngrid + 1) * j], | |
points[i + (ngrid + 1) * j + 1], | |
points[i + (ngrid + 1) * (j + 1) + 1], | |
points[i + (ngrid + 1) * (j + 1)] | |
) | |
); | |
} | |
} | |
} | |
/* ==== main loop ==== */ | |
var run = function () { | |
/* ---- clear screen ---- */ | |
ctx.clearRect(0, 0, scr.width, scr.height); | |
// ---- onclick ---- | |
if (pointer.isDown && !pointer.hasMoved) force = -4; else force = -1.5; | |
// ---- update points ---- | |
for (var i = 0; i < npoints; i++) { | |
points[i].update(); | |
} | |
// ---- update grid ---- | |
for (var i = 0; i < nplanes; i++) { | |
planes[i].update(); | |
} | |
// ---- zIndex sorting ---- | |
planes.sort(function (p0, p1) { | |
return p1.zIndex - p0.zIndex; | |
}); | |
// ---- draw grid ---- | |
for (var i = 0; i < nplanes; i++) { | |
planes[i].draw(); | |
} | |
// ---- remove cursor ---- | |
if (over) { | |
scr.setCursor("none"); | |
over = false; | |
} else { | |
scr.setCursor("default"); | |
} | |
// ---- next frame ---- | |
requestAnimFrame(run); | |
} | |
/* ==== init script ==== */ | |
var init = function () { | |
// ---- canvas ---- | |
scr = new CT.Screen({ | |
container: "screen", | |
resize: function () { | |
scr.minSize = Math.min(scr.width, scr.height); | |
var s = 0.5 * scr.minSize; | |
size = s / ngrid; | |
radius = s / 5; | |
creatGrid(); | |
} | |
}); | |
ctx = scr.ctx; | |
scr.resize(); | |
// ---- pointer ---- | |
pointer = new CT.Pointer({}); | |
pointer.X = scr.width * 0.5; | |
pointer.Y = scr.height * 0.5; | |
run(); | |
} | |
return { | |
// ---- launch script ----- | |
load : function (setup) { | |
window.addEventListener('load', function () { | |
init(setup); | |
}, false); | |
} | |
} | |
})().load(); | |
</script> | |
</head> | |
<body> | |
<canvas id="screen">CANVAS lens grid simulation</canvas> | |
</body> | |
</html> |
This file contains hidden or 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
/** | |
* texture mapping | |
* simulate on 2D context | |
*/ | |
var TextureMapping = {}; | |
/* TextureMapping.Point2 */ | |
;(function (tm) { | |
var Point2 = function (x, y) { | |
this.X = x; | |
this.Y = y; | |
}; | |
Point2.prototype = { | |
clone: function () { | |
return new Point2(this.X, this.Y); | |
}, | |
pos: function (x, y) { | |
this.X = x; | |
this.Y = y; | |
}, | |
move: function (x, y) { | |
this.X += x; | |
this.Y += y; | |
} | |
}; | |
tm.Point2 = Point2; | |
})(TextureMapping); | |
/* TextureMapping.Triangle */ | |
;(function (tm) { | |
// Triangle constructor | |
tm.Triangle = function (parent, p0, p1, p2) { | |
this.p0 = p0; | |
this.p1 = p1; | |
this.p2 = p2; | |
this.next = false; | |
// ---- pre calculation for transform---- | |
this.d = p0.tx * (p2.ty - p1.ty) - p1.tx * p2.ty + p2.tx * p1.ty + (p1.tx - p2.tx) * p0.ty; | |
this.pmy = p1.ty - p2.ty; | |
this.pmx = p1.tx - p2.tx; | |
this.pxy = p2.tx * p1.ty - p1.tx * p2.ty; | |
// ---- link for iteration ---- | |
if (!parent.firstTriangle) parent.firstTriangle = this; else parent.prev.next = this; | |
parent.prev = this; | |
} | |
})(TextureMapping); | |
/* TextureMapping.Image */ | |
;(function (tm) { | |
var Triangle = tm.Triangle; | |
tm.Image = function (canvas, imgSrc, lev) { | |
this.canvas = canvas; | |
this.ctx = canvas.getContext("2d"); | |
this.lev = lev; | |
this.isLoading = true; | |
this.firstPoint = false; | |
this.firstTriangle = false; | |
this.prev = false; | |
if (typeof imgSrc == 'string') { | |
this.texture = new Image(); | |
this.texture.src = imgSrc; | |
} else { | |
this.texture = imgSrc; | |
} | |
}; | |
// tm.Image.prototype | |
tm.Image.prototype = { | |
loading : function () { | |
if (this.texture.complete && this.texture.width) { | |
this.isLoading = false; | |
var points = []; | |
// ---- create points ---- | |
for (var i = 0; i <= this.lev; i++) { | |
for (var j = 0; j <= this.lev; j++) { | |
var tx = (i * (this.texture.width / this.lev)); | |
var ty = (j * (this.texture.height / this.lev)); | |
var p = { | |
tx: tx, | |
ty: ty, | |
nx: tx / this.texture.width, | |
ny: ty / this.texture.height, | |
next: false | |
}; | |
points.push(p); | |
if (!this.firstPoint) this.firstPoint = p; else this.prev.next = p; | |
this.prev = p; | |
} | |
} | |
var lev = this.lev + 1; | |
for (var i = 0; i < this.lev; i++) { | |
for (var j = 0; j < this.lev; j++) { | |
// ---- up ---- | |
var t = new Triangle(this, | |
points[j + i * lev], | |
points[j + i * lev + 1], | |
points[j + (i + 1) * lev] | |
); | |
// ---- down ---- | |
var t = new Triangle(this, | |
points[j + (i + 1) * lev + 1], | |
points[j + (i + 1) * lev], | |
points[j + i * lev + 1] | |
); | |
} | |
} | |
} | |
}, | |
draw3D: function (p0, p1, p2, p3) { | |
// ---- loading ---- | |
if (this.isLoading) { | |
this.loading(); | |
} else { | |
// ---- project points ---- | |
var p = this.firstPoint; | |
do { | |
var mx = p0.X + p.ny * (p3.X - p0.X); | |
var my = p0.Y + p.ny * (p3.Y - p0.Y); | |
p.px = (mx + p.nx * (p1.X + p.ny * (p2.X - p1.X) - mx)); | |
p.py = (my + p.nx * (p1.Y + p.ny * (p2.Y - p1.Y) - my)); | |
} while ( p = p.next ); | |
// ---- draw triangles ---- | |
var w = this.canvas.width; | |
var h = this.canvas.height; | |
var t = this.firstTriangle; | |
do { | |
var p0 = t.p0; | |
var p1 = t.p1; | |
var p2 = t.p2; | |
// ---- centroid ---- | |
var xc = (p0.px + p1.px + p2.px) / 3; | |
var yc = (p0.py + p1.py + p2.py) / 3; | |
// ---- clipping ---- | |
var isTriangleVisible = true; | |
if (xc < 0 || xc > w || yc < 0 || yc > h) { | |
if (Math.max(p0.px, p1.px, p2.px) < 0 || Math.min(p0.px, p1.px, p2.px) > w || Math.max(p0.py, p1.py, p2.py) < 0 || Math.min(p0.py, p1.py, p2.py) > h) { | |
isTriangleVisible = false; | |
} | |
} | |
if (isTriangleVisible) { | |
this.ctx.save(); | |
this.ctx.beginPath(); | |
var dx, dy, d; | |
// ---- draw non anti-aliased triangle ---- | |
dx = xc - p0.px; | |
dy = yc - p0.py; | |
d = Math.max(Math.abs(dx), Math.abs(dy)); | |
this.ctx.moveTo(p0.px - 2 * (dx / d), p0.py - 2 * (dy / d)); | |
dx = xc - p1.px; | |
dy = yc - p1.py; | |
d = Math.max(Math.abs(dx), Math.abs(dy)); | |
this.ctx.lineTo(p1.px - 2 * (dx / d), p1.py - 2 * (dy / d)); | |
dx = xc - p2.px; | |
dy = yc - p2.py; | |
d = Math.max(Math.abs(dx), Math.abs(dy)); | |
this.ctx.lineTo(p2.px - 2 * (dx / d), p2.py - 2 * (dy / d)); | |
this.ctx.closePath(); | |
// ---- clip ---- | |
this.ctx.clip(); | |
// ---- texture mapping ---- | |
this.ctx.transform( | |
-(p0.ty * (p2.px - p1.px) - p1.ty * p2.px + p2.ty * p1.px + t.pmy * p0.px) / t.d, // m11 | |
(p1.ty * p2.py + p0.ty * (p1.py - p2.py) - p2.ty * p1.py - t.pmy * p0.py) / t.d, // m12 | |
(p0.tx * (p2.px - p1.px) - p1.tx * p2.px + p2.tx * p1.px + t.pmx * p0.px) / t.d, // m21 | |
-(p1.tx * p2.py + p0.tx * (p1.py - p2.py) - p2.tx * p1.py - t.pmx * p0.py) / t.d, // m22 | |
(p0.tx * (p2.ty * p1.px - p1.ty * p2.px) + p0.ty * (p1.tx * p2.px - p2.tx * p1.px) + t.pxy * p0.px) / t.d, // dx | |
(p0.tx * (p2.ty * p1.py - p1.ty * p2.py) + p0.ty * (p1.tx * p2.py - p2.tx * p1.py) + t.pxy * p0.py) / t.d // dy | |
); | |
this.ctx.drawImage(this.texture, 0, 0); | |
this.ctx.restore(); | |
// ---- debug mode ---- | |
if (this.stroke) { | |
this.ctx.beginPath(); | |
this.ctx.moveTo(t.p0.px, t.p0.py); | |
this.ctx.lineTo(t.p1.px, t.p1.py); | |
this.ctx.lineTo(t.p2.px, t.p2.py); | |
this.ctx.closePath(); | |
this.ctx.strokeStyle = '#ff4a00'; | |
this.ctx.stroke(); | |
} | |
} | |
} while ( t = t.next ); | |
} | |
}, | |
pointInTriangle : function (xm, ym, p1, p2, p3) { | |
// ---- Compute vectors ---- | |
var v0x = p3.X - p1.X; | |
var v0y = p3.Y - p1.Y; | |
var v1x = p2.X - p1.X; | |
var v1y = p2.Y - p1.Y; | |
var v2x = xm - p1.X; | |
var v2y = ym - p1.Y; | |
// ---- Compute dot products ---- | |
var dot00 = v0x * v0x + v0y * v0y; | |
var dot01 = v0x * v1x + v0y * v1y; | |
var dot02 = v0x * v2x + v0y * v2y; | |
var dot11 = v1x * v1x + v1y * v1y; | |
var dot12 = v1x * v2x + v1y * v2y; | |
// ---- Compute barycentric coordinates ---- | |
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01); | |
var u = (dot11 * dot02 - dot01 * dot12) * invDenom; | |
var v = (dot00 * dot12 - dot01 * dot02) * invDenom; | |
// ---- Check if point is in triangle ---- | |
return (u >= 0) && (v >= 0) && (u + v < 1); | |
}, | |
pointerInside : function (xm, ym, p0, p1, p2, p3) { | |
if ( | |
this.pointInTriangle(xm, ym, p0, p1, p2) || | |
this.pointInTriangle(xm, ym, p0, p2, p3) | |
) return true; else return false; | |
} | |
} | |
})(TextureMapping); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment