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

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