Skip to content

Instantly share code, notes, and snippets.

@6174
Last active December 28, 2015 07:19
Show Gist options
  • Save 6174/7463137 to your computer and use it in GitHub Desktop.
Save 6174/7463137 to your computer and use it in GitHub Desktop.
fisheye
<!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>
/**
* 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