Skip to content

Instantly share code, notes, and snippets.

@lboullo0
Created June 28, 2021 21:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lboullo0/90c3608963ad07571dd5783b113b0b22 to your computer and use it in GitHub Desktop.
Save lboullo0/90c3608963ad07571dd5783b113b0b22 to your computer and use it in GitHub Desktop.
Fracture
import { SVG } from 'https://cdn.skypack.dev/@svgdotjs/svg.js'
import { Vec2 } from 'https://cdn.skypack.dev/wtc-math';
console.clear();
const config = {
drawingType: 1,
dimensions: (new Vec2(window.innerWidth, window.innerHeight)).scale(2),
breakRun: 2000,
maxDepth: 12,
maxPerRun: 100,
insertType: 1,
randomType: 0
};
const vars = {
drawing: null,
i:0,
running: true,
triangles: []
}
const setup = () => {
vars.running = false;
setTimeout(() => {
vars.running = true;
vars.triangles = [];
config.insertType = Math.floor(Math.random() * 3);
config.randomType = Math.floor(Math.random() * 3);
vars.i=0;
document.querySelector('#container').innerHTML = '';
vars.drawing = new Drawing(config.drawingType).addTo('#container').size(config.dimensions);
document.body.querySelector('#container>:first-child').addEventListener('click', () => {
setup();
});
draw();
}, 100);
};
window.addEventListener('resize', () => {
config.dimensions = ( new Vec2(window.innerWidth, window.innerHeight)).scale(2)
setup();
});
let depth = 0;
const ts = [
['a', 'b'],
['b', 'c'],
['c', 'a']
];
const drawStep = () => {
if(!vars.running) return;
if(vars.i > 2000) return;
let newTriangles = [];
vars.triangles.forEach((triangle,i) => {
if(i>config.breakRun) {
// newTriangles.splice(0,0,triangle);
return;
}
vars.drawing.polygon(triangle.points);
let c = triangle.randomCentroid;
if(config.randomType == 0) {
c = triangle.centroid;
}
// vars.drawing.circle(c, 5);
if(triangle.depth < config.maxDepth) {
ts.forEach(t => {
const tr = new Triangle(triangle[t[0]], triangle[t[1]], c);
tr.depth=triangle.depth+1;
newTriangles.push(tr);
});
}
});
if(config.insertType === 0) {
if(vars.triangles.length>config.breakRun) newTriangles = vars.triangles.splice(config.breakRun).concat(newTriangles);
} else if(config.insertType === 1) {
if(vars.triangles.length>config.breakRun) newTriangles = vars.triangles.splice(config.breakRun).reverse().concat(newTriangles);
} else {
newTriangles = newTriangles.concat(vars.triangles.splice(config.breakRun));
}
vars.triangles = newTriangles;
vars.i++;
requestAnimationFrame(drawStep);
}
let interval;
const draw = () => {
vars.drawing.linewidth = 1;
vars.drawing.fill = "#333";
vars.drawing.rect(new Vec2(0,0), config.dimensions);
vars.drawing.linewidth = 1;
vars.drawing.fill = null;
vars.drawing.stroke = 'rgba(255,255,255,.02)'
const r = Math.min(config.dimensions.x, config.dimensions.y) * .6;
const offset = r * 0.2333;
const points = [];
for(let i = 0.; i < Math.PI * 2; i += Math.PI * 2. / 3.) {
points.push( new Vec2(Math.cos(i+Math.PI*.5) * r + config.dimensions.x / 2, Math.sin(i+Math.PI*.5) * r + config.dimensions.y / 2 - offset) );
}
const t = new Triangle(...points);
t.depth=0;
vars.triangles.push(t);
drawStep();
}
class Triangle {
#points
constructor(a, b, c) {
this.points = [a, b, c];
}
set points(p) {
if(p.reduce((a, c) => c instanceof Vec2 ? a+1 : 0, 0) === 3) this.#points = p;
}
get points() {
return this.#points || [];
}
get a() {
return this.#points[0];
}
get b() {
return this.#points[1];
}
get c() {
return this.#points[2];
}
get circumcenter() {
const d = this.b.subtractNew(this.a);
const e = this.c.subtractNew(this.a);
const bl = d.x * d.x + d.y * d.y;
const cl = e.x * e.x + e.y * e.y;
const ds = 0.5 / (d.x * e.y - d.y * e.x);
return new Vec2(
this.a.x + (e.y * bl - d.y * cl) * ds,
this.a.y + (d.x * cl - e.x * bl) * ds
);
}
get randomCentroid() {
let a = floatRandomBetween(.2,1.8);
let b = floatRandomBetween(.2,1.8);
let c = floatRandomBetween(.2,1.8);
let abc = a+b+c;
const f = (3-abc)/3;
a += f;
b += f;
c += f;
return this.a.scaleNew(a).add(this.b.scaleNew(b)).add(this.c.scaleNew(c)).divideScalar(3);
}
get centroid() {
const a = floatRandomBetween(.5,1.5);
const b = floatRandomBetween(.5,1.5);
const c = floatRandomBetween(.5,1.5);
const abc = a+b+c;
return this.a.addNew(this.b).add(this.c).divideScalar(3);
}
get isComplete() {
return this.points.reduce((a, c) => c instanceof Vec2 ? a+1 : 0, 0) === 3;
}
get segments() {
return [
{
a: this.a,
b: this.b,
c: this.c,
segment: this.a.subtractNew(this.b)
},
{
a: this.b,
b: this.c,
c: this.a,
segment: this.b.subtractNew(this.c)
},
{
a: this.c,
b: this.a,
c: this.b,
segment: this.c.subtractNew(this.a)
}
];
}
get angles() {
let ab = this.b.subtractNew(this.a).normalise();
let ac = this.c.subtractNew(this.a).normalise();
let ba = this.a.subtractNew(this.b).normalise();
let bc = this.c.subtractNew(this.b).normalise();
const ta = (Math.acos(ab.dot(ac)));
const tb = (Math.acos(ba.dot(bc)));
const tc = (Math.acos(ac.dot(bc)));
return [ta,tb,tc];
}
get isEqualateral() {
return this.numEqualSides === 3;
}
get isIsosceles() {
return this.numEqualSides === 2;
}
get isScalene() {
return this.numEqualSides === 1;
}
get isRightAngle() {
return this.angles.reduce((n, s) => n || precisionRound(Math.abs(s * 180 / Math.PI), 3) === 90, false);
}
get isObtuse() {
return this.angles.reduce((n, s) => n || precisionRound(Math.abs(s * 180 / Math.PI), 3) > 90, false);
}
get isAcute() {
return !this.isObtuse;
}
get numEqualSides() {
const ls = this.segments.map(seg => precisionRound(seg.segment.length, 3));
const n = ls.map(l => ls.reduce((n, l1) => n + (l1 === l), 0));
return n.reduce((a, b) => b > a ? b : a, 0);
}
get hypot() {
const segs = this.segments;
let longest = 0;
let hypotenuse = {};
segs.forEach(seg => {
if(seg.segment.length > longest) {
longest = seg.segment.length;
hypotenuse = seg;
}
});
return hypotenuse;
}
}
setTimeout(() => {
setup();
}, 500);
class Drawing {
static #defaults = {
stroke: '#333',
pxratio: 1
}
static DT_CANVAS = 1;
static DT_SVG = 2;
#drawing;
#ctx;
#mode;
#instructions = [];
constructor(mode = Drawing.DT_CANVAS, settings) {
settings = Object.assign({}, Drawing.#defaults, settings);
this.mode = mode;
if(this.mode & Drawing.DT_CANVAS) {
this.#drawing = document.createElement('canvas');
} else if(this.mode & Drawing.DT_SVG) {
this.#drawing = SVG();
}
this.stroke = settings.stroke;
this.pxratio = settings.pxratio;
}
clear() {
if(this.mode & Drawing.DT_CANVAS) {
this.c.clearRect(0,0,...this.dimensions.array);
} else if(this.mode & Drawing.DT_SVG) {
this.drawing.clear();
}
}
rect(position, dimensions) {
if(this.saving) {
this.#instructions.push({
f: 'rect',
args: [position, dimensions]
});
}
position = position.scaleNew(this.pxratio);
dimensions = dimensions.scaleNew(this.pxratio);
if(this.mode & Drawing.DT_CANVAS) {
this.c.beginPath();
this.c.rect(...position.array, ...dimensions.array);
if(this.stroke) this.c.stroke();
if(this.fill) this.c.fill();
} else if(this.mode & Drawing.DT_SVG) {
return this.drawing.rect(dimensions.width, dimensions.height).move(...position.array).fill("none").stroke(this.strokeParams);
}
}
circle(position, radius) {
if(this.saving) {
this.#instructions.push({
f: 'circle',
args: [position, radius]
});
}
position = position.scaleNew(this.pxratio);
radius *= this.pxratio;
if(this.mode & Drawing.DT_CANVAS) {
if(!window.trace) {
console.log(this.fill)
window.trace = true;
}
if(this.fill) this.c.fillStyle = this.fill;
this.c.beginPath();
this.c.arc(position.x, position.y, radius, 0, 2 * Math.PI);
if(this.stroke) this.c.stroke();
if(this.fill) this.c.fill();
} else if(this.mode & Drawing.DT_SVG) {
return this.drawing.circle(radius*2).fill("none").stroke(this.strokeParams).move(...position.subtractScalarNew(radius).array);
}
}
line(a, b) {
if(this.saving) {
this.#instructions.push({
f: 'line',
args: [a, b]
});
}
a = a.scaleNew(this.pxratio);
b = b.scaleNew(this.pxratio);
if(this.mode & Drawing.DT_CANVAS) {
this.c.beginPath();
this.c.moveTo(a.x, a.y);
this.c.lineTo(b.x, b.y);
if(this.stroke) this.c.stroke();
} else if(this.mode & Drawing.DT_SVG) {
return this.drawing.line(...a.array, ...b.array).stroke(this.strokeParams);
}
}
polyline(points) {
if(this.saving) {
this.#instructions.push({
f: 'polyline',
args: points
});
}
if(this.mode & Drawing.DT_CANVAS) {
this.c.beginPath();
points.forEach((p, i) => {
p = p.scaleNew(this.pxratio);
if(i === 0) this.c.moveTo(p.x, p.y);
else this.c.lineTo(p.x, p.y);
})
if(this.stroke) this.c.stroke();
} else if(this.mode & Drawing.DT_SVG) {
return this.drawing.polyline(points.map(p => p.array)).fill('none').stroke(this.strokeParams);
}
}
path(path) {
if(this.mode & Drawing.DT_CANVAS) {
this.c.beginPath();
const p1 = new Path2D(path);
const m = document.createElementNS("http://www.w3.org/2000/svg", "svg").createSVGMatrix()
const p = new Path2D();
const t = m.scale(this.pxratio);
p.addPath(p1, t);
if(this.stroke) this.c.stroke(p);
} else if(this.mode & Drawing.DT_SVG) {
const _path = this.drawing.path().fill('none').stroke(this.strokeParams);
_path.plot(path);
return _path;
}
}
polygon(points) {
if(this.saving) {
this.#instructions.push({
f: 'polygon',
args: [points]
});
}
if(this.mode & Drawing.DT_CANVAS) {
this.c.beginPath();
points.forEach((p, i) => {
p = p.scaleNew(this.pxratio);
if(i === 0) this.c.moveTo(p.x, p.y);
else this.c.lineTo(p.x, p.y);
})
if(this.stroke) this.c.stroke();
if(this.fill) this.c.fill();
} else if(this.mode & Drawing.DT_SVG) {
return this.drawing.polygon(points.map(p => p.array)).fill('none').stroke(this.strokeParams);
}
}
download() {
let d;
if(this.mode & Drawing.DT_CANVAS) {
d = new Drawing(Drawing.DT_SVG).size(this.dimensions);
this.#instructions.forEach((i) => {
d[i.f](...i.args);
});
} else if(this.mode & Drawing.DT_SVG) {
d = this;
}
const fileName = "untitled.svg"
const url = "data:image/svg+xml;utf8," + encodeURIComponent(d.drawing.svg());
const link = document.createElement("a");
link.download = fileName;
link.href = url;
link.click();
}
addTo(element) {
if(typeof(element) === 'string') {
if(this.mode & Drawing.DT_CANVAS) {
document.body.querySelector(element).appendChild(this.drawing);
} else if(this.mode & Drawing.DT_SVG) {
this.drawing.addTo('#container');
}
}
return this;
}
size(d) {
if(this.mode & Drawing.DT_CANVAS) {
this.drawing.width = d.width * this.#pxratio;
this.drawing.height = d.height * this.#pxratio;
} else if(this.mode & Drawing.DT_SVG) {
this.drawing.viewbox(0, 0, d.x, d.y)
this.drawing.size(...d.scaleNew(this.#pxratio).array);
}
this.#dimensions = d;
return this;
}
#dimensions
set dimensions(v) {
if(v instanceof Vec2) {
this.#dimensions = v;
this.size(v);
}
}
get dimensions() {
return this.#dimensions;
}
#pxratio = 1;
set pxratio(v) {
if(v > 0) {
this.#pxratio = v;
}
}
get pxratio() {
return this.#pxratio || 1;
}
get drawing() {
return this.#drawing;
}
get c() {
if(this.mode & Drawing.DT_CANVAS) {
if(this.#ctx) return this.#ctx;
this.#ctx = this.drawing.getContext('2d');
return this.#ctx;
}
}
#stroke;
set stroke(v) {
this.#stroke = v;
if(this.mode & Drawing.DT_CANVAS) {
this.c.strokeStyle = v;
}
}
get stroke() {
return this.#stroke;
}
#fill = null;
set fill(v) {
this.#fill = v;
if(this.mode & Drawing.DT_CANVAS) {
this.c.fillStyle = v;
}
}
get fill() {
return this.#fill;
}
#linecap = null;
set linecap(v) {
if(['square', 'butt', 'round'].indexOf(v) > -1) {
this.#linecap = v;
if(this.mode & Drawing.DT_CANVAS) {
this.c.lineCap = v;
}
}
}
get linecap() {
return this.#linecap;
}
#lineWidth
set linewidth(v) {
if(v > 0) {
this.#lineWidth = v;
if(this.mode & Drawing.DT_CANVAS) {
this.c.lineWidth = v*this.pxratio;
}
}
}
get linewidth() {
return this.#lineWidth;
}
get strokeParams() {
const params = {};
if(this.#lineWidth) params.width = this.#lineWidth;
if(this.#stroke) params.color = this.#stroke;
if(this.#linecap) params.linecap = this.#linecap;
return params;
}
set mode(v) {
if(v & (Drawing.DT_CANVAS | Drawing.DT_SVG)) {
this.#mode = v;
}
}
get mode() {
return this.#mode || Drawing.DT_CANVAS;
}
#saving = false
set saving(v) {
this.#saving = v === true;
}
get saving() {
return this.#saving;
}
}
/// Create the download button/
/*
const dl = document.createElement('button');
dl.innerText = 'download';
dl.addEventListener('click', () => {
vars.drawing.download();
});
document.body.querySelector('#container').appendChild(dl);
*/
const floatRandomBetween = (min, max) => {
return Math.random() * (max - min) + min;
};
const clamp = function(a, b, v) {
return Math.min(b, Math.max(a, v));
}
const lerp = function(a, b, progress) {
return a + progress * (b - a);
}
const hash21 = (p) => {
const o = p.dot(new Vec2(27.609, 57.583));
return fract(Math.sin(o)*config.seed);
}
const precisionRound = (n, p) => {
const ip = Math.pow(10, p);
return Math.round(n*ip)/ip;
}
const fract = (n, p = 6) => {
const ip = Math.pow(10, p);
const _n = Math.abs(Math.floor(n*ip)/ip);
if(_n < 1) return _n;
return Math.floor(_n % Math.floor(_n)*ip)/ip;
}
const pal = ( t, a, b, c, d ) => {
const mp = c.scaleNew(t).add(d).scale(6.28318);
mp.x = Math.cos(mp.x);
mp.y = Math.cos(mp.y);
mp.z = Math.cos(mp.z);
return a.addNew(b.multiplyNew(mp));
}
const getColour = (d) => {
const col = pal(
d/70+.65,
new Vec3(0.5,0.5,0.5),
new Vec3(0.5,0.5,0.5),
new Vec3(1.0,1.0,1.0),
new Vec3(0.0,0.1,0.2) );
const colour = Math.floor(col.x * 255).toString(16) + Math.floor(col.y * 255).toString(16) + Math.floor(col.z * 255).toString(16);
const a = Math.floor((1.-d/30)*255).toString(16);
return colour+a;
}
document, body {
margin: 0;
min-height: 100vh;
}
body {
align-items: center;
display: flex;
justify-content: center;
}
#container {
align-items: center;
display: flex;
flex-direction: column;
}
#container>:first-child {
cursor: pointer;
}
button {
max-width: 200px;
margin-top: 10px;
}
canvas, svg {
width:100vw;
height: 100vh;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment