cheese
Log in to post a comment.
// You can find the Turtle API reference here: https://turtletoy.net/syntax // let c_r = 15; // min = 1, max=40, step=0.1 let c_h = 8; // min = 1, max=40, step=0.1 let c_angle = 40; // min=1, max=180, step=1 let holes = 50; // min=1, max=300, step=1 let hole_size = 0.8;// min=0.01, max=10, step=0.001 let a1 = 15; // min = -720, max=720, step=1 let a2 = 30; // min = -90, max=90, step=1 let cd = 20; // min = 0, max=360, step=0.1 let perspective = 1; // min=0, max=1, step=1, (off, on) let viewSize = 200; // DIS min=10, max=600, step=1 let fov = 60; // min=10, max=160, step=0.1 let subdiv = 16; // DIS min=1, max=1024, step=1 let subdiv_target = 0.5; // min=0.01, max=50, step=0.01 const subdivExtra = 1; // DIS min=0, max=1, step=1 let grid_size = 1.5; // min = 0.05, max=20, step=0.05 let scene = null; let turtle = null; let circleSubdiv = 16; // min=3, max=256, step=1 let useGrid = 1; // min=0, max=2, step=1 const DEBUG_N_ON_SURFACE = true; const INCREMENTAL = true; let sceneGen = null; function init2() { // Global code will be evaluated once. //const turtle = new Turtle(); scene = new Scene(); scene.w = scene.h = viewSize; if (perspective > 0) { scene.fov = [fov, fov]; scene.setPerspective(new V3(0, 0, 0), new V3(0, 0, 0)); } else { scene.setOrthographic(100 / cd, new V3(0, 0, 0), new V3(0, 0, 0)); } scene.camera_pos = Scene.worldCameraOrbit(new V3(0, 0, 0), cd, a1, a2); let sdf2 = new SDF2(); sdf2.SUBDIV_TARGET = subdiv_target; sdf2.enableGrid = useGrid; sdf2.grid_step = grid_size; let p = new SDF2.Box(V(5, 5, 5)); let holeSphere = []; for (let i=0; i<holes; i++) { let y = Util.rndRange(-0.5*c_h, 0.5*c_h); let rd = Math.sqrt(Math.random()+0.01)*c_r; let a = Util.rndRange(0, c_angle); let holeR = Util.rndRange(0.8*hole_size, 1.3*hole_size); let pos = new V3(rd, 0).rotDeg(90-a).changez(y); holeSphere.push(new SDF2.Sphere(holeR).tr(M4.translate(pos))); } //let slice = sdf2.addObj( //new SDF2.Sphere(5) new SDF2.Cylinder(c_r, c_h*0.5) .sub(new SDF2.Box(new V3(c_r, c_r, c_h*0.51)).tr(M4.translate(-c_r, 0, 0))) .sub(new SDF2.Box(new V3(c_r, c_r, c_h*0.51)).tr(M4.euler(0, 0, Util.radians(180-c_angle)).mul(M4.translate(-c_r, 0, 0)))) .sub(holeSphere.reduce((a, b) => new SDF2.Union(a, b)).format({invisible_style: null})) .tr(M4.translate(0, -0.5*c_r, 0)) .format({textures: [ //{ id: "slice_local", step: 0.2, dir: V(0, 0, 1) }, //{ id: "slice_local", step: 0.2, dir: V(0, 1, 0) } ], detect_edges: 17, line_style: 1}) ); sdf2.process(scene); if (!INCREMENTAL) { sdf2.draw_to_scene(scene); scene.draw(); } else { sceneGen = sdf2.drawIncremental(scene); } } // The walk function will be called until it returns false. function walk(i) { if (!sceneGen) { return false; } let r = sceneGen.next(); if (r.value) { scene.drawincremental(r.value); } return !r.done; } class V3 { constructor(x = 0, y = 0, z = 0) { this.x = x; this.y = y; this.z = z; } static V0 = new V3(0, 0, 0); toString() { return `V3(${this.x}, ${this.y}, ${this.z})`; } add(b) { return new V3(this.x + b.x, this.y + b.y, this.z + b.z); } sub(b) { return new V3(this.x - b.x, this.y - b.y, this.z - b.z); } mul(b) { return new V3(this.x * b, this.y * b, this.z * b); } scale(b) { return new V3(this.x * b.x, this.y * b.y, this.z * b.z); } flipx() { return new V3(-this.x, this.y, this.z); } flipy() { return new V3(this.x, -this.y, this.z); } copy() { return new V3(this.x, this.y, this.z); } changex(v) { let res = this.copy(); res.x = v; return res; } changey(v) { let res = this.copy(); res.y = v; return res; } changez(v) { let res = this.copy(); res.z = v; return res; } len2() { return this.x * this.x + this.y * this.y + this.z * this.z; } static lerp(a, b, x) { return a.mul(1 - x).add(b.mul(x)); } magnitude() { return Math.sqrt(this.len2()); } normalized() { return this.mul(1 / this.magnitude()); } xyzs() { return this.x + this.y + this.z } xyzMax() { return Math.max(this.x, this.y, this.z); } xyzMin() { return Math.min(this.x, this.y, this.z); } yzx() { return new V3(this.y, this.z, this.x); } zxy() { return new V3(this.z, this.x, this.y); } swizzleSimple(v) { if (v.x > 0) { return this.zxy(); } else if (v.y > 0) { return this.yzx(); } return this; } abs() { return new V3(Math.abs(this.x), Math.abs(this.y), Math.abs(this.z)); } max(b) { return new V3(Math.max(this.x, b.x), Math.max(this.y, b.y), Math.max(this.z, b.z)); } min(b) { return new V3(Math.min(this.x, b.x), Math.min(this.y, b.y), Math.min(this.z, b.z)); } maxK(k) { return new V3(Math.max(this.x, k), Math.max(this.y, k), Math.max(this.z, k)); } minK(k) { return new V3(Math.min(this.x, k), Math.min(this.y, k), Math.min(this.z, k)); } sgn() { return new V3(Math.sign(this.x), Math.sign(this.y), Math.sign(this.z)); } dot(b) { return this.scale(b).xyzs(); } cross(b) { let a = this; return new V3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); } rotDeg(deg) { const a = deg * Math.PI / 180; const s = Math.sin(a); const c = Math.cos(a); return new V3(this.x * c - this.y * s, this.x * s + this.y * c); } } class M4 { constructor(d = null) { this.d = d; if (!d) { this.d = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]; } } copy() { let result = new M4(); for (let i = 0; i < 4; i++) { for (let j = 0; j < 4; j++) { result.d[i][j] = this.d[i][j]; } } return result; } mul(b) { let res = new M4(); for (let i = 0; i < 4; i++) { for (let j = 0; j < 4; j++) { let s = 0; for (let k = 0; k < 4; k++) { s += this.d[i][k] * b.d[k][j] } res.d[i][j] = s; } } return res; } transpose() { let res = new M4(); for (let i = 0; i < 4; i++) { for (let j = 0; j < 4; j++) { res.d[i][j] = this.d[j][i]; } } return res; } mulv(v) { let vv = [v.x, v.y, v.z, 1]; let res = [0, 0, 0, 0]; for (let i = 0; i < 4; i++) { for (let k = 0; k < 4; k++) { res[i] += this.d[i][k] * vv[k]; } } return new V3(res[0], res[1], res[2]); } muldir(v) { let vv = [v.x, v.y, v.z, 1]; let res = [0, 0, 0, 0]; for (let i = 0; i < 4; i++) { for (let k = 0; k < 3; k++) { res[i] += this.d[i][k] * vv[k]; } } return new V3(res[0], res[1], res[2]); } static translate(v, y = undefined, z = undefined) { if (y !== undefined) { let r = new M4(); r.d[0][3] = v; r.d[1][3] = y; r.d[2][3] = z; return r; } let r = new M4(); r.setTranslate(v); return r; } setTranslate(v) { this.d[0][3] = v.x; this.d[1][3] = v.y; this.d[2][3] = v.z; } translation(v) { return new V3(this.d[0][3], this.d[1][3], this.d[2][3]); } static scale(x) { let r = new M4(); r.d[0][0] = x; r.d[1][1] = x; r.d[2][2] = x; return r; } static scale3(x, y, z = 1) { let r = new M4(); r.d[0][0] = x; r.d[1][1] = y; r.d[2][2] = z; return r; } static euler(a, b, c) { let m1 = new M4(); let m2 = new M4(); let m3 = new M4(); m1.d = [[1, 0, 0, 0], [0, Math.cos(a), -Math.sin(a), 0], [0, Math.sin(a), Math.cos(a), 0], [0, 0, 0, 1]]; m2.d = [[Math.cos(b), 0, Math.sin(b), 0], [0, 1, 0, 0], [-Math.sin(b), 0, Math.cos(b), 0], [0, 0, 0, 1]]; m3.d = [[Math.cos(c), -Math.sin(c), 0, 0], [Math.sin(c), Math.cos(c), 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]; return m1.mul(m2).mul(m3); } static eulerv(v) { return euler(v.x, v.y, v.z); } inverse() { let res = []; for (let i = 0; i < 4; i++) { res[i] = this.d[i].slice(); for (let j = 0; j < 4; j++) { res[i].push(i == j ? 1 : 0); } } for (let i = 0; i < 4; i++) { let r = i; for (let j = i; j < 4; j++) { if (Math.abs(res[j][i]) > Math.abs(res[r][i])) { r = j; } } [res[i], res[r]] = [res[r], res[i]]; if (res[i][i] != 0) { let k = 1 / res[i][i]; res[i] = res[i].map((x) => x * k); for (let j = i + 1; j < 4; j++) { let k2 = res[j][i]; for (let column = i; column < 8; column++) { res[j][column] = res[j][column] - k2 * res[i][column]; } } } } for (let i = 3; i >= 0; i--) { for (let j = 0; j < i; j++) { let mul = res[j][i]; for (let k = 0; k < 8; k++) { res[j][k] -= res[i][k] * mul; } } } for (let i = 0; i < 4; i++) { res[i] = res[i].slice(4); } return new M4(res); } } class Util { static rndRange(min, max) { return Math.random() * (max - min) + min; } static radians(a) { return a * Math.PI / 180; } static inverseLerp(a, b, x) { let d = b - a; if (d != 0) { return (x - a) / d; } else { return 0; } } static lerp(a, b, x) { return (1 - x) * a + x * b; } static planeDistance(p0, pnorm, x) { pnorm = pnorm.normalized(); let k = p0.dot(pnorm); return x.dot(pnorm) - k; } static rectContains(r, p) { return p.x >= r[0] && p.x <= r[1] && p.y >= r[2] && p.y <= r[3]; } static planeClip(p0, pnorm, a, b) { let d1 = Util.planeDistance(p0, pnorm, a); let d2 = Util.planeDistance(p0, pnorm, b); if (d1 >= 0 && d2 >= 0) { return [a, b]; } if (d1 < 0 && d2 < 0) { return null; } let m = Util.inverseLerp(d1, d2, 0); let p = V3.lerp(a, b, m); if (d1 < 0) { return [p, b]; } else { return [a, p]; } } static clipRect(a, b, r) { let ca = rectContains(r, a); let cb = rectContains(r, b); if (ca && cb) { return [a, b]; } } /** * * @param {V3} p0 * @param {V3} p1 * @param {V3} p2 * @param {V3} p3 * @param {float} t * @returns */ static cubic_bezier(p0, p1, p2, p3, t) { let t1 = 1 - t; return p0.mul(t1 * t1 * t1).add(p1.mul(t1 * t1 * t * 3)).add(p2.mul(t1 * t * t * 3)).add(p3.mul(t * t * t)); } /** * * @param {V3} p0 * @param {V3} p1 * @param {V3} p2 * @returns {[V3]} */ static quad_bezier_to_cubic(p0, p1, p2) { return [p0, p0.add(p1.sub(p0).mul(2 / 3)), p2.add(p1.sub(p2).mul(2 / 3)), p2]; } } class Scene { constructor() { this.camera_pos_inverse = new M4(); this.camera_pos = Scene.worldCameraM(new V3(0, 0, 0), new V3(1, 0, 0)); this.camera_inverse = new M4(); this.camera = Scene.orthographic1(); this.lines = []; this.ortho = true; this.w = 100; this.h = 100; this.fov = [90, 90]; } set camera_pos(v) { this._camera_pos = v; this.camera_pos_inverse = v.inverse(); } /** * @type {M4} */ get camera_pos() { return this._camera_pos; } set camera(m) { this._camera = m; this.camera_inverse = m.inverse(); } /** * @type {M4} */ get camera() { return this._camera; } addLine(a, b) { this.lines.push([a, b]); } addOb(x) { for (var line of x.lines) { let lineData = line.data; for (let i = 1; i < lineData.length; i++) { this.lines.push([lineData[i - 1], lineData[i]]); } } } mapPoint(x) { let p = this.camera.mulv(x); if (this.ortho) { // ok } else if (p.z != 0) { p = p.mul(1 / Math.abs(p.z)); } else { p.x = 0; p.y = 0; } return p; } mapWorldPoint(p) { let cam = this.camera_pos.mulv(p); let z = cam.z; return this.mapPoint(cam).changez(z); } drawincremental(ob) { let lastPoint = null; turtle.pendown(); ob.lines.forEach((line) => { for (let i = 1; i < line.data.length; i++) { let debug = false; let l = [line.data[i - 1], line.data[i]]; if (l[0].z > 0 && l[1].z > 0) { debug = true; } let p1 = this.camera_pos.mulv(l[0]); let p2 = this.camera_pos.mulv(l[1]); if (!this.ortho) { let p0 = new V3(0, 0, 0); let norm = new V3(); let line = [p1, p2]; let a1 = Math.PI * 0.5 * this.fov[0] / 180; let a2 = Math.PI * 0.5 * this.fov[1] / 180; if (line) { norm = new V3(Math.cos(a1), 0, -Math.sin(a1)); line = Util.planeClip(p0, norm, line[0], line[1]); } if (line) { norm = new V3(-Math.cos(a1), 0, -Math.sin(a1)); line = Util.planeClip(p0, norm, line[0], line[1]); } if (line) { norm = new V3(0, -Math.cos(a2), -Math.sin(a2)); line = Util.planeClip(p0, norm, line[0], line[1]); } if (line) { norm = new V3(0, Math.cos(a2), -Math.sin(a2)); line = Util.planeClip(p0, norm, line[0], line[1]); } if (line) { p1 = line[0]; p2 = line[1]; } else { continue } } p1 = this.mapPoint(p1); p2 = this.mapPoint(p2); let connected = false; if (lastPoint != null) { connected = lastPoint.sub(p1).len2() < 0.00001; } if (!connected) { turtle.penup(); turtle.jump(p1.x, p1.y) turtle.pendown(); } turtle.goto(p2.x, p2.y); lastPoint = p2; } }); turtle.penup(); } draw() { let lastPoint = null; turtle.pendown(); this.lines.forEach((l) => { let p1 = this.camera_pos.mulv(l[0]); let p2 = this.camera_pos.mulv(l[1]); /*if (p1.z > 0 && p2.z > 0) { return; } else if (p1.z > 0 || p2.z > 0) { let x = Util.inverseLerp(p1.z, p2.z, 0.011); if (p1.z > 0) { p1 = V3.lerp(p1, p2, x); } else { p2 = V3.lerp(p1, p2, x); } }*/ if (!this.ortho) { let p0 = new V3(0, 0, 0); let norm = new V3(); let line = [p1, p2]; let a1 = Math.PI * 0.5 * this.fov[0] / 180; let a2 = Math.PI * 0.5 * this.fov[1] / 180; if (line) { norm = new V3(Math.cos(a1), 0, -Math.sin(a1)); line = Util.planeClip(p0, norm, line[0], line[1]); } if (line) { norm = new V3(-Math.cos(a1), 0, -Math.sin(a1)); line = Util.planeClip(p0, norm, line[0], line[1]); } if (line) { norm = new V3(0, -Math.cos(a2), -Math.sin(a2)); line = Util.planeClip(p0, norm, line[0], line[1]); } if (line) { norm = new V3(0, Math.cos(a2), -Math.sin(a2)); line = Util.planeClip(p0, norm, line[0], line[1]); } if (line) { p1 = line[0]; p2 = line[1]; } else { return; } } p1 = this.mapPoint(p1); p2 = this.mapPoint(p2); let connected = false; if (lastPoint != null) { connected = lastPoint.sub(p1).len2() < 0.00001; } if (!connected) { turtle.penup(); turtle.jump(p1.x, p1.y) turtle.pendown(); } turtle.goto(p2.x, p2.y); lastPoint = p2; }); turtle.penup(); } static worldCameraM(from, to) { let d = to.sub(from); let m1 = M4.translate(from.mul(-1)); let a1 = Math.atan2(d.y, d.x); let xy = d.changez(0); let a2 = Math.atan2(d.z, xy.magnitude()); //let m2 = (new M4()).mul(M4.euler(0, 0, +a1)).mul(M4.euler(-Math.PI/2 - a2, 0, 0)); let m2 = (new M4()).mul(M4.euler(-a2, -a1, 0)).mul(M4.euler(Math.PI / 2, Math.PI, -Math.PI / 2)); return m2.mul(m1); } static worldCameraM2(from, to) { let d = to.sub(from); let m1 = M4.translate(from.mul(-1)); let a1 = Math.atan2(-d.x, d.y); let xy = d.changez(0); let a2 = Math.atan2(xy.magnitude(), d.z); let m2 = M4.euler(0, 0, a1); let m3 = M4.euler(-a2, 0, 0); return m2.mul(m3).mul(m1); } static worldCameraOrbit(to, distance, z0, x0) { let x = x0 * Math.PI / 180; let z = z0 * Math.PI / 180; let p = M4.euler(0, 0, z).mul(M4.euler(0, x, 0)).mulv(new V3(-1, 0, 0)); let p2 = p.mul(distance).add(to); if (x0 > -90 && x0 < 90) { return Scene.worldCameraM(p2, p2.sub(p)); } else { let m1 = M4.translate(p2.mul(-1)); if (x0 > 0) { return M4.euler(0, 0, 1 * Math.PI / 2 - z).mul(m1); } else { return M4.euler(0, Math.PI, -1 * Math.PI / 2 - z).mul(m1); } } } cameraToWorld(p) { return this.camera_pos_inverse.mulv(p); } screenToWorld(p) { return this.camera_pos_inverse.mulv(this.camera_inverse.mulv(p)); } static orthographic1(scale = 1) { let res = new M4(); res.d = [ [scale, 0, 0, 0], [0, -scale, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], ]; //res = res.mul(Scene.worldCameraM(from, to)); res = res; return res; } setOrthographic(scale, from, to) { this.camera = Scene.orthographic1(scale); this.camera_pos = Scene.worldCameraM(from, to); this.ortho = true; } static perspective(scale) { let res = new M4(); res.d = [ [scale, 0, 0, 0], [0, -scale, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1], ]; return res; } setPerspective(from, to) { this.camera = Scene.perspective(this.w * 0.5 * Math.tan((90 - this.fov[0] * 0.5) / 180 * Math.PI)); this.camera_pos = Scene.worldCameraM(from, to); this.ortho = false; } } class StrokeInfo { constructor(data) { this.p0 = null; this.data = data; } copy() { let result = new StrokeInfo(this.data.slice()); result.p0 = this.data; return result; } last() { if (this.data.length == 0) { return null; } return this.data[this.data.length - 1]; } } class Ob { constructor() { /** @type [StrokeInfo] */ this.lines = []; } static fromChain(points) { let res = new Ob(); let resLine = new StrokeInfo(points.slice()); res.lines = [resLine]; return res; } static fromLoop(points) { let res = Ob.fromChain(points); if (points.length > 1) { res.lines[0].data.push(res.lines[0].data[0]); } return res; } static cubic_bezier(p0, p1, p2, p3, steps = 32) { let result = [p0]; for (let i = 1; i < steps; i++) { result.push(Util.cubic_bezier(p0, p1, p2, p3, i / steps)); } result.push(p3); return Ob.fromChain(result); } static circle(r, p0 = null, subdiv = circleSubdiv) { if (p0 == null) { p0 = new V3(0, 0, 0); } let points = [] for (let i = 0; i < subdiv; i++) { let a = (Math.PI * 2 * i) / subdiv; let pos = p0.add(new V3(Math.sin(a) * r, Math.cos(a) * r, 0)); points.push(pos); } let res = Ob.fromLoop(points); let line = res.lines[0]; line.p0 = p0; return res; } /** * * @param {string} text * @returns {V3} */ static parsePoint(text) { let t = text.split(/[, ]/) return new V3(Number.parseFloat(t[0]), Number.parseFloat(t[1])); } /** * * @function * @param {string} data * @returns {Ob} */ static fromD(data) { let res = new Ob(); let pos = new V3(0, 0, 0); let p = 0; data = data.trim(); let chunks = data.split(/(?=[a-df-zA-DF-Z])/); let drawing = false; let start = pos; let last_cubic = null; let last_quad = null; let POINT_REGEX = /[0-9.eE+\-]+[, ][0-9.eE+\-]+/g; let NUMBER_REGEX = /[0-9.eE+\-]+/g; for (const chunk of chunks) { let t = chunk[0]; if (t == 'm' || t == 'M') { let pointText = chunk.matchAll(POINT_REGEX); /** @type {[V3]} */ let points = Array.from(pointText.map((x) => { return Ob.parsePoint(x[0]) })); for (let i = 0; i < points.length; i++) { let p2 = points[i]; if (t == 'm') { p2 = p2.add(pos); } if (i == 0) { pos = p2; start = pos; drawing = true; } else { res.addLine(pos, p2); pos = p2; } } last_cubic = null; last_quad = null; } else if (t == 'l' || t == 'L') { let pointText = chunk.matchAll(POINT_REGEX); /** @type {[V3]} */ let points = Array.from(pointText.map((x) => { return Ob.parsePoint(x[0]) })); if (!drawing) { drawing = true; start = pos; } for (let i = 0; i < points.length; i++) { let p2 = points[i]; if (t == 'l') { p2 = p2.add(pos); } res.addLine(pos, p2); pos = p2; } last_cubic = null; last_quad = null; } else if (t == 'h' || t == 'H' || t == 'v' || t == 'V') { let pointText = chunk.matchAll(NUMBER_REGEX); /** @type {[float]} */ let points = Array.from(pointText.map((x) => { return Number.parseFloat(x[0]) })); if (!drawing) { drawing = true; start = pos; } for (let v of points) { let p2 = pos; if (t == 'h') { p2 = p2.add(V(v, 0)); } else if (t == 'v') { p2 = p2.add(V(0, v)); } else if (t == 'H') { p2 = p2.changex(v); } else if (t == 'V') { p2 = p2.changey(v); } res.addLine(pos, p2); pos = p2; } last_cubic = null; last_quad = null; } else if (t == 'c' || t == 'C' || t == 's' || t == 'S') { let pointText = chunk.matchAll(POINT_REGEX); /** @type {[V3]} */ let points = Array.from(pointText.map((x) => { return Ob.parsePoint(x[0]) })); if (!drawing) { drawing = true; start = pos; } let step = 3; if (t == 's' || t == 'S') { step = 2; } for (let i = 0; i + step - 1 < points.length; i += step) { let px = null; if (step == 3) { px = [points[i + 0], points[i + 1], points[i + 2]]; } else { px = [points[i + 0], points[i + 0], points[i + 1]]; } if (t == 'c' || t == 's') { for (let j = 0; j < px.length; j++) { px[j] = px[j].add(pos); } } if (step == 2) { if (last_cubic) { px[0] = pos.mul(2).sub(last_cubic); } else { px[0] = pos; } } res.addObMove(Ob.cubic_bezier(pos, px[0], px[1], px[2])); pos = px[2]; last_cubic = px[1]; } last_quad = null; } else if (t == 'Q' || t == 'q' || t == 'T' || t == 't') { let pointText = chunk.matchAll(POINT_REGEX); /** @type {[V3]} */ let points = Array.from(pointText.map((x) => { return Ob.parsePoint(x[0]) })); if (!drawing) { drawing = true; start = pos; } let step = 2; if (t == 't' || t == 'T') { step = 1; } for (let i = 0; i + step - 1 < points.length; i += step) { let px = null; if (step == 2) { px = [points[i + 1], points[i + 2]]; } else { px = [points[i + 0], points[i + 0]]; } if (t == 'q' || t == 't') { for (let j = 0; j < px.length; j++) { px[j] = px[j].add(pos); } } if (step == 1) { if (last_quad) { px[0] = pos.mul(2).sub(last_quad); } else { px[0] = pos; } } last_quad = px[0]; px = Util.quad_bezier_to_cubic(pos, px[0], px[1]); res.addObMove(Ob.cubic_bezier(pos, px[1], px[2], px[3])); pos = px[3]; } last_cubic = null; } else if (t == 'a' || t == 'A') { let pointText = chunk.matchAll(NUMBER_REGEX); /** @type {[V3]} */ let numbers = Array.from(pointText.map((x) => { return Number.parseFloat(x[0]) })); if (!drawing) { drawing = true; start = pos; } for (let i = 0; i + 6 < numbers.length; i += 7) { let rx = numbers[i + 0]; let ry = numbers[i + 1]; let angle = numbers[i + 2]; let large_arc = numbers[i + 3]; let sweep_flag = numbers[i + 4]; let p2 = new V3(numbers[i + 4], numbers[i + 5]); if (t == 'a') { p2 = p2.add(pos); } res.addLine(pos, p2); // TODO: implement arcs pos = p2; } last_cubic = null; last_quad = null; } else if (t == 'z' || t == 'Z') { if (drawing) { res.addLine(pos, start); pos = start; drawing = false; } last_cubic = null; last_quad = null; } } return res; } addLine(a, b) { if (this.lines.length > 0) { let line = this.lines[0]; if (line.last() == a) { line.data.push(b); return; } } this.lines.push(new StrokeInfo([a, b])); } addLines(ar) { for (const x of ar) { this.addLine(new V3(x[0], x[1], x[2]), new V3(x[3], x[4], x[5])); } } addLineArray(ar) { for (const x of ar) { this.addLine(x[0], x[1]); } } addOb(o) { for (const line of o.lines) { this.lines.push(line.copy()); } } addObMove(o) { for (const line of o.lines) { this.lines.push(line); } } transform(m) { for (let line of this.lines) { for (let i = 0; i < line.data.length; i++) { line.data[i] = m.mulv(line.data[i]); } if (line.p0) { line.p0 = m.mulv(line.p0); } } } transformed(m) { let r = new Ob(); r.addOb(this); r.transform(m); return r; } } class SDFR { constructor(d, norm, obj) { /** @type {float} */ this.d = d; /** @type {V3} */ this.norm = norm; this.obj = obj; } } class SDFF { static DETECT_EDGE_EMPTY = 1; static DETECT_EDGE_FRONT = 2; static DETECT_EDGE_EXTERNALG = 4; static DETECT_EDGE_INTERNALG = 8; static DETECT_EDGE_SURFACE = 16; constructor(args) { if (!args) { args = {} } this.transform = new M4(); this.format(args); } get primitive() { return false; } get curved() { return true; } tr(t) { this.transform = t.mul(this.transform); return this; } format(args) { if ("textures" in args) { this.textures = args.textures; } if ("line_style" in args) { this.line_style = args.line_style; } if ("invisible_style" in args) { this.invisible_style = args.invisible_style; } if ("detect_edges" in args) { this.detect_edges = args.detect_edges; } if ("groot" in args) { this.groot = args.groot; } return this; } /** * * @param {Scene} camera_info * @param {SDFNode} t * @returns {null|Ob} */ get_lines(camera_info, t) { return null; } get_texture(texture, node, camera_info) { return null; } add(x) { return new SDF2.Union(this, x); } sub(x) { return new SDF2.Diff(this, x); } } class SDFNode { /** * * @param {SDFNode} parent */ constructor(parent) { this.a = null; this.b = null; /** @type{M4} */ this.transform = null; /** @type{M4} */ this.inverse_transform = null; this.line_style = 1; this.invisible_style = null; this.textures = null; this.detect_edges = 0; this.group = -1; if (parent) { this.line_style = parent.line_style; this.invisible_style = parent.invisible_style; this.textures = parent.textures; this.detect_edges = parent.detect_edges; this.group = parent.group; } this.sign = 1; this.index = -1; /** @type{SDFF} */ this.func = null; } } let sdf_runs = 0; class SDF2 { static Sphere = class extends SDFF { constructor(r, args = null) { super(args); this.r = r; } do(p, id = null) { let v = p; let m = v.magnitude(); let d = m - this.r; let norm = null; norm = v; /*if (d > 0) { norm = v; } else { norm = v.mul(-1); }*/ return new SDFR(d, norm, id); } get primitive() { return true; } /** * * @param {Scene} camera_info * @param {SDFNode} node * @returns {Ob} */ get_lines(camera_info, node) { let result = null; let p0 = camera_info.cameraToWorld(V(0, 0, 0)); let forward = camera_info.cameraToWorld(V(0, 0, -1)).sub(p0); if (camera_info.ortho) { result = Ob.circle(this.r); let transform = camera_info.camera_pos_inverse.copy(); transform.setTranslate(new V3()); let objTransform = node.inverse_transform.copy(); objTransform.setTranslate(new V3()) result.transform(objTransform.mul(transform)); } else { let pObj = node.transform.translation(); let posRelative = pObj.sub(p0); let d = posRelative.magnitude(); if (d < this.r) { return null; } let r = this.r; let tangent = Math.sqrt(d * d - r * r); let radiusOutline = r * tangent / d; let offset = r * r / d; let o = Ob.circle(radiusOutline, new V3(0, 0, offset)); let objTransform = node.inverse_transform.copy(); objTransform.setTranslate(new V3()) let tr = Scene.worldCameraM2(new V3(), posRelative.mul(-1)); tr.setTranslate(new V3()); result = o; o.transform(objTransform.mul(tr)); } return result; } get_texture(texture, node, camera_info) { let result = new Ob(); switch (texture.id) { case "slice_local": { let dir = V(0, 0, 1); if (texture.dir) { dir = texture.dir; } let step = texture.step; for (let z = -this.r + step; z < this.r; z += step) { let r2 = Math.sqrt(this.r * this.r - z * z); let ring = Ob.circle(r2, new V3(0, 0, z)); let ringData = ring.lines[0].data; for (let i = 0; i < ringData.length; i++) { ringData[i] = ringData[i].swizzleSimple(dir); } ring.lines[0].p0 = ring.lines[0].p0.swizzleSimple(dir); result.addObMove(ring); } return result; } break; } return super.get_texture(texture, node, camera_info); } } static Cylinder = class extends SDFF { constructor(r, h, args = null) { super(args); this.r = r; this.h = h; } /** * * @param {V3} p * @param {*} id * @returns {SDFR} */ do(p, id = null) { let flip = p.z < 0 ? -1 : 1; p.z *= flip; let v = p.changez(0); let d = v.magnitude() - this.r; let dh = p.z - this.h; if (dh < 0) { //d = v.magnitude() - this.r; } else { if (d < 0) { d = dh; v = new V3(0, 0, d); } else { v = v.sub(v.normalized().mul(this.r)).changez(dh) d = v.magnitude(); } } v.z = v.z * flip; return new SDFR(d, v, id); } get primitive() { return true; } /** * * @param {Scene} camera_info * @param {SDFNode} node * @returns */ get_lines(camera_info, node) { let result = new Ob(); result.addObMove(Ob.circle(this.r, new V3(0, 0, this.h))); result.addObMove(Ob.circle(this.r, new V3(0, 0, -this.h))); let p0 = camera_info.cameraToWorld(new V3()); if (camera_info.ortho) { let forwardGlobal = camera_info.cameraToWorld(new V3(0, 0, -1)) let flocal = node.inverse_transform.muldir(forwardGlobal); let side = flocal.cross(new V3(0, 0, 1)); side = side.mul(this.r / side.magnitude()); result.addLine(side.changez(-this.h), side.changez(this.h)); side = side.mul(-1); result.addLine(side.changez(-this.h), side.changez(this.h)); } else { let cameraObjLocal = node.inverse_transform.mulv(p0).changez(0); let d = cameraObjLocal.magnitude(); if (d <= this.r) { return result; } let r = this.r; let tangent = Math.sqrt(d * d - r * r); let radiusOutline = r * tangent / d; let offset = r * r / d; let v1 = cameraObjLocal.normalized(); let v2 = new V3(v1.y, -v1.x, 0); v1 = v1.mul(offset); v2 = v2.mul(radiusOutline); let side1 = v1.add(v2); let side2 = v1.sub(v2); result.addLine(side1.changez(-this.h), side1.changez(this.h)); result.addLine(side2.changez(-this.h), side2.changez(this.h)); } return result; } get_texture(texture, node, camera_info) { let result = new Ob(); switch (texture.id) { case "slice_local": { let dir = V(0, 0, 1); if (texture.dir) { dir = texture.dir; } let step = texture.step; if (dir.z > 0) { for (let z = -this.h; z < this.h; z += step) { result.addObMove(Ob.circle(this.r, new V3(0, 0, z))); } } return result; } break; } return super.get_texture(texture, node, camera_info); } } static Box = class extends SDFF { constructor(size, args = null) { super(args); /** @type{V3} */ this.size = size; } /** * * @param {V3} p * @returns {SDFR} */ do(p, id = null) { let sc1 = p.sgn(); p = p.scale(sc1); let q = p.sub(this.size); let [a, b, c] = [q.x, q.y, q.z]; let d = 0; let norm = null; if (a < 0 && b < 0 && c < 0) { if (a > b && a > c) { d = a; norm = new V3(-d, 0, 0); } else if (b > c) { d = b; norm = new V3(0, -d, 0); } else { d = c; norm = new V3(0, 0, -c); } } else { norm = q.maxK(0); d = norm.magnitude(); } return new SDFR(d, norm.scale(sc1), id); } get primitive() { return true; } get curved() { return false; } corner(m) { let r = this.size.mul(-1); if (m & 1) { r.x = -r.x; } if (m & 2) { r.y = -r.y; } if (m & 4) { r.z = -r.z; } return r; } get_lines(camera_info, node) { let result = new Ob(); for (let mask = 0; mask < 7; mask++) { for (let j = 0; j < 3; j++) { let m2 = mask | (1 << j); if (m2 == mask) { continue; } result.addLine(this.corner(mask), this.corner(m2)); } } return result; } get_texture(texture, node, camera_info) { let result = new Ob(); switch (texture.id) { case "slice_local": { let dir = V(0, 0, 1); if (texture.dir) { dir = texture.dir; } let step = texture.step; if (dir.z > 0) { for (let z = -this.size.z; z < this.size.z; z += step) { result.addObMove(Ob.fromLoop([ new V3(this.size.x, this.size.y, z), new V3(this.size.x, -this.size.y, z), new V3(-this.size.x, -this.size.y, z), new V3(-this.size.x, this.size.y, z) ])); } } else if (dir.y > 0) { for (let z = -this.size.y; z < this.size.y; z += step) { result.addObMove(Ob.fromLoop([ new V3(this.size.x, z, this.size.z), new V3(this.size.x, z, -this.size.z), new V3(-this.size.x, z, -this.size.z), new V3(-this.size.x, z, this.size.z), new V3(this.size.x, z, this.size.z), ])); } } else { for (let z = -this.size.x; z < this.size.x; z += step) { result.addObMove(Ob.fromLoop([ new V3(z, this.size.y, this.size.z), new V3(z, this.size.y, -this.size.z), new V3(z, -this.size.y, -this.size.z), new V3(z, -this.size.y, this.size.z), new V3(z, this.size.y, this.size.z), ])); } } return result; } break; } return super.get_texture(texture, node, camera_info); } } static Union = class extends SDFF { constructor(a, b, args = null) { super(args) this.a = a; this.b = b; } /** * * @param {V3} p * @returns {SDFR} */ do(p, r1, r2) { if (r1.d < r2.d) { return r1; } else { return r2; } } static doStatic(p, r1, r2) { if (r1.d < r2.d) { return r1; } else { return r2; } } } static Diff = class extends SDFF { constructor(a, b, args = null) { super(args) this.a = a; this.b = b; } /** * * @param {V3} p * @returns {SDFR} */ do(p, r1, r2) { r2.d *= -1; if (r1.d > r2.d) { return r1; } else { r2.norm = r2.norm.mul(-1); return r2; } } } static Intersection = class extends SDFF { constructor(a, b, args = null) { super(args) this.a = a; this.b = b; } /** * * @param {V3} p * @returns {SDFR} */ do(p, r1, r2) { if (r1.d > r2.d) { return r1; } else { return r2; } } } static Xor = class extends SDFF { constructor(a, b, args = null) { super(args) this.a = a; this.b = b; } /** * * @param {V3} p * @returns {SDFR} */ do(p, r1, r2) { if (r1.d >= r2.d) { [r1, r2] = [r2, r1]; } if (r1.d > r2.d) { return r1; } else { r2.d *= -1; r2.norm = r2.norm.mul(-1); return r2; } } } constructor() { /** @type {SDFF} */ this.objs = [] this.tmpd = [] /** @type {[SDFNode]} */ this.o2 = []; this.MAX_STEPS = 300; this.RAY_MARCH_LIMIT = 0.0001; this.TANGENT_HACK = 0.01; this.SUBDIV_TARGET = 0.5; this.MAX_DISTANCE = 10000; this.EDGE_SEARCH_ITER = 16; this.grid_step = 1; this.enableGrid = 1; /** @type {[[[V3, float, SDFR]]]} */ this.grid = null; } addObj(x) { this.objs.push(x); } /** * * @param {V3} p * @param {SDFNode} offsetNode * @returns {SDFR} */ calcSDF(p, offsetNode = null) { sdf_runs++; for (let i of this.primitive) { let node = this.o2[i]; let p2 = node.inverse_transform.mulv(p); this.tmpd[i] = node.func.do(p2, this.o2[i]); } if (offsetNode) { this.tmpd[offsetNode.index].d += offsetNode.sign * this.TANGENT_HACK; } for (let i = this.combine.length - 1; i >= 0; --i) { let index = this.combine[i]; let node = this.o2[index]; let r1 = this.tmpd[node.a]; let r2 = this.tmpd[node.b]; this.tmpd[index] = node.func.do(p, r1, r2); } let res = null; for (let i of this.root) { if (res) { res = SDF2.Union.doStatic(p, res, this.tmpd[i]); } else { res = this.tmpd[i]; } } return res; } runRay(p0, dir, limit, offsetNode = null) { let travel = 0; let p = p0; let result = null; let steps = 0; while (travel < limit) { steps++; /** @type {SDFR} */ let hit = this.calcSDF(p, offsetNode); let distance = hit.d; if (distance < this.RAY_MARCH_LIMIT || steps > this.MAX_STEPS) { result = hit; break; } distance = Math.min(limit - travel, distance); p = p.add(dir.mul(distance)); travel += distance; } return [p, travel, result]; } clipReach(p0, point, ortho, forward, mustBeOnSurface, node = null) { if (mustBeOnSurface && DEBUG_N_ON_SURFACE) { let d = 0; if (!node) { d = Math.abs(this.calcSDF(point).d); } else { let hit = this.calcSDF(point); if (hit.obj != node) { return -1; } d = Math.abs(hit.d); } if (d > 0.001) { return -1; } } let dir = point.sub(p0); let dis = null; if (ortho) { let fnorm = forward.normalized(); let l = fnorm.dot(dir); let sideway = dir.sub(fnorm.mul(l)); p0 = p0.add(sideway); dir = fnorm; dis = point.sub(p0).magnitude(); } else { forward = point.sub(p0); dir = forward; let len2 = dir.len2(); if (len2 <= 0.001) { return 0; } dis = Math.sqrt(len2); dir = dir.mul(1 / dis); } let [pRay, pTravel, hit] = this.runRay(p0, dir, dis, node); if (pRay.sub(point).len2() > 0.0001) { return 0; } return 1; } /** * * @param {V3} p * @param {SDFF} node */ snap_to_surface(p, node) { if (!DEBUG_N_ON_SURFACE) { return p; } let p2Local = node.inverse_transform.mulv(p); /** @type {SDFR} */ let sdfi = node.func.do(p2Local); let norm = sdfi.norm; let l2 = norm.len2(); if (l2 <= 0.00001) { return p; } norm = norm.mul(sdfi.d / Math.sqrt(l2)); return p.sub(node.transform.muldir(norm)); } /** * * @param {V3} a * @param {V3} b * @param {float} k * @param {StrokeInfo} stroke * @param {SDFNode} node */ interpolate_point(a, b, k, stroke, node) { let p = V3.lerp(a, b, k); if (stroke.p0) { let p0 = stroke.p0; let r1 = a.sub(p0).len2(); let plocal = p.sub(p0); let r2 = plocal.len2() if (r2 > 0.00001 && r1 > 0.00001) { p = plocal.mul(Math.sqrt(r1 / r2)).add(p0); } } if (node.func.curved) { p = this.snap_to_surface(p, node); } return p; } /** * * @param {Scene} camera_info * @param {Ob} shape * @param {SDFNode} node */ clip_lines(camera_info, shape, node, style, invisible_style = null) { let result = new Ob(); let p0 = camera_info.cameraToWorld(V(0, 0, 0)); let ortho = camera_info.ortho; let forward = camera_info.cameraToWorld(V(0, 0, -1)).sub(p0); let show_invisible = false; if (invisible_style) { show_invisible = true; } for (let line of shape.lines) { let chain = null; let chain_invisible = null; let last_normal = null; let last_invisible = null; //TODO: camera clipping let addSegment = function (a, b, invisible, show_invisible = false) { let cur_style = invisible ? invisible_style : style; if (cur_style == null) { chain = null; last_normal = null; chain_invisible = null; last_invisible = null; return; } let last = invisible ? last_invisible : last_normal; if (last && last.length > 0) { last[last.length - 1] = b; return; } let active_chain = invisible ? chain_invisible : chain; if (!active_chain) { active_chain = new StrokeInfo([a]); if (invisible) { active_chain.style = invisible_style; chain_invisible = active_chain; } else { active_chain.style = style; chain = active_chain; } result.lines.push(active_chain); } active_chain.data.push(b); if (invisible) { last_invisible = active_chain.data; chain = null; last_normal = null; } else { last_normal = active_chain.data; chain_invisible = null; last_invisible = null; } } let points = line.data; for (let j = 1; j < points.length; j++) { let pa = points[j - 1]; let pb = points[j]; last_normal = last_invisible = null; let prev = pa; let prevOnLine = pa; let k = 1 / subdiv; let recalcSize = 0.26; let recalcStep = 0.0 - 0.001; for (let progress = 0; progress < 1; progress += k) { let next = progress + k; if (next > recalcStep) { let sc1 = camera_info.mapWorldPoint(prevOnLine); recalcStep += recalcSize; let nextP = null let nextScreen = null; let target2 = this.SUBDIV_TARGET * this.SUBDIV_TARGET; do { k *= 0.5; next = Math.min(progress + k, 1); nextP = V3.lerp(pa, pb, next); nextScreen = camera_info.mapWorldPoint(nextP); } while (sc1.sub(nextScreen).changez(0).len2() > target2 && k > 0.01); do { k *= 2; next = Math.min(progress + k, 1); nextP = V3.lerp(pa, pb, next); nextScreen = camera_info.mapWorldPoint(nextP) } while (sc1.sub(nextScreen).changez(0).len2() < target2 && k < 1 - progress); next = progress + k; //console.log(`recalc ${k} ${sc1.sub(nextScreen).changez(0).magnitude()}`); } next = Math.min(next, 1); prevOnLine = V3.lerp(pa, pb, next); let p2 = this.interpolate_point(pa, pb, next, line, node); if (node.func.curved || line.p0) { last_normal = last_invisible = null; } // todo snap to surface let r1 = this.clipReach(p0, prev, ortho, forward, true, node); let r2 = this.clipReach(p0, p2, ortho, forward, true, node); if (r1 > 0 && r2 > 0) { addSegment(prev, p2, false); } else if (subdivExtra && (r1 > 0) != (r2 > 0)) { let [l, r] = [prev, p2]; let m = null; for (let iter = 0; iter < 10; iter++) { m = this.interpolate_point(l, r, 0.5, line, node); let good = this.clipReach(p0, m, ortho, forward, true, node) > 0; if ((r1 > 0) == good) { l = m; } else { r = m; } } if (r1 > 0) { addSegment(prev, m, false); if (r2 == 0) { addSegment(m, p2, true, show_invisible); } else { chain = chain_invisible = last_normal = last_invisible = null; } } else { if (r1 == 0) { addSegment(prev, m, true, show_invisible); } else { chain = chain_invisible = last_normal = last_invisible = null; } addSegment(m, p2, false); } } else { if (r1 == 0 && r2 == 0) { addSegment(prev, p2, true, show_invisible); } else { chain = chain_invisible = last_normal = last_invisible = null; } } prev = p2; } } } return result; } process(camera_info) { let o2 = []; let root = []; let combine = []; let primitive = []; function recursiveProc(x, transform, parent, sign) { let index = o2.length; let t2 = transform.mul(x.transform); if (x.primitive) { primitive.push(index); } else { combine.push(index); } let node = new SDFNode(parent); node.sign *= sign; node.func = x; node.transform = t2; if (x.textures !== undefined) { node.textures = x.textures; } if (x.line_style !== undefined) { node.line_style = x.line_style; } if (x.invisible_style !== undefined) { node.invisible_style = x.invisible_style; } if (x.detect_edges !== undefined) { node.detect_edges = x.detect_edges; } if (x.groot) { node.group = index; } node.index = index; o2.push(node); if (!x.primitive) { node.a = recursiveProc(x.a, t2, node, 1); let subSign = 1; if (x instanceof SDF2.Diff) { subSign = -1; } node.b = recursiveProc(x.b, t2, node, subSign); } return index; } let identity_transform = new M4(); let dummyParent = new SDFNode(); for (let item of this.objs) { root.push(recursiveProc(item, identity_transform, dummyParent)); } this.o2 = o2; this.root = root; this.combine = combine; this.primitive = primitive; for (let o of this.o2) { o.inverse_transform = o.transform.inverse(); } } gridPos(i, j, camera_info) { let w = camera_info.w; let n = this.grid.length; return new V3((j * w / n) - 0.5 * w, (i * w / n) - 0.5 * w, 0); } /** * * @param {Scene} camera_info */ *calcGrid(camera_info) { let c = camera_info.screenToWorld(new V3(0, 0, 1)); let vy = camera_info.screenToWorld(new V3(0, 1, 1)).sub(c); let vx = camera_info.screenToWorld(new V3(1, 0, 1)).sub(c); let n = Math.floor(camera_info.w / this.grid_step); let res = []; for (let i = 0; i < n; i++) { let a = []; a.length = n; res.push(a); } this.grid = res; let forward = camera_info.camera_pos_inverse.muldir(new V3(0, 0, -1)); let p0 = camera_info.cameraToWorld(new V3()); let w = camera_info.w; /*let debug_points = [ camera_info.screenToWorld(new V3(w*0.5, 0, 1)), camera_info.screenToWorld(new V3(0, w*0.5, 1)), camera_info.screenToWorld(new V3(-w*0.5, 0, 1)), camera_info.screenToWorld(new V3(0, -w*0.5, 1)), ]; let debugOb = Ob.fromLoop(debug_points); camera_info.drawincremental(debugOb);*/ for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { let p = this.gridPos(i, j, camera_info); let worldP = c.add(vx.mul(p.x)).add(vy.mul(p.y)); let f = forward; if (!camera_info.ortho) { f = worldP.sub(p0).normalized(); worldP = p0; } let rayResult = this.runRay(worldP, f, this.MAX_DISTANCE, null); res[i][j] = rayResult; } console.log(`${i}/${n}`); yield null; } //camera_info.drawincremental(ob); } /** * * @param {Scene} camera_info */ searchEdges(camera_info) { const DIR1 = [[0, 1], [1, 0]]; let n = this.grid.length; let visited = new Uint8Array(n * n * 4); let depth = new Uint32Array(n * n * 4); let NEXT2 = [ [[0, 0, 1], [0, 1, 1], [1, 0, 0], [-1, 0, 1], [-1, 1, 1], [-1, 0, 0]], [[0, 0, 0], [1, 0, 0], [0, 1, 1], [0, -1, 0], [1, -1, 0], [0, -1, 1]], ] function splitIndex(v) { let side = v % 4; v = Math.trunc(v / 4); let j = v % n; v = Math.trunc(v / n); let i = v; return [i, j, side]; } function joinIndex(i, j, side) { return (((i * n) + j) * 4) + side; } function needEdge(hit1, hit2) { let o1 = hit1[2] ? hit1[2].obj : null; let o2 = hit2[2] ? hit2[2].obj : null; if (o1 == null) { [o1, o2] = [o2, o1]; [hit1, hit2] = [hit2, hit1]; } if (o1 == null) { return false; } if (o2 == null) { return (o1.detect_edges & (SDFF.DETECT_EDGE_EMPTY)); } if (o1.detect_edges == 0 && o2.detect_edges == 0){ return false; } let frontO1 = !(o1.detect_edges & SDFF.DETECT_EDGE_FRONT) || hit1[1] < hit2[1]; let frontO2 = !(o2.detect_edges & SDFF.DETECT_EDGE_FRONT) || hit2[1] < hit1[1]; let mask = 0; if (frontO1) { mask |= o1.detect_edges; } if (frontO2) { mask |= o2.detect_edges; } if (o1 != o2 && (mask & SDFF.DETECT_EDGE_SURFACE)) { return true; } if (o1.group == o2.group && (mask & SDFF.DETECT_EDGE_INTERNALG)) { return true; } if (o1.group != o2.group && (mask & SDFF.DETECT_EDGE_EXTERNALG)) { return true; } return false; } let context = this; function hasEdge(i, j, ni, nj) { return needEdge(context.grid[i][j], context.grid[ni][nj]); } let forward = camera_info.camera_pos_inverse.muldir(new V3(0, 0, -1)); let p0 = camera_info.cameraToWorld(new V3()); //let edges = []; let followPath = function (i, j, side, o1, o2) { let s = []; let i0 = joinIndex(i, j, side); if (visited[i0]) { return; } let startPos = context.grid[i][j][0];// todo s.push([i0, 0, null]) while (s.length > 0) { let [index, it, edgePos] = s[s.length - 1]; let [ci, cj, cs] = splitIndex(index); if (it == 0) { let hit1 = context.grid[ci][cj]; let i2 = ci + DIR1[cs][1]; let j2 = cj + DIR1[cs][0]; let hit2 = context.grid[i2][j2]; let p1World = camera_info.screenToWorld(context.gridPos(ci, cj, camera_info).changez(1)); let p2World = camera_info.screenToWorld(context.gridPos(i2, j2, camera_info).changez(1)); let left = p1World, right = p2World; let hitleft = hit1; let hitRight = hit2; /*{ //DEBUG let tmp = new Ob(); tmp.addLine(p1World, p2World); camera_info.drawincremental(tmp); }*/ for (let t = 0; t < context.EDGE_SEARCH_ITER; t++) { let m = V3.lerp(left, right, 0.5); let rayStart = m; let f = forward; if (!camera_info.ortho) { f = rayStart.sub(p0).normalized(); rayStart = p0; } let rayResult = context.runRay(rayStart, f, context.MAX_DISTANCE, null); if (needEdge(hit1, rayResult)) { right = m; hitRight = rayResult; } else { left = m; hitleft = rayResult; } } if (hitleft[1] < hitRight[1]) { edgePos = hitleft[0]; } else { edgePos = hitRight[0]; } s[s.length - 1][2] = edgePos; if (s.length > 1) { let prevPos = s[s.length - 2][2]; let tmp = new Ob(); tmp.addLine(prevPos, edgePos); camera_info.drawincremental(tmp); } } if (it >= 6) { s.pop(); } else { s[s.length - 1][1] = it + 1; let diff = NEXT2[cs][it]; let ni = ci + diff[1]; let nj = cj + diff[0]; let ns = diff[2]; if (ni < 0 || nj < 0 || ni >= n || nj >= n) { continue; } let nextIndex = joinIndex(ni, nj, ns); let n2i = ni + DIR1[ns][1]; let n2j = nj + DIR1[ns][0]; if (n2i >= n || n2j >= n) { continue; } //let ns = diff[2]; if (visited[nextIndex] || !hasEdge(ni, nj, n2i, n2j) ) { continue; } visited[nextIndex] = 1; depth[nextIndex] = depth[index] + 1; /*let pos1 = context.grid[ni][nj][0]; let pos2 = context.grid[n2i][n2j][0]; let posNew = V3.lerp(pos1, pos2, 0.5); let tmp = new Ob(); tmp.addLine(prevp, posNew); camera_info.drawincremental(tmp);*/ s.push([nextIndex, 0, null]); } } } for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { let hit1 = this.grid[i][j]; for (let side = 0; side < DIR1.length; side++) { let tx = j + DIR1[side][0]; let ty = i + DIR1[side][1]; if (tx < 0 || ty < 0 || tx >= n || ty >= n) { continue; } let hit2 = this.grid[ty][tx]; if (needEdge(hit1, hit2)) { followPath(i, j, side); } } } } } /** * * @param {Scene} camera_info * @returns {[any, [V3]]} */ draw(camera_info) { let result = new Ob(); for (let i of this.primitive) { let node = this.o2[i]; let lines = node.func.get_lines(camera_info, node); if (lines) { lines.transform(node.transform); let clipped = this.clip_lines(camera_info, lines, node, node.line_style, node.invisible_style); result.addObMove(clipped); } let textures = node.textures; if (textures) { let textureLines = new Ob(); for (let texture of textures) { let text = node.func.get_texture(texture, node, camera_info); if (text) { textureLines.addObMove(text); } } textureLines.transform(node.transform); let clipped = this.clip_lines(camera_info, textureLines, node, 1, null); result.addObMove(clipped); } } return result; } *drawIncremental(camera_info) { let t0 = Date.now(); let t1 = t0; let tim = function () { let t2 = Date.now(); //console.log(`time ${t2 - t1} ${t2 - t0}`); t1 = t2; } console.log(`t: ${Date.now() - t0}`); for (let i of this.primitive) { let node = this.o2[i]; let lines = node.func.get_lines(camera_info, node); if (lines) { lines.transform(node.transform); let clipped = this.clip_lines(camera_info, lines, node, node.line_style, node.invisible_style); tim(); yield clipped; } } for (let i of this.primitive) { let node = this.o2[i]; let textures = node.textures; if (textures) { for (let texture of textures) { let text = node.func.get_texture(texture, node, camera_info); text.transform(node.transform); let clipped = this.clip_lines(camera_info, text, node, 1, null); //console.log(`sometext ${i}/${this.o2.length}`); tim(); yield clipped; } } } if (this.enableGrid == 1) { for (let x of this.calcGrid(camera_info)) { yield x; } //this.calcGrid(camera_info); this.searchEdges(camera_info); } } /** * * @param {Scene} scene */ draw_to_scene(scene) { let r = this.draw(scene); scene.addOb(r); } } function initlib() { this.V3 = V3; this.V = (x, y, z) => new V3(x, y, z); this.M4 = M4; this.Ob = Ob; this.Scene = Scene; } initlib(); if (typeof Canvas != 'undefined') { //console.log("init browser"); Canvas.setpenopacity(1); turtle = new Turtle(); init2(); } else { //console.log("init standalone"); import('./testTurtle.js').then((mod) => { turtle = new mod.TestTurtle(); init2(); let i = 0; while (walk(i)) { ++i; } turtle.finishSVG(); }); } //init2();