It all works....
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,3 +3,7 @@
|
||||
/.vscode
|
||||
/.idea
|
||||
/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"?>
|
||||
<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">
|
||||
<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$/public/solver/main.wasm" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/SomaSolver.ts" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/solver/assembly/SomaSolution.ts" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/solver/assembly/VoxelSpace.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 afterPath="$PROJECT_DIR$/src/desktop/build.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/desktop/main.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/desktop/preload.js" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/solver/js/SomaSolver.ts" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/solver/js/VoxelSpaceBoolean.ts" 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$/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$/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/resources/bevel-cube/000000.mtl" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/000001.mtl" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/resources/bevel-cube/000001.obj" 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/SomaSolution.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/js/SomaSolution.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/solver/SomaSolver.js" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/solver/VoxelSpace.js" beforeDir="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$/src/CubeInput.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/CubeInput.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/OBJLoader.js" 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/PolycubeScene.ts" beforeDir="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/Sidebar.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/Sidebar.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/SolutionInteractor.svelte" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/SolutionList.svelte" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/main.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/main.ts" 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/SomaSolver.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/assembly/SomaSolver.ts" 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/main.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/assembly/index.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/public/solver/main.wasm" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/SomaSolution.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/SomaSolution.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/SomaSolver.ts" beforeDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/VoxelSpace.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/VoxelSpaceBigInt.ts" afterDir="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/solver/assembly/SomaSolution.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/wasm/assembly/SomaSolution.ts" 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/solver/assembly/VoxelSpace.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/wasm/assembly/VoxelSpace.ts" afterDir="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/solver/assembly/tsconfig.json" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/wasm/assembly/tsconfig.json" 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/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/src/solver/wasm/package-lock.json" 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/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" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
@@ -64,13 +70,13 @@
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="HTML File" />
|
||||
<option value="Sidebar" />
|
||||
<option value="SolutionInteractor" />
|
||||
<option value="CubeInput" />
|
||||
<option value="store.svelte" />
|
||||
<option value="JavaScript File" />
|
||||
<option value="SolutionList.svelte" />
|
||||
<option value="HTML File" />
|
||||
<option value="JavaScript File" />
|
||||
<option value="TypeScript File" />
|
||||
</list>
|
||||
</option>
|
||||
@@ -78,6 +84,11 @@
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="JsbtTreeLayoutManager">
|
||||
<layout place="tools.popupGrunt">
|
||||
<scroll-view-position x="0" y="0" />
|
||||
</layout>
|
||||
</component>
|
||||
<component name="ProjectId" id="1shUz7NMSUxY207ip64fWIgaebh" />
|
||||
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
||||
<component name="ProjectViewState">
|
||||
@@ -88,7 +99,7 @@
|
||||
<property name="DefaultHtmlFileTemplate" value="HTML File" />
|
||||
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||
<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.tslint" value="true" />
|
||||
<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.tslint" value="(autodetect)" />
|
||||
<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="ts.external.directory.path" value="$PROJECT_DIR$/node_modules/typescript/lib" />
|
||||
<property name="use.deno.dismiss" value="true" />
|
||||
@@ -103,13 +115,15 @@
|
||||
</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/public/solver" />
|
||||
<recent name="$PROJECT_DIR$/src" />
|
||||
<recent name="$PROJECT_DIR$/src/ui" />
|
||||
<recent name="$PROJECT_DIR$/src/solver" />
|
||||
<recent name="$PROJECT_DIR$/src/ui/threedee" />
|
||||
<recent name="$PROJECT_DIR$/src/webapp" />
|
||||
<recent name="$PROJECT_DIR$/src/solver/js" />
|
||||
<recent name="$PROJECT_DIR$/src/solver/wasm" />
|
||||
<recent name="$PROJECT_DIR$/resources/app" />
|
||||
</key>
|
||||
<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$/public/solver" />
|
||||
<recent name="$PROJECT_DIR$/src" />
|
||||
@@ -147,6 +161,17 @@
|
||||
<workItem from="1622971349849" duration="20106000" />
|
||||
<workItem from="1623052849485" duration="37878000" />
|
||||
<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>
|
||||
<servers />
|
||||
</component>
|
||||
@@ -154,25 +179,82 @@
|
||||
<option name="version" value="3" />
|
||||
<option name="exactExcludedFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/src/solver/assembly/index.js" />
|
||||
<option value="$PROJECT_DIR$/src/solver/assembly/SomaSolution.js" />
|
||||
<option value="$PROJECT_DIR$/src/solver/assembly/SomaSolver.js" />
|
||||
<option value="$PROJECT_DIR$/src/solver/assembly/VoxelSpace.js" />
|
||||
<option value="$PROJECT_DIR$/src/solver/assembly/SomaSolution.js" />
|
||||
<option value="$PROJECT_DIR$/src/solver/assembly/SomaSolver.js" />
|
||||
<option value="$PROJECT_DIR$/src/solver/assembly/VoxelSpace.js" />
|
||||
<option value="$PROJECT_DIR$/src/solver/js/SomaSolver.js" />
|
||||
<option value="$PROJECT_DIR$/src/SomaSolution.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/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>
|
||||
</option>
|
||||
</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">
|
||||
<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" />
|
||||
</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="643" y="194" width="614" height="706" key="#com.intellij.refactoring.rename.AutomaticRenamingDialog" timestamp="1621349929863">
|
||||
<screen x="0" y="27" width="1920" height="1053" />
|
||||
<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="692" y="20" width="1213" height="1060" key="#com.intellij.refactoring.rename.AutomaticRenamingDialog" timestamp="1625307697424">
|
||||
<screen x="0" y="27" width="3840" height="1053" />
|
||||
</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="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">
|
||||
<screen x="0" y="27" width="1920" height="1053" />
|
||||
</state>
|
||||
@@ -189,11 +271,15 @@
|
||||
<screen x="0" y="27" width="1920" height="1053" />
|
||||
</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="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" />
|
||||
</state>
|
||||
<state x="1248" y="245" key="run.anything.popup/0.27.3840.1053@0.27.3840.1053" timestamp="1621713204008" />
|
||||
<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="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="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">
|
||||
<screen x="0" y="27" width="1920" height="1053" />
|
||||
</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",
|
||||
"version": "1.0.0",
|
||||
"description": "Custom Somaesque cube solver webapp",
|
||||
"main": "src/desktop/main.js",
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"dev": "rollup -c -w",
|
||||
"start": "sirv public --no-clear",
|
||||
"validate": "svelte-check"
|
||||
"validate": "svelte-check",
|
||||
"pack": "electron-builder --dir",
|
||||
"dist": "electron-builder"
|
||||
},
|
||||
"author": "Daniel Ledda",
|
||||
"license": "ISC",
|
||||
@@ -16,6 +19,9 @@
|
||||
"@rollup/plugin-typescript": "^8.0.0",
|
||||
"@tsconfig/svelte": "^1.0.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-plugin-css-only": "^3.1.0",
|
||||
"rollup-plugin-livereload": "^2.0.0",
|
||||
@@ -28,9 +34,25 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"as-bind": "^0.7.1",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"sirv-cli": "^1.0.0",
|
||||
"source-map-support": "^0.5.19",
|
||||
"three": "^0.128.0",
|
||||
"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 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='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 {
|
||||
private solutionSpaces: VoxelSpace[];
|
||||
private dim: number;
|
||||
constructor(dim: number) {
|
||||
if (dim < 0 || dim % 1 !== 0) {
|
||||
throw new Error("Dimension must be a whole positive integer!");
|
||||
}
|
||||
this.dim = dim;
|
||||
private solutionSpaces: (VoxelSpaceBoolean | VoxelSpaceBigInt)[];
|
||||
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 = [];
|
||||
}
|
||||
|
||||
@@ -40,7 +43,7 @@ export default class SomaSolution {
|
||||
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.dim);
|
||||
const solnRot = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||
allRots.forEach(rotGroup => solnRot.addSpace(rotGroup[i]));
|
||||
result.push(solnRot);
|
||||
}
|
||||
@@ -56,16 +59,16 @@ export default class SomaSolution {
|
||||
return true;
|
||||
}
|
||||
|
||||
addSpace(space: VoxelSpace) {
|
||||
addSpace(space: VoxelSpaceBoolean | VoxelSpaceBigInt) {
|
||||
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 (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();
|
||||
@@ -75,7 +78,7 @@ export default class SomaSolution {
|
||||
console.log(accum);
|
||||
accum = "";
|
||||
}
|
||||
if (x !== this.dim - 1) {
|
||||
if (x !== this.dimX - 1) {
|
||||
console.log("-");
|
||||
}
|
||||
}
|
||||
@@ -92,19 +95,19 @@ export default class SomaSolution {
|
||||
}
|
||||
|
||||
clone() {
|
||||
const clone = new SomaSolution(this.dim);
|
||||
const clone = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||
clone.solutionSpaces = this.solutionSpaces.slice();
|
||||
return clone;
|
||||
}
|
||||
|
||||
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) {
|
||||
loopStart: for (let x = 0; x < this.dim; x++) {
|
||||
for (let y = 0; y < this.dim; y++) {
|
||||
for (let z = 0; z < this.dim; z++) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
const enum NeighbourDirection {
|
||||
@@ -9,30 +11,28 @@ const enum NeighbourDirection {
|
||||
NEGZ,
|
||||
}
|
||||
|
||||
export default class VoxelSpace {
|
||||
export default class VoxelSpaceBigInt {
|
||||
private dims: DimensionDef;
|
||||
private length: number;
|
||||
private space: bigint;
|
||||
private id: number;
|
||||
constructor(id: number, dims: DimensionDef, space?: boolean[] | bigint, cullEmpty?: boolean) {
|
||||
if (!space) {
|
||||
space = 0n;
|
||||
} else if (Array.isArray(space)) {
|
||||
if (space.length !== dims[0] * dims[1] * dims[2]) {
|
||||
throw new Error("Vals don't fit in given dimensions.");
|
||||
private color: string;
|
||||
|
||||
constructor(options: {id: number, dims: DimensionDef, space?: bigint, cullEmpty: boolean, color?: string}) {
|
||||
if (!options.space) {
|
||||
options.space = 0n;
|
||||
}
|
||||
space = VoxelSpace.boolArrayToBigInt(space)
|
||||
}
|
||||
this.id = id;
|
||||
this.length = dims[0] * dims[1] * dims[2];
|
||||
this.dims = dims;
|
||||
this.space = space;
|
||||
if (cullEmpty) {
|
||||
this.id = options.id;
|
||||
this.length = options.dims[0] * options.dims[1] * options.dims[2];
|
||||
this.dims = options.dims;
|
||||
this.space = BigInt(options.space);
|
||||
this.color = options.color ?? "red";
|
||||
if (options.cullEmpty !== false) {
|
||||
this.cullEmptySpace();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolArrayToBigInt(boolArray: boolean[]): bigint {
|
||||
static boolArrayToBigInt(boolArray: boolean[]) {
|
||||
let result = 0n;
|
||||
for (let i = 0; i < boolArray.length; i++) {
|
||||
if (boolArray[i]) {
|
||||
@@ -42,18 +42,26 @@ export default class VoxelSpace {
|
||||
return result;
|
||||
}
|
||||
|
||||
setColor(color: string) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
getColor() {
|
||||
return this.color;
|
||||
}
|
||||
|
||||
binaryRep() {
|
||||
return this.space.toString(2);
|
||||
}
|
||||
|
||||
getExtrema() {
|
||||
const extrema = {
|
||||
xMax: -Infinity,
|
||||
xMin: Infinity,
|
||||
yMax: -Infinity,
|
||||
yMin: Infinity,
|
||||
zMax: -Infinity,
|
||||
zMin: Infinity,
|
||||
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) {
|
||||
@@ -68,7 +76,7 @@ export default class VoxelSpace {
|
||||
return extrema;
|
||||
}
|
||||
|
||||
private cullEmptySpace() {
|
||||
cullEmptySpace() {
|
||||
const extrema = this.getExtrema();
|
||||
let index = 0n;
|
||||
let newSpace = 0n;
|
||||
@@ -123,25 +131,25 @@ export default class VoxelSpace {
|
||||
}
|
||||
|
||||
getUniqueRotations() {
|
||||
const rotations: VoxelSpace[] = [];
|
||||
const rotations: VoxelSpaceBigInt[] = [];
|
||||
const refSpace = this.clone();
|
||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
VoxelSpaceBigInt.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
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'));
|
||||
return rotations;
|
||||
}
|
||||
|
||||
getAllRotations() {
|
||||
const rotations: VoxelSpace[] = [];
|
||||
const rotations: VoxelSpaceBigInt[] = [];
|
||||
const refSpace = this.clone();
|
||||
rotations.push(...refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
@@ -158,7 +166,7 @@ export default class VoxelSpace {
|
||||
return rotations;
|
||||
}
|
||||
|
||||
protected static pushNewUniqueSpaces(existingSpaces: VoxelSpace[], newSpaces: VoxelSpace[]) {
|
||||
protected static pushNewUniqueSpaces(existingSpaces: VoxelSpaceBigInt[], newSpaces: VoxelSpaceBigInt[]) {
|
||||
for (const newSpace of newSpaces) {
|
||||
let matchFound = false;
|
||||
for (const existingSpace of existingSpaces) {
|
||||
@@ -173,41 +181,44 @@ export default class VoxelSpace {
|
||||
}
|
||||
}
|
||||
|
||||
getAllPositionsInCube(cubeDim: number): VoxelSpace[] {
|
||||
if ((cubeDim > 0) && (cubeDim % 1 === 0)) {
|
||||
const cubePositions: VoxelSpace[] = [];
|
||||
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);
|
||||
getAllPositionsInPrism(cubeDimX: number, cubeDimY: number, cubeDimZ: number): VoxelSpaceBigInt[] {
|
||||
const cubePositions: VoxelSpaceBigInt[] = [];
|
||||
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 VoxelSpaceBigInt({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;
|
||||
} else {
|
||||
throw new Error("cubeDim must be a positive integer.");
|
||||
}
|
||||
}
|
||||
|
||||
matches(space: VoxelSpace) {
|
||||
matches(space: VoxelSpaceBigInt | VoxelSpaceBoolean) {
|
||||
const otherDims = space.getDims();
|
||||
for (let i = 0; i < this.dims.length; i++) {
|
||||
if (otherDims[i] !== this.dims[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (typeof space.getRaw() === "bigint") {
|
||||
return this.space === space.getRaw();
|
||||
} else {
|
||||
return this.binaryRep() === space.binaryRep();
|
||||
}
|
||||
}
|
||||
|
||||
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()];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
rotations.push(rotations[i].rotated90(axis));
|
||||
@@ -250,30 +261,33 @@ export default class VoxelSpace {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
this.space |= mask;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
this.space &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
rotated90(dim: 'x' | 'y' | 'z') {
|
||||
let newSpace = 0n;
|
||||
let newDims: DimensionDef;
|
||||
let rotIndex: (i: number, j: number, k: number) => number;
|
||||
let newDims;
|
||||
let rotIndex;
|
||||
if (dim === 'x') {
|
||||
newDims = [this.dims[0], this.dims[2], this.dims[1]];
|
||||
rotIndex = this.newIndexRotX.bind(this);
|
||||
} else if (dim === 'y') {
|
||||
}
|
||||
else if (dim === 'y') {
|
||||
newDims = [this.dims[2], this.dims[1], this.dims[0]];
|
||||
rotIndex = this.newIndexRotY.bind(this);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
newDims = [this.dims[1], this.dims[0], this.dims[2]];
|
||||
rotIndex = this.newIndexRotZ.bind(this);
|
||||
}
|
||||
@@ -281,8 +295,8 @@ export default class VoxelSpace {
|
||||
if (val) {
|
||||
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') {
|
||||
@@ -291,10 +305,10 @@ export default class VoxelSpace {
|
||||
this.dims = rot.getDims();
|
||||
}
|
||||
|
||||
plus(space: VoxelSpace): VoxelSpace | null {
|
||||
plus(space: VoxelSpaceBigInt): VoxelSpaceBigInt | null {
|
||||
const otherSpace = space.getRaw();
|
||||
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;
|
||||
}
|
||||
@@ -331,4 +345,13 @@ export default class VoxelSpace {
|
||||
}
|
||||
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 {
|
||||
constructor(dim) {
|
||||
if (dim < 0 || dim % 1 !== 0) {
|
||||
throw new Error("Dimension must be a whole positive integer!");
|
||||
}
|
||||
this.dim = dim;
|
||||
constructor(dimX, dimY, dimZ) {
|
||||
this.dimX = dimX;
|
||||
this.dimY = dimY;
|
||||
this.dimZ = dimZ;
|
||||
this.solutionSpaces = [];
|
||||
}
|
||||
static filterUnique(solutions) {
|
||||
@@ -13,7 +12,7 @@ export default class SomaSolution {
|
||||
const uniqueSolns = [solutions[0]];
|
||||
for (const solution of solutions) {
|
||||
let foundMatch = false;
|
||||
for (const rotation of solution.getUniqueRotations()) {
|
||||
for (const rotation of solution.getRotations()) {
|
||||
let end = uniqueSolns.length;
|
||||
for (let i = 0; i < end; i++) {
|
||||
if (rotation.matches(uniqueSolns[i])) {
|
||||
@@ -27,14 +26,14 @@ export default class SomaSolution {
|
||||
}
|
||||
return uniqueSolns;
|
||||
}
|
||||
getUniqueRotations() {
|
||||
getRotations() {
|
||||
if (this.solutionSpaces.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const result = [];
|
||||
const allRots = this.solutionSpaces.map(space => space.getAllRotations());
|
||||
for (let i = 0; i < allRots[0].length; i++) {
|
||||
const solnRot = new SomaSolution(this.dim);
|
||||
const solnRot = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||
allRots.forEach(rotGroup => solnRot.addSpace(rotGroup[i]));
|
||||
result.push(solnRot);
|
||||
}
|
||||
@@ -54,9 +53,9 @@ export default class SomaSolution {
|
||||
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 (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();
|
||||
@@ -66,7 +65,7 @@ export default class SomaSolution {
|
||||
console.log(accum);
|
||||
accum = "";
|
||||
}
|
||||
if (x !== this.dim - 1) {
|
||||
if (x !== this.dimX - 1) {
|
||||
console.log("-");
|
||||
}
|
||||
}
|
||||
@@ -81,8 +80,23 @@ export default class SomaSolution {
|
||||
return 0;
|
||||
}
|
||||
clone() {
|
||||
const clone = new SomaSolution(this.dim);
|
||||
const clone = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||
clone.solutionSpaces = this.solutionSpaces.slice();
|
||||
return clone;
|
||||
}
|
||||
getDims() {
|
||||
return [this.dimX, this.dimY, this.dimZ];
|
||||
}
|
||||
forEachCell(cb) {
|
||||
loopStart: for (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
cb(this.at(x, y, z), x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
getPieces() {
|
||||
return this.solutionSpaces.slice();
|
||||
}
|
||||
}
|
||||
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 {
|
||||
constructor(id, dims, space, cullEmpty) {
|
||||
if (!space) {
|
||||
space = 0n;
|
||||
export default class VoxelSpaceBoolean {
|
||||
constructor(options) {
|
||||
this.length = options.dims[0] * options.dims[1] * options.dims[2];
|
||||
if (!options.space) {
|
||||
options.space = new Array(options.dims[0] * options.dims[1] * options.dims[2]);
|
||||
options.space.fill(false);
|
||||
}
|
||||
else if (Array.isArray(space)) {
|
||||
if (space.length !== dims[0] * dims[1] * dims[2]) {
|
||||
throw new Error("Vals don't fit in given dimensions.");
|
||||
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);
|
||||
}
|
||||
space = VoxelSpace.boolArrayToBigInt(space);
|
||||
options.space = newSpace;
|
||||
}
|
||||
this.id = id;
|
||||
this.length = dims[0] * dims[1] * dims[2];
|
||||
this.dims = dims;
|
||||
this.space = space;
|
||||
if (cullEmpty) {
|
||||
this.id = options.id;
|
||||
this.dims = options.dims;
|
||||
this.space = options.space;
|
||||
this.color = options.color ?? "red";
|
||||
if (options.cullEmpty !== false) {
|
||||
this.cullEmptySpace();
|
||||
}
|
||||
}
|
||||
static boolArrayToBigInt(boolArray) {
|
||||
let result = 0n;
|
||||
for (let i = 0; i < boolArray.length; i++) {
|
||||
if (boolArray[i]) {
|
||||
result |= BigInt(1 << i);
|
||||
setColor(color) {
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
getColor() {
|
||||
return this.color;
|
||||
}
|
||||
binaryRep() {
|
||||
return this.space.toString(2);
|
||||
return this.space.reduce((prev, curr) => prev + (curr ? "1" : "0"), "");
|
||||
}
|
||||
getExtrema() {
|
||||
const extrema = {
|
||||
xMax: -Infinity,
|
||||
xMin: Infinity,
|
||||
yMax: -Infinity,
|
||||
yMin: Infinity,
|
||||
zMax: -Infinity,
|
||||
zMin: Infinity,
|
||||
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) {
|
||||
@@ -52,21 +53,25 @@ export default class VoxelSpace {
|
||||
}
|
||||
cullEmptySpace() {
|
||||
const extrema = this.getExtrema();
|
||||
let index = 0n;
|
||||
let newSpace = 0n;
|
||||
const newX = extrema.xMax - extrema.xMin + 1;
|
||||
const newY = extrema.yMax - extrema.yMin + 1;
|
||||
const newZ = extrema.zMax - extrema.zMin + 1;
|
||||
const newSpace = new Array(newX * newY * newZ);
|
||||
newSpace.fill(false);
|
||||
let index = 0;
|
||||
for (let x = extrema.xMin; x <= extrema.xMax; x++) {
|
||||
for (let y = extrema.yMin; y <= extrema.yMax; y++) {
|
||||
for (let z = extrema.zMin; z <= extrema.zMax; z++) {
|
||||
if (this.at(x, y, z)) {
|
||||
newSpace |= 1n << index;
|
||||
newSpace[index] = true;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.dims[0] = extrema.xMax - extrema.xMin + 1;
|
||||
this.dims[1] = extrema.yMax - extrema.yMin + 1;
|
||||
this.dims[2] = extrema.zMax - extrema.zMin + 1;
|
||||
this.dims[0] = newX;
|
||||
this.dims[1] = newY;
|
||||
this.dims[2] = newZ;
|
||||
this.space = newSpace;
|
||||
}
|
||||
forEachCell(cb) {
|
||||
@@ -103,18 +108,18 @@ export default class VoxelSpace {
|
||||
getUniqueRotations() {
|
||||
const rotations = [];
|
||||
const refSpace = this.clone();
|
||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('y');
|
||||
VoxelSpace.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
VoxelSpaceBoolean.pushNewUniqueSpaces(rotations, refSpace.getAxisSpins('x'));
|
||||
refSpace.rot90('z');
|
||||
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'));
|
||||
return rotations;
|
||||
}
|
||||
getAllRotations() {
|
||||
@@ -148,15 +153,17 @@ export default class VoxelSpace {
|
||||
}
|
||||
}
|
||||
}
|
||||
getAllPositionsInCube(cubeDim) {
|
||||
if ((cubeDim > 0) && (cubeDim % 1 === 0)) {
|
||||
getAllPositionsInPrism(cubeDimX, cubeDimY, cubeDimZ) {
|
||||
const cubePositions = [];
|
||||
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);
|
||||
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);
|
||||
}
|
||||
@@ -164,10 +171,6 @@ export default class VoxelSpace {
|
||||
}
|
||||
return cubePositions;
|
||||
}
|
||||
else {
|
||||
throw new Error("cubeDim must be a positive integer.");
|
||||
}
|
||||
}
|
||||
matches(space) {
|
||||
const otherDims = space.getDims();
|
||||
for (let i = 0; i < this.dims.length; i++) {
|
||||
@@ -175,10 +178,14 @@ export default class VoxelSpace {
|
||||
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() {
|
||||
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) {
|
||||
const rotations = [this.clone()];
|
||||
@@ -191,7 +198,7 @@ export default class VoxelSpace {
|
||||
return this.dims.slice();
|
||||
}
|
||||
getRaw() {
|
||||
return this.space;
|
||||
return this.space.slice();
|
||||
}
|
||||
// [1, 0, 0] [x] [ x]
|
||||
// [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;
|
||||
}
|
||||
at(x, y, z) {
|
||||
const mask = 1n << BigInt(this.dims[1] * this.dims[2] * x + this.dims[2] * y + z);
|
||||
return (this.space & mask) !== 0n;
|
||||
return this.space[this.index(x, y, z)];
|
||||
}
|
||||
index(x, y, z) {
|
||||
return this.dims[1] * this.dims[2] * x + this.dims[2] * y + z;
|
||||
}
|
||||
toggle(x, y, z) {
|
||||
const mask = BigInt(1 << this.dims[1] * this.dims[2] * x + this.dims[2] * y + z);
|
||||
this.space ^= mask;
|
||||
const index = this.index(x, y, z);
|
||||
this.space[index] = !this.space[index];
|
||||
}
|
||||
set(x, y, z, val) {
|
||||
const mask = BigInt(1 << this.dims[1] * this.dims[2] * x + this.dims[2] * y + z);
|
||||
if (val) {
|
||||
this.space |= mask;
|
||||
}
|
||||
else {
|
||||
this.space &= ~mask;
|
||||
}
|
||||
this.space[this.index(x, y, z)] = val;
|
||||
}
|
||||
rotated90(dim) {
|
||||
let newSpace = 0n;
|
||||
const newSpace = new Array(this.dims[0] * this.dims[1] * this.dims[2]);
|
||||
newSpace.fill(false);
|
||||
let newDims;
|
||||
let rotIndex;
|
||||
if (dim === 'x') {
|
||||
@@ -246,10 +250,10 @@ export default class VoxelSpace {
|
||||
}
|
||||
this.forEachCell((val, i, j, k) => {
|
||||
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) {
|
||||
const rot = this.rotated90(dim);
|
||||
@@ -257,9 +261,21 @@ export default class VoxelSpace {
|
||||
this.dims = rot.getDims();
|
||||
}
|
||||
plus(space) {
|
||||
const otherSpace = space.getRaw();
|
||||
if ((this.space | otherSpace) === (this.space ^ otherSpace)) {
|
||||
return new VoxelSpace(this.id, this.dims, otherSpace | this.space);
|
||||
const newSpace = new Array(this.dims[0] * this.dims[1] * this.dims[2]);
|
||||
newSpace.fill(false);
|
||||
let clash = false;
|
||||
space.forEachCell((val, x, y, z) => {
|
||||
if (this.at(x, y, z) !== val) {
|
||||
newSpace[this.index(x, y, z)] = true;
|
||||
}
|
||||
else {
|
||||
if (val) {
|
||||
clash = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!clash) {
|
||||
return new VoxelSpaceBoolean({ id: this.id, dims: this.getDims(), space: newSpace, color: this.color, cullEmpty: false });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -272,4 +288,34 @@ export default class VoxelSpace {
|
||||
});
|
||||
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
|
||||
},
|
||||
"release": {
|
||||
"binaryFile": "../../public/solver/main.wasm",
|
||||
"textFile": "../../public/solver/main.wat",
|
||||
"binaryFile": "../../../public/solver/main.wasm",
|
||||
"textFile": "../../../public/solver/main.wat",
|
||||
"sourceMap": false,
|
||||
"optimizeLevel": 3,
|
||||
"shrinkLevel": 1,
|
||||
@@ -2,12 +2,14 @@ import VoxelSpace from "./VoxelSpace";
|
||||
|
||||
export default class SomaSolution {
|
||||
private solutionSpaces: VoxelSpace[];
|
||||
private dim: i32;
|
||||
constructor(dim: i32) {
|
||||
if (dim < 0 || dim % 1 !== 0) {
|
||||
throw new Error("Dimension must be a whole positive integer!");
|
||||
}
|
||||
this.dim = dim;
|
||||
private dimX: i32;
|
||||
private dimY: i32;
|
||||
private dimZ: i32;
|
||||
|
||||
constructor(dimX: i32, dimY: i32, dimZ: i32) {
|
||||
this.dimX = dimX;
|
||||
this.dimY = dimY;
|
||||
this.dimZ = dimZ;
|
||||
this.solutionSpaces = [];
|
||||
}
|
||||
|
||||
@@ -42,7 +44,7 @@ export default class SomaSolution {
|
||||
}
|
||||
const allRots: VoxelSpace[][] = this.solutionSpaces.map<VoxelSpace[]>(space => space.getAllRotations());
|
||||
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++) {
|
||||
solnRot.addSpace(allRots[j][i]);
|
||||
}
|
||||
@@ -74,15 +76,11 @@ export default class 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);
|
||||
return clone;
|
||||
}
|
||||
|
||||
getDims(): i32[] {
|
||||
return [this.dim, this.dim, this.dim];
|
||||
}
|
||||
|
||||
getPieces(): VoxelSpace[] {
|
||||
return this.solutionSpaces;
|
||||
}
|
||||
@@ -3,34 +3,30 @@ import SomaSolution from "./SomaSolution";
|
||||
|
||||
export default class SomaSolver {
|
||||
private solutionCube: VoxelSpace;
|
||||
private dim: i32;
|
||||
private dimX: i32;
|
||||
private dimY: i32;
|
||||
private dimZ: i32;
|
||||
private solutions: SomaSolution[] = new Array<SomaSolution>();
|
||||
private iterations: i32 = 0;
|
||||
constructor(dimension: i32) {
|
||||
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, 0);
|
||||
constructor(dimX: i32, dimY: i32, dimZ: i32) {
|
||||
this.dimX = dimX;
|
||||
this.dimY = dimY;
|
||||
this.dimZ = dimZ;
|
||||
this.solutionCube = new VoxelSpace(0, dimX, dimY, dimZ, 0);
|
||||
}
|
||||
|
||||
solve(polycubes: VoxelSpace[]): void {
|
||||
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.splice(0, this.solutions.length);
|
||||
const combosWithRots: VoxelSpace[][] = new Array<Array<VoxelSpace>>();
|
||||
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>>();
|
||||
combos.push(polycubes[0].getAllPositionsInCube(this.dim));
|
||||
combos.push(polycubes[0].getAllPositionsInPrism(this.dimX, this.dimY, this.dimZ));
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -47,7 +43,7 @@ export default class SomaSolver {
|
||||
nextSoln.addSpace(nextCubeGroup[i]);
|
||||
if (polycubes.length == 1) {
|
||||
this.solutions.push(nextSoln);
|
||||
currentSoln = new SomaSolution(this.dim);
|
||||
currentSoln = new SomaSolution(this.dimX, this.dimY, this.dimZ);
|
||||
return;
|
||||
} else {
|
||||
this.backtrackSolve(fusionAttempt, polycubes.slice(1), nextSoln, depth + 1);
|
||||
@@ -13,19 +13,16 @@ export default class VoxelSpace {
|
||||
private length: i32;
|
||||
private space: i64;
|
||||
private id: i32;
|
||||
private dimx: i32;
|
||||
private dimy: i32;
|
||||
private dimz: i32;
|
||||
private dimX: i32;
|
||||
private dimY: i32;
|
||||
private dimZ: i32;
|
||||
|
||||
constructor(id: i32, dimx: i32, dimy: i32, dimz: i32, space: i64 = 0, cullEmpty: boolean = false) {
|
||||
if (!space) {
|
||||
space = 0;
|
||||
}
|
||||
this.id = id;
|
||||
this.length = dimx * dimy * dimz;
|
||||
this.dimx = dimx;
|
||||
this.dimy = dimy;
|
||||
this.dimz = dimz;
|
||||
this.dimX = dimx;
|
||||
this.dimY = dimy;
|
||||
this.dimZ = dimz;
|
||||
this.space = space;
|
||||
if (cullEmpty) {
|
||||
this.cullEmptySpace();
|
||||
@@ -35,15 +32,15 @@ export default class VoxelSpace {
|
||||
getExtrema(): Extrema {
|
||||
const extrema = new Extrema(
|
||||
0,
|
||||
i32.MAX_VALUE,
|
||||
this.dimX,
|
||||
0,
|
||||
i32.MAX_VALUE,
|
||||
this.dimY,
|
||||
0,
|
||||
i32.MAX_VALUE,
|
||||
this.dimZ,
|
||||
);
|
||||
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 (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
const val = this.at(x, y, z);
|
||||
if (val) {
|
||||
extrema.xMax = Math.max(extrema.xMax, x) as i32;
|
||||
@@ -61,8 +58,8 @@ export default class VoxelSpace {
|
||||
|
||||
private cullEmptySpace(): void {
|
||||
const extrema = this.getExtrema();
|
||||
let index = 0;
|
||||
let newSpace = 0;
|
||||
let index: i32 = 0;
|
||||
let newSpace: i64 = 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++) {
|
||||
@@ -73,9 +70,9 @@ export default class VoxelSpace {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.dimx = extrema.xMax - extrema.xMin + 1;
|
||||
this.dimy = extrema.yMax - extrema.yMin + 1;
|
||||
this.dimz = extrema.zMax - extrema.zMin + 1;
|
||||
this.dimX = extrema.xMax - extrema.xMin + 1;
|
||||
this.dimY = extrema.yMax - extrema.yMin + 1;
|
||||
this.dimZ = extrema.zMax - extrema.zMin + 1;
|
||||
this.space = newSpace;
|
||||
}
|
||||
|
||||
@@ -134,17 +131,19 @@ export default class VoxelSpace {
|
||||
}
|
||||
}
|
||||
|
||||
getAllPositionsInCube(cubeDim: i32): VoxelSpace[] {
|
||||
if ((cubeDim > 0) && (cubeDim % 1 == 0)) {
|
||||
getAllPositionsInPrism(cubeDimX: i32, cubeDimY: i32, cubeDimZ: i32): VoxelSpace[] {
|
||||
const cubePositions: VoxelSpace[] = [];
|
||||
for (let x = 0; x < cubeDim - this.dimx + 1; x++) {
|
||||
for (let y = 0; y < cubeDim - this.dimy + 1; y++) {
|
||||
for (let z = 0; z < cubeDim - this.dimz + 1; z++) {
|
||||
const cubePos = new VoxelSpace(this.id, cubeDim, cubeDim, cubeDim);
|
||||
for (let rotX = 0; rotX < this.dimx; rotX++) {
|
||||
for (let rotY = 0; rotY < this.dimy; rotY++) {
|
||||
for (let rotZ = 0; rotZ < this.dimz; rotZ++) {
|
||||
cubePos.set(x + rotX, y + rotY, z + rotZ, this.at(rotX, rotY, rotZ));
|
||||
if (this.dimX > cubeDimX || this.dimY > cubeDimY || this.dimZ > cubeDimZ) {
|
||||
return cubePositions;
|
||||
}
|
||||
for (let x = 0; x < (cubeDimX - this.dimX + 1); x++) {
|
||||
for (let y = 0; y < (cubeDimY - this.dimY + 1); y++) {
|
||||
for (let z = 0; z < (cubeDimZ - this.dimZ + 1); z++) {
|
||||
const cubePos = new VoxelSpace(this.id, cubeDimX, cubeDimY, cubeDimZ);
|
||||
for (let posX = 0; posX < this.dimX; posX++) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,26 +152,23 @@ export default class VoxelSpace {
|
||||
}
|
||||
}
|
||||
return cubePositions;
|
||||
} else {
|
||||
throw new Error("cubeDim must be a positive integer.");
|
||||
}
|
||||
}
|
||||
|
||||
matches(space: VoxelSpace): boolean {
|
||||
if (space.dimx !== this.dimx) {
|
||||
if (space.dimX !== this.dimX) {
|
||||
return false;
|
||||
}
|
||||
if (space.dimy !== this.dimy) {
|
||||
if (space.dimY !== this.dimY) {
|
||||
return false;
|
||||
}
|
||||
if (space.dimz !== this.dimz) {
|
||||
if (space.dimZ !== this.dimZ) {
|
||||
return false;
|
||||
}
|
||||
return this.space == space.getRaw();
|
||||
}
|
||||
|
||||
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[] {
|
||||
@@ -192,35 +188,35 @@ export default class VoxelSpace {
|
||||
// [0, 0, -1] * [y] = [-z]
|
||||
// [0, 1, 0] [z] [ y]
|
||||
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, 1, 0] * [y] = [ y]
|
||||
// [-1, 0, 0] [z] [-x]
|
||||
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]
|
||||
// [1, 0, 0] * [y] = [ x]
|
||||
// [0, 0, 1] [z] [ z]
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
this.space |= mask;
|
||||
} else {
|
||||
@@ -230,83 +226,83 @@ export default class VoxelSpace {
|
||||
|
||||
rotated90X(): VoxelSpace {
|
||||
let newSpace = 0;
|
||||
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 (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
if (this.at(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 {
|
||||
let newSpace = 0;
|
||||
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 (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
if (this.at(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 {
|
||||
let newSpace = 0;
|
||||
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 (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
if (this.at(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 {
|
||||
const rot = this.rotated90X();
|
||||
this.space = rot.getRaw();
|
||||
this.dimx = rot.dimx;
|
||||
this.dimy = rot.dimy;
|
||||
this.dimz = rot.dimz;
|
||||
this.dimX = rot.dimX;
|
||||
this.dimY = rot.dimY;
|
||||
this.dimZ = rot.dimZ;
|
||||
}
|
||||
|
||||
rot90Y(): void {
|
||||
const rot = this.rotated90Y();
|
||||
this.space = rot.getRaw();
|
||||
this.dimx = rot.dimx;
|
||||
this.dimy = rot.dimy;
|
||||
this.dimz = rot.dimz;
|
||||
this.dimX = rot.dimX;
|
||||
this.dimY = rot.dimY;
|
||||
this.dimZ = rot.dimZ;
|
||||
}
|
||||
|
||||
rot90Z(): void {
|
||||
const rot = this.rotated90Z();
|
||||
this.space = rot.getRaw();
|
||||
this.dimx = rot.dimx;
|
||||
this.dimy = rot.dimy;
|
||||
this.dimz = rot.dimz;
|
||||
this.dimX = rot.dimX;
|
||||
this.dimY = rot.dimY;
|
||||
this.dimZ = rot.dimZ;
|
||||
}
|
||||
|
||||
plus(space: VoxelSpace): VoxelSpace | null {
|
||||
const otherSpace = space.getRaw();
|
||||
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;
|
||||
}
|
||||
|
||||
size(): i32 {
|
||||
let size = 0;
|
||||
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 (let x = 0; x < this.dimX; x++) {
|
||||
for (let y = 0; y < this.dimY; y++) {
|
||||
for (let z = 0; z < this.dimZ; z++) {
|
||||
if (this.at(x, y, z)) {
|
||||
size++;
|
||||
}
|
||||
@@ -316,11 +312,11 @@ export default class VoxelSpace {
|
||||
return size;
|
||||
}
|
||||
|
||||
getAllPermutationsInCubeOfSize(dim: i32): VoxelSpace[] {
|
||||
getAllPermutationsInPrism(prismDimX: i32, prismDimY: i32, prismDimZ: i32): VoxelSpace[] {
|
||||
const rotations = this.getUniqueRotations();
|
||||
let result = new Array<VoxelSpace>();
|
||||
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;
|
||||
}
|
||||
@@ -2,11 +2,11 @@ import SomaSolver from "./SomaSolver";
|
||||
import VoxelSpace from "./VoxelSpace";
|
||||
|
||||
|
||||
export function solve(polycubes: Array<i64>, dim: i32): Int64Array[] {
|
||||
const solver = new SomaSolver(dim);
|
||||
export function solve(polycubes: Array<i64>, dimX: i32, dimY: i32, dimZ: i32): Int64Array[] {
|
||||
const solver = new SomaSolver(dimX, dimY, dimZ);
|
||||
const voxelSpaces = new Array<VoxelSpace>();
|
||||
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);
|
||||
const solutions = solver.getSolutions();
|
||||
@@ -6,7 +6,7 @@ const asyncTask = async () => {
|
||||
|
||||
// You can now use your wasm / as-bind instance!
|
||||
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!
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"scripts": {
|
||||
"asbuild:untouched": "asc assembly/index.ts --target debug",
|
||||
"asbuild:optimized": "asc assembly/index.ts --target release",
|
||||
"asbuild:untouched": "asc assembly/index.ts --exportRuntime --transform as-bind --target debug",
|
||||
"asbuild:optimized": "asc assembly/index.ts --exportRuntime --transform as-bind --target release",
|
||||
"asbuild": "npm run asbuild:untouched && npm run asbuild:optimized",
|
||||
"test": "node tests"
|
||||
},
|
||||
282
src/store.ts
282
src/store.ts
@@ -1,111 +1,119 @@
|
||||
import { derived, writable } 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 = {
|
||||
color: string,
|
||||
rep: bigint,
|
||||
}
|
||||
|
||||
const MAX_DIMS = 5;
|
||||
const MIN_DIMS = 2;
|
||||
|
||||
const store = {
|
||||
polycubes: writable<PolycubeInput[]>([{rep: BigInt(0), color: colorFromIndex(0)}]),
|
||||
somaDimension: writable(3),
|
||||
};
|
||||
const MAX_DIMS = 20;
|
||||
const MIN_DIMS = 1;
|
||||
|
||||
export const solving = writable(false);
|
||||
export const debug = writable(false);
|
||||
export const somaDimX = dimStore(3);
|
||||
export const somaDimY = dimStore(3);
|
||||
export const somaDimZ = dimStore(3);
|
||||
export const polycubes = polycubeStore();
|
||||
export const selectedCube = writable(0);
|
||||
export const 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 activeSolution = writable<number | null>(null);
|
||||
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 = {
|
||||
subscribe: store.somaDimension.subscribe,
|
||||
inc() {
|
||||
if (!get(isMaxDimension)) {
|
||||
store.somaDimension.update((dims: number) => {
|
||||
polycubes.reset(dims + 1);
|
||||
return dims + 1;
|
||||
function dimStore(init: number) {
|
||||
const dimStore = writable(init);
|
||||
return {
|
||||
subscribe: dimStore.subscribe,
|
||||
set(dim: number) {
|
||||
if (dim > MAX_DIMS || dim < MIN_DIMS) {
|
||||
return;
|
||||
}
|
||||
dimStore.set(dim);
|
||||
polycubes.reset();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function polycubeStore() {
|
||||
function freshCube(id: number) {
|
||||
return new VoxelSpaceBigInt({
|
||||
id: id,
|
||||
dims: [get(somaDimX), get(somaDimY), get(somaDimZ)],
|
||||
color: colorFromIndex(id),
|
||||
cullEmpty: false
|
||||
});
|
||||
}
|
||||
const polycubeStore = writable<VoxelSpaceBigInt[]>([freshCube(0)]);
|
||||
return {
|
||||
subscribe: polycubeStore.subscribe,
|
||||
setColor(cubeIndex: number, color: string) {
|
||||
const cubes = get(polycubeStore);
|
||||
cubes[cubeIndex].setColor(color);
|
||||
polycubeStore.set(cubes);
|
||||
},
|
||||
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),
|
||||
}));
|
||||
if (!get(isMaxPolycubes)) {
|
||||
polycubeStore.update((polycubes: VoxelSpaceBigInt[]) =>
|
||||
polycubes.concat(freshCube(polycubes.length)));
|
||||
}
|
||||
},
|
||||
removeCube() {
|
||||
const isMinPolycubes = get(store.polycubes).length <= 1;
|
||||
if (!isMinPolycubes) {
|
||||
store.polycubes.update((polycubes: PolycubeInput[]) => polycubes.splice(0, polycubes.length - 1));
|
||||
if (!get(isMinPolycubes)) {
|
||||
polycubeStore.update((polycubes: VoxelSpaceBigInt[]) => polycubes.splice(0, polycubes.length - 1));
|
||||
}
|
||||
const newLength = get(store.polycubes).length;
|
||||
const newLength = get(polycubeStore).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);
|
||||
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 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);
|
||||
const cubes = get(polycubeStore);
|
||||
cubes[cubeIndex].set(x, y, z, val);
|
||||
polycubeStore.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),
|
||||
});
|
||||
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 darknessCycle = Math.floor(index / 12);
|
||||
const spacing = (360 / 6);
|
||||
@@ -113,5 +121,121 @@ function colorFromIndex(index: number) {
|
||||
let hue = spacing * (index % 6) + offset;
|
||||
const saturation = 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">
|
||||
import {somaDimension, polycubes, selectedCube, showingSolution} from "../store";
|
||||
import {somaDimX, somaDimY, somaDimZ, polycubes, selectedCube, showingSolution} from "../store";
|
||||
import VoxelSpaceBoolean from "../VoxelSpaceBoolean";
|
||||
export let cubeNo: number;
|
||||
|
||||
$: dimension = $somaDimension;
|
||||
$: cube = $polycubes[cubeNo];
|
||||
$: cubeColor = cube.color;
|
||||
$: cube = $polycubes[cubeNo] as VoxelSpaceBoolean;
|
||||
$: cubeColor = cube.getColor();
|
||||
$: currentlyVisualised = $selectedCube === cubeNo && !$showingSolution;
|
||||
let cellStartDragInitialVal: boolean = false;
|
||||
let cellStartDrag: number = 0;
|
||||
let cellDragStartPos: {x: number, y: number} = {x: 0, y: 0};
|
||||
let cellEndDrag: number = 0;
|
||||
let cellDragEndPos: {x: number, y: number} = {x: 0, y: 0};
|
||||
let picker: HTMLInputElement;
|
||||
|
||||
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) {
|
||||
const mask = BigInt(1) << BigInt(cellNo(x, y, z));
|
||||
return (rep & mask) !== BigInt(0);
|
||||
function at(cube: VoxelSpaceBoolean, x: number, y: number, z: number) {
|
||||
return cube.at(x, y, z);
|
||||
}
|
||||
|
||||
function onMouseOverCell(event: MouseEvent, x: number, y: number, z: number) {
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
function onMouseDownCell(event: MouseEvent, x: number, y: number, z: number) {
|
||||
cellStartDrag = cellNo(x, y, z);
|
||||
cellStartDragInitialVal = at(cube.rep, x, y, z);
|
||||
cellStartDragInitialVal = at(cube, x, y, z);
|
||||
cellDragStartPos.x = event.clientX;
|
||||
cellDragStartPos.y = event.clientY;
|
||||
}
|
||||
@@ -56,29 +56,43 @@
|
||||
|
||||
function onClickCube() {
|
||||
showingSolution.set(false);
|
||||
selectedCube.set(cubeNo)
|
||||
selectedCube.set(cubeNo);
|
||||
}
|
||||
|
||||
function onColorChange(event: InputEvent) {
|
||||
polycubes.setColor(cubeNo, event.target.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="cube"
|
||||
class:active={currentlyVisualised}
|
||||
style="--color: {cubeColor}; --dimension: {dimension};"
|
||||
style="--color: {cubeColor};"
|
||||
on:contextmenu|preventDefault
|
||||
on:mousedown={onClickCube}
|
||||
>
|
||||
<div class="header">
|
||||
<h1>Cube: {cubeNo + 1}</h1>
|
||||
{#each {length: dimension} as _, x}
|
||||
<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">
|
||||
{#each {length: dimension} as _, y}
|
||||
{#each {length: $somaDimY} as _, y}
|
||||
<div class="row">
|
||||
{#each {length: dimension} as _, z}
|
||||
{#each {length: $somaDimZ} as _, z}
|
||||
<div
|
||||
class="cell"
|
||||
class:filled={at(cube.rep, z, dimension-1-x, y)}
|
||||
on:mousemove={(event) => onMouseOverCell(event, z, dimension-1-x, y)}
|
||||
on:mousedown={(event) => onMouseDownCell(event, z, dimension-1-x, y)}
|
||||
on:mouseup={(event) => onMouseUpCell(event, z, dimension-1-x, y)}
|
||||
class:filled={at(cube, x, y, z)}
|
||||
on:mousemove={(event) => onMouseOverCell(event, x, y, z)}
|
||||
on:mousedown={(event) => onMouseDownCell(event, x, y, z)}
|
||||
on:mouseup={(event) => onMouseUpCell(event, x, y, z)}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -91,6 +105,27 @@
|
||||
* {
|
||||
--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 {
|
||||
border: 3px solid #ff3e00;
|
||||
}
|
||||
@@ -125,6 +160,8 @@
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
}
|
||||
.layer {
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
<script lang="ts">
|
||||
import {isMaxPolycubes, isMinPolycubes, somaDimension, polycubes, solutions} from "../store";
|
||||
import SomaSolution from "../SomaSolution";
|
||||
import {
|
||||
isMaxPolycubes,
|
||||
isMinPolycubes,
|
||||
polycubes,
|
||||
solutions,
|
||||
colorFromIndex,
|
||||
activeSolution, showingSolution, totalVolume, somaDimX, somaDimY, somaDimZ, debug
|
||||
} from "../store";
|
||||
import SolutionList from "./SolutionList.svelte";
|
||||
import VoxelSpace from "../VoxelSpace";
|
||||
|
||||
$: numCubes = $polycubes.length;
|
||||
$: cubes = $polycubes;
|
||||
@@ -11,57 +16,16 @@
|
||||
let readyToSolve: boolean;
|
||||
let size: number;
|
||||
$: {
|
||||
const dim = $somaDimension as number;
|
||||
const polycubes: VoxelSpace[] = cubes.map(cubeInput => new VoxelSpace(0, [dim, dim, dim], cubeInput.rep));
|
||||
size = polycubes.reduce((prev, cube) => cube.size() + prev, 0);
|
||||
noEmpties = polycubes.reduce((prev, cube) => (cube.size() !== 0) && prev, true);
|
||||
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});
|
||||
size = cubes.reduce((prev, cube) => cube.size() + prev, 0);
|
||||
noEmpties = cubes.reduce((prev, cube) => (cube.size() !== 0) && prev, true);
|
||||
enoughSubcubes = size === $totalVolume;
|
||||
readyToSolve = enoughSubcubes && noEmpties;
|
||||
}
|
||||
|
||||
function genTooltip() {
|
||||
let messages = [];
|
||||
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) {
|
||||
messages.push("You have left some of the polycube inputs empty. Remove them to solve.");
|
||||
@@ -74,34 +38,34 @@
|
||||
<h1>Somaesque</h1>
|
||||
<div class="widgets">
|
||||
<div class="option">
|
||||
<p>Dimension:</p>
|
||||
<p>Dimensions:</p>
|
||||
<div class="choice">
|
||||
<button
|
||||
class:selected={$somaDimension === 2}
|
||||
on:click={() => somaDimension.set(2)}
|
||||
disabled={$somaDimension === 2}>
|
||||
2
|
||||
</button>
|
||||
<button
|
||||
class:selected={$somaDimension === 3}
|
||||
on:click={() => somaDimension.set(3)}
|
||||
disabled={$somaDimension === 3}>
|
||||
3
|
||||
</button>
|
||||
<button
|
||||
class:selected={$somaDimension === 4}
|
||||
on:click={() => somaDimension.set(4)}
|
||||
disabled={$somaDimension === 4}>
|
||||
4
|
||||
</button>
|
||||
X
|
||||
<input
|
||||
type="number"
|
||||
value="3"
|
||||
on:input={(e) => somaDimX.set(e.target.valueAsNumber)}/>
|
||||
Y
|
||||
<input
|
||||
type="number"
|
||||
value="3"
|
||||
on:input={(e) => somaDimY.set(e.target.valueAsNumber)}/>
|
||||
Z
|
||||
<input
|
||||
type="number"
|
||||
value="3"
|
||||
on:input={(e) => somaDimZ.set(e.target.valueAsNumber)}/>
|
||||
{#if $totalVolume > 32}
|
||||
<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>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option">
|
||||
<p>Cubes:</p>
|
||||
<div class="choice">
|
||||
<p>{numCubes}</p>
|
||||
<button on:click={polycubes.removeCube} disabled={$isMinPolycubes}>-</button>
|
||||
<p>{numCubes}</p>
|
||||
<button on:click={polycubes.addCube} disabled={$isMaxPolycubes}>+</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -109,7 +73,7 @@
|
||||
<div class="option">
|
||||
<button
|
||||
class="solve"
|
||||
on:click={solveWasm}
|
||||
on:click={solve}
|
||||
title="{genTooltip(enoughSubcubes, noEmpties, size)}"
|
||||
disabled="{solving || !readyToSolve}">
|
||||
{solving ? "Solving..." : "Solve!"}
|
||||
@@ -121,6 +85,9 @@
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.warn {
|
||||
color: red;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
@@ -130,10 +97,10 @@
|
||||
text-align: center;
|
||||
margin-top: 1em;
|
||||
}
|
||||
button {
|
||||
input {
|
||||
display: inline-block;
|
||||
background-color: #999999;
|
||||
width: 2em;
|
||||
width: 3em;
|
||||
height: 2em;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import {polycubes, activeSolution, showingSolution, solutions} from "../store";
|
||||
import {activeSolution, showingSolution, solutions} from "../store";
|
||||
import SomaSolution from "../SomaSolution";
|
||||
|
||||
$: solutionDisplayed = $solutions[$activeSolution];
|
||||
$: dimension = (solutionDisplayed && solutionDisplayed.getDims?.()[0]) ?? 3;
|
||||
$: solnToShow = $solutions[$activeSolution];
|
||||
$: dims = (solnToShow?.getDims?.()) ?? [3, 3, 3];
|
||||
|
||||
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>
|
||||
|
||||
@@ -14,19 +14,18 @@
|
||||
<div
|
||||
class="cube"
|
||||
class:active={$showingSolution}
|
||||
style="--dimension: {dimension};"
|
||||
on:click={() => showingSolution.set(true)}
|
||||
>
|
||||
<h1>Solution #{$activeSolution + 1}</h1>
|
||||
<div class="center">
|
||||
{#each {length: dimension} as _, x}
|
||||
{#each {length: dims[0]} as _, x}
|
||||
<div class="layer">
|
||||
{#each {length: dimension} as _, y}
|
||||
{#each {length: dims[1]} as _, y}
|
||||
<div class="row">
|
||||
{#each {length: dimension} as _, z}
|
||||
{#each {length: dims[2]} as _, z}
|
||||
<div
|
||||
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}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
@@ -1,36 +1,49 @@
|
||||
<script lang="ts">
|
||||
import PolycubeScene from "./threedee/PolycubeScene.ts";
|
||||
import PolycubeScene from "./threedee/PolycubeScene";
|
||||
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 VoxelSpaceBoolean from "../VoxelSpaceBoolean";
|
||||
|
||||
$: cube = $polycubes[$selectedCube];
|
||||
$: soln = $solutions[$activeSolution];
|
||||
let el: HTMLCanvasElement;
|
||||
let threeTest: PolycubeScene;
|
||||
let scene: PolycubeScene;
|
||||
let loaded: boolean = false;
|
||||
|
||||
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 ($showingSolution) {
|
||||
const colorMap = {};
|
||||
$polycubes.forEach((polycube, i) => colorMap[i] = polycube.color);
|
||||
threeTest?.showSolution(soln, colorMap);
|
||||
scene?.showSolution(soln);
|
||||
} else {
|
||||
threeTest?.showPolycube(cube.rep, $somaDimension, cube.color);
|
||||
scene?.showPolycube(cube);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="top">
|
||||
{#if $activeSolution !== null}
|
||||
<div class="soln2d-container">
|
||||
<Solution2D/>
|
||||
</div>
|
||||
{/if}
|
||||
<canvas
|
||||
bind:this={el}
|
||||
width="640"
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import * as THREE from "three";
|
||||
import type VoxelSpace from "../../VoxelSpace";
|
||||
import type VoxelSpaceBoolean from "../../VoxelSpaceBoolean";
|
||||
import type GeometryManager from "./GeometryManager";
|
||||
import type VoxelSpaceBigInt from "../../VoxelSpaceBigInt";
|
||||
|
||||
export default class PolycubeMesh {
|
||||
private static geometryManager: GeometryManager;
|
||||
private group: THREE.Group;
|
||||
private meshes: THREE.Mesh[] = [];
|
||||
private currentPolycube: bigint = 0n;
|
||||
private currentPolycube: boolean[] | bigint = [];
|
||||
private material: THREE.MeshPhongMaterial;
|
||||
private numActiveCubes: number = 0;
|
||||
private flyDirection: THREE.Vector3 = new THREE.Vector3();
|
||||
|
||||
constructor(polycube: VoxelSpace, color: string) {
|
||||
this.material = new THREE.MeshPhongMaterial({color: 'red', shininess: 100, reflectivity: 100});
|
||||
constructor(polycube: VoxelSpaceBoolean | VoxelSpaceBigInt) {
|
||||
this.material = new THREE.MeshPhongMaterial({color: polycube.getColor(), shininess: 100, reflectivity: 100});
|
||||
this.group = new THREE.Group();
|
||||
this.swapColor(color);
|
||||
this.swapPolycube(polycube);
|
||||
}
|
||||
|
||||
@@ -22,11 +22,7 @@ export default class PolycubeMesh {
|
||||
PolycubeMesh.geometryManager = manager;
|
||||
}
|
||||
|
||||
swapColor(color: string) {
|
||||
this.material.color.set(color);
|
||||
}
|
||||
|
||||
swapPolycube(polycube: VoxelSpace) {
|
||||
swapPolycube(polycube: VoxelSpaceBoolean | VoxelSpaceBigInt) {
|
||||
if (polycube.getRaw() === this.currentPolycube) {
|
||||
return;
|
||||
}
|
||||
@@ -40,10 +36,11 @@ export default class PolycubeMesh {
|
||||
}
|
||||
});
|
||||
this.currentPolycube = polycube.getRaw();
|
||||
this.material.color.set(polycube.getColor());
|
||||
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 neighbourProfile = refPolycube.getDirectNeighbourProfile(x, y, z);
|
||||
const mesh = new THREE.Mesh(
|
||||
@@ -51,9 +48,9 @@ export default class PolycubeMesh {
|
||||
this.material
|
||||
);
|
||||
mesh.position.set(
|
||||
-((dims[0] - 1)/2) + x,
|
||||
-((dims[1] - 1)/2) + y,
|
||||
-((dims[2] - 1)/2) + z,
|
||||
((dims[0] - 1)/2) - x,
|
||||
-((dims[1] - 1)/2) + y,
|
||||
);
|
||||
this.meshes.push(mesh);
|
||||
this.group.add(mesh);
|
||||
|
||||
@@ -2,7 +2,8 @@ import * as THREE from 'three';
|
||||
import type SomaSolution from "../../SomaSolution";
|
||||
import RotationControl from "./RotationControl";
|
||||
import PolycubeMesh from "./PolycubeMesh";
|
||||
import VoxelSpace, {DimensionDef} from "../../VoxelSpace";
|
||||
import type VoxelSpaceBoolean from "../../VoxelSpaceBoolean";
|
||||
import type VoxelSpaceBigInt from "../../VoxelSpaceBigInt";
|
||||
import GeometryManager from "./GeometryManager";
|
||||
|
||||
export default class PolycubeScene {
|
||||
@@ -44,20 +45,19 @@ export default class PolycubeScene {
|
||||
this.camera.lookAt(0, 0, 0);
|
||||
}
|
||||
|
||||
private showPolycube(polycube: bigint, dims: number, color: string) {
|
||||
showPolycube(voxelSpace: VoxelSpaceBoolean) {
|
||||
this.controls.disableFly();
|
||||
const voxelSpace = new VoxelSpace(0, [dims, dims, dims], polycube, true);
|
||||
this.clearScene();
|
||||
this.addPolycube(voxelSpace, color);
|
||||
this.addPolycube(voxelSpace);
|
||||
this.polycubeMeshes[0].center();
|
||||
}
|
||||
|
||||
private showSolution(solution: SomaSolution, colorMap: Record<number, string>) {
|
||||
showSolution(solution: SomaSolution) {
|
||||
this.controls.enableFly();
|
||||
this.clearScene();
|
||||
const pieces = solution.getPieces();
|
||||
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();
|
||||
}
|
||||
|
||||
private addPolycube(voxelSpace: VoxelSpace, color: string) {
|
||||
const newMesh = new PolycubeMesh(voxelSpace, color);
|
||||
private addPolycube(voxelSpace: VoxelSpaceBoolean | VoxelSpaceBigInt) {
|
||||
const newMesh = new PolycubeMesh(voxelSpace);
|
||||
this.polycubeMeshes.push(newMesh);
|
||||
this.cubeScene.add(newMesh.asObj3D());
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"target": "ESNext"
|
||||
},
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": ["./node_modules/*", "./__sapper__/*", "./public/*", "./src/solver/**/*"]
|
||||
"exclude": ["./node_modules/*", "./__sapper__/*", "./public/*", "./src/solver/wasm/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user