opencoil-paracity/works/Sarah Grant/otastaImg/data/js/kh.js
2020-10-16 18:36:19 +02:00

802 lines
25 KiB
JavaScript

/* PLAGUE RAVE 2020 by chootka for e-ridin`dirty / OPENCOIL 2020 * Dennis de Bel * Anton ???
Adapted from kathack.com, Alex Leone, David Nufer, David Truong, © 2011-03-11 */
var CSS_TRANSFORM = null,
CSS_TRANSFORM_ORIGIN = null,
POSSIBLE_TRANSFORM_PREFIXES = ['-webkit-', '-moz-', '-o-', '-ms-', ''],
khFirst = false,
vibrateInterval = null;
/* When running twice on one page, update pick-uppable nodes instead of
* creating more.
*/
if (!window.khNodes) {
khFirst = true;
window.khNodes = new StickyNodes();
}
function getCssTransform() {
var i, d = document.createElement('div'), pre;
for (i = 0; i < POSSIBLE_TRANSFORM_PREFIXES.length; i++) {
pre = POSSIBLE_TRANSFORM_PREFIXES[i];
d.style.setProperty(pre + 'transform', 'rotate(1rad) scaleX(2)', null);
if (d.style.getPropertyValue(pre + 'transform')) {
CSS_TRANSFORM = pre + 'transform';
CSS_TRANSFORM_ORIGIN = pre + 'transform-origin';
return;
}
}
alert("Your browser doesn't support CSS tranforms!");
throw "Your browser doesn't support CSS tranforms!";
}
getCssTransform();
/**
* Vibration support
*/
// Starts vibration at passed in level
function startVibrate(duration) {
navigator.vibrate(duration);
}
// Stops vibration
function stopVibrate() {
// Clear interval and stop persistent vibrating
if(vibrateInterval) clearInterval(vibrateInterval);
navigator.vibrate(0);
}
// Start persistent vibration at given duration and interval
// Assumes a number value is given
function startPeristentVibrate(duration, interval) {
vibrateInterval = setInterval(function() {
startVibrate(duration);
}, interval);
}
/**
* Returns true if the circle intersects the element rectangle.
* 0 | 1 | 2
* ------------------
* 3 | 4 | 5
* ------------------
* 6 | 7 | 9
*/
function circleGridObjInt(cx, cy, cr, cr2, go) {
var dx, dy;
if (cx < go.left) {
dx = go.left - cx;
if (cy < go.top) { /* zone 0. */
dy = go.top - cy;
return ((dx * dx + dy * dy) <= cr2);
} else if (cy <= go.bottom) { /* zone 3. */
return (dx <= cr);
} else { /* zone 6. */
dy = cy - go.bottom;
return ((dx * dx + dy * dy) <= cr2);
}
} else if (cx <= go.right) {
if (cy < go.top) { /* zone 1. */
return ((go.top - cy) <= cr);
} else if (cy <= go.bottom) { /* zone 4. */
return true;
} else { /* zone 7. */
return ((cy - go.bottom) <= cr);
}
} else {
dx = cx - go.right;
if (cy < go.top) { /* zone 2. */
dy = go.top - cy;
return ((dx * dx + dy * dy) <= cr2);
} else if (cy <= go.bottom) { /* zone 5. */
return (dx <= cr);
} else { /* zone 9. */
dy = cy - go.bottom;
return ((dx * dx + dy * dy) <= cr2);
}
}
}
/**
* Returns [x,y] where the rectangle is closest to (cx, cy).
* 0 | 1 | 2
* ------------------
* 3 | 4 | 5
* ------------------
* 6 | 7 | 9
*/
function getClosestPoint(cx, cy, go) {
var dx, dy;
if (cx < go.left) {
dx = go.left - cx;
if (cy < go.top) { /* zone 0. */
return [go.left, go.top];
} else if (cy <= go.bottom) { /* zone 3. */
return [go.left, cy];
} else { /* zone 6. */
return [go.left, go.bottom];
}
} else if (cx <= go.right) {
if (cy < go.top) { /* zone 1. */
return [cx, go.top];
} else if (cy <= go.bottom) { /* zone 4. */
return [cx, cy];
} else { /* zone 7. */
return [cx, go.bottom];
}
} else {
dx = cx - go.right;
if (cy < go.top) { /* zone 2. */
return [go.right, go.top];
} else if (cy <= go.bottom) { /* zone 5. */
return [go.right, cy];
} else { /* zone 9. */
return [go.right, go.bottom];
}
}
}
/**
* Returns the "volume" of the grid object.
*/
function gridObjVol(go) {
return go.w * go.h * Math.min(go.w, go.h);
}
function StickyNodes() {
var domNodes = [],
grid = [],
GRIDX = 100,
GRIDY = 100,
REPLACE_WORDS_IN = {
a: 1, b: 1, big: 1, body: 1, cite:1, code: 1, dd: 1, div: 1,
dt: 1, em: 1, font: 1, h1: 1, h2: 1, h3: 1, h4: 1, h5: 1, h6: 1,
i: 1, label: 1, legend: 1, li: 1, p: 1, pre: 1, small: 1,
span: 1, strong: 1, sub: 1, sup: 1, td: 1, th: 1, tt: 1
};
function addDomNode(el) {
if (el !== undefined && el !== null) {
el.khIgnore = true;
domNodes.push(el);
}
}
this.addDomNode = addDomNode;
this.addWords = function (el) {
var textEls = [];
function shouldAddChildren(el) {
return el.tagName && REPLACE_WORDS_IN[el.tagName.toLowerCase()];
}
function buildTextEls(el, shouldAdd) {
var i, len;
if (shouldAdd && el.nodeType === Node.TEXT_NODE &&
el.nodeValue.trim().length > 0) {
textEls.push(el);
return;
}
if (!el.childNodes || el.khIgnore) {
return;
}
shouldAdd = shouldAddChildren(el);
for (i = 0, len = el.childNodes.length; i < len; i++) {
buildTextEls(el.childNodes[i], shouldAdd);
}
}
function wordsToSpans(textEl) {
var p = textEl.parentNode,
words = textEl.nodeValue.split(/\s+/),
ws = textEl.nodeValue.split(/\S+/),
i, n, len = Math.max(words.length, ws.length);
/* preserve whitespace for pre tags. */
if (ws.length > 0 && ws[0].length === 0) {
ws.shift();
}
for (i = 0; i < len; i++) {
if (i < words.length && words[i].length > 0) {
n = document.createElement('span');
n.innerHTML = words[i];
p.insertBefore(n, textEl);
addDomNode(n);
}
if (i < ws.length && ws[i].length > 0) {
n = document.createTextNode(ws[i]);
p.insertBefore(n, textEl);
}
}
p.removeChild(textEl);
}
buildTextEls(el, shouldAddChildren(el));
textEls.map(wordsToSpans);
};
/* includes el. */
this.addTagNames = function (el, tagNames) {
var tname = el.tagName && el.tagName.toLowerCase(),
i, j, els, len;
if (el.khIgnore) {
return;
}
if (tagNames.indexOf(tname) !== -1) {
addDomNode(el);
}
if (!el.getElementsByTagName) {
return;
}
for (i = 0; i < tagNames.length; i++) {
els = el.getElementsByTagName(tagNames[i]);
for (j = 0, len = els.length; j < len; j++) {
if (!els[j].khIgnore) {
addDomNode(els[j]);
}
}
}
};
this.finalize = function (docW, docH) {
var xi, yi, i, len, startXI, startYI, el, go, off, w, h,
endXI = Math.floor(docW / GRIDX) + 1,
endYI = Math.floor(docH / GRIDY) + 1;
/* initialize grid. */
grid = new Array(endXI);
for (xi = 0; xi < endXI; xi++) {
grid[xi] = new Array(endYI);
}
/* add nodes into grid. */
for (i = 0, len = domNodes.length; i < len; i++) {
el = domNodes[i];
if (el.khPicked) {
continue;
}
off = jQuery(el).offset();
w = jQuery(el).width();
h = jQuery(el).height();
go = {
el: domNodes[i], /* dom element. */
left: off.left,
right: off.left + w,
top: off.top,
bottom: off.top + h,
w: w,
h: h,
x: off.left + (w / 2), /* center x. */
y: off.top + (h / 2), /* center y. */
diag: Math.sqrt(((w * w) + (h * h)) / 4), /* center to corner */
/* these are for removing ourselves from the grid. */
arrs: [], /* which arrays we're in (grid[x][y]). */
idxs: [] /* what indexes. */
};
startXI = Math.floor(go.left / GRIDX);
startYI = Math.floor(go.top / GRIDY);
endXI = Math.floor((go.left + go.w) / GRIDX) + 1;
endYI = Math.floor((go.top + go.h) / GRIDY) + 1;
for (xi = startXI; xi < endXI; xi++) {
for (yi = startYI; yi < endYI; yi++) {
if (grid[xi] === undefined) {
grid[xi] = [];
}
if (grid[xi][yi] === undefined) {
grid[xi][yi] = [go];
} else {
grid[xi][yi].push(go);
}
go.arrs.push(grid[xi][yi]);
go.idxs.push(grid[xi][yi].length - 1);
}
}
}
};
function removeGridObj(go) {
var i;
for (i = 0; i < go.arrs.length; i++) {
go.arrs[i][go.idxs[i]] = undefined;
}
go.el.style.visibility = "hidden";
go.el.khPicked = true;
delete go.arrs;
delete go.idxs;
}
/**
* cb(gridObj) -> boolean true if the object should be removed.
*/
this.removeIntersecting = function (x, y, r, cb) {
var xi, yi, arr, i, r2 = r * r, go,
startXI = Math.floor((x - r) / GRIDX),
startYI = Math.floor((y - r) / GRIDY),
endXI = Math.floor((x + r) / GRIDX) + 1,
endYI = Math.floor((y + r) / GRIDY) + 1;
for (xi = startXI; xi < endXI; xi++) {
if (grid[xi] === undefined) {
continue;
}
for (yi = startYI; yi < endYI; yi++) {
arr = grid[xi][yi];
if (arr === undefined) {
continue;
}
for (i = 0; i < arr.length; i++) {
go = arr[i];
if (go !== undefined &&
circleGridObjInt(x, y, r, r2, go) &&
cb(go)) {
removeGridObj(go);
}
}
}
}
};
}
function PlayerBall(parentNode, stickyNodes, ballOpts) {
var x = 300, y = 300,
vx = 0, vy = 0,
radius = 20,
lastR = 0, /**< optimization: only resize when necessary. */
docW = 10000, docH = 10000,
attached = [],
attachedDiv, /* div to put attached nodes into. */
canvas_el,
canvas_ctx,
color = ballOpts.color,
accelTargetX = 0, accelTargetY = 0,
accel = false,
VOL_MULT = ballOpts.VOL_MULT,
MAX_ATTACHED_VISIBLE = ballOpts.MAX_ATTACHED_VISIBLE,
CHECK_VOLS = ballOpts.CHECK_VOLS,
/**
* which direction the ball is facing in the xy axis, in radians.
* th: 0 is facing dead East
* th: 1/2 PI is facing dead South
* note that this is like regular th on a graph with y inverted.
* Same rotation as css transform.
*/
th = 0,
/**
* Ball angle in the rotation axis / z plane, in radians.
* phi: 0 is pointing in the direction the ball is rolling.
* phi: 1/2 PI is pointing straight up (out of the page).
* note that forward rotation means phi -= 0.1.
*/
phi = 0;
this.init = function () {
canvas_el = document.createElement('canvas');
canvas_el.width = radius * 2;
canvas_el.height = radius * 2;
canvas_el.style.cssText = 'position: absolute; z-index: 500;';
parentNode.appendChild(canvas_el);
canvas_ctx = canvas_el.getContext('2d');
attachedDiv = document.createElement('div');
// attachedDiv.setAttribute("id", "capture")
parentNode.appendChild(attachedDiv);
};
this.setRadius = function (r) {
radius = r;
};
this.getState = function () {
return {
x: x,
y: y,
vx: vx,
vy: vy,
radius: radius,
th: th,
phi: phi,
};
};
this.setState = function (s) {
x = s.x;
y = s.y;
vx = s.vx;
vy = s.vy;
radius = s.radius;
th = s.th;
phi = s.phi;
};
this.setXY = function (sx, sy) {
x = sx;
y = sy;
};
this.setTh = function (sth) {
th = sth;
};
this.setPhi = function (sphi) {
phi = sphi;
};
this.setColor = function (c) {
color = c;
};
this.setDocSize = function (w, h) {
docW = w;
docH = h;
};
this.setAccel = function (bool) {
accel = bool;
};
this.setAccelTarget = function (tx, ty) {
accelTargetX = tx;
accelTargetY = ty;
};
function getVol() {
return (4 * Math.PI * radius * radius * radius / 3);
}
function grow(go) {
var newVol = getVol() + gridObjVol(go) * VOL_MULT;
radius = Math.pow(newVol * 3 / (4 * Math.PI), 1 / 3);
}
function attachGridObj(go) {
var attXY = getClosestPoint(x, y, go),
dx = attXY[0] - x,
dy = attXY[1] - y,w
r = Math.sqrt(dx * dx + dy * dy),
attTh = 0 - th,
offLeft = attXY[0] - go.left,
offTop = attXY[1] - go.top,
offTh = Math.atan2(dy, dx) - th,
attX = r * Math.cos(offTh),
attY = r * Math.sin(offTh),
el = go.el.cloneNode(true),
go_jel = jQuery(go.el),
newAtt = {
el: el,
attX: attX,
attY: attY,
attT: 'translate(' + Math.round(attX) + 'px,' +
Math.round(attY) + 'px) ' +
'rotate(' + attTh + 'rad)',
r: r,
offTh: offTh,
offPhi: 0 - phi,
diag: go.diag,
removeR: r + go.diag,
visible: false,
display: go_jel.css('display')
};
attached.push(newAtt);
// grow(go);
el.style.position = 'absolute';
el.style.left = (-offLeft) + 'px';
el.style.top = (-offTop) + 'px';
el.style.setProperty(CSS_TRANSFORM_ORIGIN,
offLeft + 'px ' + offTop + 'px', null);
el.style.display = 'none';
/* copy computed styles from old object. */
el.style.color = go_jel.css('color');
el.style.textDecoration = go_jel.css('text-decoration');
el.style.fontSize = go_jel.css('font-size');
el.style.fontWeight = go_jel.css('font-weight');
el.khIgnore = true;
attachedDiv.appendChild(el);
}
/**
* @return true if the object should be removed from stickyNodes.
*/
function removeIntCb(go) {
if (CHECK_VOLS && gridObjVol(go) > getVol()) {
return false;
}
attachGridObj(go);
return true;
}
this.updatePhysics = function () {
var oldX = x, oldY = y, dx, dy,
bounce = false,
accelTh;
if (accel) {
accelTh = Math.atan2(accelTargetY - y, accelTargetX - x);
vx += Math.cos(accelTh) * 0.5;
vy += Math.sin(accelTh) * 0.5;
} else {
vx *= 0.95;
vy *= 0.95;
}
x += vx;
y += vy;
/* bounce ball on edges of document. */
if (x - radius < 0) {
bounce = true;
x = radius + 1;
vx = -vx;
} else if (x + radius > docW) {
bounce = true;
x = docW - radius - 1;
vx = -vx;
}
if (y - radius < 0) {
bounce = true;
y = radius + 1;
vy = -vy;
} else if (y + radius > docH) {
bounce = true;
y = docH - radius - 1;
vy = -vy;
}
if (vx !== 0 || vy !== 0) {
th = Math.atan2(vy, vx);
dx = x - oldX;
dy = y - oldY;
/* arclen = th * r, so th = arclen / r. */
phi -= Math.sqrt(dx * dx + dy * dy) / radius;
}
stickyNodes.removeIntersecting(x, y, radius, removeIntCb);
this.draw();
};
function drawBall() {
var sx1, sy1, sx2, sy2, dx, dy, i, pct1, pct2, z1, z2;
/* move/resize canvas element. */
canvas_el.style.left = (x - radius) + 'px';
canvas_el.style.top = (y - radius) + 'px';
if (radius != lastR) {
canvas_el.width = 2 * radius + 1;
canvas_el.height = 2 * radius + 1;
lastR = radius;
}
/* draw white circle. */
canvas_ctx.clearRect(0, 0, 2 * radius, 2 * radius);
canvas_ctx.fillStyle = "#ff0000";
canvas_ctx.beginPath();
canvas_ctx.arc(radius, radius, radius - 1, 0, Math.PI * 2, true);
canvas_ctx.fill();
/* draw outer border. */
canvas_ctx.strokeStyle = color;
canvas_ctx.beginPath();
canvas_ctx.arc(radius, radius, radius - 1, 0, Math.PI * 2, true);
canvas_ctx.stroke();
/* draw stripes. */
canvas_ctx.fillStyle = color;
sx1 = radius + radius * Math.cos(th + Math.PI / 16);
sy1 = radius + radius * Math.sin(th + Math.PI / 16);
sx2 = radius + radius * Math.cos(th - Math.PI / 16);
sy2 = radius + radius * Math.sin(th - Math.PI / 16);
dx = (radius + radius * Math.cos(th + Math.PI * 15 / 16)) - sx1;
dy = (radius + radius * Math.sin(th + Math.PI * 15 / 16)) - sy1;
for (i = 0; i < Math.PI * 2; i += Math.PI / 7) {
pct1 = (-Math.cos(phi + i) + 1) / 2;
pct2 = (-Math.cos(phi + i + Math.PI / 32) + 1) / 2;
z1 = Math.sin(phi + i);
z2 = Math.sin(phi + i + Math.PI / 32);
if (z1 > 0 && z2 > 0) {
canvas_ctx.beginPath();
canvas_ctx.moveTo(sx1 + pct1 * dx, sy1 + pct1 * dy);
canvas_ctx.lineTo(sx1 + pct2 * dx, sy1 + pct2 * dy);
canvas_ctx.lineTo(sx2 + pct2 * dx, sy2 + pct2 * dy);
canvas_ctx.lineTo(sx2 + pct1 * dx, sy2 + pct1 * dy);
canvas_ctx.fill();
}
}
}
/**
* @return true if the attached object is roughly visible.
*/
function drawAttached(att) {
var oth = th + att.offTh,
ophi = phi + att.offPhi,
ox = att.r * Math.cos(oth),
oy = att.r * Math.sin(oth),
dx = (att.r * Math.cos((th - att.offTh) + Math.PI)) - ox,
dy = (att.r * Math.sin((th - att.offTh) + Math.PI)) - oy,
pct = (-Math.cos(ophi) + 1) / 2,
cx = ox + pct * dx,
cy = oy + pct * dy,
oz = att.r * Math.sin(ophi);
if (oz < 0 && Math.sqrt(cx * cx + cy * cy) + att.diag < radius) {
/* hidden behind circle. */
if (att.visible) {
att.visible = false;
att.el.style.display = "none";
}
return false;
}
/* attached node is visible. */
if (!att.visible) {
att.visible = true;
att.el.style.display = att.display;
}
//att.el.style.zIndex = 500 + Math.round(oz);
att.el.style.zIndex = (oz > 0)? 501 : 499;
att.el.style.setProperty(
CSS_TRANSFORM,
'translate(' + x + 'px,' + y + 'px) ' +
'rotate(' + th + 'rad) ' +
'scaleX(' + Math.cos(ophi) + ') ' +
att.attT, null);
return true;
}
function onAttachedRemoved(att) {
attachedDiv.removeChild(att.el);
delete att.el;
}
this.draw = function () {
var i, att, numAttachedVisible = 0;
drawBall();
for (i = attached.length; --i >= 0;) {
att = attached[i];
if (att.removeR < radius) {
attached.splice(i, 1).map(onAttachedRemoved);
} else if (drawAttached(att)) {
if (++numAttachedVisible > MAX_ATTACHED_VISIBLE) {
/* remove older items and stop. */
attached.splice(0, i).map(onAttachedRemoved);
break;
}
}
}
};
}
function preventDefault(event) {
event.preventDefault();
event.returnValue = false;
return false;
}
function Game(gameDiv, stickyNodes, ballOpts) {
var stickyNodes, player1, physicsInterval, resizeInterval, listeners = [];
player1 = new PlayerBall(gameDiv, stickyNodes, ballOpts, false);
player1.init();
player1.setXY(300, 300);
window.scrollTo(0, 200);
function on_resize() {
player1.setDocSize(jQuery(document).width() - 5,
jQuery(document).height() - 5);
}
on_resize();
player1.setAccel(true)
// if (ballOpts.MOUSEB === -5) {
document.addEventListener('touchstart', function (event) {
if (event.touches.length === 1) {
player1.setAccel(true);
return preventDefault(event);
}
}, true);
document.addEventListener('touchmove', function (event) {
player1.setAccelTarget(event.touches[0].pageX,
event.touches[0].pageY);
}, true);
document.addEventListener('touchend', function (event) {
if (event.touches.length === 0) {
player1.setAccel(false);
return preventDefault(event);
}
}, true);
// }
// else {
// /* mouse buttons */
// document.addEventListener('mousemove', function (event) {
// player1.setAccelTarget(event.pageX, event.pageY);
// }, true);
// document.addEventListener('mousedown', function (event) {
// if (event.button === ballOpts.MOUSEB) {
// player1.setAccel(true);
// return preventDefault(event);
// }
// }, true);
// document.addEventListener('mouseup', function (event) {
// if (event.button === ballOpts.MOUSEB) {
// player1.setAccel(false);
// return preventDefault(event);
// }
// }, true);
// }
physicsInterval = setInterval(function () {
player1.accelTargetX = Math.random()*window.innerHeight;
player1.accelTargetY = Math.random()*window.innerWidth;
player1.updatePhysics();
}, 25);
resizeInterval = setInterval(on_resize, 1000);
}
function whenAllLoaded(gameDiv, stickyNodes) {
stickyNodes.finalize(jQuery(document).width(), jQuery(document).height());
var game, ballOpts;
ballOpts = {
color: 0x00FF00,
VOL_MULT: 1.0,
MAX_ATTACHED_VISIBLE: 9000,
CHECK_VOLS: false,
MOUSEB: 0 // 0 FOR DESKTOP OR -5 FOR MOBILE
};
game = new Game(gameDiv, stickyNodes, ballOpts);
// Download image
var btn = document.getElementById('imgButton');
btn.addEventListener("touchend", function(event) {
console.log("capture", document.getElementById("capture"));
html2canvas(document.getElementById("capture")).then(function(canvas) {
var link = document.createElement("a");
document.body.appendChild(link);
link.download = "ridindirtyberlin.png";
link.href = canvas.toDataURL("image/png");
link.target = '_blank';
link.click();
});
})
startPeristentVibrate(300, 430);
}
function main() {
var gameDiv, checkInterval, stickyNodes, dlbtn;
gameDiv = document.createElement('div');
gameDiv.khIgnore = true;
dlbtn = document.getElementById("button-wrapper");
dlbtn.khIgnore = true;
document.body.appendChild(gameDiv);
setTimeout(function () {
var i, len, el;
window.khNodes.addWords(document.body);
for (i = 0, len = document.body.childNodes.length; i < len; i++) {
el = document.body.childNodes[i];
window.khNodes.addTagNames(el, [
'canvas', 'iframe', 'img', 'input', 'select',
'textarea', 'video', 'marquee', 'progress'
]);
}
checkInterval = setInterval(function () {
if (window.jQuery) {
clearInterval(checkInterval);
whenAllLoaded(gameDiv, window.khNodes);
}
}, 100);
}, 0);
}
if (!window.noMain) {
main();
var sound = new Howl({
src: ['snd/gabba140.mp3'],
autoplay: true,
loop: true,
volume: 1
});
}