Created
June 28, 2021 21:40
-
-
Save lboullo0/90c3608963ad07571dd5783b113b0b22 to your computer and use it in GitHub Desktop.
Fracture
This file contains 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
#container |
This file contains 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
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; | |
} | |
This file contains 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
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