It all works....
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,3 +3,7 @@
|
|||||||
/.vscode
|
/.vscode
|
||||||
/.idea
|
/.idea
|
||||||
/src/solver/node_modules/
|
/src/solver/node_modules/
|
||||||
|
/desktop-dist
|
||||||
|
/**/node_modules
|
||||||
|
/src/solver/wasm/build
|
||||||
|
out/
|
||||||
218
.idea/workspace.xml
generated
218
.idea/workspace.xml
generated
@@ -1,59 +1,65 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
|
<component name="BranchesTreeState">
|
||||||
|
<expand>
|
||||||
|
<path>
|
||||||
|
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||||
|
<item name="LOCAL_ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||||
|
<item name="REMOTE_ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||||
|
<item name="REMOTE_ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||||
|
<item name="GROUP_NODE:origin" type="e8cecc67:BranchNodeDescriptor" />
|
||||||
|
</path>
|
||||||
|
</expand>
|
||||||
|
<select>
|
||||||
|
<path>
|
||||||
|
<item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||||
|
<item name="LOCAL_ROOT" type="e8cecc67:BranchNodeDescriptor" />
|
||||||
|
<item name="BRANCH:master" type="e8cecc67:BranchNodeDescriptor" />
|
||||||
|
</path>
|
||||||
|
</select>
|
||||||
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="70635ef7-86ab-4681-b98d-dc8e4999995b" name="Default Changelist" comment="">
|
<list default="true" id="70635ef7-86ab-4681-b98d-dc8e4999995b" name="Default Changelist" comment="">
|
||||||
<change afterPath="$PROJECT_DIR$/public/solver/as-bind.esm.js" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/src/desktop/build.js" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/public/solver/main.wasm" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/src/desktop/main.js" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/src/SomaSolver.ts" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/src/desktop/preload.js" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/src/solver/assembly/SomaSolution.ts" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/src/solver/js/SomaSolver.ts" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/src/solver/assembly/VoxelSpace.ts" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/src/solver/js/VoxelSpaceBoolean.ts" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/src/ui/Interactor.svelte" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/src/ui/Solution2D.svelte" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/src/ui/SolutionList.svelte" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/src/ui/threedee/GeometryManager.ts" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/src/ui/threedee/PolycubeMesh.ts" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/src/ui/threedee/PolycubeScene.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/public/global.css" beforeDir="false" afterPath="$PROJECT_DIR$/public/global.css" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/public/index.html" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/public/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/public/index.html" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/000000.mtl" beforeDir="false" />
|
<change beforePath="$PROJECT_DIR$/public/solver/SomaSolution.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/js/SomaSolution.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/000001.mtl" beforeDir="false" />
|
<change beforePath="$PROJECT_DIR$/public/solver/SomaSolver.js" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/000001.obj" beforeDir="false" />
|
<change beforePath="$PROJECT_DIR$/public/solver/VoxelSpace.js" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/none.obj" beforeDir="false" afterPath="$PROJECT_DIR$/public/resources/c000000.obj" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/x.mtl" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/x.obj" beforeDir="false" afterPath="$PROJECT_DIR$/public/resources/c000001.obj" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/xy.mtl" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/xy.obj" beforeDir="false" afterPath="$PROJECT_DIR$/public/resources/c000011.obj" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/xymx.mtl" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/xymx.obj" beforeDir="false" afterPath="$PROJECT_DIR$/public/resources/c001011.obj" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/xymxmy.mtl" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/xymxmy.obj" beforeDir="false" afterPath="$PROJECT_DIR$/public/resources/c011011.obj" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/xyz.mtl" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/xyz.obj" beforeDir="false" afterPath="$PROJECT_DIR$/public/resources/c000111.obj" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/xyzmx.mtl" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/xyzmx.obj" beforeDir="false" afterPath="$PROJECT_DIR$/public/resources/c001111.obj" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel_cube.mtl" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel_cube.obj" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/solver/SomaSolver.js" beforeDir="false" afterPath="$PROJECT_DIR$/public/solver/SomaSolver.js" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/public/solver/main.js" beforeDir="false" afterPath="$PROJECT_DIR$/public/solver/main.js" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/public/solver/main.js" beforeDir="false" afterPath="$PROJECT_DIR$/public/solver/main.js" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/App.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/App.svelte" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/public/solver/main.wasm" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/CubeInput.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/CubeInput.svelte" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/SomaSolution.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/SomaSolution.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/OBJLoader.js" beforeDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/SomaSolver.ts" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/Polycube3D.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/SolutionViewer.svelte" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/VoxelSpace.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/VoxelSpaceBigInt.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/PolycubeScene.ts" beforeDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/solver/asconfig.json" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/wasm/asconfig.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/RotationControl.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/threedee/RotationControl.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/solver/assembly/SomaSolution.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/wasm/assembly/SomaSolution.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/Sidebar.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/Sidebar.svelte" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/solver/assembly/SomaSolver.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/wasm/assembly/SomaSolver.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/SolutionInteractor.svelte" beforeDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/solver/assembly/VoxelSpace.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/wasm/assembly/VoxelSpace.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/SolutionList.svelte" beforeDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/solver/assembly/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/wasm/assembly/index.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/main.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/main.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/solver/assembly/tsconfig.json" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/wasm/assembly/tsconfig.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/solver/SomaSolution.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/SomaSolution.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/solver/index.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/wasm/index.js" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/solver/SomaSolver.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/assembly/SomaSolver.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/solver/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/wasm/package-lock.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/solver/VoxelSpace.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/VoxelSpace.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/solver/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/wasm/package.json" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/solver/main.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/assembly/index.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/store.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/store.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/store.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/store.ts" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/ui/CubeInput.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/CubeInput.svelte" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/ui/Sidebar.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/Sidebar.svelte" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/ui/Solution2D.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/Solution2D.svelte" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/ui/SolutionViewer.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/SolutionViewer.svelte" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/ui/threedee/PolycubeMesh.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/threedee/PolycubeMesh.ts" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/ui/threedee/PolycubeScene.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/threedee/PolycubeScene.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/tsconfig.json" beforeDir="false" afterPath="$PROJECT_DIR$/tsconfig.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/tsconfig.json" beforeDir="false" afterPath="$PROJECT_DIR$/tsconfig.json" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
@@ -64,13 +70,13 @@
|
|||||||
<component name="FileTemplateManagerImpl">
|
<component name="FileTemplateManagerImpl">
|
||||||
<option name="RECENT_TEMPLATES">
|
<option name="RECENT_TEMPLATES">
|
||||||
<list>
|
<list>
|
||||||
<option value="HTML File" />
|
|
||||||
<option value="Sidebar" />
|
<option value="Sidebar" />
|
||||||
<option value="SolutionInteractor" />
|
<option value="SolutionInteractor" />
|
||||||
<option value="CubeInput" />
|
<option value="CubeInput" />
|
||||||
<option value="store.svelte" />
|
<option value="store.svelte" />
|
||||||
<option value="JavaScript File" />
|
|
||||||
<option value="SolutionList.svelte" />
|
<option value="SolutionList.svelte" />
|
||||||
|
<option value="HTML File" />
|
||||||
|
<option value="JavaScript File" />
|
||||||
<option value="TypeScript File" />
|
<option value="TypeScript File" />
|
||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
@@ -78,6 +84,11 @@
|
|||||||
<component name="Git.Settings">
|
<component name="Git.Settings">
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
|
<component name="JsbtTreeLayoutManager">
|
||||||
|
<layout place="tools.popupGrunt">
|
||||||
|
<scroll-view-position x="0" y="0" />
|
||||||
|
</layout>
|
||||||
|
</component>
|
||||||
<component name="ProjectId" id="1shUz7NMSUxY207ip64fWIgaebh" />
|
<component name="ProjectId" id="1shUz7NMSUxY207ip64fWIgaebh" />
|
||||||
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
||||||
<component name="ProjectViewState">
|
<component name="ProjectViewState">
|
||||||
@@ -88,7 +99,7 @@
|
|||||||
<property name="DefaultHtmlFileTemplate" value="HTML File" />
|
<property name="DefaultHtmlFileTemplate" value="HTML File" />
|
||||||
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||||
<property name="WebServerToolWindowFactoryState" value="false" />
|
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||||
<property name="last_opened_file_path" value="$PROJECT_DIR$/src/ui" />
|
<property name="last_opened_file_path" value="$PROJECT_DIR$/src/solver/js" />
|
||||||
<property name="node.js.detected.package.eslint" value="true" />
|
<property name="node.js.detected.package.eslint" value="true" />
|
||||||
<property name="node.js.detected.package.tslint" value="true" />
|
<property name="node.js.detected.package.tslint" value="true" />
|
||||||
<property name="node.js.path.for.package.eslint" value="project" />
|
<property name="node.js.path.for.package.eslint" value="project" />
|
||||||
@@ -96,6 +107,7 @@
|
|||||||
<property name="node.js.selected.package.eslint" value="(autodetect)" />
|
<property name="node.js.selected.package.eslint" value="(autodetect)" />
|
||||||
<property name="node.js.selected.package.tslint" value="(autodetect)" />
|
<property name="node.js.selected.package.tslint" value="(autodetect)" />
|
||||||
<property name="nodejs_package_manager_path" value="npm" />
|
<property name="nodejs_package_manager_path" value="npm" />
|
||||||
|
<property name="run.code.analysis.last.selected.profile" value="pProject Default" />
|
||||||
<property name="settings.editor.selected.configurable" value="terminal" />
|
<property name="settings.editor.selected.configurable" value="terminal" />
|
||||||
<property name="ts.external.directory.path" value="$PROJECT_DIR$/node_modules/typescript/lib" />
|
<property name="ts.external.directory.path" value="$PROJECT_DIR$/node_modules/typescript/lib" />
|
||||||
<property name="use.deno.dismiss" value="true" />
|
<property name="use.deno.dismiss" value="true" />
|
||||||
@@ -103,13 +115,15 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="RecentsManager">
|
<component name="RecentsManager">
|
||||||
<key name="MoveFile.RECENT_KEYS">
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$/public/solver" />
|
||||||
<recent name="$PROJECT_DIR$/src" />
|
<recent name="$PROJECT_DIR$/src" />
|
||||||
<recent name="$PROJECT_DIR$/src/ui" />
|
<recent name="$PROJECT_DIR$/src/solver/js" />
|
||||||
<recent name="$PROJECT_DIR$/src/solver" />
|
<recent name="$PROJECT_DIR$/src/solver/wasm" />
|
||||||
<recent name="$PROJECT_DIR$/src/ui/threedee" />
|
<recent name="$PROJECT_DIR$/resources/app" />
|
||||||
<recent name="$PROJECT_DIR$/src/webapp" />
|
|
||||||
</key>
|
</key>
|
||||||
<key name="CopyFile.RECENT_KEYS">
|
<key name="CopyFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$/src/solver/js" />
|
||||||
|
<recent name="$PROJECT_DIR$/resources/app" />
|
||||||
<recent name="$PROJECT_DIR$/src/ui" />
|
<recent name="$PROJECT_DIR$/src/ui" />
|
||||||
<recent name="$PROJECT_DIR$/public/solver" />
|
<recent name="$PROJECT_DIR$/public/solver" />
|
||||||
<recent name="$PROJECT_DIR$/src" />
|
<recent name="$PROJECT_DIR$/src" />
|
||||||
@@ -147,6 +161,17 @@
|
|||||||
<workItem from="1622971349849" duration="20106000" />
|
<workItem from="1622971349849" duration="20106000" />
|
||||||
<workItem from="1623052849485" duration="37878000" />
|
<workItem from="1623052849485" duration="37878000" />
|
||||||
<workItem from="1623141089245" duration="24731000" />
|
<workItem from="1623141089245" duration="24731000" />
|
||||||
|
<workItem from="1623261066851" duration="590000" />
|
||||||
|
<workItem from="1623485615537" duration="593000" />
|
||||||
|
<workItem from="1623668677088" duration="8416000" />
|
||||||
|
<workItem from="1623740877224" duration="26074000" />
|
||||||
|
<workItem from="1624715065692" duration="610000" />
|
||||||
|
<workItem from="1624802539718" duration="6683000" />
|
||||||
|
<workItem from="1624820040894" duration="7878000" />
|
||||||
|
<workItem from="1624862787465" duration="3754000" />
|
||||||
|
<workItem from="1624953370630" duration="10086000" />
|
||||||
|
<workItem from="1625293881668" duration="18235000" />
|
||||||
|
<workItem from="1625330866437" duration="2495000" />
|
||||||
</task>
|
</task>
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
@@ -154,25 +179,82 @@
|
|||||||
<option name="version" value="3" />
|
<option name="version" value="3" />
|
||||||
<option name="exactExcludedFiles">
|
<option name="exactExcludedFiles">
|
||||||
<list>
|
<list>
|
||||||
<option value="$PROJECT_DIR$/src/solver/assembly/index.js" />
|
<option value="$PROJECT_DIR$/src/solver/js/SomaSolver.js" />
|
||||||
<option value="$PROJECT_DIR$/src/solver/assembly/SomaSolution.js" />
|
<option value="$PROJECT_DIR$/src/SomaSolution.js" />
|
||||||
<option value="$PROJECT_DIR$/src/solver/assembly/SomaSolver.js" />
|
<option value="$PROJECT_DIR$/src/SomaSolution.js" />
|
||||||
<option value="$PROJECT_DIR$/src/solver/assembly/VoxelSpace.js" />
|
<option value="$PROJECT_DIR$/src/solver/js/SomaSolver.js" />
|
||||||
<option value="$PROJECT_DIR$/src/solver/assembly/SomaSolution.js" />
|
<option value="$PROJECT_DIR$/src/VoxelSpaceBigInt.js" />
|
||||||
<option value="$PROJECT_DIR$/src/solver/assembly/SomaSolver.js" />
|
<option value="$PROJECT_DIR$/src/VoxelSpaceBoolean.js" />
|
||||||
<option value="$PROJECT_DIR$/src/solver/assembly/VoxelSpace.js" />
|
<option value="$PROJECT_DIR$/src/SomaSolution.js" />
|
||||||
|
<option value="$PROJECT_DIR$/src/VoxelSpaceBigInt.js" />
|
||||||
|
<option value="$PROJECT_DIR$/src/VoxelSpaceBoolean.js" />
|
||||||
|
<option value="$PROJECT_DIR$/src/SomaSolution.js" />
|
||||||
|
<option value="$PROJECT_DIR$/src/VoxelSpaceBigInt.js" />
|
||||||
|
<option value="$PROJECT_DIR$/src/VoxelSpaceBoolean.js" />
|
||||||
|
<option value="$PROJECT_DIR$/src/SomaSolution.js" />
|
||||||
|
<option value="$PROJECT_DIR$/src/solver/js/SomaSolver.js" />
|
||||||
|
<option value="$PROJECT_DIR$/src/VoxelSpaceBigInt.js" />
|
||||||
|
<option value="$PROJECT_DIR$/src/VoxelSpaceBoolean.js" />
|
||||||
|
<option value="$PROJECT_DIR$/src/SomaSolution.js" />
|
||||||
|
<option value="$PROJECT_DIR$/src/solver/js/VoxelSpaceBoolean.js" />
|
||||||
|
<option value="$PROJECT_DIR$/src/solver/js/SomaSolution.js" />
|
||||||
|
<option value="$PROJECT_DIR$/src/solver/js/SomaSolver.js" />
|
||||||
|
<option value="$PROJECT_DIR$/src/solver/js/main.js" />
|
||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
<component name="Vcs.Log.History.Properties">
|
||||||
|
<option name="COLUMN_ORDER">
|
||||||
|
<list>
|
||||||
|
<option value="0" />
|
||||||
|
<option value="2" />
|
||||||
|
<option value="3" />
|
||||||
|
<option value="1" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="Vcs.Log.Tabs.Properties">
|
||||||
|
<option name="TAB_STATES">
|
||||||
|
<map>
|
||||||
|
<entry key="MAIN">
|
||||||
|
<value>
|
||||||
|
<State>
|
||||||
|
<option name="FILTERS">
|
||||||
|
<map>
|
||||||
|
<entry key="branch">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="master" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</State>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
<option name="oldMeFiltersMigrated" value="true" />
|
||||||
|
</component>
|
||||||
<component name="WindowStateProjectService">
|
<component name="WindowStateProjectService">
|
||||||
<state x="633" y="342" width="643" height="403" key="#com.intellij.fileTypes.FileTypeChooser" timestamp="1623101908210">
|
<state x="633" y="342" width="643" height="403" key="#com.intellij.fileTypes.FileTypeChooser" timestamp="1623671479936">
|
||||||
<screen x="0" y="27" width="1920" height="1053" />
|
<screen x="0" y="27" width="1920" height="1053" />
|
||||||
</state>
|
</state>
|
||||||
<state x="633" y="342" width="643" height="403" key="#com.intellij.fileTypes.FileTypeChooser/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1623101908210" />
|
<state x="633" y="342" width="643" height="403" key="#com.intellij.fileTypes.FileTypeChooser/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1623671479936" />
|
||||||
<state x="643" y="194" width="614" height="706" key="#com.intellij.refactoring.rename.AutomaticRenamingDialog" timestamp="1621349929863">
|
<state x="692" y="20" width="1213" height="1060" key="#com.intellij.refactoring.rename.AutomaticRenamingDialog" timestamp="1625307697424">
|
||||||
<screen x="0" y="27" width="1920" height="1053" />
|
<screen x="0" y="27" width="3840" height="1053" />
|
||||||
</state>
|
</state>
|
||||||
|
<state x="692" y="20" width="1213" height="1060" key="#com.intellij.refactoring.rename.AutomaticRenamingDialog/0.27.3840.1053@0.27.3840.1053" timestamp="1625307697424" />
|
||||||
<state x="643" y="194" width="614" height="706" key="#com.intellij.refactoring.rename.AutomaticRenamingDialog/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1621349929863" />
|
<state x="643" y="194" width="614" height="706" key="#com.intellij.refactoring.rename.AutomaticRenamingDialog/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1621349929863" />
|
||||||
|
<state x="696" y="393" width="518" height="301" key="ANALYSIS_DLG_com.intellij.analysis.BaseAnalysisAction$1" timestamp="1624963157966">
|
||||||
|
<screen x="0" y="27" width="1920" height="1053" />
|
||||||
|
</state>
|
||||||
|
<state x="696" y="393" width="518" height="301" key="ANALYSIS_DLG_com.intellij.analysis.BaseAnalysisAction$1/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1624963157966" />
|
||||||
|
<state x="92" y="69" width="1720" height="880" key="DiffContextDialog" timestamp="1624822125389">
|
||||||
|
<screen x="0" y="27" width="1920" height="1053" />
|
||||||
|
</state>
|
||||||
|
<state x="92" y="69" width="1720" height="880" key="DiffContextDialog/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1624822125389" />
|
||||||
<state x="743" y="301" width="716" height="623" key="FileChooserDialogImpl" timestamp="1621932819206">
|
<state x="743" y="301" width="716" height="623" key="FileChooserDialogImpl" timestamp="1621932819206">
|
||||||
<screen x="0" y="27" width="1920" height="1053" />
|
<screen x="0" y="27" width="1920" height="1053" />
|
||||||
</state>
|
</state>
|
||||||
@@ -189,11 +271,15 @@
|
|||||||
<screen x="0" y="27" width="1920" height="1053" />
|
<screen x="0" y="27" width="1920" height="1053" />
|
||||||
</state>
|
</state>
|
||||||
<state x="651" y="279" width="618" height="522" key="find.popup/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1623164695220" />
|
<state x="651" y="279" width="618" height="522" key="find.popup/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1623164695220" />
|
||||||
<state x="624" y="245" key="run.anything.popup" timestamp="1623150034896">
|
<state x="834" y="395" width="250" height="278" key="jetbrains.javascript.buildTools.run-task-popup" timestamp="1623671403065">
|
||||||
<screen x="0" y="27" width="1920" height="1053" />
|
<screen x="0" y="27" width="1920" height="1053" />
|
||||||
</state>
|
</state>
|
||||||
<state x="1248" y="245" key="run.anything.popup/0.27.3840.1053@0.27.3840.1053" timestamp="1621713204008" />
|
<state x="834" y="395" width="250" height="278" key="jetbrains.javascript.buildTools.run-task-popup/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1623671403065" />
|
||||||
<state x="624" y="245" key="run.anything.popup/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1623150034896" />
|
<state x="1248" y="245" key="run.anything.popup" timestamp="1625305747628">
|
||||||
|
<screen x="0" y="27" width="3840" height="1053" />
|
||||||
|
</state>
|
||||||
|
<state x="1248" y="245" key="run.anything.popup/0.27.3840.1053@0.27.3840.1053" timestamp="1625305747628" />
|
||||||
|
<state x="624" y="245" key="run.anything.popup/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1624805204463" />
|
||||||
<state x="755" y="405" width="400" height="284" key="scopes" timestamp="1621932851405">
|
<state x="755" y="405" width="400" height="284" key="scopes" timestamp="1621932851405">
|
||||||
<screen x="0" y="27" width="1920" height="1053" />
|
<screen x="0" y="27" width="1920" height="1053" />
|
||||||
</state>
|
</state>
|
||||||
|
|||||||
2601
package-lock.json
generated
2601
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@@ -2,11 +2,14 @@
|
|||||||
"name": "soma",
|
"name": "soma",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Custom Somaesque cube solver webapp",
|
"description": "Custom Somaesque cube solver webapp",
|
||||||
|
"main": "src/desktop/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rollup -c",
|
"build": "rollup -c",
|
||||||
"dev": "rollup -c -w",
|
"dev": "rollup -c -w",
|
||||||
"start": "sirv public --no-clear",
|
"start": "sirv public --no-clear",
|
||||||
"validate": "svelte-check"
|
"validate": "svelte-check",
|
||||||
|
"pack": "electron-builder --dir",
|
||||||
|
"dist": "electron-builder"
|
||||||
},
|
},
|
||||||
"author": "Daniel Ledda",
|
"author": "Daniel Ledda",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
@@ -16,6 +19,9 @@
|
|||||||
"@rollup/plugin-typescript": "^8.0.0",
|
"@rollup/plugin-typescript": "^8.0.0",
|
||||||
"@tsconfig/svelte": "^1.0.0",
|
"@tsconfig/svelte": "^1.0.0",
|
||||||
"@types/three": "^0.128.0",
|
"@types/three": "^0.128.0",
|
||||||
|
"electron": "^13.1.2",
|
||||||
|
"electron-builder": "^22.11.7",
|
||||||
|
"electron-winstaller": "^5.0.0",
|
||||||
"rollup": "^2.3.4",
|
"rollup": "^2.3.4",
|
||||||
"rollup-plugin-css-only": "^3.1.0",
|
"rollup-plugin-css-only": "^3.1.0",
|
||||||
"rollup-plugin-livereload": "^2.0.0",
|
"rollup-plugin-livereload": "^2.0.0",
|
||||||
@@ -28,9 +34,25 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"as-bind": "^0.7.1",
|
"as-bind": "^0.7.1",
|
||||||
|
"electron-squirrel-startup": "^1.0.0",
|
||||||
"sirv-cli": "^1.0.0",
|
"sirv-cli": "^1.0.0",
|
||||||
"source-map-support": "^0.5.19",
|
"source-map-support": "^0.5.19",
|
||||||
"three": "^0.128.0",
|
"three": "^0.128.0",
|
||||||
"typescript": "^4.4.0-dev.20210525"
|
"typescript": "^4.4.0-dev.20210525"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"appId": "de.djledda.somaesque",
|
||||||
|
"copyright": "Copyright © 2021 Daniel Ledda",
|
||||||
|
"win": {
|
||||||
|
"target": ["portable"]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"**/*",
|
||||||
|
"./public/**",
|
||||||
|
"./src/desktop${/*}"
|
||||||
|
],
|
||||||
|
"directories": {
|
||||||
|
"output": "desktop-dist"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset='utf-8'>
|
<meta charset='utf-8'>
|
||||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||||
|
|
||||||
<title>Svelte app</title>
|
<title>Somaesque</title>
|
||||||
|
|
||||||
<link rel='icon' type='image/png' href='./favicon.png'>
|
<link rel='icon' type='image/png' href='./favicon.png'>
|
||||||
<link rel='stylesheet' href='./global.css'>
|
<link rel='stylesheet' href='./global.css'>
|
||||||
|
|||||||
BIN
public/resources/3x3x4.png
Normal file
BIN
public/resources/3x3x4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
BIN
public/resources/ColorWheel.png
Normal file
BIN
public/resources/ColorWheel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
@@ -1,138 +0,0 @@
|
|||||||
import VoxelSpace from "./VoxelSpace.js";
|
|
||||||
import SomaSolution from "./SomaSolution.js";
|
|
||||||
export default class SomaSolver {
|
|
||||||
constructor(dimension) {
|
|
||||||
this.solutions = [];
|
|
||||||
this.iterations = 0;
|
|
||||||
if (dimension % 1 !== 0 || dimension < 0) {
|
|
||||||
throw new Error("The argument 'dimension' must be a positive whole number");
|
|
||||||
}
|
|
||||||
this.dim = dimension;
|
|
||||||
this.solutionCube = new VoxelSpace(0, [dimension, dimension, dimension], Array(dimension ** 3).fill(0));
|
|
||||||
}
|
|
||||||
async solve(polycubes) {
|
|
||||||
if (polycubes.length === 0) {
|
|
||||||
throw new Error("You must pass at least one polycube to solve the puzzle.");
|
|
||||||
}
|
|
||||||
let cumulativeSize = polycubes.reduce((prev, curr) => prev + curr.size(), 0);
|
|
||||||
if (cumulativeSize !== this.dim ** 3) {
|
|
||||||
throw new Error(`The polycubes passed do not add up to exactly enough units to form a cube of dimension ${this.dim}! Got: ${cumulativeSize}, need: ${this.dim ** 3}`);
|
|
||||||
}
|
|
||||||
this.solutions = [];
|
|
||||||
const combosWithRots = polycubes.slice(1).map(polycube => polycube.getUniqueRotations().map((rot) => rot.getAllPositionsInCube(this.dim)).flat());
|
|
||||||
const combos = [polycubes[0].getAllPositionsInCube(this.dim), ...combosWithRots];
|
|
||||||
console.log(combos.flat().length);
|
|
||||||
this.backtrackSolve(this.solutionCube, combos, new SomaSolution(this.dim));
|
|
||||||
this.solutions = SomaSolution.filterUnique(this.solutions);
|
|
||||||
}
|
|
||||||
getSolutions() {
|
|
||||||
return this.solutions.slice();
|
|
||||||
}
|
|
||||||
backtrackSolve(workingSolution, polycubes, currentSoln) {
|
|
||||||
const nextCubeGroup = polycubes[0];
|
|
||||||
for (let i = 0; i < nextCubeGroup.length; i++) {
|
|
||||||
const fusionAttempt = workingSolution.plus(nextCubeGroup[i]);
|
|
||||||
if (fusionAttempt) {
|
|
||||||
const nextSoln = currentSoln.clone();
|
|
||||||
nextSoln.addSpace(nextCubeGroup[i]);
|
|
||||||
if (polycubes.length === 1) {
|
|
||||||
this.solutions.push(nextSoln);
|
|
||||||
currentSoln = new SomaSolution(this.dim);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.backtrackSolve(fusionAttempt, polycubes.slice(1), nextSoln);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SomaSolution {
|
|
||||||
constructor(dim) {
|
|
||||||
if (dim < 0 || dim % 1 !== 0) {
|
|
||||||
throw new Error("Dimension must be a whole positive integer!");
|
|
||||||
}
|
|
||||||
this.dim = dim;
|
|
||||||
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.getUniqueRotations()) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
getUniqueRotations() {
|
|
||||||
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.dim);
|
|
||||||
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.dim; x++) {
|
|
||||||
for (let y = 0; y < this.dim; y++) {
|
|
||||||
for (let z = 0; z < this.dim; z++) {
|
|
||||||
for (const space of this.solutionSpaces) {
|
|
||||||
if (space.at(x, y, z)) {
|
|
||||||
accum += space.getId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(accum);
|
|
||||||
accum = "";
|
|
||||||
}
|
|
||||||
if (x !== this.dim - 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.dim);
|
|
||||||
clone.solutionSpaces = this.solutionSpaces.slice();
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
public/solver/false
Normal file
1
public/solver/false
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
5644
public/solver/main.wat
Normal file
5644
public/solver/main.wat
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,16 @@
|
|||||||
import type VoxelSpace from "./VoxelSpace";
|
import type VoxelSpaceBoolean from "./VoxelSpaceBoolean";
|
||||||
|
import type VoxelSpaceBigInt from "./VoxelSpaceBigInt";
|
||||||
|
|
||||||
export default class SomaSolution {
|
export default class SomaSolution {
|
||||||
private solutionSpaces: VoxelSpace[];
|
private solutionSpaces: (VoxelSpaceBoolean | VoxelSpaceBigInt)[];
|
||||||
private dim: number;
|
private dimX: number;
|
||||||
constructor(dim: number) {
|
private dimY: number;
|
||||||
if (dim < 0 || dim % 1 !== 0) {
|
private dimZ: number;
|
||||||
throw new Error("Dimension must be a whole positive integer!");
|
|
||||||
}
|
constructor(dimX: number, dimY: number, dimZ: number) {
|
||||||
this.dim = dim;
|
this.dimX = dimX;
|
||||||
|
this.dimY = dimY;
|
||||||
|
this.dimZ = dimZ;
|
||||||
this.solutionSpaces = [];
|
this.solutionSpaces = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +43,7 @@ export default class SomaSolution {
|
|||||||
const result: SomaSolution[] = [];
|
const result: SomaSolution[] = [];
|
||||||
const allRots = this.solutionSpaces.map(space => space.getAllRotations());
|
const allRots = this.solutionSpaces.map(space => space.getAllRotations());
|
||||||
for (let i = 0; i < allRots[0].length; i++) {
|
for (let i = 0; i < allRots[0].length; i++) {
|
||||||
const solnRot = new SomaSolution(this.dim);
|
const solnRot = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||||
allRots.forEach(rotGroup => solnRot.addSpace(rotGroup[i]));
|
allRots.forEach(rotGroup => solnRot.addSpace(rotGroup[i]));
|
||||||
result.push(solnRot);
|
result.push(solnRot);
|
||||||
}
|
}
|
||||||
@@ -56,16 +59,16 @@ export default class SomaSolution {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addSpace(space: VoxelSpace) {
|
addSpace(space: VoxelSpaceBoolean | VoxelSpaceBigInt) {
|
||||||
this.solutionSpaces.push(space);
|
this.solutionSpaces.push(space);
|
||||||
}
|
}
|
||||||
|
|
||||||
print() {
|
print() {
|
||||||
let accum = "";
|
let accum = "";
|
||||||
console.log("---");
|
console.log("---");
|
||||||
for (let x = 0; x < this.dim; x++) {
|
for (let x = 0; x < this.dimX; x++) {
|
||||||
for (let y = 0; y < this.dim; y++) {
|
for (let y = 0; y < this.dimY; y++) {
|
||||||
for (let z = 0; z < this.dim; z++) {
|
for (let z = 0; z < this.dimZ; z++) {
|
||||||
for (const space of this.solutionSpaces) {
|
for (const space of this.solutionSpaces) {
|
||||||
if (space.at(x, y, z)) {
|
if (space.at(x, y, z)) {
|
||||||
accum += space.getId();
|
accum += space.getId();
|
||||||
@@ -75,7 +78,7 @@ export default class SomaSolution {
|
|||||||
console.log(accum);
|
console.log(accum);
|
||||||
accum = "";
|
accum = "";
|
||||||
}
|
}
|
||||||
if (x !== this.dim - 1) {
|
if (x !== this.dimX - 1) {
|
||||||
console.log("-");
|
console.log("-");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,19 +95,19 @@ export default class SomaSolution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clone() {
|
clone() {
|
||||||
const clone = new SomaSolution(this.dim);
|
const clone = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||||
clone.solutionSpaces = this.solutionSpaces.slice();
|
clone.solutionSpaces = this.solutionSpaces.slice();
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDims() {
|
getDims() {
|
||||||
return [this.dim, this.dim, this.dim];
|
return [this.dimX, this.dimY, this.dimZ];
|
||||||
}
|
}
|
||||||
|
|
||||||
forEachCell(cb: (val: number, x: number, y: number, z: number) => any) {
|
forEachCell(cb: (val: number, x: number, y: number, z: number) => any) {
|
||||||
loopStart: for (let x = 0; x < this.dim; x++) {
|
loopStart: for (let x = 0; x < this.dimX; x++) {
|
||||||
for (let y = 0; y < this.dim; y++) {
|
for (let y = 0; y < this.dimY; y++) {
|
||||||
for (let z = 0; z < this.dim; z++) {
|
for (let z = 0; z < this.dimZ; z++) {
|
||||||
cb(this.at(x, y, z), x, y, z);
|
cb(this.at(x, y, z), x, y, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
import VoxelSpace from "./VoxelSpace";
|
|
||||||
import SomaSolution from "./SomaSolution";
|
|
||||||
|
|
||||||
export default class SomaSolver {
|
|
||||||
private solutionCube: VoxelSpace;
|
|
||||||
private dim: number;
|
|
||||||
private solutions: SomaSolution[] = [];
|
|
||||||
private iterations: number = 0;
|
|
||||||
constructor(dimension: number) {
|
|
||||||
if (dimension % 1 !== 0 || dimension < 0) {
|
|
||||||
throw new Error("The argument 'dimension' must be a positive whole number");
|
|
||||||
}
|
|
||||||
this.dim = dimension;
|
|
||||||
this.solutionCube = new VoxelSpace(0, [dimension, dimension, dimension], Array(dimension**3).fill(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
async solve(polycubes: VoxelSpace[]) {
|
|
||||||
if (polycubes.length === 0) {
|
|
||||||
throw new Error("You must pass at least one polycube to solve the puzzle.");
|
|
||||||
}
|
|
||||||
let cumulativeSize = polycubes.reduce((prev, curr) => prev + curr.size(), 0);
|
|
||||||
if (cumulativeSize !== this.dim**3) {
|
|
||||||
throw new Error(`The polycubes passed do not add up to exactly enough units to form a cube of dimension ${this.dim}! Got: ${cumulativeSize}, need: ${this.dim**3}`);
|
|
||||||
}
|
|
||||||
this.solutions = [];
|
|
||||||
const combosWithRots = polycubes.slice(1).map(polycube => polycube.getUniqueRotations().map((rot: VoxelSpace) => rot.getAllPositionsInCube(this.dim)).flat());
|
|
||||||
const combos = [polycubes[0].getAllPositionsInCube(this.dim), ...combosWithRots];
|
|
||||||
this.backtrackSolve(this.solutionCube, combos, new SomaSolution(this.dim));
|
|
||||||
this.solutions = SomaSolution.filterUnique(this.solutions);
|
|
||||||
}
|
|
||||||
|
|
||||||
getSolutions() {
|
|
||||||
return this.solutions.slice();
|
|
||||||
}
|
|
||||||
|
|
||||||
private backtrackSolve(workingSolution: VoxelSpace, polycubes: VoxelSpace[][], currentSoln: SomaSolution, depth = 0) {
|
|
||||||
const nextCubeGroup = polycubes[0];
|
|
||||||
for (let i = 0; i < nextCubeGroup.length; i++) {
|
|
||||||
const fusionAttempt = workingSolution.plus(nextCubeGroup[i]);
|
|
||||||
if (fusionAttempt) {
|
|
||||||
const nextSoln = currentSoln.clone();
|
|
||||||
nextSoln.addSpace(nextCubeGroup[i]);
|
|
||||||
if (polycubes.length === 1) {
|
|
||||||
this.solutions.push(nextSoln);
|
|
||||||
currentSoln = new SomaSolution(this.dim);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.backtrackSolve(fusionAttempt, polycubes.slice(1), nextSoln, depth + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type VoxelSpaceBoolean from "./VoxelSpaceBoolean";
|
||||||
|
|
||||||
export type DimensionDef = [number, number, number];
|
export type DimensionDef = [number, number, number];
|
||||||
|
|
||||||
const enum NeighbourDirection {
|
const enum NeighbourDirection {
|
||||||
@@ -9,30 +11,28 @@ const enum NeighbourDirection {
|
|||||||
NEGZ,
|
NEGZ,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class VoxelSpace {
|
export default class VoxelSpaceBigInt {
|
||||||
private dims: DimensionDef;
|
private dims: DimensionDef;
|
||||||
private length: number;
|
private length: number;
|
||||||
private space: bigint;
|
private space: bigint;
|
||||||
private id: number;
|
private id: number;
|
||||||
constructor(id: number, dims: DimensionDef, space?: boolean[] | bigint, cullEmpty?: boolean) {
|
private color: string;
|
||||||
if (!space) {
|
|
||||||
space = 0n;
|
constructor(options: {id: number, dims: DimensionDef, space?: bigint, cullEmpty: boolean, color?: string}) {
|
||||||
} else if (Array.isArray(space)) {
|
if (!options.space) {
|
||||||
if (space.length !== dims[0] * dims[1] * dims[2]) {
|
options.space = 0n;
|
||||||
throw new Error("Vals don't fit in given dimensions.");
|
|
||||||
}
|
|
||||||
space = VoxelSpace.boolArrayToBigInt(space)
|
|
||||||
}
|
}
|
||||||
this.id = id;
|
this.id = options.id;
|
||||||
this.length = dims[0] * dims[1] * dims[2];
|
this.length = options.dims[0] * options.dims[1] * options.dims[2];
|
||||||
this.dims = dims;
|
this.dims = options.dims;
|
||||||
this.space = space;
|
this.space = BigInt(options.space);
|
||||||
if (cullEmpty) {
|
this.color = options.color ?? "red";
|
||||||
|
if (options.cullEmpty !== false) {
|
||||||
this.cullEmptySpace();
|
this.cullEmptySpace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolArrayToBigInt(boolArray: boolean[]): bigint {
|
static boolArrayToBigInt(boolArray: boolean[]) {
|
||||||
let result = 0n;
|
let result = 0n;
|
||||||
for (let i = 0; i < boolArray.length; i++) {
|
for (let i = 0; i < boolArray.length; i++) {
|
||||||
if (boolArray[i]) {
|
if (boolArray[i]) {
|
||||||
@@ -42,18 +42,26 @@ export default class VoxelSpace {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setColor(color: string) {
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
getColor() {
|
||||||
|
return this.color;
|
||||||
|
}
|
||||||
|
|
||||||
binaryRep() {
|
binaryRep() {
|
||||||
return this.space.toString(2);
|
return this.space.toString(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
getExtrema() {
|
getExtrema() {
|
||||||
const extrema = {
|
const extrema = {
|
||||||
xMax: -Infinity,
|
xMax: 0,
|
||||||
xMin: Infinity,
|
xMin: this.dims[0],
|
||||||
yMax: -Infinity,
|
yMax: 0,
|
||||||
yMin: Infinity,
|
yMin: this.dims[1],
|
||||||
zMax: -Infinity,
|
zMax: 0,
|
||||||
zMin: Infinity,
|
zMin: this.dims[2],
|
||||||
};
|
};
|
||||||
this.forEachCell((val, x, y, z) => {
|
this.forEachCell((val, x, y, z) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
@@ -68,7 +76,7 @@ export default class VoxelSpace {
|
|||||||
return extrema;
|
return extrema;
|
||||||
}
|
}
|
||||||
|
|
||||||
private cullEmptySpace() {
|
cullEmptySpace() {
|
||||||
const extrema = this.getExtrema();
|
const extrema = this.getExtrema();
|
||||||
let index = 0n;
|
let index = 0n;
|
||||||
let newSpace = 0n;
|
let newSpace = 0n;
|
||||||
@@ -123,25 +131,25 @@ export default class VoxelSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getUniqueRotations() {
|
getUniqueRotations() {
|
||||||
const rotations: VoxelSpace[] = [];
|
const rotations: VoxelSpaceBigInt[] = [];
|
||||||
const refSpace = this.clone();
|
const refSpace = this.clone();
|
||||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||||
refSpace.rot90('y');
|
refSpace.rot90('y');
|
||||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||||
refSpace.rot90('y');
|
refSpace.rot90('y');
|
||||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||||
refSpace.rot90('y');
|
refSpace.rot90('y');
|
||||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||||
refSpace.rot90('z');
|
refSpace.rot90('z');
|
||||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||||
refSpace.rot90('z');
|
refSpace.rot90('z');
|
||||||
refSpace.rot90('z');
|
refSpace.rot90('z');
|
||||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||||
return rotations;
|
return rotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllRotations() {
|
getAllRotations() {
|
||||||
const rotations: VoxelSpace[] = [];
|
const rotations: VoxelSpaceBigInt[] = [];
|
||||||
const refSpace = this.clone();
|
const refSpace = this.clone();
|
||||||
rotations.push(...refSpace.getAxisSpins('x'));
|
rotations.push(...refSpace.getAxisSpins('x'));
|
||||||
refSpace.rot90('y');
|
refSpace.rot90('y');
|
||||||
@@ -158,7 +166,7 @@ export default class VoxelSpace {
|
|||||||
return rotations;
|
return rotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static pushNewUniqueSpaces(existingSpaces: VoxelSpace[], newSpaces: VoxelSpace[]) {
|
protected static pushNewUniqueSpaces(existingSpaces: VoxelSpaceBigInt[], newSpaces: VoxelSpaceBigInt[]) {
|
||||||
for (const newSpace of newSpaces) {
|
for (const newSpace of newSpaces) {
|
||||||
let matchFound = false;
|
let matchFound = false;
|
||||||
for (const existingSpace of existingSpaces) {
|
for (const existingSpace of existingSpaces) {
|
||||||
@@ -173,41 +181,44 @@ export default class VoxelSpace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllPositionsInCube(cubeDim: number): VoxelSpace[] {
|
getAllPositionsInPrism(cubeDimX: number, cubeDimY: number, cubeDimZ: number): VoxelSpaceBigInt[] {
|
||||||
if ((cubeDim > 0) && (cubeDim % 1 === 0)) {
|
const cubePositions: VoxelSpaceBigInt[] = [];
|
||||||
const cubePositions: VoxelSpace[] = [];
|
if (this.dims[0] > cubeDimX || this.dims[1] > cubeDimY || this.dims[2] > cubeDimZ) {
|
||||||
for (let x = 0; x < cubeDim - this.dims[0] + 1; x++) {
|
return cubePositions;
|
||||||
for (let y = 0; y < cubeDim - this.dims[1] + 1; y++) {
|
}
|
||||||
for (let z = 0; z < cubeDim - this.dims[2] + 1; z++) {
|
for (let xOffset = 0; xOffset < (cubeDimX - this.dims[0] + 1); xOffset++) {
|
||||||
const cubePos = new VoxelSpace(this.id, [cubeDim, cubeDim, cubeDim]);
|
for (let yOffset = 0; yOffset < (cubeDimY - this.dims[1] + 1); yOffset++) {
|
||||||
this.forEachCell((val, rotX, rotY, rotZ) => {
|
for (let zOffset = 0; zOffset < (cubeDimZ - this.dims[2] + 1); zOffset++) {
|
||||||
cubePos.set(x + rotX, y + rotY, z + rotZ, val);
|
const cubePos = new VoxelSpaceBigInt({id: this.id, dims: [cubeDimX, cubeDimY, cubeDimZ], color: this.color, cullEmpty: false});
|
||||||
});
|
this.forEachCell((val, x, y, z) => {
|
||||||
cubePositions.push(cubePos);
|
cubePos.set(xOffset + x, yOffset + y, zOffset + z, val);
|
||||||
}
|
});
|
||||||
|
cubePositions.push(cubePos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cubePositions;
|
|
||||||
} else {
|
|
||||||
throw new Error("cubeDim must be a positive integer.");
|
|
||||||
}
|
}
|
||||||
|
return cubePositions;
|
||||||
}
|
}
|
||||||
|
|
||||||
matches(space: VoxelSpace) {
|
matches(space: VoxelSpaceBigInt | VoxelSpaceBoolean) {
|
||||||
const otherDims = space.getDims();
|
const otherDims = space.getDims();
|
||||||
for (let i = 0; i < this.dims.length; i++) {
|
for (let i = 0; i < this.dims.length; i++) {
|
||||||
if (otherDims[i] !== this.dims[i]) {
|
if (otherDims[i] !== this.dims[i]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.space === space.getRaw();
|
if (typeof space.getRaw() === "bigint") {
|
||||||
|
return this.space === space.getRaw();
|
||||||
|
} else {
|
||||||
|
return this.binaryRep() === space.binaryRep();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clone() {
|
clone() {
|
||||||
return new VoxelSpace(this.id, this.getDims(), this.getRaw());
|
return new VoxelSpaceBigInt({id: this.id, dims: this.getDims(), space: this.getRaw(), color: this.getColor(), cullEmpty: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAxisSpins(axis: 'x' | 'y' | 'z'): VoxelSpace[] {
|
private getAxisSpins(axis: 'x' | 'y' | 'z'): VoxelSpaceBigInt[] {
|
||||||
const rotations = [this.clone()];
|
const rotations = [this.clone()];
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
rotations.push(rotations[i].rotated90(axis));
|
rotations.push(rotations[i].rotated90(axis));
|
||||||
@@ -250,30 +261,33 @@ export default class VoxelSpace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggle(x: number, y: number, z: number) {
|
toggle(x: number, y: number, z: number) {
|
||||||
const mask = BigInt(1 << this.dims[1] * this.dims[2] * x + this.dims[2] * y + z);
|
const mask = 1n << BigInt(this.dims[1] * this.dims[2] * x + this.dims[2] * y + z);
|
||||||
this.space ^= mask;
|
this.space ^= mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
set(x: number, y: number, z: number, val: boolean) {
|
set(x: number, y: number, z: number, val: boolean) {
|
||||||
const mask = BigInt(1 << this.dims[1] * this.dims[2] * x + this.dims[2] * y + z);
|
const mask = 1n << BigInt(this.dims[1] * this.dims[2] * x + this.dims[2] * y + z);
|
||||||
if (val) {
|
if (val) {
|
||||||
this.space |= mask;
|
this.space |= mask;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
this.space &= ~mask;
|
this.space &= ~mask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rotated90(dim: 'x' | 'y' | 'z') {
|
rotated90(dim: 'x' | 'y' | 'z') {
|
||||||
let newSpace = 0n;
|
let newSpace = 0n;
|
||||||
let newDims: DimensionDef;
|
let newDims;
|
||||||
let rotIndex: (i: number, j: number, k: number) => number;
|
let rotIndex;
|
||||||
if (dim === 'x') {
|
if (dim === 'x') {
|
||||||
newDims = [this.dims[0], this.dims[2], this.dims[1]];
|
newDims = [this.dims[0], this.dims[2], this.dims[1]];
|
||||||
rotIndex = this.newIndexRotX.bind(this);
|
rotIndex = this.newIndexRotX.bind(this);
|
||||||
} else if (dim === 'y') {
|
}
|
||||||
|
else if (dim === 'y') {
|
||||||
newDims = [this.dims[2], this.dims[1], this.dims[0]];
|
newDims = [this.dims[2], this.dims[1], this.dims[0]];
|
||||||
rotIndex = this.newIndexRotY.bind(this);
|
rotIndex = this.newIndexRotY.bind(this);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
newDims = [this.dims[1], this.dims[0], this.dims[2]];
|
newDims = [this.dims[1], this.dims[0], this.dims[2]];
|
||||||
rotIndex = this.newIndexRotZ.bind(this);
|
rotIndex = this.newIndexRotZ.bind(this);
|
||||||
}
|
}
|
||||||
@@ -281,8 +295,8 @@ export default class VoxelSpace {
|
|||||||
if (val) {
|
if (val) {
|
||||||
newSpace |= BigInt(1 << rotIndex(i, j, k));
|
newSpace |= BigInt(1 << rotIndex(i, j, k));
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
return new VoxelSpace(this.id, newDims, newSpace);
|
return new VoxelSpaceBigInt({ id: this.id, dims: newDims, space: newSpace, color: this.color, cullEmpty: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
rot90(dim: 'x' | 'y' | 'z') {
|
rot90(dim: 'x' | 'y' | 'z') {
|
||||||
@@ -291,10 +305,10 @@ export default class VoxelSpace {
|
|||||||
this.dims = rot.getDims();
|
this.dims = rot.getDims();
|
||||||
}
|
}
|
||||||
|
|
||||||
plus(space: VoxelSpace): VoxelSpace | null {
|
plus(space: VoxelSpaceBigInt): VoxelSpaceBigInt | null {
|
||||||
const otherSpace = space.getRaw();
|
const otherSpace = space.getRaw();
|
||||||
if ((this.space | otherSpace) === (this.space ^ otherSpace)) {
|
if ((this.space | otherSpace) === (this.space ^ otherSpace)) {
|
||||||
return new VoxelSpace(this.id, this.dims, otherSpace | this.space);
|
return new VoxelSpaceBigInt({ id: this.id, dims: this.getDims(), space: otherSpace | this.space, color: this.color, cullEmpty: false });
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -331,4 +345,13 @@ export default class VoxelSpace {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAllPermutationsInPrism(prismDimX: number, prismDimY: number, prismDimZ: number): VoxelSpaceBigInt[] {
|
||||||
|
const rotations = this.getUniqueRotations();
|
||||||
|
let result = new Array<VoxelSpaceBigInt>();
|
||||||
|
for (let i = 0; i < rotations.length; i++) {
|
||||||
|
result = result.concat(rotations[i].getAllPositionsInPrism(prismDimX, prismDimY, prismDimZ));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
366
src/VoxelSpaceBoolean.ts
Normal file
366
src/VoxelSpaceBoolean.ts
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
import type VoxelSpaceBigInt from "./VoxelSpaceBigInt";
|
||||||
|
|
||||||
|
export type DimensionDef = [number, number, number];
|
||||||
|
|
||||||
|
const enum NeighbourDirection {
|
||||||
|
POSX,
|
||||||
|
POSY,
|
||||||
|
POSZ,
|
||||||
|
NEGX,
|
||||||
|
NEGY,
|
||||||
|
NEGZ,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class VoxelSpaceBoolean {
|
||||||
|
private dims: DimensionDef;
|
||||||
|
private length: number;
|
||||||
|
private space: boolean[];
|
||||||
|
private id: number;
|
||||||
|
private color: string;
|
||||||
|
|
||||||
|
constructor(options: {id: number, dims: DimensionDef, space?: boolean[] | bigint, cullEmpty: boolean, color?: string}) {
|
||||||
|
this.length = options.dims[0] * options.dims[1] * options.dims[2];
|
||||||
|
if (!options.space) {
|
||||||
|
options.space = new Array<boolean>(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: string) {
|
||||||
|
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<boolean>(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: (val: boolean, x: number, y: number, z: number) => any) {
|
||||||
|
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: VoxelSpaceBoolean[] = [];
|
||||||
|
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: VoxelSpaceBoolean[] = [];
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static pushNewUniqueSpaces(existingSpaces: VoxelSpaceBoolean[], newSpaces: VoxelSpaceBoolean[]) {
|
||||||
|
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: number, cubeDimY: number, cubeDimZ: number): VoxelSpaceBoolean[] {
|
||||||
|
const cubePositions: VoxelSpaceBoolean[] = [];
|
||||||
|
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: VoxelSpaceBoolean | VoxelSpaceBigInt) {
|
||||||
|
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});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAxisSpins(axis: 'x' | 'y' | 'z'): VoxelSpaceBoolean[] {
|
||||||
|
const rotations = [this.clone()];
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
rotations.push(rotations[i].rotated90(axis));
|
||||||
|
}
|
||||||
|
return rotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDims(): DimensionDef {
|
||||||
|
return this.dims.slice() as DimensionDef;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRaw() {
|
||||||
|
return this.space.slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
// [1, 0, 0] [x] [ x]
|
||||||
|
// [0, 0, -1] * [y] = [-z]
|
||||||
|
// [0, 1, 0] [z] [ y]
|
||||||
|
private newIndexRotX(x: number, y: number, z: number) {
|
||||||
|
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]
|
||||||
|
private newIndexRotY(x: number, y: number, z: number) {
|
||||||
|
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]
|
||||||
|
private newIndexRotZ(x: number, y: number, z: number) {
|
||||||
|
return this.dims[0] * this.dims[2] * (this.dims[1] - 1 - y) + this.dims[2] * x + z;
|
||||||
|
}
|
||||||
|
|
||||||
|
at(x: number, y: number, z: number) {
|
||||||
|
return this.space[this.index(x, y, z)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private index(x: number, y: number, z: number) {
|
||||||
|
return this.dims[1] * this.dims[2] * x + this.dims[2] * y + z;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(x: number, y: number, z: number) {
|
||||||
|
const index = this.index(x, y, z);
|
||||||
|
this.space[index] = !this.space[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
set(x: number, y: number, z: number, val: boolean) {
|
||||||
|
this.space[this.index(x, y, z)] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
rotated90(dim: 'x' | 'y' | 'z') {
|
||||||
|
const newSpace = new Array<boolean>(this.dims[0] * this.dims[1] * this.dims[2]);
|
||||||
|
newSpace.fill(false);
|
||||||
|
let newDims: DimensionDef;
|
||||||
|
let rotIndex: (i: number, j: number, k: number) => number;
|
||||||
|
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: 'x' | 'y' | 'z') {
|
||||||
|
const rot = this.rotated90(dim);
|
||||||
|
this.space = rot.getRaw();
|
||||||
|
this.dims = rot.getDims();
|
||||||
|
}
|
||||||
|
|
||||||
|
plus(space: VoxelSpaceBoolean): VoxelSpaceBoolean | null {
|
||||||
|
const newSpace = new Array<boolean>(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: number, y: number, z: number): number {
|
||||||
|
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: number, prismDimY: number, prismDimZ: number): VoxelSpaceBoolean[] {
|
||||||
|
const rotations = this.getUniqueRotations();
|
||||||
|
let result = new Array<VoxelSpaceBoolean>();
|
||||||
|
for (let i = 0; i < rotations.length; i++) {
|
||||||
|
result = result.concat(rotations[i].getAllPositionsInPrism(prismDimX, prismDimY, prismDimZ));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/desktop/build.js
Normal file
0
src/desktop/build.js
Normal file
30
src/desktop/main.js
Normal file
30
src/desktop/main.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
const { app, BrowserWindow } = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function createWindow() {
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
win.loadFile(path.join(__dirname, '../../public/index.html'));
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
createWindow();
|
||||||
|
|
||||||
|
app.on('activate', function() {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('window-all-close', function() {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
12
src/desktop/preload.js
Normal file
12
src/desktop/preload.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
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]);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
export default class SomaSolution {
|
export default class SomaSolution {
|
||||||
constructor(dim) {
|
constructor(dimX, dimY, dimZ) {
|
||||||
if (dim < 0 || dim % 1 !== 0) {
|
this.dimX = dimX;
|
||||||
throw new Error("Dimension must be a whole positive integer!");
|
this.dimY = dimY;
|
||||||
}
|
this.dimZ = dimZ;
|
||||||
this.dim = dim;
|
|
||||||
this.solutionSpaces = [];
|
this.solutionSpaces = [];
|
||||||
}
|
}
|
||||||
static filterUnique(solutions) {
|
static filterUnique(solutions) {
|
||||||
@@ -13,7 +12,7 @@ export default class SomaSolution {
|
|||||||
const uniqueSolns = [solutions[0]];
|
const uniqueSolns = [solutions[0]];
|
||||||
for (const solution of solutions) {
|
for (const solution of solutions) {
|
||||||
let foundMatch = false;
|
let foundMatch = false;
|
||||||
for (const rotation of solution.getUniqueRotations()) {
|
for (const rotation of solution.getRotations()) {
|
||||||
let end = uniqueSolns.length;
|
let end = uniqueSolns.length;
|
||||||
for (let i = 0; i < end; i++) {
|
for (let i = 0; i < end; i++) {
|
||||||
if (rotation.matches(uniqueSolns[i])) {
|
if (rotation.matches(uniqueSolns[i])) {
|
||||||
@@ -27,14 +26,14 @@ export default class SomaSolution {
|
|||||||
}
|
}
|
||||||
return uniqueSolns;
|
return uniqueSolns;
|
||||||
}
|
}
|
||||||
getUniqueRotations() {
|
getRotations() {
|
||||||
if (this.solutionSpaces.length === 0) {
|
if (this.solutionSpaces.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const result = [];
|
const result = [];
|
||||||
const allRots = this.solutionSpaces.map(space => space.getAllRotations());
|
const allRots = this.solutionSpaces.map(space => space.getAllRotations());
|
||||||
for (let i = 0; i < allRots[0].length; i++) {
|
for (let i = 0; i < allRots[0].length; i++) {
|
||||||
const solnRot = new SomaSolution(this.dim);
|
const solnRot = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||||
allRots.forEach(rotGroup => solnRot.addSpace(rotGroup[i]));
|
allRots.forEach(rotGroup => solnRot.addSpace(rotGroup[i]));
|
||||||
result.push(solnRot);
|
result.push(solnRot);
|
||||||
}
|
}
|
||||||
@@ -54,9 +53,9 @@ export default class SomaSolution {
|
|||||||
print() {
|
print() {
|
||||||
let accum = "";
|
let accum = "";
|
||||||
console.log("---");
|
console.log("---");
|
||||||
for (let x = 0; x < this.dim; x++) {
|
for (let x = 0; x < this.dimX; x++) {
|
||||||
for (let y = 0; y < this.dim; y++) {
|
for (let y = 0; y < this.dimY; y++) {
|
||||||
for (let z = 0; z < this.dim; z++) {
|
for (let z = 0; z < this.dimZ; z++) {
|
||||||
for (const space of this.solutionSpaces) {
|
for (const space of this.solutionSpaces) {
|
||||||
if (space.at(x, y, z)) {
|
if (space.at(x, y, z)) {
|
||||||
accum += space.getId();
|
accum += space.getId();
|
||||||
@@ -66,7 +65,7 @@ export default class SomaSolution {
|
|||||||
console.log(accum);
|
console.log(accum);
|
||||||
accum = "";
|
accum = "";
|
||||||
}
|
}
|
||||||
if (x !== this.dim - 1) {
|
if (x !== this.dimX - 1) {
|
||||||
console.log("-");
|
console.log("-");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,8 +80,23 @@ export default class SomaSolution {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
clone() {
|
clone() {
|
||||||
const clone = new SomaSolution(this.dim);
|
const clone = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||||
clone.solutionSpaces = this.solutionSpaces.slice();
|
clone.solutionSpaces = this.solutionSpaces.slice();
|
||||||
return clone;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
119
src/solver/js/SomaSolution.ts
Normal file
119
src/solver/js/SomaSolution.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import type VoxelSpaceBoolean from "./VoxelSpaceBoolean";
|
||||||
|
|
||||||
|
export default class SomaSolution {
|
||||||
|
private solutionSpaces: VoxelSpaceBoolean[];
|
||||||
|
private dimX: number;
|
||||||
|
private dimY: number;
|
||||||
|
private dimZ: number;
|
||||||
|
|
||||||
|
constructor(dimX: number, dimY: number, dimZ: number) {
|
||||||
|
this.dimX = dimX;
|
||||||
|
this.dimY = dimY;
|
||||||
|
this.dimZ = dimZ;
|
||||||
|
this.solutionSpaces = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
static filterUnique(solutions: SomaSolution[]): SomaSolution[] {
|
||||||
|
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(): SomaSolution[] {
|
||||||
|
if (this.solutionSpaces.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const result: SomaSolution[] = [];
|
||||||
|
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: SomaSolution) {
|
||||||
|
for (let i = 0; i < this.solutionSpaces.length; i++) {
|
||||||
|
if (!this.solutionSpaces[i].matches(solution.solutionSpaces[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
addSpace(space: VoxelSpaceBoolean) {
|
||||||
|
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: number, y: number, z: number) {
|
||||||
|
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: (val: number, x: number, y: number, z: number) => any) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/solver/js/SomaSolver.js
Normal file
60
src/solver/js/SomaSolver.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import VoxelSpaceBoolean from "./VoxelSpaceBoolean";
|
||||||
|
import SomaSolution from "./SomaSolution";
|
||||||
|
export default 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/solver/js/SomaSolver.ts
Normal file
73
src/solver/js/SomaSolver.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import VoxelSpaceBoolean from "./VoxelSpaceBoolean";
|
||||||
|
import SomaSolution from "./SomaSolution";
|
||||||
|
|
||||||
|
interface DebugVisualiser {
|
||||||
|
showSoln(soln: SomaSolution): Promise<void>;
|
||||||
|
showSpace(cube: VoxelSpaceBoolean): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SomaSolver {
|
||||||
|
private solutionCube: VoxelSpaceBoolean;
|
||||||
|
private dimX: number;
|
||||||
|
private dimY: number;
|
||||||
|
private dimZ: number;
|
||||||
|
private visualiser: DebugVisualiser = {async showSoln(soln: SomaSolution) {}, async showSpace(cube: VoxelSpaceBoolean) {}};
|
||||||
|
private solutions: SomaSolution[] = new Array<SomaSolution>();
|
||||||
|
private iterations: number = 0;
|
||||||
|
constructor(dimX: number, dimY: number, dimZ: number) {
|
||||||
|
this.dimX = dimX;
|
||||||
|
this.dimY = dimY;
|
||||||
|
this.dimZ = dimZ;
|
||||||
|
this.solutionCube = new VoxelSpaceBoolean({id: 0, dims: [dimX, dimY, dimZ], cullEmpty: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
setDebug(visualiser: DebugVisualiser) {
|
||||||
|
this.visualiser = visualiser;
|
||||||
|
}
|
||||||
|
|
||||||
|
async solve(polycubes: VoxelSpaceBoolean[]) {
|
||||||
|
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: VoxelSpaceBoolean[][] = new Array<Array<VoxelSpaceBoolean>>();
|
||||||
|
for (let i = 1; i < polycubes.length; i++) {
|
||||||
|
const rots = polycubes[i].getAllPermutationsInPrism(this.dimX, this.dimY, this.dimZ);
|
||||||
|
combosWithRots.push(rots);
|
||||||
|
}
|
||||||
|
let combos: VoxelSpaceBoolean[][] = new Array<Array<VoxelSpaceBoolean>>();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async backtrackSolve(workingSolution: VoxelSpaceBoolean, polycubes: VoxelSpaceBoolean[][], currentSoln: SomaSolution, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,42 +1,43 @@
|
|||||||
export default class VoxelSpace {
|
export default class VoxelSpaceBoolean {
|
||||||
constructor(id, dims, space, cullEmpty) {
|
constructor(options) {
|
||||||
if (!space) {
|
this.length = options.dims[0] * options.dims[1] * options.dims[2];
|
||||||
space = 0n;
|
if (!options.space) {
|
||||||
|
options.space = new Array(options.dims[0] * options.dims[1] * options.dims[2]);
|
||||||
|
options.space.fill(false);
|
||||||
}
|
}
|
||||||
else if (Array.isArray(space)) {
|
else if (!Array.isArray(options.space)) {
|
||||||
if (space.length !== dims[0] * dims[1] * dims[2]) {
|
const newSpace = [];
|
||||||
throw new Error("Vals don't fit in given dimensions.");
|
for (let i = 0; i < this.length; i++) {
|
||||||
|
const mask = 1n << BigInt(i);
|
||||||
|
newSpace.push((options.space & mask) !== 0n);
|
||||||
}
|
}
|
||||||
space = VoxelSpace.boolArrayToBigInt(space);
|
options.space = newSpace;
|
||||||
}
|
}
|
||||||
this.id = id;
|
this.id = options.id;
|
||||||
this.length = dims[0] * dims[1] * dims[2];
|
this.dims = options.dims;
|
||||||
this.dims = dims;
|
this.space = options.space;
|
||||||
this.space = space;
|
this.color = options.color ?? "red";
|
||||||
if (cullEmpty) {
|
if (options.cullEmpty !== false) {
|
||||||
this.cullEmptySpace();
|
this.cullEmptySpace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static boolArrayToBigInt(boolArray) {
|
setColor(color) {
|
||||||
let result = 0n;
|
this.color = color;
|
||||||
for (let i = 0; i < boolArray.length; i++) {
|
}
|
||||||
if (boolArray[i]) {
|
getColor() {
|
||||||
result |= BigInt(1 << i);
|
return this.color;
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
binaryRep() {
|
binaryRep() {
|
||||||
return this.space.toString(2);
|
return this.space.reduce((prev, curr) => prev + (curr ? "1" : "0"), "");
|
||||||
}
|
}
|
||||||
getExtrema() {
|
getExtrema() {
|
||||||
const extrema = {
|
const extrema = {
|
||||||
xMax: -Infinity,
|
xMax: 0,
|
||||||
xMin: Infinity,
|
xMin: this.dims[0],
|
||||||
yMax: -Infinity,
|
yMax: 0,
|
||||||
yMin: Infinity,
|
yMin: this.dims[1],
|
||||||
zMax: -Infinity,
|
zMax: 0,
|
||||||
zMin: Infinity,
|
zMin: this.dims[2],
|
||||||
};
|
};
|
||||||
this.forEachCell((val, x, y, z) => {
|
this.forEachCell((val, x, y, z) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
@@ -52,21 +53,25 @@ export default class VoxelSpace {
|
|||||||
}
|
}
|
||||||
cullEmptySpace() {
|
cullEmptySpace() {
|
||||||
const extrema = this.getExtrema();
|
const extrema = this.getExtrema();
|
||||||
let index = 0n;
|
const newX = extrema.xMax - extrema.xMin + 1;
|
||||||
let newSpace = 0n;
|
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 x = extrema.xMin; x <= extrema.xMax; x++) {
|
||||||
for (let y = extrema.yMin; y <= extrema.yMax; y++) {
|
for (let y = extrema.yMin; y <= extrema.yMax; y++) {
|
||||||
for (let z = extrema.zMin; z <= extrema.zMax; z++) {
|
for (let z = extrema.zMin; z <= extrema.zMax; z++) {
|
||||||
if (this.at(x, y, z)) {
|
if (this.at(x, y, z)) {
|
||||||
newSpace |= 1n << index;
|
newSpace[index] = true;
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.dims[0] = extrema.xMax - extrema.xMin + 1;
|
this.dims[0] = newX;
|
||||||
this.dims[1] = extrema.yMax - extrema.yMin + 1;
|
this.dims[1] = newY;
|
||||||
this.dims[2] = extrema.zMax - extrema.zMin + 1;
|
this.dims[2] = newZ;
|
||||||
this.space = newSpace;
|
this.space = newSpace;
|
||||||
}
|
}
|
||||||
forEachCell(cb) {
|
forEachCell(cb) {
|
||||||
@@ -103,18 +108,18 @@ export default class VoxelSpace {
|
|||||||
getUniqueRotations() {
|
getUniqueRotations() {
|
||||||
const rotations = [];
|
const rotations = [];
|
||||||
const refSpace = this.clone();
|
const refSpace = this.clone();
|
||||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||||
refSpace.rot90('y');
|
refSpace.rot90('y');
|
||||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||||
refSpace.rot90('y');
|
refSpace.rot90('y');
|
||||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||||
refSpace.rot90('y');
|
refSpace.rot90('y');
|
||||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||||
refSpace.rot90('z');
|
refSpace.rot90('z');
|
||||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||||
refSpace.rot90('z');
|
refSpace.rot90('z');
|
||||||
refSpace.rot90('z');
|
refSpace.rot90('z');
|
||||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||||
return rotations;
|
return rotations;
|
||||||
}
|
}
|
||||||
getAllRotations() {
|
getAllRotations() {
|
||||||
@@ -148,25 +153,23 @@ export default class VoxelSpace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getAllPositionsInCube(cubeDim) {
|
getAllPositionsInPrism(cubeDimX, cubeDimY, cubeDimZ) {
|
||||||
if ((cubeDim > 0) && (cubeDim % 1 === 0)) {
|
const cubePositions = [];
|
||||||
const cubePositions = [];
|
if (this.dims[0] > cubeDimX || this.dims[1] > cubeDimY || this.dims[2] > cubeDimZ) {
|
||||||
for (let x = 0; x < cubeDim - this.dims[0] + 1; x++) {
|
|
||||||
for (let y = 0; y < cubeDim - this.dims[1] + 1; y++) {
|
|
||||||
for (let z = 0; z < cubeDim - this.dims[2] + 1; z++) {
|
|
||||||
const cubePos = new VoxelSpace(this.id, [cubeDim, cubeDim, cubeDim]);
|
|
||||||
this.forEachCell((val, rotX, rotY, rotZ) => {
|
|
||||||
cubePos.set(x + rotX, y + rotY, z + rotZ, val);
|
|
||||||
});
|
|
||||||
cubePositions.push(cubePos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cubePositions;
|
return cubePositions;
|
||||||
}
|
}
|
||||||
else {
|
for (let xOffset = 0; xOffset < (cubeDimX - this.dims[0] + 1); xOffset++) {
|
||||||
throw new Error("cubeDim must be a positive integer.");
|
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) {
|
matches(space) {
|
||||||
const otherDims = space.getDims();
|
const otherDims = space.getDims();
|
||||||
@@ -175,10 +178,14 @@ export default class VoxelSpace {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.space === space.getRaw();
|
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() {
|
clone() {
|
||||||
return new VoxelSpace(this.id, this.getDims(), this.getRaw());
|
return new VoxelSpaceBoolean({ id: this.id, dims: this.getDims(), space: this.getRaw(), color: this.getColor(), cullEmpty: false });
|
||||||
}
|
}
|
||||||
getAxisSpins(axis) {
|
getAxisSpins(axis) {
|
||||||
const rotations = [this.clone()];
|
const rotations = [this.clone()];
|
||||||
@@ -191,7 +198,7 @@ export default class VoxelSpace {
|
|||||||
return this.dims.slice();
|
return this.dims.slice();
|
||||||
}
|
}
|
||||||
getRaw() {
|
getRaw() {
|
||||||
return this.space;
|
return this.space.slice();
|
||||||
}
|
}
|
||||||
// [1, 0, 0] [x] [ x]
|
// [1, 0, 0] [x] [ x]
|
||||||
// [0, 0, -1] * [y] = [-z]
|
// [0, 0, -1] * [y] = [-z]
|
||||||
@@ -212,24 +219,21 @@ export default class VoxelSpace {
|
|||||||
return this.dims[0] * this.dims[2] * (this.dims[1] - 1 - y) + this.dims[2] * x + z;
|
return this.dims[0] * this.dims[2] * (this.dims[1] - 1 - y) + this.dims[2] * x + z;
|
||||||
}
|
}
|
||||||
at(x, y, z) {
|
at(x, y, z) {
|
||||||
const mask = 1n << BigInt(this.dims[1] * this.dims[2] * x + this.dims[2] * y + z);
|
return this.space[this.index(x, y, z)];
|
||||||
return (this.space & mask) !== 0n;
|
}
|
||||||
|
index(x, y, z) {
|
||||||
|
return this.dims[1] * this.dims[2] * x + this.dims[2] * y + z;
|
||||||
}
|
}
|
||||||
toggle(x, y, z) {
|
toggle(x, y, z) {
|
||||||
const mask = BigInt(1 << this.dims[1] * this.dims[2] * x + this.dims[2] * y + z);
|
const index = this.index(x, y, z);
|
||||||
this.space ^= mask;
|
this.space[index] = !this.space[index];
|
||||||
}
|
}
|
||||||
set(x, y, z, val) {
|
set(x, y, z, val) {
|
||||||
const mask = BigInt(1 << this.dims[1] * this.dims[2] * x + this.dims[2] * y + z);
|
this.space[this.index(x, y, z)] = val;
|
||||||
if (val) {
|
|
||||||
this.space |= mask;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.space &= ~mask;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
rotated90(dim) {
|
rotated90(dim) {
|
||||||
let newSpace = 0n;
|
const newSpace = new Array(this.dims[0] * this.dims[1] * this.dims[2]);
|
||||||
|
newSpace.fill(false);
|
||||||
let newDims;
|
let newDims;
|
||||||
let rotIndex;
|
let rotIndex;
|
||||||
if (dim === 'x') {
|
if (dim === 'x') {
|
||||||
@@ -246,10 +250,10 @@ export default class VoxelSpace {
|
|||||||
}
|
}
|
||||||
this.forEachCell((val, i, j, k) => {
|
this.forEachCell((val, i, j, k) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
newSpace |= BigInt(1 << rotIndex(i, j, k));
|
newSpace[rotIndex(i, j, k)] = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return new VoxelSpace(this.id, newDims, newSpace);
|
return new VoxelSpaceBoolean({ id: this.id, dims: newDims, space: newSpace, color: this.color, cullEmpty: false });
|
||||||
}
|
}
|
||||||
rot90(dim) {
|
rot90(dim) {
|
||||||
const rot = this.rotated90(dim);
|
const rot = this.rotated90(dim);
|
||||||
@@ -257,9 +261,21 @@ export default class VoxelSpace {
|
|||||||
this.dims = rot.getDims();
|
this.dims = rot.getDims();
|
||||||
}
|
}
|
||||||
plus(space) {
|
plus(space) {
|
||||||
const otherSpace = space.getRaw();
|
const newSpace = new Array(this.dims[0] * this.dims[1] * this.dims[2]);
|
||||||
if ((this.space | otherSpace) === (this.space ^ otherSpace)) {
|
newSpace.fill(false);
|
||||||
return new VoxelSpace(this.id, this.dims, otherSpace | this.space);
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -272,4 +288,34 @@ export default class VoxelSpace {
|
|||||||
});
|
});
|
||||||
return 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
364
src/solver/js/VoxelSpaceBoolean.ts
Normal file
364
src/solver/js/VoxelSpaceBoolean.ts
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
export type DimensionDef = [number, number, number];
|
||||||
|
|
||||||
|
const enum NeighbourDirection {
|
||||||
|
POSX,
|
||||||
|
POSY,
|
||||||
|
POSZ,
|
||||||
|
NEGX,
|
||||||
|
NEGY,
|
||||||
|
NEGZ,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class VoxelSpaceBoolean {
|
||||||
|
private dims: DimensionDef;
|
||||||
|
private length: number;
|
||||||
|
private space: boolean[];
|
||||||
|
private id: number;
|
||||||
|
private color: string;
|
||||||
|
|
||||||
|
constructor(options: {id: number, dims: DimensionDef, space?: boolean[] | bigint, cullEmpty: boolean, color?: string}) {
|
||||||
|
this.length = options.dims[0] * options.dims[1] * options.dims[2];
|
||||||
|
if (!options.space) {
|
||||||
|
options.space = new Array<boolean>(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: string) {
|
||||||
|
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<boolean>(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: (val: boolean, x: number, y: number, z: number) => any) {
|
||||||
|
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: VoxelSpaceBoolean[] = [];
|
||||||
|
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: VoxelSpaceBoolean[] = [];
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static pushNewUniqueSpaces(existingSpaces: VoxelSpaceBoolean[], newSpaces: VoxelSpaceBoolean[]) {
|
||||||
|
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: number, cubeDimY: number, cubeDimZ: number): VoxelSpaceBoolean[] {
|
||||||
|
const cubePositions: VoxelSpaceBoolean[] = [];
|
||||||
|
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: VoxelSpaceBoolean) {
|
||||||
|
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});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAxisSpins(axis: 'x' | 'y' | 'z'): VoxelSpaceBoolean[] {
|
||||||
|
const rotations = [this.clone()];
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
rotations.push(rotations[i].rotated90(axis));
|
||||||
|
}
|
||||||
|
return rotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDims(): DimensionDef {
|
||||||
|
return this.dims.slice() as DimensionDef;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRaw() {
|
||||||
|
return this.space.slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
// [1, 0, 0] [x] [ x]
|
||||||
|
// [0, 0, -1] * [y] = [-z]
|
||||||
|
// [0, 1, 0] [z] [ y]
|
||||||
|
private newIndexRotX(x: number, y: number, z: number) {
|
||||||
|
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]
|
||||||
|
private newIndexRotY(x: number, y: number, z: number) {
|
||||||
|
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]
|
||||||
|
private newIndexRotZ(x: number, y: number, z: number) {
|
||||||
|
return this.dims[0] * this.dims[2] * (this.dims[1] - 1 - y) + this.dims[2] * x + z;
|
||||||
|
}
|
||||||
|
|
||||||
|
at(x: number, y: number, z: number) {
|
||||||
|
return this.space[this.index(x, y, z)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private index(x: number, y: number, z: number) {
|
||||||
|
return this.dims[1] * this.dims[2] * x + this.dims[2] * y + z;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(x: number, y: number, z: number) {
|
||||||
|
const index = this.index(x, y, z);
|
||||||
|
this.space[index] = !this.space[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
set(x: number, y: number, z: number, val: boolean) {
|
||||||
|
this.space[this.index(x, y, z)] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
rotated90(dim: 'x' | 'y' | 'z') {
|
||||||
|
const newSpace = new Array<boolean>(this.dims[0] * this.dims[1] * this.dims[2]);
|
||||||
|
newSpace.fill(false);
|
||||||
|
let newDims: DimensionDef;
|
||||||
|
let rotIndex: (i: number, j: number, k: number) => number;
|
||||||
|
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: 'x' | 'y' | 'z') {
|
||||||
|
const rot = this.rotated90(dim);
|
||||||
|
this.space = rot.getRaw();
|
||||||
|
this.dims = rot.getDims();
|
||||||
|
}
|
||||||
|
|
||||||
|
plus(space: VoxelSpaceBoolean): VoxelSpaceBoolean | null {
|
||||||
|
const newSpace = new Array<boolean>(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: number, y: number, z: number): number {
|
||||||
|
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: number, prismDimY: number, prismDimZ: number): VoxelSpaceBoolean[] {
|
||||||
|
const rotations = this.getUniqueRotations();
|
||||||
|
let result = new Array<VoxelSpaceBoolean>();
|
||||||
|
for (let i = 0; i < rotations.length; i++) {
|
||||||
|
result = result.concat(rotations[i].getAllPositionsInPrism(prismDimX, prismDimY, prismDimZ));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/solver/js/main.js
Normal file
16
src/solver/js/main.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import SomaSolver from "./SomaSolver";
|
||||||
|
import VoxelSpaceBoolean from "./VoxelSpaceBoolean";
|
||||||
|
export function solve(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
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
solver.solve(voxelSpaces);
|
||||||
|
return solver.getSolutions();
|
||||||
|
}
|
||||||
18
src/solver/js/main.ts
Normal file
18
src/solver/js/main.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import SomaSolver from "./SomaSolver";
|
||||||
|
import VoxelSpaceBoolean from "./VoxelSpaceBoolean";
|
||||||
|
import type SomaSolution from "./SomaSolution";
|
||||||
|
|
||||||
|
export function solve(polycubes: bigint[], dimX: number, dimY: number, dimZ: number): SomaSolution[] {
|
||||||
|
const solver = new SomaSolver(dimX, dimY, dimZ);
|
||||||
|
const voxelSpaces = new Array<VoxelSpaceBoolean>();
|
||||||
|
for (let i = 0; i < polycubes.length; i++) {
|
||||||
|
voxelSpaces.push(new VoxelSpaceBoolean({
|
||||||
|
id: i,
|
||||||
|
dims: [dimX, dimY, dimZ],
|
||||||
|
space: polycubes[i],
|
||||||
|
cullEmpty: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
solver.solve(voxelSpaces);
|
||||||
|
return solver.getSolutions();
|
||||||
|
}
|
||||||
11
src/solver/js/package.json
Normal file
11
src/solver/js/package.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "soma-solve-js",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "npx tsc main.ts --target ES2020 --moduleResolution node"
|
||||||
|
},
|
||||||
|
"author": "Daniel Ledda",
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
||||||
@@ -7,8 +7,8 @@
|
|||||||
"debug": true
|
"debug": true
|
||||||
},
|
},
|
||||||
"release": {
|
"release": {
|
||||||
"binaryFile": "../../public/solver/main.wasm",
|
"binaryFile": "../../../public/solver/main.wasm",
|
||||||
"textFile": "../../public/solver/main.wat",
|
"textFile": "../../../public/solver/main.wat",
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"optimizeLevel": 3,
|
"optimizeLevel": 3,
|
||||||
"shrinkLevel": 1,
|
"shrinkLevel": 1,
|
||||||
@@ -2,12 +2,14 @@ import VoxelSpace from "./VoxelSpace";
|
|||||||
|
|
||||||
export default class SomaSolution {
|
export default class SomaSolution {
|
||||||
private solutionSpaces: VoxelSpace[];
|
private solutionSpaces: VoxelSpace[];
|
||||||
private dim: i32;
|
private dimX: i32;
|
||||||
constructor(dim: i32) {
|
private dimY: i32;
|
||||||
if (dim < 0 || dim % 1 !== 0) {
|
private dimZ: i32;
|
||||||
throw new Error("Dimension must be a whole positive integer!");
|
|
||||||
}
|
constructor(dimX: i32, dimY: i32, dimZ: i32) {
|
||||||
this.dim = dim;
|
this.dimX = dimX;
|
||||||
|
this.dimY = dimY;
|
||||||
|
this.dimZ = dimZ;
|
||||||
this.solutionSpaces = [];
|
this.solutionSpaces = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +44,7 @@ export default class SomaSolution {
|
|||||||
}
|
}
|
||||||
const allRots: VoxelSpace[][] = this.solutionSpaces.map<VoxelSpace[]>(space => space.getAllRotations());
|
const allRots: VoxelSpace[][] = this.solutionSpaces.map<VoxelSpace[]>(space => space.getAllRotations());
|
||||||
for (let i = 0; i < allRots[0].length; i++) {
|
for (let i = 0; i < allRots[0].length; i++) {
|
||||||
const solnRot = new SomaSolution(this.dim);
|
const solnRot = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||||
for (let j = 0; j < allRots.length; j++) {
|
for (let j = 0; j < allRots.length; j++) {
|
||||||
solnRot.addSpace(allRots[j][i]);
|
solnRot.addSpace(allRots[j][i]);
|
||||||
}
|
}
|
||||||
@@ -74,15 +76,11 @@ export default class SomaSolution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clone(): SomaSolution {
|
clone(): SomaSolution {
|
||||||
const clone = new SomaSolution(this.dim);
|
const clone = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||||
clone.solutionSpaces = this.solutionSpaces.slice(0, this.solutionSpaces.length);
|
clone.solutionSpaces = this.solutionSpaces.slice(0, this.solutionSpaces.length);
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDims(): i32[] {
|
|
||||||
return [this.dim, this.dim, this.dim];
|
|
||||||
}
|
|
||||||
|
|
||||||
getPieces(): VoxelSpace[] {
|
getPieces(): VoxelSpace[] {
|
||||||
return this.solutionSpaces;
|
return this.solutionSpaces;
|
||||||
}
|
}
|
||||||
@@ -3,34 +3,30 @@ import SomaSolution from "./SomaSolution";
|
|||||||
|
|
||||||
export default class SomaSolver {
|
export default class SomaSolver {
|
||||||
private solutionCube: VoxelSpace;
|
private solutionCube: VoxelSpace;
|
||||||
private dim: i32;
|
private dimX: i32;
|
||||||
|
private dimY: i32;
|
||||||
|
private dimZ: i32;
|
||||||
private solutions: SomaSolution[] = new Array<SomaSolution>();
|
private solutions: SomaSolution[] = new Array<SomaSolution>();
|
||||||
private iterations: i32 = 0;
|
constructor(dimX: i32, dimY: i32, dimZ: i32) {
|
||||||
constructor(dimension: i32) {
|
this.dimX = dimX;
|
||||||
if (dimension % 1 !== 0 || dimension < 0) {
|
this.dimY = dimY;
|
||||||
throw new Error("The argument 'dimension' must be a positive whole number");
|
this.dimZ = dimZ;
|
||||||
}
|
this.solutionCube = new VoxelSpace(0, dimX, dimY, dimZ, 0);
|
||||||
this.dim = dimension;
|
|
||||||
this.solutionCube = new VoxelSpace(0, dimension, dimension, dimension, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
solve(polycubes: VoxelSpace[]): void {
|
solve(polycubes: VoxelSpace[]): void {
|
||||||
if (polycubes.length === 0) {
|
if (polycubes.length === 0) {
|
||||||
throw new Error("You must pass at least one polycube to solve the puzzle.");
|
throw new Error("You must pass at least one polycube to solve the puzzle.");
|
||||||
}
|
}
|
||||||
let cumulativeSize = polycubes.reduce((prev, curr) => prev + curr.size(), 0);
|
|
||||||
if (cumulativeSize !== this.dim**3) {
|
|
||||||
throw new Error(`The polycubes passed do not add up to exactly enough units to form a cube of dimension ${this.dim}! Got: ${cumulativeSize}, need: ${this.dim**3}`);
|
|
||||||
}
|
|
||||||
this.solutions.splice(0, this.solutions.length);
|
this.solutions.splice(0, this.solutions.length);
|
||||||
const combosWithRots: VoxelSpace[][] = new Array<Array<VoxelSpace>>();
|
const combosWithRots: VoxelSpace[][] = new Array<Array<VoxelSpace>>();
|
||||||
for (let i = 1; i < polycubes.length; i++) {
|
for (let i = 1; i < polycubes.length; i++) {
|
||||||
combosWithRots.push(polycubes[i].getAllPermutationsInCubeOfSize(this.dim));
|
combosWithRots.push(polycubes[i].getAllPermutationsInPrism(this.dimX, this.dimY, this.dimZ));
|
||||||
}
|
}
|
||||||
let combos: VoxelSpace[][] = new Array<Array<VoxelSpace>>();
|
let combos: VoxelSpace[][] = new Array<Array<VoxelSpace>>();
|
||||||
combos.push(polycubes[0].getAllPositionsInCube(this.dim));
|
combos.push(polycubes[0].getAllPositionsInPrism(this.dimX, this.dimY, this.dimZ));
|
||||||
combos = combos.concat(combosWithRots);
|
combos = combos.concat(combosWithRots);
|
||||||
this.backtrackSolve(this.solutionCube, combos, new SomaSolution(this.dim));
|
this.backtrackSolve(this.solutionCube, combos, new SomaSolution(this.dimX, this.dimY, this.dimZ));
|
||||||
this.solutions = SomaSolution.filterUnique(this.solutions);
|
this.solutions = SomaSolution.filterUnique(this.solutions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +43,7 @@ export default class SomaSolver {
|
|||||||
nextSoln.addSpace(nextCubeGroup[i]);
|
nextSoln.addSpace(nextCubeGroup[i]);
|
||||||
if (polycubes.length == 1) {
|
if (polycubes.length == 1) {
|
||||||
this.solutions.push(nextSoln);
|
this.solutions.push(nextSoln);
|
||||||
currentSoln = new SomaSolution(this.dim);
|
currentSoln = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
this.backtrackSolve(fusionAttempt, polycubes.slice(1), nextSoln, depth + 1);
|
this.backtrackSolve(fusionAttempt, polycubes.slice(1), nextSoln, depth + 1);
|
||||||
@@ -13,19 +13,16 @@ export default class VoxelSpace {
|
|||||||
private length: i32;
|
private length: i32;
|
||||||
private space: i64;
|
private space: i64;
|
||||||
private id: i32;
|
private id: i32;
|
||||||
private dimx: i32;
|
private dimX: i32;
|
||||||
private dimy: i32;
|
private dimY: i32;
|
||||||
private dimz: i32;
|
private dimZ: i32;
|
||||||
|
|
||||||
constructor(id: i32, dimx: i32, dimy: i32, dimz: i32, space: i64 = 0, cullEmpty: boolean = false) {
|
constructor(id: i32, dimx: i32, dimy: i32, dimz: i32, space: i64 = 0, cullEmpty: boolean = false) {
|
||||||
if (!space) {
|
|
||||||
space = 0;
|
|
||||||
}
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.length = dimx * dimy * dimz;
|
this.length = dimx * dimy * dimz;
|
||||||
this.dimx = dimx;
|
this.dimX = dimx;
|
||||||
this.dimy = dimy;
|
this.dimY = dimy;
|
||||||
this.dimz = dimz;
|
this.dimZ = dimz;
|
||||||
this.space = space;
|
this.space = space;
|
||||||
if (cullEmpty) {
|
if (cullEmpty) {
|
||||||
this.cullEmptySpace();
|
this.cullEmptySpace();
|
||||||
@@ -35,15 +32,15 @@ export default class VoxelSpace {
|
|||||||
getExtrema(): Extrema {
|
getExtrema(): Extrema {
|
||||||
const extrema = new Extrema(
|
const extrema = new Extrema(
|
||||||
0,
|
0,
|
||||||
i32.MAX_VALUE,
|
this.dimX,
|
||||||
0,
|
0,
|
||||||
i32.MAX_VALUE,
|
this.dimY,
|
||||||
0,
|
0,
|
||||||
i32.MAX_VALUE,
|
this.dimZ,
|
||||||
);
|
);
|
||||||
for (let x = 0; x < this.dimx; x++) {
|
for (let x = 0; x < this.dimX; x++) {
|
||||||
for (let y = 0; y < this.dimy; y++) {
|
for (let y = 0; y < this.dimY; y++) {
|
||||||
for (let z = 0; z < this.dimz; z++) {
|
for (let z = 0; z < this.dimZ; z++) {
|
||||||
const val = this.at(x, y, z);
|
const val = this.at(x, y, z);
|
||||||
if (val) {
|
if (val) {
|
||||||
extrema.xMax = Math.max(extrema.xMax, x) as i32;
|
extrema.xMax = Math.max(extrema.xMax, x) as i32;
|
||||||
@@ -61,8 +58,8 @@ export default class VoxelSpace {
|
|||||||
|
|
||||||
private cullEmptySpace(): void {
|
private cullEmptySpace(): void {
|
||||||
const extrema = this.getExtrema();
|
const extrema = this.getExtrema();
|
||||||
let index = 0;
|
let index: i32 = 0;
|
||||||
let newSpace = 0;
|
let newSpace: i64 = 0;
|
||||||
for (let x = extrema.xMin; x <= extrema.xMax; x++) {
|
for (let x = extrema.xMin; x <= extrema.xMax; x++) {
|
||||||
for (let y = extrema.yMin; y <= extrema.yMax; y++) {
|
for (let y = extrema.yMin; y <= extrema.yMax; y++) {
|
||||||
for (let z = extrema.zMin; z <= extrema.zMax; z++) {
|
for (let z = extrema.zMin; z <= extrema.zMax; z++) {
|
||||||
@@ -73,9 +70,9 @@ export default class VoxelSpace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.dimx = extrema.xMax - extrema.xMin + 1;
|
this.dimX = extrema.xMax - extrema.xMin + 1;
|
||||||
this.dimy = extrema.yMax - extrema.yMin + 1;
|
this.dimY = extrema.yMax - extrema.yMin + 1;
|
||||||
this.dimz = extrema.zMax - extrema.zMin + 1;
|
this.dimZ = extrema.zMax - extrema.zMin + 1;
|
||||||
this.space = newSpace;
|
this.space = newSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,45 +131,44 @@ export default class VoxelSpace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllPositionsInCube(cubeDim: i32): VoxelSpace[] {
|
getAllPositionsInPrism(cubeDimX: i32, cubeDimY: i32, cubeDimZ: i32): VoxelSpace[] {
|
||||||
if ((cubeDim > 0) && (cubeDim % 1 == 0)) {
|
const cubePositions: VoxelSpace[] = [];
|
||||||
const cubePositions: VoxelSpace[] = [];
|
if (this.dimX > cubeDimX || this.dimY > cubeDimY || this.dimZ > cubeDimZ) {
|
||||||
for (let x = 0; x < cubeDim - this.dimx + 1; x++) {
|
return cubePositions;
|
||||||
for (let y = 0; y < cubeDim - this.dimy + 1; y++) {
|
}
|
||||||
for (let z = 0; z < cubeDim - this.dimz + 1; z++) {
|
for (let x = 0; x < (cubeDimX - this.dimX + 1); x++) {
|
||||||
const cubePos = new VoxelSpace(this.id, cubeDim, cubeDim, cubeDim);
|
for (let y = 0; y < (cubeDimY - this.dimY + 1); y++) {
|
||||||
for (let rotX = 0; rotX < this.dimx; rotX++) {
|
for (let z = 0; z < (cubeDimZ - this.dimZ + 1); z++) {
|
||||||
for (let rotY = 0; rotY < this.dimy; rotY++) {
|
const cubePos = new VoxelSpace(this.id, cubeDimX, cubeDimY, cubeDimZ);
|
||||||
for (let rotZ = 0; rotZ < this.dimz; rotZ++) {
|
for (let posX = 0; posX < this.dimX; posX++) {
|
||||||
cubePos.set(x + rotX, y + rotY, z + rotZ, this.at(rotX, rotY, rotZ));
|
for (let posY = 0; posY < this.dimY; posY++) {
|
||||||
}
|
for (let posZ = 0; posZ < this.dimZ; posZ++) {
|
||||||
|
cubePos.set(x + posX, y + posY, z + posZ, this.at(posX, posY, posZ));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cubePositions.push(cubePos);
|
|
||||||
}
|
}
|
||||||
|
cubePositions.push(cubePos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cubePositions;
|
|
||||||
} else {
|
|
||||||
throw new Error("cubeDim must be a positive integer.");
|
|
||||||
}
|
}
|
||||||
|
return cubePositions;
|
||||||
}
|
}
|
||||||
|
|
||||||
matches(space: VoxelSpace): boolean {
|
matches(space: VoxelSpace): boolean {
|
||||||
if (space.dimx !== this.dimx) {
|
if (space.dimX !== this.dimX) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (space.dimy !== this.dimy) {
|
if (space.dimY !== this.dimY) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (space.dimz !== this.dimz) {
|
if (space.dimZ !== this.dimZ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.space == space.getRaw();
|
return this.space == space.getRaw();
|
||||||
}
|
}
|
||||||
|
|
||||||
clone(): VoxelSpace {
|
clone(): VoxelSpace {
|
||||||
return new VoxelSpace(this.id, this.dimx, this.dimy, this.dimz, this.getRaw());
|
return new VoxelSpace(this.id, this.dimX, this.dimY, this.dimZ, this.getRaw());
|
||||||
}
|
}
|
||||||
|
|
||||||
private getXAxisSpins(): VoxelSpace[] {
|
private getXAxisSpins(): VoxelSpace[] {
|
||||||
@@ -192,35 +188,35 @@ export default class VoxelSpace {
|
|||||||
// [0, 0, -1] * [y] = [-z]
|
// [0, 0, -1] * [y] = [-z]
|
||||||
// [0, 1, 0] [z] [ y]
|
// [0, 1, 0] [z] [ y]
|
||||||
private newIndexRotX(x: i32, y: i32, z: i32): i32 {
|
private newIndexRotX(x: i32, y: i32, z: i32): i32 {
|
||||||
return this.dimz * this.dimy * x + this.dimy * (this.dimz - 1 - z) + y;
|
return this.dimZ * this.dimY * x + this.dimY * (this.dimZ - 1 - z) + y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// [ 0, 0, 1] [x] [ z]
|
// [ 0, 0, 1] [x] [ z]
|
||||||
// [ 0, 1, 0] * [y] = [ y]
|
// [ 0, 1, 0] * [y] = [ y]
|
||||||
// [-1, 0, 0] [z] [-x]
|
// [-1, 0, 0] [z] [-x]
|
||||||
private newIndexRotY(x: i32, y: i32, z: i32): i32 {
|
private newIndexRotY(x: i32, y: i32, z: i32): i32 {
|
||||||
return this.dimy * this.dimx * z + this.dimx * y + (this.dimx - 1 - x);
|
return this.dimY * this.dimX * z + this.dimX * y + (this.dimX - 1 - x);
|
||||||
}
|
}
|
||||||
|
|
||||||
// [0, -1, 0] [x] [-y]
|
// [0, -1, 0] [x] [-y]
|
||||||
// [1, 0, 0] * [y] = [ x]
|
// [1, 0, 0] * [y] = [ x]
|
||||||
// [0, 0, 1] [z] [ z]
|
// [0, 0, 1] [z] [ z]
|
||||||
private newIndexRotZ(x: i32, y: i32, z: i32): i32 {
|
private newIndexRotZ(x: i32, y: i32, z: i32): i32 {
|
||||||
return this.dimx * this.dimz * (this.dimy - 1 - y) + this.dimz * x + z;
|
return this.dimX * this.dimZ * (this.dimY - 1 - y) + this.dimZ * x + z;
|
||||||
}
|
}
|
||||||
|
|
||||||
at(x: i32, y: i32, z: i32): boolean {
|
at(x: i32, y: i32, z: i32): boolean {
|
||||||
const mask = 1 << (this.dimy * this.dimz * x + this.dimz * y + z);
|
const mask = 1 << (this.dimY * this.dimZ * x + this.dimZ * y + z);
|
||||||
return (this.space & mask) !== 0;
|
return (this.space & mask) !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle(x: i32, y: i32, z: i32): void {
|
toggle(x: i32, y: i32, z: i32): void {
|
||||||
const mask = 1 << this.dimy * this.dimz * x + this.dimz * y + z;
|
const mask = 1 << this.dimY * this.dimZ * x + this.dimZ * y + z;
|
||||||
this.space ^= mask;
|
this.space ^= mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
set(x: i32, y: i32, z: i32, val: boolean): void {
|
set(x: i32, y: i32, z: i32, val: boolean): void {
|
||||||
const mask = 1 << this.dimy * this.dimz * x + this.dimz * y + z;
|
const mask = 1 << this.dimY * this.dimZ * x + this.dimZ * y + z;
|
||||||
if (val) {
|
if (val) {
|
||||||
this.space |= mask;
|
this.space |= mask;
|
||||||
} else {
|
} else {
|
||||||
@@ -230,83 +226,83 @@ export default class VoxelSpace {
|
|||||||
|
|
||||||
rotated90X(): VoxelSpace {
|
rotated90X(): VoxelSpace {
|
||||||
let newSpace = 0;
|
let newSpace = 0;
|
||||||
for (let x = 0; x < this.dimx; x++) {
|
for (let x = 0; x < this.dimX; x++) {
|
||||||
for (let y = 0; y < this.dimy; y++) {
|
for (let y = 0; y < this.dimY; y++) {
|
||||||
for (let z = 0; z < this.dimz; z++) {
|
for (let z = 0; z < this.dimZ; z++) {
|
||||||
if (this.at(x, y, z)) {
|
if (this.at(x, y, z)) {
|
||||||
newSpace |= 1 << this.newIndexRotX(x, y, z);
|
newSpace |= 1 << this.newIndexRotX(x, y, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new VoxelSpace(this.id, this.dimx, this.dimz, this.dimy, newSpace);
|
return new VoxelSpace(this.id, this.dimX, this.dimZ, this.dimY, newSpace);
|
||||||
}
|
}
|
||||||
|
|
||||||
rotated90Y(): VoxelSpace {
|
rotated90Y(): VoxelSpace {
|
||||||
let newSpace = 0;
|
let newSpace = 0;
|
||||||
for (let x = 0; x < this.dimx; x++) {
|
for (let x = 0; x < this.dimX; x++) {
|
||||||
for (let y = 0; y < this.dimy; y++) {
|
for (let y = 0; y < this.dimY; y++) {
|
||||||
for (let z = 0; z < this.dimz; z++) {
|
for (let z = 0; z < this.dimZ; z++) {
|
||||||
if (this.at(x, y, z)) {
|
if (this.at(x, y, z)) {
|
||||||
newSpace |= 1 << this.newIndexRotY(x, y, z);
|
newSpace |= 1 << this.newIndexRotY(x, y, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new VoxelSpace(this.id, this.dimz, this.dimy, this.dimx, newSpace);
|
return new VoxelSpace(this.id, this.dimZ, this.dimY, this.dimX, newSpace);
|
||||||
}
|
}
|
||||||
|
|
||||||
rotated90Z(): VoxelSpace {
|
rotated90Z(): VoxelSpace {
|
||||||
let newSpace = 0;
|
let newSpace = 0;
|
||||||
for (let x = 0; x < this.dimx; x++) {
|
for (let x = 0; x < this.dimX; x++) {
|
||||||
for (let y = 0; y < this.dimy; y++) {
|
for (let y = 0; y < this.dimY; y++) {
|
||||||
for (let z = 0; z < this.dimz; z++) {
|
for (let z = 0; z < this.dimZ; z++) {
|
||||||
if (this.at(x, y, z)) {
|
if (this.at(x, y, z)) {
|
||||||
newSpace |= 1 << this.newIndexRotZ(x, y, z);
|
newSpace |= 1 << this.newIndexRotZ(x, y, z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new VoxelSpace(this.id, this.dimy, this.dimx, this.dimz, newSpace);
|
return new VoxelSpace(this.id, this.dimY, this.dimX, this.dimZ, newSpace);
|
||||||
}
|
}
|
||||||
|
|
||||||
rot90X(): void {
|
rot90X(): void {
|
||||||
const rot = this.rotated90X();
|
const rot = this.rotated90X();
|
||||||
this.space = rot.getRaw();
|
this.space = rot.getRaw();
|
||||||
this.dimx = rot.dimx;
|
this.dimX = rot.dimX;
|
||||||
this.dimy = rot.dimy;
|
this.dimY = rot.dimY;
|
||||||
this.dimz = rot.dimz;
|
this.dimZ = rot.dimZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
rot90Y(): void {
|
rot90Y(): void {
|
||||||
const rot = this.rotated90Y();
|
const rot = this.rotated90Y();
|
||||||
this.space = rot.getRaw();
|
this.space = rot.getRaw();
|
||||||
this.dimx = rot.dimx;
|
this.dimX = rot.dimX;
|
||||||
this.dimy = rot.dimy;
|
this.dimY = rot.dimY;
|
||||||
this.dimz = rot.dimz;
|
this.dimZ = rot.dimZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
rot90Z(): void {
|
rot90Z(): void {
|
||||||
const rot = this.rotated90Z();
|
const rot = this.rotated90Z();
|
||||||
this.space = rot.getRaw();
|
this.space = rot.getRaw();
|
||||||
this.dimx = rot.dimx;
|
this.dimX = rot.dimX;
|
||||||
this.dimy = rot.dimy;
|
this.dimY = rot.dimY;
|
||||||
this.dimz = rot.dimz;
|
this.dimZ = rot.dimZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
plus(space: VoxelSpace): VoxelSpace | null {
|
plus(space: VoxelSpace): VoxelSpace | null {
|
||||||
const otherSpace = space.getRaw();
|
const otherSpace = space.getRaw();
|
||||||
if ((this.space | otherSpace) == (this.space ^ otherSpace)) {
|
if ((this.space | otherSpace) == (this.space ^ otherSpace)) {
|
||||||
return new VoxelSpace(this.id, this.dimx, this.dimy, this.dimz, otherSpace | this.space);
|
return new VoxelSpace(this.id, this.dimX, this.dimY, this.dimZ, otherSpace | this.space);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
size(): i32 {
|
size(): i32 {
|
||||||
let size = 0;
|
let size = 0;
|
||||||
for (let x = 0; x < this.dimx; x++) {
|
for (let x = 0; x < this.dimX; x++) {
|
||||||
for (let y = 0; y < this.dimy; y++) {
|
for (let y = 0; y < this.dimY; y++) {
|
||||||
for (let z = 0; z < this.dimz; z++) {
|
for (let z = 0; z < this.dimZ; z++) {
|
||||||
if (this.at(x, y, z)) {
|
if (this.at(x, y, z)) {
|
||||||
size++;
|
size++;
|
||||||
}
|
}
|
||||||
@@ -316,11 +312,11 @@ export default class VoxelSpace {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllPermutationsInCubeOfSize(dim: i32): VoxelSpace[] {
|
getAllPermutationsInPrism(prismDimX: i32, prismDimY: i32, prismDimZ: i32): VoxelSpace[] {
|
||||||
const rotations = this.getUniqueRotations();
|
const rotations = this.getUniqueRotations();
|
||||||
let result = new Array<VoxelSpace>();
|
let result = new Array<VoxelSpace>();
|
||||||
for (let i = 0; i < rotations.length; i++) {
|
for (let i = 0; i < rotations.length; i++) {
|
||||||
result = result.concat(rotations[i].getAllPositionsInCube(dim));
|
result = result.concat(rotations[i].getAllPositionsInPrism(prismDimX, prismDimY, prismDimZ));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,11 @@ import SomaSolver from "./SomaSolver";
|
|||||||
import VoxelSpace from "./VoxelSpace";
|
import VoxelSpace from "./VoxelSpace";
|
||||||
|
|
||||||
|
|
||||||
export function solve(polycubes: Array<i64>, dim: i32): Int64Array[] {
|
export function solve(polycubes: Array<i64>, dimX: i32, dimY: i32, dimZ: i32): Int64Array[] {
|
||||||
const solver = new SomaSolver(dim);
|
const solver = new SomaSolver(dimX, dimY, dimZ);
|
||||||
const voxelSpaces = new Array<VoxelSpace>();
|
const voxelSpaces = new Array<VoxelSpace>();
|
||||||
for (let i = 0; i < polycubes.length; i++) {
|
for (let i = 0; i < polycubes.length; i++) {
|
||||||
voxelSpaces.push(new VoxelSpace(i, dim, dim, dim, polycubes[i], true));
|
voxelSpaces.push(new VoxelSpace(i, dimX, dimY, dimZ, polycubes[i], true));
|
||||||
}
|
}
|
||||||
solver.solve(voxelSpaces);
|
solver.solve(voxelSpaces);
|
||||||
const solutions = solver.getSolutions();
|
const solutions = solver.getSolutions();
|
||||||
@@ -6,7 +6,7 @@ const asyncTask = async () => {
|
|||||||
|
|
||||||
// You can now use your wasm / as-bind instance!
|
// You can now use your wasm / as-bind instance!
|
||||||
const response = asBindInstance.exports.solve(
|
const response = asBindInstance.exports.solve(
|
||||||
[16875584n, 16810176n, 65688n, 77952n, 12296n, 2109456n, 4184n], 3
|
[16875584n, 16810176n, 65688n, 77952n, 12296n, 2109456n, 4184n], 3, 3, 3
|
||||||
);
|
);
|
||||||
console.log(response); // AsBind: Hello World!
|
console.log(response); // AsBind: Hello World!
|
||||||
};
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"asbuild:untouched": "asc assembly/index.ts --target debug",
|
"asbuild:untouched": "asc assembly/index.ts --exportRuntime --transform as-bind --target debug",
|
||||||
"asbuild:optimized": "asc assembly/index.ts --target release",
|
"asbuild:optimized": "asc assembly/index.ts --exportRuntime --transform as-bind --target release",
|
||||||
"asbuild": "npm run asbuild:untouched && npm run asbuild:optimized",
|
"asbuild": "npm run asbuild:untouched && npm run asbuild:optimized",
|
||||||
"test": "node tests"
|
"test": "node tests"
|
||||||
},
|
},
|
||||||
316
src/store.ts
316
src/store.ts
@@ -1,111 +1,119 @@
|
|||||||
import { derived, writable } from 'svelte/store';
|
import { derived, writable } from 'svelte/store';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import type SomaSolution from "./SomaSolution";
|
import SomaSolution from "./SomaSolution";
|
||||||
|
import VoxelSpaceBigInt from "./VoxelSpaceBigInt";
|
||||||
|
import type {DimensionDef} from "./VoxelSpaceBoolean";
|
||||||
|
|
||||||
type PolycubeInput = {
|
const MAX_DIMS = 20;
|
||||||
color: string,
|
const MIN_DIMS = 1;
|
||||||
rep: bigint,
|
|
||||||
}
|
|
||||||
|
|
||||||
const MAX_DIMS = 5;
|
|
||||||
const MIN_DIMS = 2;
|
|
||||||
|
|
||||||
const store = {
|
|
||||||
polycubes: writable<PolycubeInput[]>([{rep: BigInt(0), color: colorFromIndex(0)}]),
|
|
||||||
somaDimension: writable(3),
|
|
||||||
};
|
|
||||||
|
|
||||||
|
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 selectedCube = writable(0);
|
||||||
export const isMaxDimension = derived(store.somaDimension, ($somaDimension: number) => $somaDimension >= MAX_DIMS);
|
|
||||||
export const isMinDimension = derived(store.somaDimension, ($somaDimension: number) => $somaDimension <= MIN_DIMS);
|
|
||||||
export const isMaxPolycubes = derived(
|
|
||||||
[store.polycubes, store.somaDimension],
|
|
||||||
([$polycubes, $somaDimension]: [PolycubeInput[], number]) => $polycubes.length >= $somaDimension ** 3);
|
|
||||||
export const isMinPolycubes = derived(store.polycubes, ($polycubes: PolycubeInput[]) => $polycubes.length <= 1);
|
|
||||||
export const solutions = writable([] as SomaSolution[]);
|
export const solutions = writable([] as SomaSolution[]);
|
||||||
export const activeSolution = writable<number | null>(null);
|
export const activeSolution = writable<number | null>(null);
|
||||||
export const showingSolution = writable(false);
|
export const showingSolution = writable(false);
|
||||||
|
export const totalVolume = derived(
|
||||||
|
[somaDimX, somaDimY, somaDimZ],
|
||||||
|
([$dimX, $dimY, $dimZ]: [number, number, number]) => $dimX*$dimY*$dimZ
|
||||||
|
);
|
||||||
|
export const isMaxPolycubes = derived(
|
||||||
|
[polycubes, totalVolume],
|
||||||
|
([$cubes, $vol]: [VoxelSpaceBigInt[], number]) => $cubes.length >= $vol
|
||||||
|
);
|
||||||
|
export const isMinPolycubes = derived(
|
||||||
|
polycubes,
|
||||||
|
($polycubes: VoxelSpaceBigInt[]) => $polycubes.length <= 1
|
||||||
|
);
|
||||||
|
|
||||||
export const somaDimension = {
|
function dimStore(init: number) {
|
||||||
subscribe: store.somaDimension.subscribe,
|
const dimStore = writable(init);
|
||||||
inc() {
|
return {
|
||||||
if (!get(isMaxDimension)) {
|
subscribe: dimStore.subscribe,
|
||||||
store.somaDimension.update((dims: number) => {
|
set(dim: number) {
|
||||||
polycubes.reset(dims + 1);
|
if (dim > MAX_DIMS || dim < MIN_DIMS) {
|
||||||
return dims + 1;
|
return;
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dec() {
|
|
||||||
if (!get(isMinDimension)) {
|
|
||||||
store.somaDimension.update((dims: number) => {
|
|
||||||
polycubes.reset(dims - 1);
|
|
||||||
return dims - 1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set(dims: number) {
|
|
||||||
if (dims <= MAX_DIMS && dims >= MIN_DIMS) {
|
|
||||||
polycubes.reset(dims);
|
|
||||||
store.somaDimension.set(dims);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const polycubes = {
|
|
||||||
subscribe: store.polycubes.subscribe,
|
|
||||||
addCube() {
|
|
||||||
const isMaxPolycubes = get(store.polycubes).length >= get(store.somaDimension) ** 3;
|
|
||||||
if (!isMaxPolycubes) {
|
|
||||||
store.polycubes.update((polycubes: PolycubeInput[]) => polycubes.concat({
|
|
||||||
rep: BigInt(0),
|
|
||||||
color: colorFromIndex(polycubes.length),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeCube() {
|
|
||||||
const isMinPolycubes = get(store.polycubes).length <= 1;
|
|
||||||
if (!isMinPolycubes) {
|
|
||||||
store.polycubes.update((polycubes: PolycubeInput[]) => polycubes.splice(0, polycubes.length - 1));
|
|
||||||
}
|
|
||||||
const newLength = get(store.polycubes).length;
|
|
||||||
if (newLength <= get(selectedCube)) {
|
|
||||||
selectedCube.set(newLength - 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
toggle(cubeIndex: number, x: number, y: number, z: number) {
|
|
||||||
const dims = get(store.somaDimension);
|
|
||||||
const mask = BigInt(1) << BigInt(dims ** 2 * x + dims * y + z);
|
|
||||||
const cubes = get(store.polycubes);
|
|
||||||
cubes[cubeIndex].rep ^= mask;
|
|
||||||
store.polycubes.set(cubes);
|
|
||||||
},
|
|
||||||
set(cubeIndex: number, val: boolean, x: number, y: number, z: number) {
|
|
||||||
const dims = get(store.somaDimension);
|
|
||||||
const mask = BigInt(1) << BigInt(dims ** 2 * x + dims * y + z);
|
|
||||||
const cubes = get(store.polycubes);
|
|
||||||
if (val) {
|
|
||||||
cubes[cubeIndex].rep |= mask
|
|
||||||
} else {
|
|
||||||
cubes[cubeIndex].rep &= ~mask
|
|
||||||
}
|
|
||||||
store.polycubes.set(cubes);
|
|
||||||
},
|
|
||||||
reset(dims: number) {
|
|
||||||
store.polycubes.update((polycubes: PolycubeInput[]) => {
|
|
||||||
const result: PolycubeInput[] = [];
|
|
||||||
for (let i = 0; i < Math.min(polycubes.length, dims**3); i++) {
|
|
||||||
result.push({
|
|
||||||
rep: BigInt(0),
|
|
||||||
color: colorFromIndex(i),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return result;
|
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<VoxelSpaceBigInt[]>([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 colorFromIndex(index: number) {
|
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 colorWheelCycle = Math.floor(index / 6);
|
||||||
const darknessCycle = Math.floor(index / 12);
|
const darknessCycle = Math.floor(index / 12);
|
||||||
const spacing = (360 / 6);
|
const spacing = (360 / 6);
|
||||||
@@ -113,5 +121,121 @@ function colorFromIndex(index: number) {
|
|||||||
let hue = spacing * (index % 6) + offset;
|
let hue = spacing * (index % 6) + offset;
|
||||||
const saturation = 100;
|
const saturation = 100;
|
||||||
const lightness = 1 / (2 + darknessCycle) * 100;
|
const lightness = 1 / (2 + darknessCycle) * 100;
|
||||||
return `hsl(${hue},${saturation}%,${Math.round(lightness)}%)`;
|
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<void>((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);
|
||||||
|
// }
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {somaDimension, polycubes, selectedCube, showingSolution} from "../store";
|
import {somaDimX, somaDimY, somaDimZ, polycubes, selectedCube, showingSolution} from "../store";
|
||||||
|
import VoxelSpaceBoolean from "../VoxelSpaceBoolean";
|
||||||
export let cubeNo: number;
|
export let cubeNo: number;
|
||||||
|
|
||||||
$: dimension = $somaDimension;
|
$: cube = $polycubes[cubeNo] as VoxelSpaceBoolean;
|
||||||
$: cube = $polycubes[cubeNo];
|
$: cubeColor = cube.getColor();
|
||||||
$: cubeColor = cube.color;
|
|
||||||
$: currentlyVisualised = $selectedCube === cubeNo && !$showingSolution;
|
$: currentlyVisualised = $selectedCube === cubeNo && !$showingSolution;
|
||||||
let cellStartDragInitialVal: boolean = false;
|
let cellStartDragInitialVal: boolean = false;
|
||||||
let cellStartDrag: number = 0;
|
let cellStartDrag: number = 0;
|
||||||
let cellDragStartPos: {x: number, y: number} = {x: 0, y: 0};
|
let cellDragStartPos: {x: number, y: number} = {x: 0, y: 0};
|
||||||
let cellEndDrag: number = 0;
|
let cellEndDrag: number = 0;
|
||||||
let cellDragEndPos: {x: number, y: number} = {x: 0, y: 0};
|
let cellDragEndPos: {x: number, y: number} = {x: 0, y: 0};
|
||||||
|
let picker: HTMLInputElement;
|
||||||
|
|
||||||
function cellNo(x: number, y: number, z: number) {
|
function cellNo(x: number, y: number, z: number) {
|
||||||
return dimension ** 2 * x + dimension * y + z;
|
return $somaDimY * $somaDimZ * x + $somaDimZ * y + z;
|
||||||
}
|
}
|
||||||
|
|
||||||
function at(rep: bigint, x: number, y: number, z: number) {
|
function at(cube: VoxelSpaceBoolean, x: number, y: number, z: number) {
|
||||||
const mask = BigInt(1) << BigInt(cellNo(x, y, z));
|
return cube.at(x, y, z);
|
||||||
return (rep & mask) !== BigInt(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseOverCell(event: MouseEvent, x: number, y: number, z: number) {
|
function onMouseOverCell(event: MouseEvent, x: number, y: number, z: number) {
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
function onMouseDownCell(event: MouseEvent, x: number, y: number, z: number) {
|
function onMouseDownCell(event: MouseEvent, x: number, y: number, z: number) {
|
||||||
cellStartDrag = cellNo(x, y, z);
|
cellStartDrag = cellNo(x, y, z);
|
||||||
cellStartDragInitialVal = at(cube.rep, x, y, z);
|
cellStartDragInitialVal = at(cube, x, y, z);
|
||||||
cellDragStartPos.x = event.clientX;
|
cellDragStartPos.x = event.clientX;
|
||||||
cellDragStartPos.y = event.clientY;
|
cellDragStartPos.y = event.clientY;
|
||||||
}
|
}
|
||||||
@@ -56,29 +56,43 @@
|
|||||||
|
|
||||||
function onClickCube() {
|
function onClickCube() {
|
||||||
showingSolution.set(false);
|
showingSolution.set(false);
|
||||||
selectedCube.set(cubeNo)
|
selectedCube.set(cubeNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onColorChange(event: InputEvent) {
|
||||||
|
polycubes.setColor(cubeNo, event.target.value);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="cube"
|
class="cube"
|
||||||
class:active={currentlyVisualised}
|
class:active={currentlyVisualised}
|
||||||
style="--color: {cubeColor}; --dimension: {dimension};"
|
style="--color: {cubeColor};"
|
||||||
on:contextmenu|preventDefault
|
on:contextmenu|preventDefault
|
||||||
on:mousedown={onClickCube}
|
on:mousedown={onClickCube}
|
||||||
>
|
>
|
||||||
<h1>Cube: {cubeNo + 1}</h1>
|
<div class="header">
|
||||||
{#each {length: dimension} as _, x}
|
<h1>Cube: {cubeNo + 1}</h1>
|
||||||
|
<div class="colorPickerBtn" on:click={picker.click()}>
|
||||||
|
<input
|
||||||
|
bind:this={picker}
|
||||||
|
class="colorPicker"
|
||||||
|
type="color"
|
||||||
|
value="{cubeColor}"
|
||||||
|
on:change={(event) => onColorChange(event)}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#each {length: $somaDimX} as _, x}
|
||||||
<div class="layer">
|
<div class="layer">
|
||||||
{#each {length: dimension} as _, y}
|
{#each {length: $somaDimY} as _, y}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{#each {length: dimension} as _, z}
|
{#each {length: $somaDimZ} as _, z}
|
||||||
<div
|
<div
|
||||||
class="cell"
|
class="cell"
|
||||||
class:filled={at(cube.rep, z, dimension-1-x, y)}
|
class:filled={at(cube, x, y, z)}
|
||||||
on:mousemove={(event) => onMouseOverCell(event, z, dimension-1-x, y)}
|
on:mousemove={(event) => onMouseOverCell(event, x, y, z)}
|
||||||
on:mousedown={(event) => onMouseDownCell(event, z, dimension-1-x, y)}
|
on:mousedown={(event) => onMouseDownCell(event, x, y, z)}
|
||||||
on:mouseup={(event) => onMouseUpCell(event, z, dimension-1-x, y)}
|
on:mouseup={(event) => onMouseUpCell(event, x, y, z)}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -91,6 +105,27 @@
|
|||||||
* {
|
* {
|
||||||
--cell-size: 30px;
|
--cell-size: 30px;
|
||||||
}
|
}
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.header > * {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.colorPicker {
|
||||||
|
visibility: hidden;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.colorPickerBtn {
|
||||||
|
align-self: center;
|
||||||
|
background-image: url("../resources/ColorWheel.png");
|
||||||
|
background-size: cover;
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
}
|
||||||
.cube.active {
|
.cube.active {
|
||||||
border: 3px solid #ff3e00;
|
border: 3px solid #ff3e00;
|
||||||
}
|
}
|
||||||
@@ -125,6 +160,8 @@
|
|||||||
}
|
}
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.layer {
|
.layer {
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {isMaxPolycubes, isMinPolycubes, somaDimension, polycubes, solutions} from "../store";
|
import {
|
||||||
import SomaSolution from "../SomaSolution";
|
isMaxPolycubes,
|
||||||
|
isMinPolycubes,
|
||||||
|
polycubes,
|
||||||
|
solutions,
|
||||||
|
colorFromIndex,
|
||||||
|
activeSolution, showingSolution, totalVolume, somaDimX, somaDimY, somaDimZ, debug
|
||||||
|
} from "../store";
|
||||||
import SolutionList from "./SolutionList.svelte";
|
import SolutionList from "./SolutionList.svelte";
|
||||||
import VoxelSpace from "../VoxelSpace";
|
|
||||||
|
|
||||||
$: numCubes = $polycubes.length;
|
$: numCubes = $polycubes.length;
|
||||||
$: cubes = $polycubes;
|
$: cubes = $polycubes;
|
||||||
@@ -11,57 +16,16 @@
|
|||||||
let readyToSolve: boolean;
|
let readyToSolve: boolean;
|
||||||
let size: number;
|
let size: number;
|
||||||
$: {
|
$: {
|
||||||
const dim = $somaDimension as number;
|
size = cubes.reduce((prev, cube) => cube.size() + prev, 0);
|
||||||
const polycubes: VoxelSpace[] = cubes.map(cubeInput => new VoxelSpace(0, [dim, dim, dim], cubeInput.rep));
|
noEmpties = cubes.reduce((prev, cube) => (cube.size() !== 0) && prev, true);
|
||||||
size = polycubes.reduce((prev, cube) => cube.size() + prev, 0);
|
enoughSubcubes = size === $totalVolume;
|
||||||
noEmpties = polycubes.reduce((prev, cube) => (cube.size() !== 0) && prev, true);
|
readyToSolve = enoughSubcubes && noEmpties;
|
||||||
enoughSubcubes = size === dim**3;
|
|
||||||
readyToSolve = size === dim**3 && noEmpties;
|
|
||||||
}
|
|
||||||
let solving = false;
|
|
||||||
const worker = new Worker('../solver/main.js', {type: "module"});
|
|
||||||
|
|
||||||
async function respondWasm(event: MessageEvent) {
|
|
||||||
const dim = $somaDimension as number;
|
|
||||||
solutions.set(event.data.map((wasmSolution) => {
|
|
||||||
const solnObj = new SomaSolution(dim);
|
|
||||||
const spaceReps = wasmSolution.split(",");
|
|
||||||
for (let i = 0; i < spaceReps.length; i++) {
|
|
||||||
solnObj.addSpace(new VoxelSpace(i, [dim, dim, dim], BigInt(parseInt(spaceReps[i]))));
|
|
||||||
}
|
|
||||||
return solnObj;
|
|
||||||
}));
|
|
||||||
solving = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function respondJs(event: MessageEvent) {
|
|
||||||
solutions.set(event.data.map(solnData => {
|
|
||||||
const solution = new SomaSolution(solnData.dim);
|
|
||||||
solnData.solutionSpaces.forEach((voxelSpace, i) => solution.addSpace(new VoxelSpace(i, [solnData.dim, solnData.dim, solnData.dim], voxelSpace.space)));
|
|
||||||
return solution;
|
|
||||||
}));
|
|
||||||
solving = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function solveJs() {
|
|
||||||
worker.onmessage = (e) => respondJs(e);
|
|
||||||
const polycubes = cubes.map(cubeInput => cubeInput.rep);
|
|
||||||
solving = true;
|
|
||||||
worker.postMessage({type: 'js', polycubes, dims: $somaDimension});
|
|
||||||
}
|
|
||||||
|
|
||||||
function solveWasm() {
|
|
||||||
worker.onmessage = (e) => respondWasm(e);
|
|
||||||
const polycubes = cubes.map(cubeInput => cubeInput.rep);
|
|
||||||
console.log(polycubes);
|
|
||||||
solving = true;
|
|
||||||
worker.postMessage({type: 'wasm', polycubes, dims: $somaDimension});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function genTooltip() {
|
function genTooltip() {
|
||||||
let messages = [];
|
let messages = [];
|
||||||
if (!enoughSubcubes) {
|
if (!enoughSubcubes) {
|
||||||
messages.push(`You have not input enough subcubes to form a cube with a side length of ${$somaDimension}. Needed: ${$somaDimension**3}, current: ${size}.`);
|
messages.push(`You have not input enough subcubes to form a rectangular prism with side lengths ${$somaDimX}, ${$somaDimY}, and ${$somaDimZ}. Needed: ${$totalVolume}, current: ${size}.`);
|
||||||
}
|
}
|
||||||
if (!noEmpties) {
|
if (!noEmpties) {
|
||||||
messages.push("You have left some of the polycube inputs empty. Remove them to solve.");
|
messages.push("You have left some of the polycube inputs empty. Remove them to solve.");
|
||||||
@@ -74,34 +38,34 @@
|
|||||||
<h1>Somaesque</h1>
|
<h1>Somaesque</h1>
|
||||||
<div class="widgets">
|
<div class="widgets">
|
||||||
<div class="option">
|
<div class="option">
|
||||||
<p>Dimension:</p>
|
<p>Dimensions:</p>
|
||||||
<div class="choice">
|
<div class="choice">
|
||||||
<button
|
X
|
||||||
class:selected={$somaDimension === 2}
|
<input
|
||||||
on:click={() => somaDimension.set(2)}
|
type="number"
|
||||||
disabled={$somaDimension === 2}>
|
value="3"
|
||||||
2
|
on:input={(e) => somaDimX.set(e.target.valueAsNumber)}/>
|
||||||
</button>
|
Y
|
||||||
<button
|
<input
|
||||||
class:selected={$somaDimension === 3}
|
type="number"
|
||||||
on:click={() => somaDimension.set(3)}
|
value="3"
|
||||||
disabled={$somaDimension === 3}>
|
on:input={(e) => somaDimY.set(e.target.valueAsNumber)}/>
|
||||||
3
|
Z
|
||||||
</button>
|
<input
|
||||||
<button
|
type="number"
|
||||||
class:selected={$somaDimension === 4}
|
value="3"
|
||||||
on:click={() => somaDimension.set(4)}
|
on:input={(e) => somaDimZ.set(e.target.valueAsNumber)}/>
|
||||||
disabled={$somaDimension === 4}>
|
{#if $totalVolume > 32}
|
||||||
4
|
<p class="warn">The total number of units exceeds 32. Attempting to solve puzzles with more than 32 units results in significantly slower computation time.</p>
|
||||||
</button>
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="option">
|
<div class="option">
|
||||||
<p>Cubes:</p>
|
<p>Cubes:</p>
|
||||||
<div class="choice">
|
<div class="choice">
|
||||||
<p>{numCubes}</p>
|
|
||||||
<button on:click={polycubes.removeCube} disabled={$isMinPolycubes}>-</button>
|
<button on:click={polycubes.removeCube} disabled={$isMinPolycubes}>-</button>
|
||||||
|
<p>{numCubes}</p>
|
||||||
<button on:click={polycubes.addCube} disabled={$isMaxPolycubes}>+</button>
|
<button on:click={polycubes.addCube} disabled={$isMaxPolycubes}>+</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,7 +73,7 @@
|
|||||||
<div class="option">
|
<div class="option">
|
||||||
<button
|
<button
|
||||||
class="solve"
|
class="solve"
|
||||||
on:click={solveWasm}
|
on:click={solve}
|
||||||
title="{genTooltip(enoughSubcubes, noEmpties, size)}"
|
title="{genTooltip(enoughSubcubes, noEmpties, size)}"
|
||||||
disabled="{solving || !readyToSolve}">
|
disabled="{solving || !readyToSolve}">
|
||||||
{solving ? "Solving..." : "Solve!"}
|
{solving ? "Solving..." : "Solve!"}
|
||||||
@@ -121,6 +85,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.warn {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -130,10 +97,10 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
button {
|
input {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: #999999;
|
background-color: #999999;
|
||||||
width: 2em;
|
width: 3em;
|
||||||
height: 2em;
|
height: 2em;
|
||||||
border-style: none;
|
border-style: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {polycubes, activeSolution, showingSolution, solutions} from "../store";
|
import {activeSolution, showingSolution, solutions} from "../store";
|
||||||
import SomaSolution from "../SomaSolution";
|
import SomaSolution from "../SomaSolution";
|
||||||
|
|
||||||
$: solutionDisplayed = $solutions[$activeSolution];
|
$: solnToShow = $solutions[$activeSolution];
|
||||||
$: dimension = (solutionDisplayed && solutionDisplayed.getDims?.()[0]) ?? 3;
|
$: dims = (solnToShow?.getDims?.()) ?? [3, 3, 3];
|
||||||
|
|
||||||
function colorAt(soln: SomaSolution, x: number, y: number, z: number) {
|
function colorAt(soln: SomaSolution, x: number, y: number, z: number) {
|
||||||
return $polycubes[soln.at(z, dimension-1-x, y)].color;
|
return solnToShow.getPieces()[soln.at(x, y, z)]?.getColor?.() ?? "red";
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -14,19 +14,18 @@
|
|||||||
<div
|
<div
|
||||||
class="cube"
|
class="cube"
|
||||||
class:active={$showingSolution}
|
class:active={$showingSolution}
|
||||||
style="--dimension: {dimension};"
|
|
||||||
on:click={() => showingSolution.set(true)}
|
on:click={() => showingSolution.set(true)}
|
||||||
>
|
>
|
||||||
<h1>Solution #{$activeSolution + 1}</h1>
|
<h1>Solution #{$activeSolution + 1}</h1>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
{#each {length: dimension} as _, x}
|
{#each {length: dims[0]} as _, x}
|
||||||
<div class="layer">
|
<div class="layer">
|
||||||
{#each {length: dimension} as _, y}
|
{#each {length: dims[1]} as _, y}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{#each {length: dimension} as _, z}
|
{#each {length: dims[2]} as _, z}
|
||||||
<div
|
<div
|
||||||
class="cell"
|
class="cell"
|
||||||
style="background-color:{colorAt(solutionDisplayed, x, y, z)}; border-color: {colorAt(solutionDisplayed, x, y, z)}"
|
style="background-color:{colorAt(solnToShow, x, y, z)}; border-color: {colorAt(solnToShow, x, y, z)}"
|
||||||
class:filled={true}
|
class:filled={true}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -1,36 +1,49 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import PolycubeScene from "./threedee/PolycubeScene.ts";
|
import PolycubeScene from "./threedee/PolycubeScene";
|
||||||
import {onMount} from "svelte";
|
import {onMount} from "svelte";
|
||||||
import {polycubes, somaDimension, selectedCube, solutions, activeSolution, showingSolution} from "../store";
|
import {polycubes, selectedCube, solutions, activeSolution, showingSolution, somaDimX, somaDimY, somaDimZ} from "../store";
|
||||||
import Solution2D from "./Solution2D.svelte";
|
import Solution2D from "./Solution2D.svelte";
|
||||||
|
import VoxelSpaceBoolean from "../VoxelSpaceBoolean";
|
||||||
|
|
||||||
$: cube = $polycubes[$selectedCube];
|
$: cube = $polycubes[$selectedCube];
|
||||||
$: soln = $solutions[$activeSolution];
|
$: soln = $solutions[$activeSolution];
|
||||||
let el: HTMLCanvasElement;
|
let el: HTMLCanvasElement;
|
||||||
let threeTest: PolycubeScene;
|
let scene: PolycubeScene;
|
||||||
let loaded: boolean = false;
|
let loaded: boolean = false;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
threeTest = new PolycubeScene(el, () => loaded = true, console.log);
|
scene = new PolycubeScene(el, () => loaded = true, console.log);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.getPermutations = () => {
|
||||||
|
const newCube: VoxelSpaceBoolean = cube.clone() as VoxelSpaceBoolean;
|
||||||
|
(newCube as VoxelSpaceBoolean).cullEmptySpace();
|
||||||
|
return (newCube as VoxelSpaceBoolean).getAllPermutationsInPrism($somaDimX, $somaDimY, $somaDimZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.showRot = (rot: VoxelSpaceBoolean) => {
|
||||||
|
scene?.showPolycube(rot);
|
||||||
|
}
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
if ($showingSolution) {
|
if ($showingSolution) {
|
||||||
const colorMap = {};
|
const colorMap = {};
|
||||||
$polycubes.forEach((polycube, i) => colorMap[i] = polycube.color);
|
$polycubes.forEach((polycube, i) => colorMap[i] = polycube.color);
|
||||||
threeTest?.showSolution(soln, colorMap);
|
scene?.showSolution(soln);
|
||||||
} else {
|
} else {
|
||||||
threeTest?.showPolycube(cube.rep, $somaDimension, cube.color);
|
scene?.showPolycube(cube);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="top">
|
<div class="top">
|
||||||
<div class="soln2d-container">
|
{#if $activeSolution !== null}
|
||||||
<Solution2D/>
|
<div class="soln2d-container">
|
||||||
</div>
|
<Solution2D/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<canvas
|
<canvas
|
||||||
bind:this={el}
|
bind:this={el}
|
||||||
width="640"
|
width="640"
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import type VoxelSpace from "../../VoxelSpace";
|
import type VoxelSpaceBoolean from "../../VoxelSpaceBoolean";
|
||||||
import type GeometryManager from "./GeometryManager";
|
import type GeometryManager from "./GeometryManager";
|
||||||
|
import type VoxelSpaceBigInt from "../../VoxelSpaceBigInt";
|
||||||
|
|
||||||
export default class PolycubeMesh {
|
export default class PolycubeMesh {
|
||||||
private static geometryManager: GeometryManager;
|
private static geometryManager: GeometryManager;
|
||||||
private group: THREE.Group;
|
private group: THREE.Group;
|
||||||
private meshes: THREE.Mesh[] = [];
|
private meshes: THREE.Mesh[] = [];
|
||||||
private currentPolycube: bigint = 0n;
|
private currentPolycube: boolean[] | bigint = [];
|
||||||
private material: THREE.MeshPhongMaterial;
|
private material: THREE.MeshPhongMaterial;
|
||||||
private numActiveCubes: number = 0;
|
private numActiveCubes: number = 0;
|
||||||
private flyDirection: THREE.Vector3 = new THREE.Vector3();
|
private flyDirection: THREE.Vector3 = new THREE.Vector3();
|
||||||
|
|
||||||
constructor(polycube: VoxelSpace, color: string) {
|
constructor(polycube: VoxelSpaceBoolean | VoxelSpaceBigInt) {
|
||||||
this.material = new THREE.MeshPhongMaterial({color: 'red', shininess: 100, reflectivity: 100});
|
this.material = new THREE.MeshPhongMaterial({color: polycube.getColor(), shininess: 100, reflectivity: 100});
|
||||||
this.group = new THREE.Group();
|
this.group = new THREE.Group();
|
||||||
this.swapColor(color);
|
|
||||||
this.swapPolycube(polycube);
|
this.swapPolycube(polycube);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,11 +22,7 @@ export default class PolycubeMesh {
|
|||||||
PolycubeMesh.geometryManager = manager;
|
PolycubeMesh.geometryManager = manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
swapColor(color: string) {
|
swapPolycube(polycube: VoxelSpaceBoolean | VoxelSpaceBigInt) {
|
||||||
this.material.color.set(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
swapPolycube(polycube: VoxelSpace) {
|
|
||||||
if (polycube.getRaw() === this.currentPolycube) {
|
if (polycube.getRaw() === this.currentPolycube) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -40,10 +36,11 @@ export default class PolycubeMesh {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.currentPolycube = polycube.getRaw();
|
this.currentPolycube = polycube.getRaw();
|
||||||
|
this.material.color.set(polycube.getColor());
|
||||||
this.flyDirection = this.middlePosOfGroup().normalize();
|
this.flyDirection = this.middlePosOfGroup().normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private addCube(refPolycube: VoxelSpace, x: number, y: number, z: number) {
|
private addCube(refPolycube: VoxelSpaceBoolean | VoxelSpaceBigInt, x: number, y: number, z: number) {
|
||||||
const dims = refPolycube.getDims();
|
const dims = refPolycube.getDims();
|
||||||
const neighbourProfile = refPolycube.getDirectNeighbourProfile(x, y, z);
|
const neighbourProfile = refPolycube.getDirectNeighbourProfile(x, y, z);
|
||||||
const mesh = new THREE.Mesh(
|
const mesh = new THREE.Mesh(
|
||||||
@@ -51,9 +48,9 @@ export default class PolycubeMesh {
|
|||||||
this.material
|
this.material
|
||||||
);
|
);
|
||||||
mesh.position.set(
|
mesh.position.set(
|
||||||
-((dims[0] - 1)/2) + x,
|
|
||||||
-((dims[1] - 1)/2) + y,
|
|
||||||
-((dims[2] - 1)/2) + z,
|
-((dims[2] - 1)/2) + z,
|
||||||
|
((dims[0] - 1)/2) - x,
|
||||||
|
-((dims[1] - 1)/2) + y,
|
||||||
);
|
);
|
||||||
this.meshes.push(mesh);
|
this.meshes.push(mesh);
|
||||||
this.group.add(mesh);
|
this.group.add(mesh);
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import * as THREE from 'three';
|
|||||||
import type SomaSolution from "../../SomaSolution";
|
import type SomaSolution from "../../SomaSolution";
|
||||||
import RotationControl from "./RotationControl";
|
import RotationControl from "./RotationControl";
|
||||||
import PolycubeMesh from "./PolycubeMesh";
|
import PolycubeMesh from "./PolycubeMesh";
|
||||||
import VoxelSpace, {DimensionDef} from "../../VoxelSpace";
|
import type VoxelSpaceBoolean from "../../VoxelSpaceBoolean";
|
||||||
|
import type VoxelSpaceBigInt from "../../VoxelSpaceBigInt";
|
||||||
import GeometryManager from "./GeometryManager";
|
import GeometryManager from "./GeometryManager";
|
||||||
|
|
||||||
export default class PolycubeScene {
|
export default class PolycubeScene {
|
||||||
@@ -44,20 +45,19 @@ export default class PolycubeScene {
|
|||||||
this.camera.lookAt(0, 0, 0);
|
this.camera.lookAt(0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private showPolycube(polycube: bigint, dims: number, color: string) {
|
showPolycube(voxelSpace: VoxelSpaceBoolean) {
|
||||||
this.controls.disableFly();
|
this.controls.disableFly();
|
||||||
const voxelSpace = new VoxelSpace(0, [dims, dims, dims], polycube, true);
|
|
||||||
this.clearScene();
|
this.clearScene();
|
||||||
this.addPolycube(voxelSpace, color);
|
this.addPolycube(voxelSpace);
|
||||||
this.polycubeMeshes[0].center();
|
this.polycubeMeshes[0].center();
|
||||||
}
|
}
|
||||||
|
|
||||||
private showSolution(solution: SomaSolution, colorMap: Record<number, string>) {
|
showSolution(solution: SomaSolution) {
|
||||||
this.controls.enableFly();
|
this.controls.enableFly();
|
||||||
this.clearScene();
|
this.clearScene();
|
||||||
const pieces = solution.getPieces();
|
const pieces = solution.getPieces();
|
||||||
for (let i = 0; i < pieces.length; i++) {
|
for (let i = 0; i < pieces.length; i++) {
|
||||||
this.addPolycube(pieces[i], colorMap[i]);
|
this.addPolycube(pieces[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,8 +66,8 @@ export default class PolycubeScene {
|
|||||||
this.cubeScene.clear();
|
this.cubeScene.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private addPolycube(voxelSpace: VoxelSpace, color: string) {
|
private addPolycube(voxelSpace: VoxelSpaceBoolean | VoxelSpaceBigInt) {
|
||||||
const newMesh = new PolycubeMesh(voxelSpace, color);
|
const newMesh = new PolycubeMesh(voxelSpace);
|
||||||
this.polycubeMeshes.push(newMesh);
|
this.polycubeMeshes.push(newMesh);
|
||||||
this.cubeScene.add(newMesh.asObj3D());
|
this.cubeScene.add(newMesh.asObj3D());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
"target": "ESNext"
|
"target": "ESNext"
|
||||||
},
|
},
|
||||||
"include": ["./src/**/*"],
|
"include": ["./src/**/*"],
|
||||||
"exclude": ["./node_modules/*", "./__sapper__/*", "./public/*", "./src/solver/**/*"]
|
"exclude": ["./node_modules/*", "./__sapper__/*", "./public/*", "./src/solver/wasm/*"]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user