diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 253ed73..7caad99 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -26,14 +26,33 @@ - + + + + + + + + + + + + + + + + - + + + + + @@ -74,7 +100,7 @@ - + @@ -90,18 +116,18 @@ - - - - + + + + - - - + + - + + @@ -147,7 +173,9 @@ - + + + @@ -234,18 +262,20 @@ - - + + + - - + + + \ No newline at end of file diff --git a/package.json b/package.json index 0eb70c8..be14ae9 100644 --- a/package.json +++ b/package.json @@ -46,10 +46,11 @@ "win": { "target": ["portable"] }, - "files": [ - "**/*", - "./public/**", - "./src/desktop${/*}" + "linux": { + "target": ["AppImage"] + }, + "extraResources": [ + "public/solver" ], "icon": "./public/resources/soma_icon.png", "directories": { diff --git a/public/favicon.png b/public/favicon.png index 7e6f5eb..c8b9aba 100644 Binary files a/public/favicon.png and b/public/favicon.png differ diff --git a/public/index.html b/public/index.html index cb4aca3..0f0ffda 100644 --- a/public/index.html +++ b/public/index.html @@ -12,7 +12,8 @@ - + diff --git a/public/main.wasm b/public/main.wasm new file mode 100644 index 0000000..822c696 Binary files /dev/null and b/public/main.wasm differ diff --git a/public/worker.js b/public/worker.js new file mode 100644 index 0000000..0ee7003 --- /dev/null +++ b/public/worker.js @@ -0,0 +1,525 @@ +// asbind/assemblyscript loader +var t="0.7.0";const e="undefined"!=typeof BigUint64Array,r=Symbol(),n=new TextDecoder("utf-16le");function s(t, e){const r=new Uint32Array(t)[e+-4>>>2]>>>1,s=new Uint16Array(t,e,r);return r<=32?String.fromCharCode.apply(String,s):n.decode(s)}function i(t){const e={};function r(t, e){return t?s(t.buffer,e):""}const n=t.env=t.env||{};return n.abort=n.abort||function(t, s, i, o){const a=e.memory||n.memory;throw Error(`abort: ${r(a,t)} at ${r(a,s)}:${i}:${o}`)},n.trace=n.trace||function(t, s, ...i){const o=e.memory||n.memory;console.log(`trace: ${r(o,t)}${s?" ":""}${i.slice(0,s).join(", ")}`)},n.seed=n.seed||Date.now,t.Math=t.Math||Math,t.Date=t.Date||Date,e}const o=function(){throw Error("Operation requires compiling with --exportRuntime")};function a(t, r){const n=r.exports,i=n.memory,a=n.table,c=n.__new||o,u=n.__pin||o,y=n.__unpin||o,l=n.__collect||o,p=n.__rtti_base,d=p?function(t){return t[p>>>2]}:o;function b(t){const e=function(t){const e=new Uint32Array(i.buffer);if((t>>>=0)>=d(e))throw Error(`invalid id: ${t}`);return e[(p+4>>>2)+2*t]}(t);if(!(7&e))throw Error(`not an array: ${t}, flags=${e}`);return e}function h(t){const e=new Uint32Array(i.buffer);if((t>>>=0)>=d(e))throw Error(`invalid id: ${t}`);return e[(p+4>>>2)+2*t+1]}function m(t){return 31-Math.clz32(t>>>6&31)}function g(t, e, r){const n=i.buffer;if(r)switch(t){case 2:return new Float32Array(n);case 3:return new Float64Array(n)}else switch(t){case 0:return new(e?Int8Array:Uint8Array)(n);case 1:return new(e?Int16Array:Uint16Array)(n);case 2:return new(e?Int32Array:Uint32Array)(n);case 3:return new(e?BigInt64Array:BigUint64Array)(n)}throw Error(`unsupported align: ${t}`)}function A(t){const e=new Uint32Array(i.buffer),r=b(e[t+-8>>>2]),n=m(r);let s=4&r?t:e[t+4>>>2];const o=2&r?e[t+12>>>2]:e[s+-4>>>2]>>>n;return g(n,2048&r,4096&r).subarray(s>>>=n,s+o)}function w(t, e, r){return new t(_(t,e,r))}function _(t, e, r){const n=i.buffer,s=new Uint32Array(n),o=s[r+4>>>2];return new t(n,o,s[o+-4>>>2]>>>e)}function T(e, r, n){t[`__get${r}`]=w.bind(null,e,n),t[`__get${r}View`]=_.bind(null,e,n)}return t.__new=c,t.__pin=u,t.__unpin=y,t.__collect=l,t.__newString=function(t){if(null==t)return 0;const e=t.length,r=c(e<<1,1),n=new Uint16Array(i.buffer);for(var s=0,o=r>>>1; s>>2])throw Error(`not a string: ${t}`);return s(e,t)},t.__newArray=function(t, e){const r=b(t),n=m(r),s=e.length,o=c(s<>>2]=o,l[e+4>>>2]=o,l[e+8>>>2]=s<>>2]=s),a=e}const l=g(n,2048&r,4096&r);if(16384&r)for(let t=0; t>>n)+t]=r}else l.set(e,o>>>n);return a},t.__getArrayView=A,t.__getArray=function(t){const e=A(t),r=e.length,n=new Array(r);for(let t=0; t>>2];return e.slice(t,t+r)},[Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array].forEach((t=>{T(t,t.name,31-Math.clz32(t.BYTES_PER_ELEMENT))})),e&&[BigUint64Array,BigInt64Array].forEach((t=>{T(t,t.name.slice(3),3)})),t.__instanceof=function(t, e){const r=new Uint32Array(i.buffer);let n=r[t+-8>>>2];if(n<=d(r))do{if(n==e)return!0;n=h(n)}while(n);return!1},t.memory=t.memory||i,t.table=t.table||a,f(n,t)}function c(t){return"undefined"!=typeof Response&&t instanceof Response}function u(t){return t instanceof WebAssembly.Module}async function y(t, e={}){if(c(t=await t))return l(t,e);const r=u(t)?t:await WebAssembly.compile(t),n=i(e),s=await WebAssembly.instantiate(r,e);return{module:r,instance:s,exports:a(n,s)}}async function l(t, e={}){if(!WebAssembly.instantiateStreaming)return y(c(t=await t)?t.arrayBuffer():t,e);const r=i(e),n=await WebAssembly.instantiateStreaming(t,e),s=a(r,n.instance);return{...n,exports:s}}function f(t, e={}){const n=t.__argumentsLength? e=>{t.__argumentsLength.value=e}:t.__setArgumentsLength||t.__setargc||(()=>{});for(let s in t){if(!Object.prototype.hasOwnProperty.call(t,s))continue;const i=t[s];let o=s.split("."),a=e;for(; o.length>1;){let t=o.shift();Object.prototype.hasOwnProperty.call(a,t)||(a[t]={}),a=a[t]}let c=o[0],u=c.indexOf("#");if(u>=0){const e=c.substring(0,u),o=a[e];if(void 0===o||!o.prototype){const t=function(...e){return t.wrap(t.prototype.constructor(0,...e))};t.prototype={valueOf(){return this[r]}},t.wrap=function(e){return Object.create(t.prototype,{[r]:{value:e,writable:!1}})},o&&Object.getOwnPropertyNames(o).forEach((e=>Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(o,e)))),a[e]=t}if(c=c.substring(u+1),a=a[e].prototype,/^(get|set):/.test(c)){if(!Object.prototype.hasOwnProperty.call(a,c=c.substring(4))){let e=t[s.replace("set:","get:")],n=t[s.replace("get:","set:")];Object.defineProperty(a,c,{get(){return e(this[r])},set(t){n(this[r],t)},enumerable:!0})}}else"constructor"===c?(a[c]=(...t)=>(n(t.length),i(...t))).original=i:(a[c]=function(...t){return n(t.length),i(this[r],...t)}).original=i}else/^(get|set):/.test(c)?Object.prototype.hasOwnProperty.call(a,c=c.substring(4))||Object.defineProperty(a,c,{get:t[s.replace("set:","get:")],set:t[s.replace("get:","set:")],enumerable:!0}):"function"==typeof i&&i!==n?(a[c]=(...t)=>(n(t.length),i(...t))).original=i:a[c]=i}return e}var p={instantiate:y,instantiateSync:function(t, e={}){const r=u(t)?t:new WebAssembly.Module(t),n=i(e),s=new WebAssembly.Instance(r,e);return{module:r,instance:s,exports:a(n,s)}},instantiateStreaming:l,demangle:f};function d(t, e, r){return e}function b(t, e, r){return t.exports.__getArrayBuffer(e)}function h(t, e, r){return t.exports[`__get${function(t){return t.startsWith("~lib/typedarray/")?((t=t.slice("~lib/typedarray/".length)).startsWith("Big")&&(t=t.slice(3)),t):t}(r)}View`](e)}function m(t, e, r){return t.exports.__newArray(t.getTypeId(r),e)}function g(t){if(!t.startsWith("~lib/array/Array"))throw Error(`${JSON.stringify(t)} is not an array type`);return t.slice("~lib/array/Array<".length,-1)}const A=new Map([["void",{ascToJs:d,jsToAsc:d}],[/^(i|u|f)(8|16|32|64)|[ui]size|bool|externref$/,{ascToJs:d,jsToAsc:d}],["~lib/string/String",{ascToJs:function(t, e, r){return t.exports.__getString(e)},jsToAsc:function(t, e, r){return t.exports.__newString(e)}}],["~lib/typedarray/Int8Array",{ascToJs:h,jsToAsc:m}],["~lib/typedarray/Int16Array",{ascToJs:h,jsToAsc:m}],["~lib/typedarray/Int32Array",{ascToJs:h,jsToAsc:m}],["~lib/typedarray/Uint8Array",{ascToJs:h,jsToAsc:m}],["~lib/typedarray/Uint16Array",{ascToJs:h,jsToAsc:m}],["~lib/typedarray/Uint32Array",{ascToJs:h,jsToAsc:m}],["~lib/typedarray/Int64Array",{ascToJs:h,jsToAsc:m}],["~lib/typedarray/Uint64Array",{ascToJs:h,jsToAsc:m}],["~lib/typedarray/Uint8ClampedArray",{ascToJs:h,jsToAsc:m}],["~lib/typedarray/Float32Array",{ascToJs:b,jsToAsc:m}],["~lib/typedarray/Float64Array",{ascToJs:b,jsToAsc:m}],["~lib/arraybuffer/ArrayBuffer",{ascToJs:b,jsToAsc:function(t, e, r){const n=t.exports.__new(e.byteLength,t.getTypeId(r));return new Uint8Array(t.exports.memory.buffer,n,e.byteLength).set(new Uint8Array(e)),n}}],[/^~lib\/array\/Array<.+>$/,{ascToJs:function(t, e, r){const n=g(r),s=_(n);return t.exports.__getArray(e).map((e=>s.ascToJs(t,e,n)))},jsToAsc:function(t, e, r){const n=g(r),s=_(n),i=e.map((e=>s.jsToAsc(t,e,n)));return t.exports.__newArray(t.getTypeId(r),i)}}]]),w=new Set;function _(t){for(const[e,r]of A)if("string"!=typeof e){if(e.test(t))return r}else if(e===t)return r;return w.has(t)||(console.warn(`No converter for ${JSON.stringify(t)}, using pass-through`),w.add(t)),{ascToJs:d,jsToAsc:d}}function T(t){var e;return null===(e=_(t))||void 0===e?void 0:e.ascToJs}function j(t){var e;return null===(e=_(t))||void 0===e?void 0:e.jsToAsc}function O(t, e, r){const n=r.parameters.map(T),s=j(r.returnType);return function(...i){if(i.length!=n.length)throw Error(`Expected ${n.length} arguments, got ${i.length}`);const o=i.map(((e, s)=>n[s](t,e,r.parameters[s]))),a=e(...o);return s(t,a,r.returnType)}}function U(t, e, r){const n=r.parameters.map(j),s=T(r.returnType);return(...i)=>{if(i.length!=n.length)throw Error(`Expected ${n.length} arguments, got ${i.length}`);const o=i.map(((e, s)=>n[s](t,e,r.parameters[s]))),a=e(...o);return s(t,a,r.returnType)}}function x(t, {depth:e=Number.POSITIVE_INFINITY}={}){return e<=0||!t||"object"!=typeof t?t:Object.fromEntries(Object.entries(t).map((([t,r])=>[t,x(r,{depth:e-1})])))}function E(t){const e=WebAssembly.Module.customSections(t,"as-bind_bindings"),r=new TextDecoder("utf8").decode(new Uint8Array(e[0]));try{return JSON.parse(r)}catch(t){throw Error(`Couldn’t decode type descriptor: ${t.message}`)}}class S{constructor(){this.unboundExports={},this.exports={},this.importObject={}}getTypeId(t){if(t in this.typeDescriptor.typeIds)return this.typeDescriptor.typeIds[t].id;throw Error(`Unknown type ${JSON.stringify(t)}`)}getTypeSize(t){if(t in this.typeDescriptor.typeIds)return this.typeDescriptor.typeIds[t].byteSize;throw Error(`Unknown type ${JSON.stringify(t)}`)}_validate(){if(!WebAssembly.Module.exports(this.module).find((t=>"__new"===t.name)))throw Error("The AssemblyScript wasm module was not built with --exportRuntime, which is required.");if(1!==WebAssembly.Module.customSections(this.module,"as-bind_bindings").length)throw new Error("The AssemblyScript wasm module was not built with the as-bind transform.")}async _instantiate(t, e){this.module=await async function(t){if(t=await Promise.resolve(t),"undefined"!=typeof Response&&t instanceof Response){if(WebAssembly.compileStreaming)return WebAssembly.compileStreaming(t);t=await t.arrayBuffer()}return WebAssembly.compile(t)}(t),this._validate(),this.typeDescriptor=E(this.module),this._instantiateBindImportFunctions(e),this.loadedModule=await async function(t, e){return p.instantiate(t,e)}(this.module,this.importObject),this._instantiateBindUnboundExports()}_instantiateSync(t, e){this.module=new WebAssembly.Module(t),this._validate(),this.typeDescriptor=E(this.module),this._instantiateBindImportFunctions(e),this.loadedModule=function(t, e){return p.instantiateSync(t,e)}(this.module,this.importObject),this._instantiateBindUnboundExports()}_instantiateBindImportFunctions(t){this.importObject=x(t,{depth:2});for(const[e,r]of Object.entries(this.typeDescriptor.importedFunctions))for(const[n,s]of Object.entries(r))this.importObject[e][`__asbind_unbound_${n}`]=t[e][n],this.importObject[e][n]=O(this,t[e][n],s)}_instantiateBindUnboundExports(){const t=this.loadedModule.exports;this.exports=x(t,{depth:1});for(const[e,r]of Object.entries(this.typeDescriptor.exportedFunctions))this.exports[e]=U(this,t[e],r)}}async function I(t, e){let r=new S;return await r._instantiate(t,e),r}function $(t, e){let r=new S;return r._instantiateSync(t,e),r}; +const instantiate = I; + +// js solver +async function solveFnJs(polycubes, dimX, dimY, dimZ) { + const solver = new SomaSolver(dimX, dimY, dimZ); + const voxelSpaces = new Array(); + for (let i = 0; i < polycubes.length; i++) { + voxelSpaces.push(new VoxelSpaceBoolean({ + id: i, + dims: [dimX, dimY, dimZ], + space: polycubes[i], + cullEmpty: true + })); + } + await solver.solve(voxelSpaces); + return solver.getSolutions(); +} +class VoxelSpaceBoolean { + constructor(options) { + this.length = options.dims[0] * options.dims[1] * options.dims[2]; + if (!options.space) { + options.space = new Array(options.dims[0] * options.dims[1] * options.dims[2]); + options.space.fill(false); + } + else if (!Array.isArray(options.space)) { + const newSpace = []; + for (let i = 0; i < this.length; i++) { + const mask = 1n << BigInt(i); + newSpace.push((options.space & mask) !== 0n); + } + options.space = newSpace; + } + this.id = options.id; + this.dims = options.dims; + this.space = options.space; + this.color = options.color ?? "red"; + if (options.cullEmpty !== false) { + this.cullEmptySpace(); + } + } + setColor(color) { + this.color = color; + } + getColor() { + return this.color; + } + binaryRep() { + return this.space.reduce((prev, curr) => prev + (curr ? "1" : "0"), ""); + } + getExtrema() { + const extrema = { + xMax: 0, + xMin: this.dims[0], + yMax: 0, + yMin: this.dims[1], + zMax: 0, + zMin: this.dims[2], + }; + this.forEachCell((val, x, y, z) => { + if (val) { + extrema.xMax = Math.max(extrema.xMax, x); + extrema.xMin = Math.min(extrema.xMin, x); + extrema.yMax = Math.max(extrema.yMax, y); + extrema.yMin = Math.min(extrema.yMin, y); + extrema.zMax = Math.max(extrema.zMax, z); + extrema.zMin = Math.min(extrema.zMin, z); + } + }); + return extrema; + } + cullEmptySpace() { + const extrema = this.getExtrema(); + const newX = extrema.xMax - extrema.xMin + 1; + const newY = extrema.yMax - extrema.yMin + 1; + const newZ = extrema.zMax - extrema.zMin + 1; + const newSpace = new Array(newX * newY * newZ); + newSpace.fill(false); + let index = 0; + for (let x = extrema.xMin; x <= extrema.xMax; x++) { + for (let y = extrema.yMin; y <= extrema.yMax; y++) { + for (let z = extrema.zMin; z <= extrema.zMax; z++) { + if (this.at(x, y, z)) { + newSpace[index] = true; + } + index++; + } + } + } + this.dims[0] = newX; + this.dims[1] = newY; + this.dims[2] = newZ; + this.space = newSpace; + } + forEachCell(cb) { + loopStart: for (let x = 0; x < this.dims[0]; x++) { + for (let y = 0; y < this.dims[1]; y++) { + for (let z = 0; z < this.dims[2]; z++) { + if (cb(this.at(x, y, z), x, y, z) === 0) { + break loopStart; + } + } + } + } + } + getId() { + return this.id; + } + print() { + let accum = ""; + console.log("---"); + for (let i = 0; i < this.dims[0]; i++) { + for (let j = 0; j < this.dims[1]; j++) { + for (let k = 0; k < this.dims[2]; k++) { + accum += this.at(i, j, k) ? '#' : 'O'; + } + console.log(accum); + accum = ""; + } + if (i !== this.dims[0] - 1) { + console.log("-"); + } + } + console.log("---"); + } + getUniqueRotations() { + const rotations = []; + const refSpace = this.clone(); + VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x')); + refSpace.rot90('y'); + VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x')); + refSpace.rot90('y'); + VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x')); + refSpace.rot90('y'); + VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x')); + refSpace.rot90('z'); + VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x')); + refSpace.rot90('z'); + refSpace.rot90('z'); + VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x')); + return rotations; + } + getAllRotations() { + const rotations = []; + const refSpace = this.clone(); + rotations.push(...refSpace.getAxisSpins('x')); + refSpace.rot90('y'); + rotations.push(...refSpace.getAxisSpins('x')); + refSpace.rot90('y'); + rotations.push(...refSpace.getAxisSpins('x')); + refSpace.rot90('y'); + rotations.push(...refSpace.getAxisSpins('x')); + refSpace.rot90('z'); + rotations.push(...refSpace.getAxisSpins('x')); + refSpace.rot90('z'); + refSpace.rot90('z'); + rotations.push(...refSpace.getAxisSpins('x')); + return rotations; + } + static pushNewUniqueSpaces(existingSpaces, newSpaces) { + for (const newSpace of newSpaces) { + let matchFound = false; + for (const existingSpace of existingSpaces) { + if (newSpace.matches(existingSpace)) { + matchFound = true; + break; + } + } + if (!matchFound) { + existingSpaces.push(newSpace); + } + } + } + getAllPositionsInPrism(cubeDimX, cubeDimY, cubeDimZ) { + const cubePositions = []; + if (this.dims[0] > cubeDimX || this.dims[1] > cubeDimY || this.dims[2] > cubeDimZ) { + return cubePositions; + } + for (let xOffset = 0; xOffset < (cubeDimX - this.dims[0] + 1); xOffset++) { + for (let yOffset = 0; yOffset < (cubeDimY - this.dims[1] + 1); yOffset++) { + for (let zOffset = 0; zOffset < (cubeDimZ - this.dims[2] + 1); zOffset++) { + const cubePos = new VoxelSpaceBoolean({ id: this.id, dims: [cubeDimX, cubeDimY, cubeDimZ], color: this.color, cullEmpty: false }); + this.forEachCell((val, x, y, z) => { + cubePos.set(xOffset + x, yOffset + y, zOffset + z, val); + }); + cubePositions.push(cubePos); + } + } + } + return cubePositions; + } + matches(space) { + const otherDims = space.getDims(); + for (let i = 0; i < this.dims.length; i++) { + if (otherDims[i] !== this.dims[i]) { + return false; + } + } + const otherRaw = space.getRaw(); + if (typeof otherRaw === "bigint") { + return space.binaryRep() === this.binaryRep(); + } + return this.space.reduce((prev, unit, i) => (unit === otherRaw[i]) && prev, true); + } + clone() { + return new VoxelSpaceBoolean({ id: this.id, dims: this.getDims(), space: this.getRaw(), color: this.getColor(), cullEmpty: false }); + } + getAxisSpins(axis) { + const rotations = [this.clone()]; + for (let i = 0; i < 3; i++) { + rotations.push(rotations[i].rotated90(axis)); + } + return rotations; + } + getDims() { + return this.dims.slice(); + } + getRaw() { + return this.space.slice(); + } + // [1, 0, 0] [x] [ x] + // [0, 0, -1] * [y] = [-z] + // [0, 1, 0] [z] [ y] + newIndexRotX(x, y, z) { + return this.dims[2] * this.dims[1] * x + this.dims[1] * (this.dims[2] - 1 - z) + y; + } + // [ 0, 0, 1] [x] [ z] + // [ 0, 1, 0] * [y] = [ y] + // [-1, 0, 0] [z] [-x] + newIndexRotY(x, y, z) { + return this.dims[1] * this.dims[0] * z + this.dims[0] * y + (this.dims[0] - 1 - x); + } + // [0, -1, 0] [x] [-y] + // [1, 0, 0] * [y] = [ x] + // [0, 0, 1] [z] [ z] + newIndexRotZ(x, y, z) { + return this.dims[0] * this.dims[2] * (this.dims[1] - 1 - y) + this.dims[2] * x + z; + } + at(x, y, z) { + return this.space[this.index(x, y, z)]; + } + index(x, y, z) { + return this.dims[1] * this.dims[2] * x + this.dims[2] * y + z; + } + toggle(x, y, z) { + const index = this.index(x, y, z); + this.space[index] = !this.space[index]; + } + set(x, y, z, val) { + this.space[this.index(x, y, z)] = val; + } + rotated90(dim) { + const newSpace = new Array(this.dims[0] * this.dims[1] * this.dims[2]); + newSpace.fill(false); + let newDims; + let rotIndex; + if (dim === 'x') { + newDims = [this.dims[0], this.dims[2], this.dims[1]]; + rotIndex = this.newIndexRotX.bind(this); + } + else if (dim === 'y') { + newDims = [this.dims[2], this.dims[1], this.dims[0]]; + rotIndex = this.newIndexRotY.bind(this); + } + else { + newDims = [this.dims[1], this.dims[0], this.dims[2]]; + rotIndex = this.newIndexRotZ.bind(this); + } + this.forEachCell((val, i, j, k) => { + if (val) { + newSpace[rotIndex(i, j, k)] = true; + } + }); + return new VoxelSpaceBoolean({ id: this.id, dims: newDims, space: newSpace, color: this.color, cullEmpty: false }); + } + rot90(dim) { + const rot = this.rotated90(dim); + this.space = rot.getRaw(); + this.dims = rot.getDims(); + } + plus(space) { + const newSpace = new Array(this.dims[0] * this.dims[1] * this.dims[2]); + newSpace.fill(false); + let clash = false; + space.forEachCell((val, x, y, z) => { + if (this.at(x, y, z) !== val) { + newSpace[this.index(x, y, z)] = true; + } + else { + if (val) { + clash = true; + } + } + }); + if (!clash) { + return new VoxelSpaceBoolean({ id: this.id, dims: this.getDims(), space: newSpace, color: this.color, cullEmpty: false }); + } + return null; + } + size() { + let size = 0; + this.forEachCell((val) => { + if (val) { + size++; + } + }); + return size; + } + getDirectNeighbourProfile(x, y, z) { + let result = 0; + if (x < this.dims[0] - 1 && this.at(x + 1, y, z)) { + result += 1; + } + if (y < this.dims[1] - 1 && this.at(x, y + 1, z)) { + result += 2; + } + if (z < this.dims[2] - 1 && this.at(x, y, z + 1)) { + result += 4; + } + if (x > 0 && this.at(x - 1, y, z)) { + result += 8; + } + if (y > 0 && this.at(x, y - 1, z)) { + result += 16; + } + if (z > 0 && this.at(x, y, z - 1)) { + result += 32; + } + return result; + } + getAllPermutationsInPrism(prismDimX, prismDimY, prismDimZ) { + const rotations = this.getUniqueRotations(); + let result = new Array(); + for (let i = 0; i < rotations.length; i++) { + result = result.concat(rotations[i].getAllPositionsInPrism(prismDimX, prismDimY, prismDimZ)); + } + return result; + } +} +class SomaSolver { + constructor(dimX, dimY, dimZ) { + this.visualiser = { async showSoln(soln) { }, async showSpace(cube) { } }; + this.solutions = new Array(); + this.iterations = 0; + this.dimX = dimX; + this.dimY = dimY; + this.dimZ = dimZ; + this.solutionCube = new VoxelSpaceBoolean({ id: 0, dims: [dimX, dimY, dimZ], cullEmpty: false }); + } + setDebug(visualiser) { + this.visualiser = visualiser; + } + async solve(polycubes) { + if (polycubes.length === 0) { + throw new Error("You must pass at least one polycube to solve the puzzle."); + } + this.solutions.splice(0, this.solutions.length); + const combosWithRots = new Array(); + for (let i = 1; i < polycubes.length; i++) { + const rots = polycubes[i].getAllPermutationsInPrism(this.dimX, this.dimY, this.dimZ); + combosWithRots.push(rots); + } + let combos = new Array(); + combos.push(polycubes[0].getAllPositionsInPrism(this.dimX, this.dimY, this.dimZ)); + combos = combos.concat(combosWithRots); + for (const combo of combos) { + for (const rot of combo) { + await this.visualiser.showSpace(rot); + } + } + await this.backtrackSolve(this.solutionCube, combos, new SomaSolution(this.dimX, this.dimY, this.dimZ)); + this.solutions = SomaSolution.filterUnique(this.solutions); + } + getSolutions() { + return this.solutions.slice(); + } + async backtrackSolve(workingSolution, polycubes, currentSoln, depth = 0) { + const nextCubeGroup = polycubes[0]; + for (let i = 0; i < nextCubeGroup.length; i++) { + const fusionAttempt = workingSolution.plus(nextCubeGroup[i]); + ++this.iterations; + if (fusionAttempt) { + const nextSoln = currentSoln.clone(); + nextSoln.addSpace(nextCubeGroup[i]); + await this.visualiser.showSoln(nextSoln); + if (polycubes.length === 1) { + this.solutions.push(nextSoln); + currentSoln = new SomaSolution(this.dimX, this.dimY, this.dimZ); + return; + } + else { + await this.backtrackSolve(fusionAttempt, polycubes.slice(1), nextSoln, depth + 1); + } + } + } + } +} +class SomaSolution { + constructor(dimX, dimY, dimZ) { + this.dimX = dimX; + this.dimY = dimY; + this.dimZ = dimZ; + this.solutionSpaces = []; + } + static filterUnique(solutions) { + if (solutions.length === 0) { + return []; + } + const uniqueSolns = [solutions[0]]; + for (const solution of solutions) { + let foundMatch = false; + for (const rotation of solution.getRotations()) { + let end = uniqueSolns.length; + for (let i = 0; i < end; i++) { + if (rotation.matches(uniqueSolns[i])) { + foundMatch = true; + } + } + } + if (!foundMatch) { + uniqueSolns.push(solution); + } + } + return uniqueSolns; + } + getRotations() { + if (this.solutionSpaces.length === 0) { + return []; + } + const result = []; + const allRots = this.solutionSpaces.map(space => space.getAllRotations()); + for (let i = 0; i < allRots[0].length; i++) { + const solnRot = new SomaSolution(this.dimX, this.dimY, this.dimZ); + allRots.forEach(rotGroup => solnRot.addSpace(rotGroup[i])); + result.push(solnRot); + } + return result; + } + matches(solution) { + for (let i = 0; i < this.solutionSpaces.length; i++) { + if (!this.solutionSpaces[i].matches(solution.solutionSpaces[i])) { + return false; + } + } + return true; + } + addSpace(space) { + this.solutionSpaces.push(space); + } + print() { + let accum = ""; + console.log("---"); + for (let x = 0; x < this.dimX; x++) { + for (let y = 0; y < this.dimY; y++) { + for (let z = 0; z < this.dimZ; z++) { + for (const space of this.solutionSpaces) { + if (space.at(x, y, z)) { + accum += space.getId(); + } + } + } + console.log(accum); + accum = ""; + } + if (x !== this.dimX - 1) { + console.log("-"); + } + } + console.log("---"); + } + at(x, y, z) { + for (const space of this.solutionSpaces) { + if (space.at(x, y, z)) { + return space.getId(); + } + } + return 0; + } + clone() { + const clone = new SomaSolution(this.dimX, this.dimY, this.dimZ); + clone.solutionSpaces = this.solutionSpaces.slice(); + return clone; + } + getDims() { + return [this.dimX, this.dimY, this.dimZ]; + } + forEachCell(cb) { + loopStart: for (let x = 0; x < this.dimX; x++) { + for (let y = 0; y < this.dimY; y++) { + for (let z = 0; z < this.dimZ; z++) { + cb(this.at(x, y, z), x, y, z); + } + } + } + } + getPieces() { + return this.solutionSpaces.slice(); + } +} + + + +// init wasm +let solveFnWasm; +async function load() { + const file = fetch("./main.wasm"); + const instance = await instantiate(file); + solveFnWasm = instance.exports.solve; +} +load(); + +// worker +self.addEventListener('message', async (event) => { + const { type, polycubes, dimX, dimY, dimZ } = event.data; + if (type === "wasm") { + const solutions = solveFnWasm(polycubes, dimX, dimY, dimZ); + self.postMessage(solutions.map(soln => soln.toString())); + } else if (type === "js") { + const solutions = await solveFnJs(polycubes, dimX, dimY, dimZ); + self.postMessage({solns: solutions.map(soln => soln.getPieces().map(piece => piece.binaryRep())), log: JSON.stringify(solutions)}); + } else { + self.postMessage({error: `Invalid solver type passed to worker: ${type}`}) + } +}); diff --git a/src/desktop/main.js b/src/desktop/main.js index 78576d7..a3f6697 100644 --- a/src/desktop/main.js +++ b/src/desktop/main.js @@ -9,7 +9,6 @@ function createWindow() { preload: path.join(__dirname, 'preload.js'), } }); - win.loadFile(path.join(__dirname, '../../public/index.html')); } diff --git a/src/desktop/preload.js b/src/desktop/preload.js index 190801e..e69de29 100644 --- a/src/desktop/preload.js +++ b/src/desktop/preload.js @@ -1,12 +0,0 @@ -window.addEventListener('DOMContentLoaded', () => { - const replaceText = (selector, text) => { - const element = document.getElementById(selector); - if (element) { - element.innerText = text; - } - - } - for (const dependency of ['chrome', 'node', 'electron']) { - replaceText(`${dependency}-version`, process.versions[dependency]); - } -}); \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 39e64dd..e3041cf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,10 @@ import App from './ui/App.svelte'; +import PolycubeScene from "./ui/threedee/PolycubeScene"; const app = new App({ target: document.body, props: { - name: 'world' + scene: new PolycubeScene() } }); diff --git a/src/solve.ts b/src/solve.ts new file mode 100644 index 0000000..9de804a --- /dev/null +++ b/src/solve.ts @@ -0,0 +1,128 @@ +import SomaSolution from "./SomaSolution"; +import {get} from "svelte/store"; +import VoxelSpaceBigInt from "./VoxelSpaceBigInt"; +import { + activeSolution, + polycubes, + showingSolution, + solutions, + solving, + somaDimX, + somaDimY, + somaDimZ, + totalVolume +} from "./store"; + +const worker = new Worker('./solver/main.js', {type: 'module'}); +async function respondWasm(event: MessageEvent) { + solutions.set(event.data.map((wasmSolution) => { + const solnObj = new SomaSolution(somaDimX.currentVal(), somaDimY.currentVal(), somaDimZ.currentVal()); + const spaceReps = wasmSolution.split(","); + for (let i = 0; i < spaceReps.length; i++) { + solnObj.addSpace(new VoxelSpaceBigInt({ + id: i, + dims: [somaDimX.currentVal(), somaDimY.currentVal(), somaDimZ.currentVal()], + space: BigInt(parseInt(spaceReps[i])), + color: get(polycubes)[i].getColor(), + cullEmpty: false, + })); + } + return solnObj; + })); + if (event.data.length > 0) { + activeSolution.set(0); + showingSolution.set(true); + } else { + showingSolution.set(false); + activeSolution.set(null); + } + solving.set(false); +} + +function respondJs(event: MessageEvent) { + solutions.set(event.data.solns.map(solnSpaces => { + const solnObj = new SomaSolution(somaDimX.currentVal(), somaDimY.currentVal(), somaDimZ.currentVal()); + for (let i = 0; i < solnSpaces.length; i++) { + solnObj.addSpace(new VoxelSpaceBigInt({ + id: i, + dims: [somaDimX.currentVal(), somaDimY.currentVal(), somaDimZ.currentVal()], + space: BigInt(`0b${ solnSpaces[i] }`), + color: get(polycubes)[i].getColor(), + cullEmpty: false, + })); + } + return solnObj; + })); + if (event.data.length > 0) { + activeSolution.set(0); + showingSolution.set(true); + } else { + showingSolution.set(false); + activeSolution.set(null); + } + solving.set(false); +} + +export function solve() { + const doWasm = get(totalVolume) <= 32; + let inputCubes; + if (doWasm) { + worker.onmessage = (e) => respondWasm(e); + } else { + worker.onmessage = (e) => respondJs(e); + } + inputCubes = polycubes.currentVal().map(cubeInput => cubeInput.getRaw()); + solving.set(true); + worker.postMessage({ + type: doWasm ? 'wasm' : 'js', + polycubes: inputCubes, + dimX: somaDimX.currentVal(), + dimY: somaDimY.currentVal(), + dimZ: somaDimZ.currentVal() + }); +} + +// async function solveSync() { +// const solver = new SomaSolver(somaDimX.currentVal(), somaDimY.currentVal(), somaDimZ.currentVal()); +// function showSolutionWaitUserFeedback(soln: SomaSolution) { +// activeSolution.set(0); +// solutions.set([soln]); +// showingSolution.set(true); +// return new Promise((resolve) => { +// const callback = (e: KeyboardEvent) => { +// resolve(); +// window.removeEventListener("keydown", callback); +// }; +// window.addEventListener("keydown", callback); +// }); +// } +// solver.setDebug({ +// showSoln(soln: SomaSolution) { +// return showSolutionWaitUserFeedback(soln); +// }, +// showSpace(cube: VoxelSpaceBoolean) { +// const testSoln = new SomaSolution(somaDimX.currentVal(), somaDimY.currentVal(), somaDimZ.currentVal()); +// testSoln.addSpace(cube); +// return showSolutionWaitUserFeedback(testSoln); +// } +// }); +// solving.set(true); +// await solver.solve(polycubes.currentVal().map(cubeInput => new VoxelSpaceBoolean({ +// id: cubeInput.getId(), +// dims: cubeInput.getDims(), +// space: cubeInput.getRaw(), +// color: cubeInput.getColor(), +// cullEmpty: true +// }))); +// const solns = solver.getSolutions(); +// +// if (solns.length > 0) { +// activeSolution.set(0); +// solutions.set(solns); +// showingSolution.set(true); +// } else { +// showingSolution.set(false); +// activeSolution.set(null); +// } +// solving.set(false); +// } \ No newline at end of file diff --git a/src/store.ts b/src/store.ts index 5d3b675..c8cf7ae 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,27 +1,22 @@ import { derived, writable } from 'svelte/store'; -import { get } from 'svelte/store'; -import SomaSolution from "./SomaSolution"; -import VoxelSpaceBigInt from "./VoxelSpaceBigInt"; -import type {DimensionDef} from "./VoxelSpaceBoolean"; -import PolycubeScene from "./ui/threedee/PolycubeScene"; +import type SomaSolution from "./SomaSolution"; +import type VoxelSpaceBigInt from "./VoxelSpaceBigInt"; +import DimStore from "./stores/DimStore"; +import PolycubeStore from "./stores/PolycubeStore"; -export const MAX_DIMS = 20; -export const MIN_DIMS = 1; - -export const solving = writable(false); -export const debug = writable(false); -export const somaDimX = dimStore(3); -export const somaDimY = dimStore(3); -export const somaDimZ = dimStore(3); -export const polycubes = polycubeStore(); -export const selectedCube = writable(0); -export const solutions = writable([] as SomaSolution[]); -export const activeSolution = writable(null); -export const showingSolution = writable(false); +export const somaDimX = new DimStore(3); +export const somaDimY = new DimStore(3); +export const somaDimZ = new DimStore(3); export const totalVolume = derived( [somaDimX, somaDimY, somaDimZ], ([$dimX, $dimY, $dimZ]: [number, number, number]) => $dimX*$dimY*$dimZ ); +export const polycubes = new PolycubeStore(somaDimX, somaDimY, somaDimZ); +export const solving = writable(false); +export const debug = writable(false); +export const solutions = writable([] as SomaSolution[]); +export const activeSolution = writable(null); +export const showingSolution = writable(false); export const isMaxPolycubes = derived( [polycubes, totalVolume], ([$cubes, $vol]: [VoxelSpaceBigInt[], number]) => $cubes.length >= $vol @@ -30,221 +25,93 @@ export const isMinPolycubes = derived( polycubes, ($polycubes: VoxelSpaceBigInt[]) => $polycubes.length <= 1 ); -export const cubeScene = new PolycubeScene(); - -function dimStore(init: number) { - const dimStore = writable(init); - return { - subscribe: dimStore.subscribe, - inc() { - dimStore.set(get(dimStore) + 1); - }, - dec() { - dimStore.set(get(dimStore) - 1); - }, - set(dim: number) { - if (dim > MAX_DIMS || dim < MIN_DIMS) { - return; - } - dimStore.set(dim); - polycubes.reset(); - }, - } -} - -function polycubeStore() { - function freshCube(id: number) { - return new VoxelSpaceBigInt({ - id: id, - dims: [get(somaDimX), get(somaDimY), get(somaDimZ)], - color: colorFromIndex(id), - cullEmpty: false - }); - } - const polycubeStore = writable([freshCube(0)]); - return { - subscribe: polycubeStore.subscribe, - setColor(cubeIndex: number, color: string) { - const cubes = get(polycubeStore); - cubes[cubeIndex].setColor(color); - polycubeStore.set(cubes); - }, - addCube() { - if (!get(isMaxPolycubes)) { - polycubeStore.update((polycubes: VoxelSpaceBigInt[]) => - polycubes.concat(freshCube(polycubes.length))); - } - }, - removeCube() { - if (!get(isMinPolycubes)) { - polycubeStore.update((polycubes: VoxelSpaceBigInt[]) => polycubes.splice(0, polycubes.length - 1)); - } - const newLength = get(polycubeStore).length; - if (newLength <= get(selectedCube)) { - selectedCube.set(newLength - 1); - } - }, - toggle(cubeIndex: number, x: number, y: number, z: number) { - const cubes = get(polycubeStore); - cubes[cubeIndex].toggle(x, y, z); - polycubeStore.set(cubes); - }, - set(cubeIndex: number, val: boolean, x: number, y: number, z: number) { - const cubes = get(polycubeStore); - cubes[cubeIndex].set(x, y, z, val); - polycubeStore.set(cubes); - }, - reset() { - polycubeStore.update((polycubes: VoxelSpaceBigInt[]) => { - const result: VoxelSpaceBigInt[] = []; - for (let i = 0; i < Math.min(polycubes.length, get(totalVolume)); i++) { - result.push(freshCube(i)); - } - return result; - }); - } - } -} - -function rgbToHex(rgbStr: string): string { - const sep = rgbStr.indexOf(",") > -1 ? "," : " "; - const rgb = rgbStr.substr(4).split(")")[0].split(sep); - const r = (+rgb[0]).toString(16).padStart(2, "0"); - const g = (+rgb[1]).toString(16).padStart(2, "0"); - const b = (+rgb[2]).toString(16).padStart(2, "0"); - return "#" + r + g + b; -} - -function hslToRgb(hslStr: string): string { - const opt = new Option(); - opt.style.color = hslStr; - return opt.style.color; -} - -export function colorFromIndex(index: number): string { - const colorWheelCycle = Math.floor(index / 6); - const darknessCycle = Math.floor(index / 12); - const spacing = (360 / 6); - const offset = colorWheelCycle === 0 ? 0 : spacing / (colorWheelCycle + 2); - let hue = spacing * (index % 6) + offset; - const saturation = 100; - const lightness = 1 / (2 + darknessCycle) * 100; - return rgbToHex(hslToRgb(`hsl(${hue},${saturation}%,${Math.round(lightness)}%)`)); -} - -const worker = new Worker('../solver/main.js', {type: "module"}); -async function respondWasm(event: MessageEvent) { - solutions.set(event.data.map((wasmSolution) => { - const solnObj = new SomaSolution(get(somaDimX), get(somaDimY), get(somaDimZ)); - const spaceReps = wasmSolution.split(","); - for (let i = 0; i < spaceReps.length; i++) { - solnObj.addSpace(new VoxelSpaceBigInt({ - id: i, - dims: [get(somaDimX), get(somaDimY), get(somaDimZ)] as DimensionDef, - space: BigInt(parseInt(spaceReps[i])), - color: get(polycubes)[i].getColor(), - cullEmpty: false, - })); - } - return solnObj; - })); - if (event.data.length > 0) { - activeSolution.set(0); - showingSolution.set(true); - } else { - showingSolution.set(false); - activeSolution.set(null); - } - solving.set(false); -} - -function respondJs(event: MessageEvent) { - solutions.set(event.data.solns.map(solnSpaces => { - const solnObj = new SomaSolution(get(somaDimX), get(somaDimY), get(somaDimZ)); - for (let i = 0; i < solnSpaces.length; i++) { - solnObj.addSpace(new VoxelSpaceBigInt({ - id: i, - dims: [get(somaDimX), get(somaDimY), get(somaDimZ)] as DimensionDef, - space: BigInt(`0b${ solnSpaces[i] }`), - color: get(polycubes)[i].getColor(), - cullEmpty: false, - })); - } - return solnObj; - })); - if (event.data.length > 0) { - activeSolution.set(0); - showingSolution.set(true); - } else { - showingSolution.set(false); - activeSolution.set(null); - } - solving.set(false); -} - -export function solve() { - const doWasm = get(totalVolume) <= 32; - let inputCubes; - if (doWasm) { - worker.onmessage = (e) => respondWasm(e); - } else { - worker.onmessage = (e) => respondJs(e); - } - inputCubes = get(polycubes).map(cubeInput => cubeInput.getRaw()); - solving.set(true); - worker.postMessage({ - type: doWasm ? 'wasm' : 'js', - polycubes: inputCubes, - dimX: get(somaDimX), - dimY: get(somaDimY), - dimZ: get(somaDimZ) - }); -} - -// async function solveSync() { -// const solver = new SomaSolver(get(somaDimX), get(somaDimY), get(somaDimZ)); -// function showSolutionWaitUserFeedback(soln: SomaSolution) { -// activeSolution.set(0); -// solutions.set([soln]); -// showingSolution.set(true); -// return new Promise((resolve) => { -// const callback = (e: KeyboardEvent) => { -// resolve(); -// window.removeEventListener("keydown", callback); -// }; -// window.addEventListener("keydown", callback); -// }); -// } -// if (get(debug)) { -// solver.setDebug({ -// showSoln(soln: SomaSolution) { -// return showSolutionWaitUserFeedback(soln); -// }, -// showSpace(cube: VoxelSpaceBoolean) { -// const testSoln = new SomaSolution(get(somaDimX), get(somaDimY), get(somaDimZ)); -// testSoln.addSpace(cube); -// return showSolutionWaitUserFeedback(testSoln); -// } -// }); -// } -// solving.set(true); -// await solver.solve(get(polycubes).map(cubeInput => new VoxelSpaceBoolean({ -// id: cubeInput.getId(), -// dims: cubeInput.getDims(), -// space: cubeInput.getRaw(), -// color: cubeInput.getColor(), -// cullEmpty: true -// }))); -// const solns = solver.getSolutions(); -// -// if (solns.length > 0) { -// activeSolution.set(0); -// solutions.set(solns); -// showingSolution.set(true); -// } else { -// showingSolution.set(false); -// activeSolution.set(null); -// } -// solving.set(false); -// } \ No newline at end of file +export const examples = [ + { + name: "Standard Soma Cube", + dimX: 3, + dimY: 3, + dimZ: 3, + cubes: [ + {space: 23n, color: "#ff0000"}, + {space: 30n, color: "#ffff00"}, + {space: 15n, color: "#00ff00"}, + {space: 8344n, color: "#00ffff"}, + {space: 9240n, color: "#0000ff"}, + {space: 4632n, color: "#ff00ff"}, + {space: 152n, color: "#ff5500"}, + ], + }, + { + name: "Convolution TG 5850 (4x4x4)", + dimX: 4, + dimY: 4, + dimZ: 4, + cubes: [ + {space: 12651033568030492808n, color: "#ff0000"}, + {space: 1123868011502010368n, color: "#ffff00"}, + {space: 124314703626304n, color: "#00ff00"}, + {space: 263883079155712n, color: "#00ffff"}, + {space: 3952148496n, color: "#0000ff"}, + {space: 166723584n, color: "#ff00ff"}, + {space: 1048576n, color: "#ff5500"}, + ], + }, + { + name: "3x3x4, 7 Pieces", + dimX: 3, + dimY: 3, + dimZ: 4, + cubes: [ + {space: 244n, color: "#ff0000"}, + {space: 625n, color: "#ffff00"}, + {space: 140080n, color: "#00ff00"}, + {space: 31n, color: "#00ffff"}, + {space: 738n, color: "#0000ff"}, + {space: 537002290n, color: "#ff00ff"}, + {space: 275n, color: "#ff5500"}, + ], + }, + { + name: "3x3x3, 7 Pieces", + dimX: 3, + dimY: 3, + dimZ: 3, + cubes: [ + {space: 23n, color: "#ff0000"}, + {space: 47n, color: "#ffff00"}, + {space: 474n, color: "#00ff00"}, + {space: 8n, color: "#00ffff"}, + {space: 24n, color: "#0000ff"}, + {space: 316n, color: "#ff00ff"}, + {space: 23n, color: "#ff5500"}, + ], + }, + { + name: "3x3x3, 6 Pieces", + dimX: 3, + dimY: 3, + dimZ: 3, + cubes: [ + {space: 30n, color: "#ff0000"}, + {space: 29712n, color: "#ffff00"}, + {space: 29216n, color: "#00ff00"}, + {space: 15392n, color: "#00ffff"}, + {space: 15364n, color: "#0000ff"}, + {space: 536871032n, color: "#ff00ff"}, + ], + }, + { + name: "3x3x3, 5 Pieces", + dimX: 3, + dimY: 3, + dimZ: 3, + cubes: [ + {space: 376n, color: "#ff0000"}, + {space: 2428n, color: "#ffff00"}, + {space: 28960n, color: "#00ff00"}, + {space: 48136n, color: "#00ffff"}, + {space: 120n, color: "#0000ff"}, + ], + }, +]; \ No newline at end of file diff --git a/src/stores/DimStore.ts b/src/stores/DimStore.ts new file mode 100644 index 0000000..43bb665 --- /dev/null +++ b/src/stores/DimStore.ts @@ -0,0 +1,34 @@ +import {get, Writable, writable} from "svelte/store"; + +export default class DimStore { + static readonly MAX_DIMS = 20; + static readonly MIN_DIMS = 1; + private store: Writable; + + constructor(init: number) { + this.store = writable(init); + } + + currentVal() { + return get(this.store); + } + + subscribe(cb: (val: number) => any) { + return this.store.subscribe(cb); + } + + inc() { + this.set(this.currentVal() + 1); + } + + dec() { + this.set(this.currentVal() - 1); + } + + set(dim: number) { + if (dim > DimStore.MAX_DIMS || dim < DimStore.MIN_DIMS) { + return; + } + this.store.set(dim); + } +} diff --git a/src/stores/PolycubeStore.ts b/src/stores/PolycubeStore.ts new file mode 100644 index 0000000..2bf8ba2 --- /dev/null +++ b/src/stores/PolycubeStore.ts @@ -0,0 +1,122 @@ +import VoxelSpaceBigInt from "../VoxelSpaceBigInt"; +import {get, Writable, writable} from "svelte/store"; +import type DimStore from "./DimStore"; +import {colorFromIndex} from "../utils"; + +export default class PolycubeStore { + private store: Writable; + private dimX: DimStore; + private dimY: DimStore; + private dimZ: DimStore; + private selectedCube: Writable; + + constructor(dimX: DimStore, dimY: DimStore, dimZ: DimStore) { + this.selectedCube = writable(0); + this.dimX = dimX; + this.dimY = dimY; + this.dimZ = dimZ; + this.store = writable([this.freshCube(0)]); + this.dimX.subscribe(() => this.reset()); + this.dimY.subscribe(() => this.reset()); + this.dimZ.subscribe(() => this.reset()); + + } + + private freshCube(id: number) { + return new VoxelSpaceBigInt({ + id: id, + dims: [this.dimX.currentVal(), this.dimY.currentVal(), this.dimZ.currentVal()], + color: colorFromIndex(id), + cullEmpty: false + }); + } + + private volume() { + return this.dimX.currentVal() * this.dimY.currentVal() * this.dimZ.currentVal(); + } + + private isMaxPolycubes() { + return this.currentVal().length >= this.volume(); + + } + + private isMinPolycubes() { + return this.currentVal().length <= 1; + } + + private getSelectedCube() { + return get(this.selectedCube); + } + + selected() { + return this.selectedCube; + } + + currentVal() { + return get(this.store); + } + + subscribe(cb: (cubes: VoxelSpaceBigInt[]) => any) { + return this.store.subscribe(cb); + } + + setColor(cubeIndex: number, color: string) { + const cubes = this.currentVal(); + cubes[cubeIndex].setColor(color); + this.store.set(cubes); + } + + addCube() { + if (!this.isMaxPolycubes()) { + this.store.update((polycubes: VoxelSpaceBigInt[]) => + polycubes.concat(this.freshCube(polycubes.length))); + } + } + + removeCube() { + if (!this.isMinPolycubes()) { + this.store.update((polycubes: VoxelSpaceBigInt[]) => polycubes.splice(0, polycubes.length - 1)); + } + const newLength = this.currentVal().length; + if (newLength <= this.getSelectedCube()) { + this.selectedCube.set(newLength - 1); + } + } + + toggle(cubeIndex: number, x: number, y: number, z: number) { + const cubes = this.currentVal(); + cubes[cubeIndex].toggle(x, y, z); + this.store.set(cubes); + } + + set(cubeIndex: number, val: boolean, x: number, y: number, z: number) { + const cubes = this.currentVal(); + cubes[cubeIndex].set(x, y, z, val); + this.store.set(cubes); + } + + reset() { + this.store.update((polycubes: VoxelSpaceBigInt[]) => { + const result: VoxelSpaceBigInt[] = []; + for (let i = 0; i < Math.min(polycubes.length, this.volume()); i++) { + result.push(this.freshCube(i)); + } + return result; + }); + } + + setCubes(cubes: VoxelSpaceBigInt[]) { + let lastDims = cubes[0].getDims(); + for (const cube of cubes) { + const dimsMatch = !cube.getDims().some((dim, i) => lastDims[i] !== dim); + if (!dimsMatch) { + throw new Error("Error setting cubes: not all dimensions match."); + } + } + this.dimX.set(lastDims[0]); + this.dimY.set(lastDims[1]); + this.dimZ.set(lastDims[2]); + this.store.set(cubes); + this.selectedCube.set(0); + } +} \ No newline at end of file diff --git a/src/ui/App.svelte b/src/ui/App.svelte index c0c1d69..ab400d8 100644 --- a/src/ui/App.svelte +++ b/src/ui/App.svelte @@ -1,6 +1,9 @@
@@ -8,7 +11,7 @@
- +
diff --git a/src/ui/CubeInput.svelte b/src/ui/CubeInput.svelte index 3234213..dac918a 100644 --- a/src/ui/CubeInput.svelte +++ b/src/ui/CubeInput.svelte @@ -1,11 +1,12 @@ + +
+ {#each {length: numCubes} as _, cubeNo} +
+
+ +
+
+ {/each} +
+ + \ No newline at end of file diff --git a/src/ui/ExamplesList.svelte b/src/ui/ExamplesList.svelte new file mode 100644 index 0000000..8ca9ecb --- /dev/null +++ b/src/ui/ExamplesList.svelte @@ -0,0 +1,35 @@ + + +
+ hydrateExample(i)} + /> +
+ + \ No newline at end of file diff --git a/src/ui/IncDecNum.svelte b/src/ui/IncDecNum.svelte index 159749f..184dad2 100644 --- a/src/ui/IncDecNum.svelte +++ b/src/ui/IncDecNum.svelte @@ -3,12 +3,12 @@ export let down: () => void; export let val: number; export let upDisabled: boolean; - export let title: string; + export let title: string = ""; export let downDisabled: boolean;
- {#if title} + {#if title !== ""}

{title}

{/if}
@@ -24,7 +24,7 @@ display: inline-block; } .title { - margin-bottom: 0; + margin: 0; } .val { font-weight: bold; diff --git a/src/ui/InputParameters.svelte b/src/ui/InputParameters.svelte new file mode 100644 index 0000000..5e044a5 --- /dev/null +++ b/src/ui/InputParameters.svelte @@ -0,0 +1,55 @@ + + +
+

Dimensions:

+ + + + {#if $totalVolume > 32} +

The total number of units exceeds 32. Attempting to solve puzzles with more than 32 units results in significantly slower computation time.

+ {/if} +
+ +
+

Cubes:

+ +
+ + \ No newline at end of file diff --git a/src/ui/List.svelte b/src/ui/List.svelte new file mode 100644 index 0000000..299bd20 --- /dev/null +++ b/src/ui/List.svelte @@ -0,0 +1,42 @@ + + +
    + {#if items.length === 0} +
  • {defaultText}
  • + {/if} + {#each items as item, i} +
  • onClick(i)}> + {item} +
  • + {/each} +
+ + \ No newline at end of file diff --git a/src/ui/Sidebar.svelte b/src/ui/Sidebar.svelte index c68f6eb..4c2fe85 100644 --- a/src/ui/Sidebar.svelte +++ b/src/ui/Sidebar.svelte @@ -1,122 +1,34 @@

Somaesque

-
-

Dimensions:

-
- - - - {#if $totalVolume > 32} -

The total number of units exceeds 32. Attempting to solve puzzles with more than 32 units results in significantly slower computation time.

- {/if} -
-
- -
-

Cubes:

-
- -
-
- -
- -
+ +

Solutions: {$solutions.length}

\ No newline at end of file + `Solution ${i + 1}`)} + defaultText="No solutions yet..." + onClick={(i) => selectSolution(i)} +/> \ No newline at end of file diff --git a/src/ui/SolutionViewer.svelte b/src/ui/SolutionViewer.svelte index 6a1c1db..653af53 100644 --- a/src/ui/SolutionViewer.svelte +++ b/src/ui/SolutionViewer.svelte @@ -1,13 +1,15 @@ -
+
{#if $activeSolution !== null}
@@ -45,13 +45,17 @@
\ No newline at end of file diff --git a/src/ui/SolveButton.svelte b/src/ui/SolveButton.svelte new file mode 100644 index 0000000..3a2f6bc --- /dev/null +++ b/src/ui/SolveButton.svelte @@ -0,0 +1,54 @@ + + + + + \ No newline at end of file diff --git a/src/ui/Interactor.svelte b/src/ui/Stage.svelte similarity index 52% rename from src/ui/Interactor.svelte rename to src/ui/Stage.svelte index 5ee7dab..ae227af 100644 --- a/src/ui/Interactor.svelte +++ b/src/ui/Stage.svelte @@ -1,8 +1,9 @@ + +
+ {#each Object.keys(tabs) as key} +
{key}
+ {/each} +
+
+ +
+ + \ No newline at end of file diff --git a/src/ui/threedee/GeometryManager.ts b/src/ui/threedee/GeometryManager.ts index cd7ae83..7f84149 100644 --- a/src/ui/threedee/GeometryManager.ts +++ b/src/ui/threedee/GeometryManager.ts @@ -88,6 +88,683 @@ const ROT_CODE_MAP = { c(mesh: THREE.Object3D) { mesh.rotateZ(-Math.PI/2); }, } as const; +const OBJ = ` +# Blender v2.82 (sub 7) OBJ File: '' +# www.blender.org +o Cube_Cube.001 +v 0.399961 0.500000 -0.399961 +v 0.500000 0.399961 -0.399961 +v 0.399961 0.399961 -0.500000 +v 0.438244 0.492385 -0.399961 +v 0.470699 0.470699 -0.399961 +v 0.399961 0.492385 -0.438244 +v 0.439203 0.483194 -0.439203 +v 0.465612 0.465612 -0.437212 +v 0.457718 0.457718 -0.457718 +v 0.492385 0.399961 -0.438244 +v 0.470699 0.399961 -0.470699 +v 0.492385 0.438244 -0.399961 +v 0.483194 0.439203 -0.439203 +v 0.465612 0.437212 -0.465612 +v 0.399961 0.438244 -0.492385 +v 0.399961 0.470699 -0.470699 +v 0.438244 0.399961 -0.492385 +v 0.439203 0.439203 -0.483194 +v 0.437212 0.465612 -0.465612 +v 0.500000 -0.399961 -0.399961 +v 0.399961 -0.500000 -0.399961 +v 0.399961 -0.399961 -0.500000 +v 0.492385 -0.438244 -0.399961 +v 0.470699 -0.470699 -0.399961 +v 0.492385 -0.399961 -0.438244 +v 0.483194 -0.439203 -0.439203 +v 0.465612 -0.465612 -0.437212 +v 0.457718 -0.457718 -0.457718 +v 0.399961 -0.492385 -0.438244 +v 0.399961 -0.470699 -0.470699 +v 0.438244 -0.492385 -0.399961 +v 0.439203 -0.483194 -0.439203 +v 0.437212 -0.465612 -0.465612 +v 0.438244 -0.399961 -0.492385 +v 0.470699 -0.399961 -0.470699 +v 0.399961 -0.438244 -0.492385 +v 0.439203 -0.439203 -0.483194 +v 0.465612 -0.437212 -0.465612 +v 0.500000 0.399961 0.399961 +v 0.399961 0.500000 0.399961 +v 0.399961 0.399961 0.500000 +v 0.492385 0.438244 0.399961 +v 0.470699 0.470699 0.399961 +v 0.492385 0.399961 0.438244 +v 0.483194 0.439203 0.439203 +v 0.465612 0.465612 0.437212 +v 0.457718 0.457718 0.457718 +v 0.399961 0.492385 0.438244 +v 0.399961 0.470699 0.470699 +v 0.438244 0.492385 0.399961 +v 0.439203 0.483194 0.439203 +v 0.437212 0.465612 0.465612 +v 0.438244 0.399961 0.492385 +v 0.470699 0.399961 0.470699 +v 0.399961 0.438244 0.492385 +v 0.439203 0.439203 0.483194 +v 0.465612 0.437212 0.465612 +v 0.399961 -0.399961 0.500000 +v 0.399961 -0.500000 0.399961 +v 0.500000 -0.399961 0.399961 +v 0.399961 -0.438244 0.492385 +v 0.399961 -0.470699 0.470699 +v 0.438244 -0.399961 0.492385 +v 0.439203 -0.439203 0.483194 +v 0.437212 -0.465612 0.465612 +v 0.457718 -0.457718 0.457718 +v 0.438244 -0.492385 0.399961 +v 0.470699 -0.470699 0.399961 +v 0.399961 -0.492385 0.438244 +v 0.439203 -0.483194 0.439203 +v 0.465612 -0.465612 0.437212 +v 0.492385 -0.399961 0.438244 +v 0.470699 -0.399961 0.470699 +v 0.492385 -0.438244 0.399961 +v 0.483194 -0.439203 0.439203 +v 0.465612 -0.437212 0.465612 +v -0.500000 0.399961 -0.399961 +v -0.399961 0.500000 -0.399961 +v -0.399961 0.399961 -0.500000 +v -0.492385 0.438244 -0.399961 +v -0.470699 0.470699 -0.399961 +v -0.492385 0.399961 -0.438244 +v -0.483194 0.439203 -0.439203 +v -0.465612 0.465612 -0.437212 +v -0.457718 0.457718 -0.457718 +v -0.399961 0.492385 -0.438244 +v -0.399961 0.470699 -0.470699 +v -0.438244 0.492385 -0.399961 +v -0.439203 0.483194 -0.439203 +v -0.437212 0.465612 -0.465612 +v -0.438244 0.399961 -0.492385 +v -0.470699 0.399961 -0.470699 +v -0.399961 0.438244 -0.492385 +v -0.439203 0.439203 -0.483194 +v -0.465612 0.437212 -0.465612 +v -0.399961 -0.399961 -0.500000 +v -0.399961 -0.500000 -0.399961 +v -0.500000 -0.399961 -0.399961 +v -0.399961 -0.438244 -0.492385 +v -0.399961 -0.470699 -0.470699 +v -0.438244 -0.399961 -0.492385 +v -0.439203 -0.439203 -0.483194 +v -0.437212 -0.465612 -0.465612 +v -0.457718 -0.457718 -0.457718 +v -0.438244 -0.492385 -0.399961 +v -0.470699 -0.470699 -0.399961 +v -0.399961 -0.492385 -0.438244 +v -0.439203 -0.483194 -0.439203 +v -0.465612 -0.465612 -0.437212 +v -0.492385 -0.399961 -0.438244 +v -0.470699 -0.399961 -0.470699 +v -0.492385 -0.438244 -0.399961 +v -0.483194 -0.439203 -0.439203 +v -0.465612 -0.437212 -0.465612 +v -0.500000 0.399961 0.399961 +v -0.399961 0.399961 0.500000 +v -0.399961 0.500000 0.399961 +v -0.492385 0.399961 0.438244 +v -0.470699 0.399961 0.470699 +v -0.492385 0.438244 0.399961 +v -0.483194 0.439203 0.439203 +v -0.465612 0.437212 0.465612 +v -0.457718 0.457718 0.457718 +v -0.399961 0.438244 0.492385 +v -0.399961 0.470699 0.470699 +v -0.438244 0.399961 0.492385 +v -0.439203 0.439203 0.483194 +v -0.437212 0.465612 0.465612 +v -0.438244 0.492385 0.399961 +v -0.470699 0.470699 0.399961 +v -0.399961 0.492385 0.438244 +v -0.439203 0.483194 0.439203 +v -0.465612 0.465612 0.437212 +v -0.399961 -0.399961 0.500000 +v -0.500000 -0.399961 0.399961 +v -0.399961 -0.500000 0.399961 +v -0.438244 -0.399961 0.492385 +v -0.470699 -0.399961 0.470699 +v -0.399961 -0.438244 0.492385 +v -0.439203 -0.439203 0.483194 +v -0.465612 -0.437212 0.465612 +v -0.457718 -0.457718 0.457718 +v -0.492385 -0.438244 0.399961 +v -0.470699 -0.470699 0.399961 +v -0.492385 -0.399961 0.438244 +v -0.483194 -0.439203 0.439203 +v -0.465612 -0.465612 0.437212 +v -0.399961 -0.492385 0.438244 +v -0.399961 -0.470699 0.470699 +v -0.438244 -0.492385 0.399961 +v -0.439203 -0.483194 0.439203 +v -0.437212 -0.465612 0.465612 +vt 0.150010 0.525010 +vt 0.349990 0.525010 +vt 0.349990 0.724990 +vt 0.150010 0.724990 +vt 0.650010 0.525010 +vt 0.849990 0.525010 +vt 0.849990 0.724990 +vt 0.650010 0.724990 +vt 0.400010 0.275010 +vt 0.599990 0.275010 +vt 0.599990 0.474990 +vt 0.400010 0.474990 +vt 0.400010 0.775010 +vt 0.599990 0.775010 +vt 0.599990 0.974990 +vt 0.400010 0.974990 +vt 0.400010 0.025010 +vt 0.599990 0.025010 +vt 0.599990 0.224990 +vt 0.400010 0.224990 +vt 0.400010 0.525010 +vt 0.390439 0.525010 +vt 0.390199 0.515199 +vt 0.400010 0.515439 +vt 0.375000 0.525010 +vt 0.375000 0.515697 +vt 0.390697 0.500000 +vt 0.400010 0.500000 +vt 0.375000 0.510570 +vt 0.599990 0.265439 +vt 0.609801 0.265199 +vt 0.609561 0.275010 +vt 0.599990 0.250000 +vt 0.609303 0.250000 +vt 0.625000 0.265697 +vt 0.625000 0.275010 +vt 0.614430 0.250000 +vt 0.859561 0.724990 +vt 0.859801 0.734801 +vt 0.849990 0.734561 +vt 0.875000 0.724990 +vt 0.875000 0.734303 +vt 0.859303 0.750000 +vt 0.849990 0.750000 +vt 0.875000 0.739430 +vt 0.390439 0.275010 +vt 0.390199 0.265199 +vt 0.400010 0.265439 +vt 0.375000 0.275010 +vt 0.375000 0.265697 +vt 0.390697 0.250000 +vt 0.400010 0.250000 +vt 0.375000 0.260570 +vt 0.599990 0.015439 +vt 0.609801 0.015199 +vt 0.609561 0.025010 +vt 0.599990 0.000000 +vt 0.609303 0.000000 +vt 0.625000 0.015697 +vt 0.625000 0.025010 +vt 0.614430 0.000000 +vt 0.650010 0.734561 +vt 0.640199 0.734801 +vt 0.640439 0.724990 +vt 0.650010 0.750000 +vt 0.640697 0.750000 +vt 0.625000 0.734303 +vt 0.625000 0.724990 +vt 0.635570 0.750000 +vt 0.609561 0.474990 +vt 0.609801 0.484801 +vt 0.599990 0.484561 +vt 0.625000 0.474990 +vt 0.625000 0.484303 +vt 0.609303 0.500000 +vt 0.599990 0.500000 +vt 0.625000 0.489430 +vt 0.400010 0.724990 +vt 0.400010 0.734561 +vt 0.390199 0.734801 +vt 0.390439 0.724990 +vt 0.400010 0.750000 +vt 0.390697 0.750000 +vt 0.375000 0.734303 +vt 0.375000 0.724990 +vt 0.385570 0.750000 +vt 0.349990 0.515439 +vt 0.359801 0.515199 +vt 0.359561 0.525010 +vt 0.349990 0.500000 +vt 0.359303 0.500000 +vt 0.375000 0.515697 +vt 0.375000 0.525010 +vt 0.364430 0.500000 +vt 0.140439 0.525010 +vt 0.140199 0.515199 +vt 0.150010 0.515439 +vt 0.125000 0.525010 +vt 0.125000 0.515697 +vt 0.140697 0.500000 +vt 0.150010 0.500000 +vt 0.125000 0.510570 +vt 0.609561 0.224990 +vt 0.609801 0.234801 +vt 0.599990 0.234561 +vt 0.625000 0.224990 +vt 0.625000 0.234303 +vt 0.609303 0.250000 +vt 0.599990 0.250000 +vt 0.625000 0.239430 +vt 0.400010 0.484561 +vt 0.390199 0.484801 +vt 0.390439 0.474990 +vt 0.400010 0.500000 +vt 0.390697 0.500000 +vt 0.375000 0.484303 +vt 0.375000 0.474990 +vt 0.385570 0.500000 +vt 0.609561 0.974990 +vt 0.609801 0.984801 +vt 0.599990 0.984561 +vt 0.625000 0.974990 +vt 0.625000 0.984303 +vt 0.609303 1.000000 +vt 0.599990 1.000000 +vt 0.625000 0.989430 +vt 0.599990 0.525010 +vt 0.599990 0.515439 +vt 0.609801 0.515199 +vt 0.609561 0.525010 +vt 0.599990 0.500000 +vt 0.609303 0.500000 +vt 0.625000 0.515697 +vt 0.625000 0.525010 +vt 0.614430 0.500000 +vt 0.849990 0.515439 +vt 0.859801 0.515199 +vt 0.859561 0.525010 +vt 0.849990 0.500000 +vt 0.859303 0.500000 +vt 0.875000 0.515697 +vt 0.875000 0.525010 +vt 0.864430 0.500000 +vt 0.640439 0.525010 +vt 0.640199 0.515199 +vt 0.650010 0.515439 +vt 0.625000 0.525010 +vt 0.625000 0.515697 +vt 0.640697 0.500000 +vt 0.650010 0.500000 +vt 0.625000 0.510570 +vt 0.390439 0.025010 +vt 0.390199 0.015199 +vt 0.400010 0.015439 +vt 0.375000 0.025010 +vt 0.375000 0.015697 +vt 0.390697 0.000000 +vt 0.400010 0.000000 +vt 0.375000 0.010570 +vt 0.400010 0.984561 +vt 0.390199 0.984801 +vt 0.390439 0.974990 +vt 0.400010 1.000000 +vt 0.390697 1.000000 +vt 0.375000 0.984303 +vt 0.375000 0.974990 +vt 0.385570 1.000000 +vt 0.599990 0.765439 +vt 0.609801 0.765199 +vt 0.609561 0.775010 +vt 0.599990 0.750000 +vt 0.609303 0.750000 +vt 0.625000 0.765697 +vt 0.625000 0.775010 +vt 0.614430 0.750000 +vt 0.359561 0.724990 +vt 0.359801 0.734801 +vt 0.349990 0.734561 +vt 0.375000 0.724990 +vt 0.375000 0.734303 +vt 0.359303 0.750000 +vt 0.349990 0.750000 +vt 0.375000 0.739430 +vt 0.599990 0.724990 +vt 0.609561 0.724990 +vt 0.609801 0.734801 +vt 0.599990 0.734561 +vt 0.625000 0.724990 +vt 0.625000 0.734303 +vt 0.609303 0.750000 +vt 0.599990 0.750000 +vt 0.625000 0.739430 +vt 0.150010 0.734561 +vt 0.140199 0.734801 +vt 0.140439 0.724990 +vt 0.150010 0.750000 +vt 0.140697 0.750000 +vt 0.125000 0.734303 +vt 0.125000 0.724990 +vt 0.135570 0.750000 +vt 0.390439 0.775010 +vt 0.390199 0.765199 +vt 0.400010 0.765439 +vt 0.375000 0.775010 +vt 0.375000 0.765697 +vt 0.390697 0.750000 +vt 0.400010 0.750000 +vt 0.375000 0.760570 +vt 0.400010 0.234561 +vt 0.390199 0.234801 +vt 0.390439 0.224990 +vt 0.400010 0.250000 +vt 0.390697 0.250000 +vt 0.375000 0.234303 +vt 0.375000 0.224990 +vt 0.385570 0.250000 +vn 0.1004 -0.1004 0.9899 +vn 0.1004 0.1004 0.9899 +vn -0.1004 0.1004 0.9899 +vn -0.1004 -0.1004 0.9899 +vn -0.1004 -0.1004 -0.9899 +vn -0.1004 0.1004 -0.9899 +vn 0.1004 0.1004 -0.9899 +vn 0.1004 -0.1004 -0.9899 +vn 0.9899 -0.1004 -0.1004 +vn 0.9899 0.1004 -0.1004 +vn 0.9899 0.1004 0.1004 +vn 0.9899 -0.1004 0.1004 +vn -0.9899 -0.1004 0.1004 +vn -0.9899 0.1004 0.1004 +vn -0.9899 0.1004 -0.1004 +vn -0.9899 -0.1004 -0.1004 +vn -0.1004 -0.9899 -0.1004 +vn 0.1004 -0.9899 -0.1004 +vn 0.1004 -0.9899 0.1004 +vn -0.1004 -0.9899 0.1004 +vn 0.1004 0.9899 -0.1004 +vn 0.3792 0.9201 -0.0981 +vn 0.3673 0.8545 -0.3673 +vn 0.0981 0.9201 -0.3792 +vn 0.7041 0.7041 -0.0919 +vn 0.6663 0.6663 -0.3347 +vn 0.3347 0.6663 -0.6663 +vn 0.0919 0.7041 -0.7041 +vn 0.5774 0.5774 -0.5774 +vn 0.9201 0.0981 -0.3792 +vn 0.8545 0.3673 -0.3673 +vn 0.9201 0.3792 -0.0981 +vn 0.7041 0.0919 -0.7041 +vn 0.6663 0.3347 -0.6663 +vn 0.0981 0.3792 -0.9201 +vn 0.3673 0.3673 -0.8545 +vn 0.3792 0.0981 -0.9201 +vn 0.9201 -0.3792 -0.0981 +vn 0.8545 -0.3673 -0.3673 +vn 0.9201 -0.0981 -0.3792 +vn 0.7041 -0.7041 -0.0919 +vn 0.6663 -0.6663 -0.3347 +vn 0.6663 -0.3347 -0.6663 +vn 0.7041 -0.0919 -0.7041 +vn 0.5774 -0.5774 -0.5773 +vn 0.0981 -0.9201 -0.3792 +vn 0.3673 -0.8545 -0.3673 +vn 0.3792 -0.9201 -0.0981 +vn 0.0919 -0.7041 -0.7041 +vn 0.3347 -0.6663 -0.6663 +vn 0.3792 -0.0981 -0.9201 +vn 0.3673 -0.3673 -0.8545 +vn 0.0981 -0.3792 -0.9201 +vn 0.9201 0.3792 0.0981 +vn 0.8545 0.3673 0.3673 +vn 0.9201 0.0981 0.3792 +vn 0.7041 0.7041 0.0919 +vn 0.6663 0.6663 0.3347 +vn 0.6663 0.3347 0.6663 +vn 0.7041 0.0919 0.7041 +vn 0.5774 0.5774 0.5773 +vn 0.1004 0.9899 0.1004 +vn 0.0981 0.9201 0.3792 +vn 0.3673 0.8545 0.3673 +vn 0.3792 0.9201 0.0981 +vn 0.0919 0.7041 0.7041 +vn 0.3347 0.6663 0.6663 +vn 0.3792 0.0981 0.9201 +vn 0.3673 0.3673 0.8545 +vn 0.0981 0.3792 0.9201 +vn 0.0981 -0.3792 0.9201 +vn 0.3673 -0.3673 0.8545 +vn 0.3792 -0.0981 0.9201 +vn 0.0919 -0.7041 0.7041 +vn 0.3347 -0.6663 0.6663 +vn 0.6663 -0.3347 0.6663 +vn 0.7041 -0.0919 0.7041 +vn 0.5773 -0.5774 0.5774 +vn 0.3792 -0.9201 0.0981 +vn 0.3673 -0.8545 0.3673 +vn 0.0981 -0.9201 0.3792 +vn 0.7041 -0.7041 0.0919 +vn 0.6663 -0.6663 0.3347 +vn 0.9201 -0.0981 0.3792 +vn 0.8545 -0.3673 0.3673 +vn 0.9201 -0.3792 0.0981 +vn -0.9201 0.3792 -0.0981 +vn -0.8545 0.3673 -0.3673 +vn -0.9201 0.0981 -0.3792 +vn -0.7041 0.7041 -0.0919 +vn -0.6663 0.6663 -0.3347 +vn -0.6663 0.3347 -0.6663 +vn -0.7041 0.0919 -0.7041 +vn -0.5774 0.5774 -0.5773 +vn -0.1004 0.9899 -0.1004 +vn -0.0981 0.9201 -0.3792 +vn -0.3673 0.8545 -0.3673 +vn -0.3792 0.9201 -0.0981 +vn -0.0919 0.7041 -0.7041 +vn -0.3347 0.6663 -0.6663 +vn -0.3792 0.0981 -0.9201 +vn -0.3673 0.3673 -0.8545 +vn -0.0981 0.3792 -0.9201 +vn -0.0981 -0.3792 -0.9201 +vn -0.3673 -0.3673 -0.8545 +vn -0.3792 -0.0981 -0.9201 +vn -0.0919 -0.7041 -0.7041 +vn -0.3347 -0.6663 -0.6663 +vn -0.6663 -0.3347 -0.6663 +vn -0.7041 -0.0919 -0.7041 +vn -0.5773 -0.5774 -0.5774 +vn -0.3792 -0.9201 -0.0981 +vn -0.3673 -0.8545 -0.3673 +vn -0.0981 -0.9201 -0.3792 +vn -0.7041 -0.7041 -0.0919 +vn -0.6663 -0.6663 -0.3347 +vn -0.9201 -0.0981 -0.3792 +vn -0.8545 -0.3673 -0.3673 +vn -0.9201 -0.3792 -0.0981 +vn -0.9201 0.0981 0.3792 +vn -0.8545 0.3673 0.3673 +vn -0.9201 0.3792 0.0981 +vn -0.7041 0.0919 0.7041 +vn -0.6663 0.3347 0.6663 +vn -0.6663 0.6663 0.3347 +vn -0.7041 0.7041 0.0919 +vn -0.5774 0.5774 0.5773 +vn -0.0981 0.3792 0.9201 +vn -0.3673 0.3673 0.8545 +vn -0.3792 0.0981 0.9201 +vn -0.0919 0.7041 0.7041 +vn -0.3347 0.6663 0.6663 +vn -0.1004 0.9899 0.1004 +vn -0.3792 0.9201 0.0981 +vn -0.3673 0.8545 0.3673 +vn -0.0981 0.9201 0.3792 +vn -0.3792 -0.0981 0.9201 +vn -0.3673 -0.3673 0.8545 +vn -0.0981 -0.3792 0.9201 +vn -0.7041 -0.0919 0.7041 +vn -0.6663 -0.3347 0.6663 +vn -0.3347 -0.6663 0.6663 +vn -0.0919 -0.7041 0.7041 +vn -0.5774 -0.5774 0.5773 +vn -0.9201 -0.3792 0.0981 +vn -0.8545 -0.3673 0.3673 +vn -0.9201 -0.0981 0.3792 +vn -0.7041 -0.7041 0.0919 +vn -0.6663 -0.6663 0.3347 +vn -0.0981 -0.9201 0.3792 +vn -0.3673 -0.8545 0.3673 +vn -0.3792 -0.9201 0.0981 +s 1 +f 58/1/1 41/2/2 116/3/3 134/4/4 +f 96/5/5 79/6/6 3/7/7 22/8/8 +f 20/9/9 2/10/10 39/11/11 60/12/12 +f 135/13/13 115/14/14 77/15/15 98/16/16 +f 97/17/17 21/18/18 59/19/19 136/20/20 +f 1/21/21 4/22/22 7/23/23 6/24/24 +f 4/22/22 5/25/25 8/26/26 7/23/23 +f 6/24/24 7/23/23 19/27/27 16/28/28 +f 7/23/23 8/26/26 9/29/29 19/27/27 +f 2/10/10 10/30/30 13/31/31 12/32/32 +f 10/30/30 11/33/33 14/34/34 13/31/31 +f 12/32/32 13/31/31 8/35/26 5/36/25 +f 13/31/31 14/34/34 9/37/29 8/35/26 +f 3/7/7 15/38/35 18/39/36 17/40/37 +f 15/38/35 16/41/28 19/42/27 18/39/36 +f 17/40/37 18/39/36 14/43/34 11/44/33 +f 18/39/36 19/42/27 9/45/29 14/43/34 +f 20/9/9 23/46/38 26/47/39 25/48/40 +f 23/46/38 24/49/41 27/50/42 26/47/39 +f 25/48/40 26/47/39 38/51/43 35/52/44 +f 26/47/39 27/50/42 28/53/45 38/51/43 +f 21/18/18 29/54/46 32/55/47 31/56/48 +f 29/54/46 30/57/49 33/58/50 32/55/47 +f 31/56/48 32/55/47 27/59/42 24/60/41 +f 32/55/47 33/58/50 28/61/45 27/59/42 +f 22/8/8 34/62/51 37/63/52 36/64/53 +f 34/62/51 35/65/44 38/66/43 37/63/52 +f 36/64/53 37/63/52 33/67/50 30/68/49 +f 37/63/52 38/66/43 28/69/45 33/67/50 +f 39/11/11 42/70/54 45/71/55 44/72/56 +f 42/70/54 43/73/57 46/74/58 45/71/55 +f 44/72/56 45/71/55 57/75/59 54/76/60 +f 45/71/55 46/74/58 47/77/61 57/75/59 +f 40/78/62 48/79/63 51/80/64 50/81/65 +f 48/79/63 49/82/66 52/83/67 51/80/64 +f 50/81/65 51/80/64 46/84/58 43/85/57 +f 51/80/64 52/83/67 47/86/61 46/84/58 +f 41/2/2 53/87/68 56/88/69 55/89/70 +f 53/87/68 54/90/60 57/91/59 56/88/69 +f 55/89/70 56/88/69 52/92/67 49/93/66 +f 56/88/69 57/91/59 47/94/61 52/92/67 +f 58/1/1 61/95/71 64/96/72 63/97/73 +f 61/95/71 62/98/74 65/99/75 64/96/72 +f 63/97/73 64/96/72 76/100/76 73/101/77 +f 64/96/72 65/99/75 66/102/78 76/100/76 +f 59/19/19 67/103/79 70/104/80 69/105/81 +f 67/103/79 68/106/82 71/107/83 70/104/80 +f 69/105/81 70/104/80 65/108/75 62/109/74 +f 70/104/80 71/107/83 66/110/78 65/108/75 +f 60/12/12 72/111/84 75/112/85 74/113/86 +f 72/111/84 73/114/77 76/115/76 75/112/85 +f 74/113/86 75/112/85 71/116/83 68/117/82 +f 75/112/85 76/115/76 66/118/78 71/116/83 +f 77/15/15 80/119/87 83/120/88 82/121/89 +f 80/119/87 81/122/90 84/123/91 83/120/88 +f 82/121/89 83/120/88 95/124/92 92/125/93 +f 83/120/88 84/123/91 85/126/94 95/124/92 +f 78/127/95 86/128/96 89/129/97 88/130/98 +f 86/128/96 87/131/99 90/132/100 89/129/97 +f 88/130/98 89/129/97 84/133/91 81/134/90 +f 89/129/97 90/132/100 85/135/94 84/133/91 +f 79/6/6 91/136/101 94/137/102 93/138/103 +f 91/136/101 92/139/93 95/140/92 94/137/102 +f 93/138/103 94/137/102 90/141/100 87/142/99 +f 94/137/102 95/140/92 85/143/94 90/141/100 +f 96/5/5 99/144/104 102/145/105 101/146/106 +f 99/144/104 100/147/107 103/148/108 102/145/105 +f 101/146/106 102/145/105 114/149/109 111/150/110 +f 102/145/105 103/148/108 104/151/111 114/149/109 +f 97/17/17 105/152/112 108/153/113 107/154/114 +f 105/152/112 106/155/115 109/156/116 108/153/113 +f 107/154/114 108/153/113 103/157/108 100/158/107 +f 108/153/113 109/156/116 104/159/111 103/157/108 +f 98/16/16 110/160/117 113/161/118 112/162/119 +f 110/160/117 111/163/110 114/164/109 113/161/118 +f 112/162/119 113/161/118 109/165/116 106/166/115 +f 113/161/118 114/164/109 104/167/111 109/165/116 +f 115/14/14 118/168/120 121/169/121 120/170/122 +f 118/168/120 119/171/123 122/172/124 121/169/121 +f 120/170/122 121/169/121 133/173/125 130/174/126 +f 121/169/121 122/172/124 123/175/127 133/173/125 +f 116/3/3 124/176/128 127/177/129 126/178/130 +f 124/176/128 125/179/131 128/180/132 127/177/129 +f 126/178/130 127/177/129 122/181/124 119/182/123 +f 127/177/129 128/180/132 123/183/127 122/181/124 +f 117/184/133 129/185/134 132/186/135 131/187/136 +f 129/185/134 130/188/126 133/189/125 132/186/135 +f 131/187/136 132/186/135 128/190/132 125/191/131 +f 132/186/135 133/189/125 123/192/127 128/190/132 +f 134/4/4 137/193/137 140/194/138 139/195/139 +f 137/193/137 138/196/140 141/197/141 140/194/138 +f 139/195/139 140/194/138 152/198/142 149/199/143 +f 140/194/138 141/197/141 142/200/144 152/198/142 +f 135/13/13 143/201/145 146/202/146 145/203/147 +f 143/201/145 144/204/148 147/205/149 146/202/146 +f 145/203/147 146/202/146 141/206/141 138/207/140 +f 146/202/146 147/205/149 142/208/144 141/206/141 +f 136/20/20 148/209/150 151/210/151 150/211/152 +f 148/209/150 149/212/143 152/213/142 151/210/151 +f 150/211/152 151/210/151 147/214/149 144/215/148 +f 151/210/151 152/213/142 142/216/144 147/214/149 +f 136/20/20 59/19/19 69/105/81 148/209/150 +f 148/209/150 69/105/81 62/109/74 149/212/143 +f 149/199/143 62/98/74 61/95/71 139/195/139 +f 139/195/139 61/95/71 58/1/1 134/4/4 +f 59/19/19 21/18/18 31/56/48 67/103/79 +f 67/103/79 31/56/48 24/60/41 68/106/82 +f 68/117/82 24/49/41 23/46/38 74/113/86 +f 74/113/86 23/46/38 20/9/9 60/12/12 +f 1/21/21 40/78/62 50/81/65 4/22/22 +f 4/22/22 50/81/65 43/85/57 5/25/25 +f 5/36/25 43/73/57 42/70/54 12/32/32 +f 12/32/32 42/70/54 39/11/11 2/10/10 +f 21/18/18 97/17/17 107/154/114 29/54/46 +f 29/54/46 107/154/114 100/158/107 30/57/49 +f 30/68/49 100/147/107 99/144/104 36/64/53 +f 36/64/53 99/144/104 96/5/5 22/8/8 +f 117/184/133 78/127/95 88/130/98 129/185/134 +f 129/185/134 88/130/98 81/134/90 130/188/126 +f 130/174/126 81/122/90 80/119/87 120/170/122 +f 120/170/122 80/119/87 77/15/15 115/14/14 +f 97/17/17 136/20/20 150/211/152 105/152/112 +f 105/152/112 150/211/152 144/215/148 106/155/115 +f 106/166/115 144/204/148 143/201/145 112/162/119 +f 112/162/119 143/201/145 135/13/13 98/16/16 +f 40/78/62 117/184/133 131/187/136 48/79/63 +f 48/79/63 131/187/136 125/191/131 49/82/66 +f 49/93/66 125/179/131 124/176/128 55/89/70 +f 55/89/70 124/176/128 116/3/3 41/2/2 +f 41/2/2 58/1/1 63/97/73 53/87/68 +f 53/87/68 63/97/73 73/101/77 54/90/60 +f 54/76/60 73/114/77 72/111/84 44/72/56 +f 44/72/56 72/111/84 60/12/12 39/11/11 +f 2/10/10 20/9/9 25/48/40 10/30/30 +f 10/30/30 25/48/40 35/52/44 11/33/33 +f 11/44/33 35/65/44 34/62/51 17/40/37 +f 17/40/37 34/62/51 22/8/8 3/7/7 +f 134/4/4 116/3/3 126/178/130 137/193/137 +f 137/193/137 126/178/130 119/182/123 138/196/140 +f 138/207/140 119/171/123 118/168/120 145/203/147 +f 145/203/147 118/168/120 115/14/14 135/13/13 +f 78/127/95 1/21/21 6/24/24 86/128/96 +f 86/128/96 6/24/24 16/28/28 87/131/99 +f 87/142/99 16/41/28 15/38/35 93/138/103 +f 93/138/103 15/38/35 3/7/7 79/6/6 +f 79/6/6 96/5/5 101/146/106 91/136/101 +f 91/136/101 101/146/106 111/150/110 92/139/93 +f 92/125/93 111/163/110 110/160/117 82/121/89 +f 82/121/89 110/160/117 98/16/16 77/15/15 +f 1/21/21 78/127/95 117/184/133 40/78/62 +`; + type GeomRecord = Record; export default class GeometryManager { @@ -108,12 +785,8 @@ export default class GeometryManager { }; const load = (resolve: (geom: THREE.BufferGeometry) => any, reject: (err: string) => any) => { const loader = new OBJLoader(); - loader.load( - `${this.root}${id}.obj`, - obj => onLoaded(obj, resolve), - () => {}, - (err) => reject(`Error loading OBJ file: ${err}`), - ); + const result = loader.parse(OBJ); + onLoaded(result, resolve); }; return new Promise(load); } diff --git a/src/ui/threedee/PolycubeScene.ts b/src/ui/threedee/PolycubeScene.ts index 990ce36..c196a41 100644 --- a/src/ui/threedee/PolycubeScene.ts +++ b/src/ui/threedee/PolycubeScene.ts @@ -39,7 +39,7 @@ export default class PolycubeScene { this.cubeScene.rotateX(Math.PI/4); this.cubeScene.rotateY(Math.PI/4); this.controls = new RotationControl(this.cubeScene, this.polycubeMeshes, this.camera, this.canvas); - this.geomManager = await new GeometryManager('../resources/', () => { + this.geomManager = await new GeometryManager('./', () => { requestAnimationFrame((timestamp) => this.render(timestamp)); }); PolycubeMesh.setManager(this.geomManager); diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..50041f7 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,25 @@ +function hslToRgb(hslStr: string): string { + const opt = new Option(); + opt.style.color = hslStr; + return opt.style.color; +} + +function rgbToHex(rgbStr: string): string { + const sep = rgbStr.indexOf(",") > -1 ? "," : " "; + const rgb = rgbStr.substr(4).split(")")[0].split(sep); + const r = (+rgb[0]).toString(16).padStart(2, "0"); + const g = (+rgb[1]).toString(16).padStart(2, "0"); + const b = (+rgb[2]).toString(16).padStart(2, "0"); + return "#" + r + g + b; +} + +export function colorFromIndex(index: number): string { + const colorWheelCycle = Math.floor(index / 6); + const darknessCycle = Math.floor(index / 12); + const spacing = (360 / 6); + const offset = colorWheelCycle === 0 ? 0 : spacing / (colorWheelCycle + 2); + let hue = spacing * (index % 6) + offset; + const saturation = 100; + const lightness = 1 / (2 + darknessCycle) * 100; + return rgbToHex(hslToRgb(`hsl(${hue},${saturation}%,${Math.round(lightness)}%)`)); +} \ No newline at end of file