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; }