Skip to content

Instantly share code, notes, and snippets.

@SAVE-UP
Created November 30, 2018 19:10
Show Gist options
  • Save SAVE-UP/896d0474ae689d2ced2d891c06618c55 to your computer and use it in GitHub Desktop.
Save SAVE-UP/896d0474ae689d2ced2d891c06618c55 to your computer and use it in GitHub Desktop.
Homo Noosphericus
/*
Artwork by Angelo Plessas | http://homonoosphericus.com/
Code by Gerard Ferrandez | https://codepen.io/ge1doot/pen/MzGPJY
*/
"use strict";
{
const points = [];
const constraints = [];
const shapes = [];
const kImgScale = 0.6;
const kGravity = 0.1;
const base = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/222599/";
Math.sign = Math.sign || function(x) {
return x > 0 ? 1 : -1;
};
/////////////// Point Class //////////////////
const Point = class {
constructor(p) {
this.x = canvas.width * 0.5 + p.x * canvas.scale;
this.y = p.y * canvas.scale;
this.oldX = this.x;
this.oldY = this.y;
this.radius = (p.r || 1.0) * kImgScale * canvas.scale;
this.mass = p.m === undefined ? 1.0 : p.m;
this.gravity = p.g === undefined ? kGravity : p.g;
this.static = p.static === undefined ? false : true;
this.ground = p.ground === undefined ? false : true;
this.audio = p.audio === undefined ? Math.floor(Math.random() * 5) : p.audio;
this.pointer = pointer;
this.canvas = canvas;
}
integrate(dt) {
if (this.static === true) return;
const x = this.x;
const y = this.y;
this.x += (this.x - this.oldX) + this.canvas.wind;
this.y += (this.y - this.oldY) + this.gravity;
this.oldX = x;
this.oldY = y;
if (this.ground === true) {
if (this.y > this.canvas.height - this.radius) {
this.x = x;
this.y = this.canvas.height - this.radius;
}
}
if (!this.pointer.draggable) {
const dx = this.x - this.pointer.x;
const dy = this.y - this.pointer.y;
if (Math.sqrt(dx * dx + dy * dy) < this.radius)
this.pointer.draggable = true;
}
}
dist(p) {
const dx = this.x - p.x;
const dy = this.y - p.y;
return Math.sqrt(dx * dx + dy * dy);
}
};
/////////////// Angle Constraint Class //////////////////
const AngleConstraint = class {
constructor(struct, c) {
this.p1 = struct.points[c.p1];
this.p2 = struct.points[c.p2];
this.p3 = struct.points[c.p3];
this.len1 = this.p1.dist(this.p2);
this.len2 = this.p2.dist(this.p3);
this.angle = c.a;
this.range = c.r;
this.force = c.f || 0.2;
}
// solve 2 vectors angled constraint
// http://stackoverflow.com/questions/16336702/ragdoll-joint-angle-constraints
solve() {
let e = 0;
{
const a = Math.atan2(this.p2.y - this.p1.y, this.p2.x - this.p1.x);
const b = Math.atan2(this.p3.y - this.p2.y, this.p3.x - this.p2.x);
const c = this.angle - (b - a);
const d = c > Math.PI ? c - 2 * Math.PI : c < -Math.PI ? c + 2 * Math.PI : c;
e = Math.abs(d) > this.range ? (-Math.sign(d) * this.range + d) * this.force : 0;
const m = this.p1.mass + this.p2.mass;
const m1 = this.p1.mass / m;
const m2 = this.p2.mass / m;
const cos = Math.cos(a - e);
const sin = Math.sin(a - e);
const x1 = this.p1.x + (this.p2.x - this.p1.x) * m2;
const y1 = this.p1.y + (this.p2.y - this.p1.y) * m2;
this.p1.x = x1 - cos * this.len1 * m2;
this.p1.y = y1 - sin * this.len1 * m2;
this.p2.x = x1 + cos * this.len1 * m1;
this.p2.y = y1 + sin * this.len1 * m1;
}{
const a = Math.atan2(this.p2.y - this.p3.y, this.p2.x - this.p3.x) + e;
const m = this.p2.mass + this.p3.mass;
const m2 = this.p2.mass / m;
const m3 = this.p3.mass / m;
const cos = Math.cos(a);
const sin = Math.sin(a);
const x1 = this.p3.x + (this.p2.x - this.p3.x) * m2;
const y1 = this.p3.y + (this.p2.y - this.p3.y) * m2;
this.p3.x = x1 - cos * this.len2 * m2;
this.p3.y = y1 - sin * this.len2 * m2;
this.p2.x = x1 + cos * this.len2 * m3;
this.p2.y = y1 + sin * this.len2 * m3;
}
}
};
/////////////// Constraint Class //////////////////
const Constraint = class {
constructor(struct, c) {
this.p1 = struct.points[c.p1];
this.p2 = struct.points[c.p2];
this.len = c.len === undefined ? this.p1.dist(this.p2) : c.len;
this.force = c.f || 1.0;
}
solve() {
const dx = this.p1.x - this.p2.x;
const dy = this.p1.y - this.p2.y;
const d = Math.sqrt(dx * dx + dy * dy);
const tm = this.p1.mass + this.p2.mass;
const d2 = (d - (d + (this.len - d) * this.force)) / d * 0.5;
if (this.p1.static === false) {
const s2 = d2 * (this.p2.mass / tm);
this.p1.x -= (dx * s2);
this.p1.y -= (dy * s2);
}
if (this.p2.static === false) {
const s1 = d2 * (this.p1.mass / tm);
this.p2.x += (dx * s1);
this.p2.y += (dy * s1);
}
}
};
/////////////// Shape Class //////////////////
const Shape = class {
constructor(struct, i) {
this.p1 = struct.points[i.p1];
this.p2 = struct.points[i.p2];
this.img = new Image();
this.img.src = document.getElementById(i.img) ? document.getElementById(i.img).src : base + i.img;
this.ox = i.ox * kImgScale * canvas.scale;
this.oy = i.oy * kImgScale * canvas.scale;
this.w = i.w * kImgScale * canvas.scale;
this.h = i.h * kImgScale * canvas.scale;
this.angle = i.a * 1.0;
this.loaded = false;
this.ctx = ctx;
}
draw() {
const a = Math.atan2(this.p2.y - this.p1.y, this.p2.x - this.p1.x) + this.angle;
const cos = Math.cos(a);
const sin = Math.sin(a);
this.ctx.setTransform(cos, sin, -sin, cos, this.p1.x, this.p1.y);
if (this.loaded) {
this.ctx.drawImage(this.img, -this.ox - 1, -this.oy - 1);
} else {
if (this.img.complete) {
this.loaded = true;
let img;
if (window.OffscreenCanvas) {
img = new OffscreenCanvas(this.w + 2, this.h + 2);
} else {
img = document.createElement('canvas');
img.width = this.w + 2;
img.height = this.h + 2;
}
img.getContext("2d").drawImage(this.img, 1, 1, this.w, this.h);
this.img = img;
}
ctx.beginPath();
this.ctx.strokeStyle = "#333";
this.ctx.setLineDash([]);
this.ctx.strokeRect(-this.ox, -this.oy, this.w, this.h);
}
}
};
/////////////// Stroke Class //////////////////
const Stroke = class {
constructor(struct, s) {
this.p1 = struct.points[s.p1];
this.p2 = struct.points[s.p2];
this.color = s.c;
}
draw() {
ctx.beginPath();
ctx.strokeStyle = this.color;
ctx.lineWidth = 2;
ctx.setLineDash([1, 1]);
ctx.moveTo(this.p1.x, this.p1.y);
ctx.lineTo(this.p2.x, this.p2.y);
ctx.stroke();
}
};
/////////////// Main Loop //////////////////
const run = () => {
requestAnimationFrame(run);
ctx.clearRect(0, 0, canvas.width, canvas.height);
pointer.dragging();
ctx.save();
for (const s of shapes) s.draw();
ctx.restore();
for (const p of points) p.integrate();
for (const c of constraints) c.solve();
pointer.cursor();
canvas.tick();
}
/////////////// canvas //////////////////
const canvas = {
wind: 0,
windf: 0,
elem: document.createElement("canvas"),
resize: function(struct) {
this.width = this.elem.width = this.elem.offsetWidth;
this.height = this.elem.height = this.elem.offsetHeight;
this.scale = canvas.height / 900;
if (struct) {
struct.points.mong30.x = canvas.width * 0.5 - canvas.width * 0.25 * this.scale;
struct.points.mong30.y = this.height;
struct.points.mong10.x = this.width * 0.5;
struct.points.mong20.x = this.width * 0.5;
}
},
init: function() {
const ctx = canvas.elem.getContext("2d", {lowLatency: true, alpha: false});
if (!ctx.setLineDash) ctx.setLineDash = function () {};
document.body.appendChild(canvas.elem);
window.addEventListener("resize", e => this.resize(struct), false);
this.resize();
return ctx;
},
tick() {
this.windf += 0.005;
this.wind = 0.05 * Math.sin(this.windf * 0.5) * Math.cos(this.windf);
}
};
const ctx = canvas.init();
/////////////// Pointer //////////////////
const pointer = {
x: 0,
y: 0,
drag: null,
draggable: false,
cursor: function() {
canvas.elem.className = this.drag
? "dragging"
: this.draggable ? "draggable" : "default";
},
dragging: function() {
this.draggable = false;
if (pointer.drag) {
this.drag.x += (this.x - this.drag.x) / 20;
this.drag.y += (this.y - this.drag.y) / 20;
}
},
pointer: function(e) {
let pointer;
if (e.targetTouches) {
e.preventDefault();
pointer = e.targetTouches[0];
} else pointer = e;
return pointer;
},
addEvents: function() {
[
[
window,
"mousemove,touchmove",
function(e) {
const pointer = this.pointer(e);
this.x = pointer.clientX;
this.y = pointer.clientY;
}
],
[
canvas.elem,
"mousedown,touchstart",
function(e) {
if (!this.drag) {
const pointer = this.pointer(e);
this.x = pointer.clientX;
this.y = pointer.clientY;
let dm = 9999;
for(const p of points) {
const dx = p.x - this.x;
const dy = p.y - this.y;
const d = Math.sqrt(dx * dx + dy * dy);
if (d < p.radius * 2) {
if (d < dm) {
dm = d;
this.drag = p;
}
}
}
if (this.drag) {
if (audio !== null) audio[this.drag.audio].play();
}
}
}
],
[
window,
"mouseup,touchend,touchcancel",
function() {
this.drag = null;
}
]
].forEach(
function(e) {
for (let i = 0, events = e[1].split(","); i < events.length; i++) {
e[0].addEventListener(events[i], e[2].bind(this), false);
}
}.bind(this)
);
}
};
pointer.addEvents();
/////////////// Homo Noosphericus structure //////////////////
const struct = {
points: {
p0: {x: -45, y: 45, r: 30, m: 1},
p1: {x: 0, y: -75, r: 135, m: 1, ground: true},
p2: {x: 0, y: 290, r: 20, m: 1},
p3: {x: -35, y: 280, r: 30, m: 1, ground: true},
p4: {x: 50, y: 45, r: 30, m: 1},
p5: {x: 20, y: 280, r: 30, m: 1, ground: true},
p6: {x: 50, y: 430, r: 20, m: 0.5, g: -0.06, ground: true},
p7: {x: -46, y: 430, r: 20, m: 0.5, g: -0.06, ground: true},
p8: {x: 50, y: 660, r: 40, m: 0.35, ground: true},
p9: {x: -46, y: 660, r: 40, m: 0.35, ground: true},
p10: {x: -187, y: 100, r: 30, m: 0.5},
p11: {x: 187, y: 100, r: 30, m: 0.5},
p12: {x: -380, y: 115, r: 60, m: 0.35, g: -0.02},
p13: {x: 380, y: 115, r: 60, m: 0.35, g: -0.02},
p14: {x: 0, y: 90, r: 50, m: 30, g: 0.2},
p15: {x: 0, y: 140, r: 0},
p16: {x: 0, y: -110, r: 4, m: 0.1, g: 0},
mong10: {x: 0, y: -200, static: true},
mong11: {x: -350, y: 0, r: 130, m: 10},
mong12: {x: -350, y: 10, r: 0, m: 0, g: 0},
mong20: {x: 0, y: -200, static: true},
mong21: {x: 350, y: 0, r: 130, m: 10},
mong22: {x: 350, y: 100, r: 0, m: 0, g: 0},
mong30: {x: -canvas.width * 0.25 * canvas.scale, y: 1 / canvas.scale * canvas.height, r: 0, static: true},
mong31: {x: 0, y: 1 / canvas.scale * canvas.height * 0.6, r: 250, m: 10, g: -0.03, audio: 5},
mong32: {x: 0, y: 1 / canvas.scale * canvas.height * 0.6 - 100, r: 1, m: 0, g: 0},
mong13: {x: -350, y: 300, r: 50, m: 1},
mong14: {x: -350, y: 350, r: 50, m: 0.1},
mong15: {x: -350, y: 400, r: 50, m: 1},
mong16: {x: -350, y: 450, r: 50, m: 0.1},
mong23: {x: 350, y: 300, r: 50, m: 1},
mong24: {x: 350, y: 350, r: 50, m: 0.1},
mong25: {x: 350, y: 400, r: 50, m: 1},
mong26: {x: 350, y: 450, r: 50, m: 0.1}
},
constraints: [
{p1: "p0", p2: "p1"},
{p1: "p1", p2: "p2"},
{p1: "p2", p2: "p3"},
{p1: "p0", p2: "p2"},
{p1: "p1", p2: "p3"},
{p1: "p1", p2: "p4"},
{p1: "p5", p2: "p2"},
{p1: "p1", p2: "p5"},
{p1: "p2", p2: "p4"},
{p1: "p0", p2: "p4"},
{p1: "p3", p2: "p5"},
{p1: "p3", p2: "p4"},
{p1: "p0", p2: "p10"},
{p1: "p4", p2: "p11"},
{p1: "p10", p2: "p12"},
{p1: "p11", p2: "p13"},
{p1: "p14", p2: "p3"},
{p1: "p14", p2: "p0"},
{p1: "p14", p2: "p4"},
{p1: "p14", p2: "p5"},
{p1: "p14", p2: "p15"},
{p1: "p14", p2: "p1"},
{p1: "p14", p2: "p2"},
{p1: "p1", p2: "p16"},
{p1: "mong10", p2: "mong11", f: 0.5},
{p1: "mong11", p2: "mong12"},
{p1: "mong20", p2: "mong21", f: 0.5},
{p1: "mong21", p2: "mong22"},
{p1: "mong30", p2: "mong31", f: 0.1, len: canvas.height * 0.33},
{p1: "mong31", p2: "mong32"},
{p1: "mong11", p2: "mong21", f: 0.02},
{p1: "mong11", p2: "p14", f: 0.5},
{p1: "mong21", p2: "p14", f: 0.5}
],
angleConstraints: [
{p1: "p0", p2: "p3", p3: "p7", a: 1.2, r: Math.PI / 2.5, f: 0.2},
{p1: "p4", p2: "p5", p3: "p6", a: -1.2, r: Math.PI / 2.5, f: 0.2},
{p1: "p3", p2: "p7", p3: "p9", a: -1.2, r: Math.PI / 2, f: 0.2},
{p1: "p5", p2: "p6", p3: "p8", a: 1.2, r: Math.PI / 2, f: 0.2}
],
strokes: [
{c: "#fff", p1: "mong30", p2: "mong31"},
{c: "#fff", p1: "mong10", p2: "mong11"},
{c: "#fff", p1: "mong20", p2: "mong21"},
{c: "#fff", p1: "mong11", p2: "p14"},
{c: "#fff", p1: "mong21", p2: "p14"}
],
images: [
{img: "YinYan.png", p1: "mong31", p2: "mong32", ox: 250, oy: 250, w: 500, h: 500, a: Math.PI/2},
{img:"ballleft.png", p1: "mong11", p2: "mong12", ox: 139, oy: 139, w: 278, h: 278, a: -Math.PI/2},
{img:"ballright.png", p1: "mong21", p2: "mong22", ox: 139, oy: 139, w: 278, h: 278, a: 0},
{img:"foottoer.png", p1: "p6", p2: "p8", ox: 20, oy: 150, w: 447, h: 192, a: 0.15},
{img:"upperlegr.png", p1: "p5", p2: "p6", ox: 30, oy: 60, w: 330, h: 120, a: 0},
{img:"foottoe.png", p1: "p7", p2: "p9", ox: 20, oy: 50, w: 447, h: 192, a: -0.1},
{img:"upperleg.png", p1: "p3", p2: "p7", ox: 30, oy: 60, w: 330, h: 120, a: 0},
{img:"body.png", p1: "p2", p2: "p1", ox: 30, oy: 264 * 0.5, w: 514, h: 264, a: 0},
{img:"heady.png", p1: "p1", p2: "p16", ox: 272 / 2, oy: 272 / 2, w: 272, h: 271, a: 0},
{img:"heart.png", p1: "p14", p2: "p15", ox: 75, oy: 44, w: 88, h: 91, a: Math.PI},
{img:"upperarml.png", p1: "p0", p2: "p10", ox: 16, oy: 7, w: 287, h: 91, a: -0.12},
{img:"upperarm.png", p1: "p4", p2: "p11", ox: 16, oy: 83, w: 287, h: 91, a: 0.1},
{img:"handleft.png", p1: "p11", p2: "p13", ox: 30, oy: 44, w: 447, h: 95, a: 0},
{img:"handleft.png", p1: "p10", p2: "p12", ox: 30, oy: 44, w: 447, h:95, a: 0},
]
};
////////////// sounds ///////////////
let audio = null;
if (window.Audio) {
audio = [
new Audio(base + '1.mp3'),
new Audio(base + '2.mp3'),
new Audio(base + '3.mp3'),
new Audio(base + '4.mp3'),
new Audio(base + '5.mp3'),
new Audio(base + 'YinYan.mp3')
];
}
/////////////// Build //////////////////
const init = struct => {
for (let p in struct.points) {
const o = struct.points[p];
const point = new Point(o);
struct.points[p] = point;
points.push(point);
}
for (const c of struct.constraints) {
constraints.push(
new Constraint(struct, c)
);
}
for (const c of struct.angleConstraints) {
constraints.push(new AngleConstraint(struct, c));
}
for (const s of struct.strokes) {
shapes.push(new Stroke(struct, s));
}
for (const i of struct.images) {
shapes.push(new Shape(struct, i));
}
};
init(struct);
run();
}
html {
overflow: hidden;
touch-action: none;
content-zooming: none;
}
body {
position: absolute;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #000;
}
canvas {
position:absolute;
width:100%;
height:100%;
background: #000;
}
.draggable {
cursor: pointer;
cursor: -webkit-grab;
}
.dragging {
cursor: move;
cursor: -webkit-grabbing;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment