It all works....

This commit is contained in:
Daniel Ledda
2021-07-03 20:00:13 +02:00
parent c8f37d0d98
commit c950631b5e
45 changed files with 10537 additions and 903 deletions

4
.gitignore vendored
View File

@@ -3,3 +3,7 @@
/.vscode
/.idea
/src/solver/node_modules/
/desktop-dist
/**/node_modules
/src/solver/wasm/build
out/

218
.idea/workspace.xml generated
View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}
}
}

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

@@ -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

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -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.");
}
space = VoxelSpace.boolArrayToBigInt(space)
private color: string;
constructor(options: {id: number, dims: DimensionDef, space?: bigint, cullEmpty: boolean, color?: string}) {
if (!options.space) {
options.space = 0n;
}
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);
});
cubePositions.push(cubePos);
}
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.");
}
return cubePositions;
}
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;
}
}
return this.space === space.getRaw();
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
View 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
View File

30
src/desktop/main.js Normal file
View 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
View 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]);
}
});

View File

@@ -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();
}
}

View 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();
}
}

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

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

View File

@@ -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);
}
}
return result;
setColor(color) {
this.color = color;
}
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,25 +153,23 @@ export default class VoxelSpace {
}
}
}
getAllPositionsInCube(cubeDim) {
if ((cubeDim > 0) && (cubeDim % 1 === 0)) {
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);
});
cubePositions.push(cubePos);
}
}
}
getAllPositionsInPrism(cubeDimX, cubeDimY, cubeDimZ) {
const cubePositions = [];
if (this.dims[0] > cubeDimX || this.dims[1] > cubeDimY || this.dims[2] > cubeDimZ) {
return cubePositions;
}
else {
throw new Error("cubeDim must be a positive integer.");
for (let xOffset = 0; xOffset < (cubeDimX - this.dims[0] + 1); xOffset++) {
for (let yOffset = 0; yOffset < (cubeDimY - this.dims[1] + 1); yOffset++) {
for (let zOffset = 0; zOffset < (cubeDimZ - this.dims[2] + 1); zOffset++) {
const cubePos = new VoxelSpaceBoolean({ id: this.id, dims: [cubeDimX, cubeDimY, cubeDimZ], color: this.color, cullEmpty: false });
this.forEachCell((val, x, y, z) => {
cubePos.set(xOffset + x, yOffset + y, zOffset + z, val);
});
cubePositions.push(cubePos);
}
}
}
return cubePositions;
}
matches(space) {
const otherDims = space.getDims();
@@ -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;
}
}

View 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
View 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
View 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();
}

View 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"
}

View File

@@ -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,

View File

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

View File

@@ -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);

View File

@@ -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,45 +131,44 @@ export default class VoxelSpace {
}
}
getAllPositionsInCube(cubeDim: i32): VoxelSpace[] {
if ((cubeDim > 0) && (cubeDim % 1 == 0)) {
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));
}
getAllPositionsInPrism(cubeDimX: i32, cubeDimY: i32, cubeDimZ: i32): VoxelSpace[] {
const cubePositions: VoxelSpace[] = [];
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));
}
}
cubePositions.push(cubePos);
}
cubePositions.push(cubePos);
}
}
return cubePositions;
} else {
throw new Error("cubeDim must be a positive integer.");
}
return cubePositions;
}
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;
}

View File

@@ -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();

View File

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

View File

@@ -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"
},

View File

@@ -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;
});
}
},
dec() {
if (!get(isMinDimension)) {
store.somaDimension.update((dims: number) => {
polycubes.reset(dims - 1);
return dims - 1;
});
}
},
set(dims: number) {
if (dims <= MAX_DIMS && dims >= MIN_DIMS) {
polycubes.reset(dims);
store.somaDimension.set(dims);
}
}
};
export const polycubes = {
subscribe: store.polycubes.subscribe,
addCube() {
const isMaxPolycubes = get(store.polycubes).length >= get(store.somaDimension) ** 3;
if (!isMaxPolycubes) {
store.polycubes.update((polycubes: PolycubeInput[]) => polycubes.concat({
rep: BigInt(0),
color: colorFromIndex(polycubes.length),
}));
}
},
removeCube() {
const isMinPolycubes = get(store.polycubes).length <= 1;
if (!isMinPolycubes) {
store.polycubes.update((polycubes: PolycubeInput[]) => polycubes.splice(0, polycubes.length - 1));
}
const newLength = get(store.polycubes).length;
if (newLength <= get(selectedCube)) {
selectedCube.set(newLength - 1);
}
},
toggle(cubeIndex: number, x: number, y: number, z: number) {
const dims = get(store.somaDimension);
const mask = BigInt(1) << BigInt(dims ** 2 * x + dims * y + z);
const cubes = get(store.polycubes);
cubes[cubeIndex].rep ^= mask;
store.polycubes.set(cubes);
},
set(cubeIndex: number, val: boolean, x: number, y: number, z: number) {
const dims = get(store.somaDimension);
const mask = BigInt(1) << BigInt(dims ** 2 * x + dims * y + z);
const cubes = get(store.polycubes);
if (val) {
cubes[cubeIndex].rep |= mask
} else {
cubes[cubeIndex].rep &= ~mask
}
store.polycubes.set(cubes);
},
reset(dims: number) {
store.polycubes.update((polycubes: PolycubeInput[]) => {
const result: PolycubeInput[] = [];
for (let i = 0; i < Math.min(polycubes.length, dims**3); i++) {
result.push({
rep: BigInt(0),
color: colorFromIndex(i),
});
function dimStore(init: number) {
const dimStore = writable(init);
return {
subscribe: dimStore.subscribe,
set(dim: number) {
if (dim > MAX_DIMS || dim < MIN_DIMS) {
return;
}
return result;
dimStore.set(dim);
polycubes.reset();
},
}
}
function polycubeStore() {
function freshCube(id: number) {
return new VoxelSpaceBigInt({
id: id,
dims: [get(somaDimX), get(somaDimY), get(somaDimZ)],
color: colorFromIndex(id),
cullEmpty: false
});
}
};
const polycubeStore = writable<VoxelSpaceBigInt[]>([freshCube(0)]);
return {
subscribe: polycubeStore.subscribe,
setColor(cubeIndex: number, color: string) {
const cubes = get(polycubeStore);
cubes[cubeIndex].setColor(color);
polycubeStore.set(cubes);
},
addCube() {
if (!get(isMaxPolycubes)) {
polycubeStore.update((polycubes: VoxelSpaceBigInt[]) =>
polycubes.concat(freshCube(polycubes.length)));
}
},
removeCube() {
if (!get(isMinPolycubes)) {
polycubeStore.update((polycubes: VoxelSpaceBigInt[]) => polycubes.splice(0, polycubes.length - 1));
}
const newLength = get(polycubeStore).length;
if (newLength <= get(selectedCube)) {
selectedCube.set(newLength - 1);
}
},
toggle(cubeIndex: number, x: number, y: number, z: number) {
const cubes = get(polycubeStore);
cubes[cubeIndex].toggle(x, y, z);
polycubeStore.set(cubes);
},
set(cubeIndex: number, val: boolean, x: number, y: number, z: number) {
const cubes = get(polycubeStore);
cubes[cubeIndex].set(x, y, z, val);
polycubeStore.set(cubes);
},
reset() {
polycubeStore.update((polycubes: VoxelSpaceBigInt[]) => {
const result: VoxelSpaceBigInt[] = [];
for (let i = 0; i < Math.min(polycubes.length, get(totalVolume)); i++) {
result.push(freshCube(i));
}
return result;
});
}
}
}
function colorFromIndex(index: number) {
function rgbToHex(rgbStr: string): string {
const sep = rgbStr.indexOf(",") > -1 ? "," : " ";
const rgb = rgbStr.substr(4).split(")")[0].split(sep);
const r = (+rgb[0]).toString(16).padStart(2, "0");
const g = (+rgb[1]).toString(16).padStart(2, "0");
const b = (+rgb[2]).toString(16).padStart(2, "0");
return "#" + r + g + b;
}
function hslToRgb(hslStr: string): string {
const opt = new Option();
opt.style.color = hslStr;
return opt.style.color;
}
export function colorFromIndex(index: number): string {
const colorWheelCycle = Math.floor(index / 6);
const 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);
// }

View File

@@ -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}
>
<h1>Cube: {cubeNo + 1}</h1>
{#each {length: dimension} as _, x}
<div class="header">
<h1>Cube: {cubeNo + 1}</h1>
<div class="colorPickerBtn" on:click={picker.click()}>
<input
bind:this={picker}
class="colorPicker"
type="color"
value="{cubeColor}"
on:change={(event) => onColorChange(event)}/>
</div>
</div>
{#each {length: $somaDimX} as _, x}
<div class="layer">
{#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 {

View File

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

View File

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

View File

@@ -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">
<div class="soln2d-container">
<Solution2D/>
</div>
{#if $activeSolution !== null}
<div class="soln2d-container">
<Solution2D/>
</div>
{/if}
<canvas
bind:this={el}
width="640"

View File

@@ -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);

View File

@@ -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());
}

View File

@@ -5,5 +5,5 @@
"target": "ESNext"
},
"include": ["./src/**/*"],
"exclude": ["./node_modules/*", "./__sapper__/*", "./public/*", "./src/solver/**/*"]
"exclude": ["./node_modules/*", "./__sapper__/*", "./public/*", "./src/solver/wasm/*"]
}