First 'release'L

This commit is contained in:
Daniel Ledda
2021-07-04 14:00:28 +02:00
parent c950631b5e
commit 0f3696b497
10 changed files with 245 additions and 144 deletions

View File

@@ -3,9 +3,10 @@ import { get } from 'svelte/store';
import SomaSolution from "./SomaSolution";
import VoxelSpaceBigInt from "./VoxelSpaceBigInt";
import type {DimensionDef} from "./VoxelSpaceBoolean";
import PolycubeScene from "./ui/threedee/PolycubeScene";
const MAX_DIMS = 20;
const MIN_DIMS = 1;
export const MAX_DIMS = 20;
export const MIN_DIMS = 1;
export const solving = writable(false);
export const debug = writable(false);
@@ -29,11 +30,19 @@ export const isMinPolycubes = derived(
polycubes,
($polycubes: VoxelSpaceBigInt[]) => $polycubes.length <= 1
);
export const cubeScene = new PolycubeScene();
function dimStore(init: number) {
const dimStore = writable(init);
return {
subscribe: dimStore.subscribe,
inc() {
dimStore.set(get(dimStore) + 1);
},
dec() {
dimStore.set(get(dimStore) - 1);
},
set(dim: number) {
if (dim > MAX_DIMS || dim < MIN_DIMS) {
return;

View File

@@ -38,20 +38,12 @@
color: white;
background: #333333;
}
@media(max-width: 1600px) {
.solutionBodyContainer {
width: 100%;
}
.sidebarContainer {
width: 20%;
}
}
@media(max-width: 1200px) {
.solutionBodyContainer {
width: 100%;
width: calc(100% - 18em);
}
.sidebarContainer {
width: 15em;
.sidebarContainer {
width: 18em;
}
}
</style>

48
src/ui/IncDecNum.svelte Normal file
View File

@@ -0,0 +1,48 @@
<script lang="ts">
export let up: () => void;
export let down: () => void;
export let val: number;
export let upDisabled: boolean;
export let title: string;
export let downDisabled: boolean;
</script>
<div class="container">
{#if title}
<p class="title">{title}</p>
{/if}
<div class="controls">
<button on:click={down} disabled={downDisabled}>-</button>
<p class="val">{val}</p>
<button on:click={up} disabled={upDisabled}>+</button>
</div>
</div>
<style>
.container {
margin: 0 0.5em 0 0.5em;
display: inline-block;
}
.title {
margin-bottom: 0;
}
.val {
font-weight: bold;
margin-left: 0.2em;
margin-right: 0.2em;
}
.controls {
margin-top: 0;
}
.controls > * {
display: inline-block;
}
button:hover:not(:disabled) {
cursor: pointer;
background-color: #c1c1c1;
}
button:disabled {
color: #a7a7a7;
background-color: #616161;
}
</style>

View File

@@ -3,26 +3,84 @@
import CubeInput from "./CubeInput.svelte";
import SolutionViewer from "./SolutionViewer.svelte";
$: numCubes = $polycubes.length;
let showInput = true;
let smallViewport = true;
function onMediaChange() {
smallViewport = queryListWidth.matches || queryListHeight.matches;
}
const queryListWidth = window.matchMedia("(max-width: 1200px)");
const queryListHeight = window.matchMedia("(max-height: 920px)");
queryListWidth.addEventListener("change", onMediaChange);
queryListHeight.addEventListener("change", onMediaChange);
onMediaChange();
</script>
<div class="viewport">
<div class="input-container">
{#each {length: numCubes} as _, cubeNo}
<div class="cube-input">
<div class="padder">
<CubeInput
cubeNo={cubeNo}
/>
</div>
{#if smallViewport}
<div class="tabs">
<div class="tab" class:selected={showInput} on:click="{() => showInput = true}">Input</div>
<div class="tab" class:selected={!showInput} on:click="{() => showInput = false}">3D</div>
</div>
{#if showInput}
<div class="input-container">
{#each {length: numCubes} as _, cubeNo}
<div class="cube-input">
<div class="padder">
<CubeInput
cubeNo={cubeNo}
/>
</div>
</div>
{/each}
</div>
{/each}
</div>
<div class="threedee">
<SolutionViewer/>
</div>
{:else}
<div class="threedee">
<SolutionViewer/>
</div>
{/if}
{:else}
<div class="input-container">
{#each {length: numCubes} as _, cubeNo}
<div class="cube-input">
<div class="padder">
<CubeInput
cubeNo={cubeNo}
/>
</div>
</div>
{/each}
</div>
<div class="threedee">
<SolutionViewer/>
</div>
{/if}
</div>
<style>
.tabs {
height: 3em;
flex: 0 1 auto;
display: flex;
cursor: pointer;
}
.tab {
flex: 1;
border: solid black;
text-align: center;
border-width: 0 1px 1px 1px;
background-color: #555555;
line-height: 3em;
transition: background-color 100ms;
}
.tab:hover {
background-color: #999999;
}
.tab.selected {
background-color: grey;
border-width: 1px 0 0 0;
}
.threedee {
flex: 1 1 auto;
display: flex;
@@ -38,16 +96,17 @@
margin: auto;
}
.input-container {
flex: 0 1 fit-content;
flex: 1 1 auto;
overflow-x: scroll;
display: flex;
flex-flow: row;
}
.viewport {
overflow: scroll;
display: flex;
height: 100%;
align-content: center;
justify-content: center;
justify-content: flex-start;
flex-direction: column;
}
</style>

View File

@@ -4,10 +4,17 @@
isMinPolycubes,
polycubes,
solutions,
colorFromIndex,
activeSolution, showingSolution, totalVolume, somaDimX, somaDimY, somaDimZ, debug
solving,
totalVolume,
somaDimX,
somaDimY,
somaDimZ,
MAX_DIMS,
MIN_DIMS,
solve
} from "../store";
import SolutionList from "./SolutionList.svelte";
import IncDecNum from "./IncDecNum.svelte";
$: numCubes = $polycubes.length;
$: cubes = $polycubes;
@@ -40,21 +47,30 @@
<div class="option">
<p>Dimensions:</p>
<div class="choice">
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)}/>
<IncDecNum
title="X"
val="{$somaDimX}"
upDisabled="{$somaDimX >= MAX_DIMS}"
up="{() => somaDimX.set($somaDimX + 1)}"
downDisabled="{$somaDimX <= MIN_DIMS}"
down="{() => somaDimX.set($somaDimX - 1)}"
/>
<IncDecNum
title="Y"
val="{$somaDimY}"
upDisabled="{$somaDimY >= MAX_DIMS}"
up="{() => somaDimY.set($somaDimY + 1)}"
downDisabled="{$somaDimY <= MIN_DIMS}"
down="{() => somaDimY.set($somaDimY - 1)}"
/>
<IncDecNum
title="Z"
val="{$somaDimZ}"
upDisabled="{$somaDimZ >= MAX_DIMS}"
up="{() => somaDimZ.set($somaDimZ + 1)}"
downDisabled="{$somaDimZ <= MIN_DIMS}"
down="{() => somaDimZ.set($somaDimZ - 1)}"
/>
{#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}
@@ -64,9 +80,13 @@
<div class="option">
<p>Cubes:</p>
<div class="choice">
<button on:click={polycubes.removeCube} disabled={$isMinPolycubes}>-</button>
<p>{numCubes}</p>
<button on:click={polycubes.addCube} disabled={$isMaxPolycubes}>+</button>
<IncDecNum
down="{polycubes.removeCube}"
downDisabled="{$isMinPolycubes}"
up="{polycubes.addCube}"
upDisabled="{$isMaxPolycubes}"
val="{numCubes}"
/>
</div>
</div>
@@ -75,8 +95,8 @@
class="solve"
on:click={solve}
title="{genTooltip(enoughSubcubes, noEmpties, size)}"
disabled="{solving || !readyToSolve}">
{solving ? "Solving..." : "Solve!"}
disabled="{$solving || !readyToSolve}">
{$solving ? "Solving..." : "Solve!"}
</button>
</div>
</div>
@@ -95,7 +115,7 @@
.choice {
display: block;
text-align: center;
margin-top: 1em;
margin-top: 0.5em;
}
input {
display: inline-block;
@@ -108,14 +128,6 @@
color: white;
background-color: #ff3e00;
}
button:hover:not(:disabled) {
cursor: pointer;
background-color: #c1c1c1;
}
button:disabled {
color: #a7a7a7;
background-color: #616161;
}
button.solve {
width: auto;
color: white;
@@ -124,6 +136,7 @@
border-radius: 0.5em;
border-style: none;
margin: 0;
cursor: pointer;
}
button.solve:disabled {
width: auto;
@@ -151,8 +164,8 @@
padding-bottom: 0;
}
.widgets > * {
padding-top: 1em;
padding-bottom: 1em;
padding-top: 0.5em;
padding-bottom: 0.5em;
}
h1 {
margin: 0;

View File

@@ -1,38 +1,35 @@
<script lang="ts">
import PolycubeScene from "./threedee/PolycubeScene";
import {onMount} from "svelte";
import {polycubes, selectedCube, solutions, activeSolution, showingSolution, somaDimX, somaDimY, somaDimZ} from "../store";
import {polycubes, selectedCube, solutions, activeSolution, showingSolution, cubeScene} from "../store";
import Solution2D from "./Solution2D.svelte";
import VoxelSpaceBoolean from "../VoxelSpaceBoolean";
$: cube = $polycubes[$selectedCube];
$: soln = $solutions[$activeSolution];
let el: HTMLCanvasElement;
let el: HTMLDivElement;
let scene: PolycubeScene;
let loaded: boolean = false;
const canvasStyle: Partial<CSSStyleDeclaration> = {
borderRadius: "1em",
};
onMount(() => {
scene = new PolycubeScene(el, () => loaded = true, console.log);
cubeScene.onLoaded(() => {
cubeScene.mount(el);
Object.assign((el.children.item(0) as HTMLElement).style, canvasStyle);
loaded = true;
});
});
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);
scene?.showSolution(soln);
cubeScene.showSolution(soln);
} else {
scene?.showPolycube(cube);
cubeScene.showPolycube(cube);
}
}
}
@@ -44,11 +41,7 @@
<Solution2D/>
</div>
{/if}
<canvas
bind:this={el}
width="640"
height="480"
></canvas>
<div class="stage" bind:this={el}></div>
</div>
<style>
@@ -58,10 +51,7 @@
align-items: center;
}
.soln2d-container {
flex: 0 1 auto;
display: inline-block;
}
canvas {
display: inline-block;
border-radius: 1em;
}
</style>

View File

@@ -6,34 +6,44 @@ import type VoxelSpaceBoolean from "../../VoxelSpaceBoolean";
import type VoxelSpaceBigInt from "../../VoxelSpaceBigInt";
import GeometryManager from "./GeometryManager";
const DEFAULT_WIDTH = 640;
const DEFAULT_HEIGHT = 480;
export default class PolycubeScene {
private renderer: THREE.WebGLRenderer;
private camera: THREE.Camera;
private camera: THREE.PerspectiveCamera;
private mainScene: THREE.Scene;
private polycubeMeshes: PolycubeMesh[] = [];
private controls: RotationControl;
private light: THREE.Light;
private cubeScene: THREE.Scene;
private geomManager: GeometryManager;
private canvas: HTMLCanvasElement;
private loadedCb: () => void = () => {};
private loaded: boolean = false;
constructor(el: HTMLCanvasElement, onReady: () => any, onError: (err: Error) => any) {
this.init(el).then(onReady).catch(onError);
constructor() {
this.init().then(() => this.loadedCb()).catch(e => console.log(e));
}
private async init(el: HTMLCanvasElement) {
this.renderer = new THREE.WebGLRenderer({canvas: el, antialias: true});
this.setupCamera(el.clientWidth / el.clientHeight);
private async init() {
this.canvas = document.createElement("canvas");
this.canvas.width = 0;
this.canvas.height = 0;
this.renderer = new THREE.WebGLRenderer({canvas: this.canvas, antialias: true});
this.setupCamera(this.canvas.clientWidth / this.canvas.clientHeight);
this.setupLight();
this.mainScene = new THREE.Scene();
this.cubeScene = new THREE.Scene();
this.mainScene.add(this.cubeScene, this.camera, this.light);
this.cubeScene.rotateX(Math.PI/4);
this.cubeScene.rotateY(Math.PI/4);
this.controls = new RotationControl(this.cubeScene, this.polycubeMeshes, this.camera, el);
this.controls = new RotationControl(this.cubeScene, this.polycubeMeshes, this.camera, this.canvas);
this.geomManager = await new GeometryManager('../resources/', () => {
requestAnimationFrame((timestamp) => this.render(timestamp));
});
PolycubeMesh.setManager(this.geomManager);
this.loaded = true;
}
private setupCamera(aspect: number) {
@@ -45,6 +55,26 @@ export default class PolycubeScene {
this.camera.lookAt(0, 0, 0);
}
mount(el: HTMLDivElement) {
this.canvas.width = DEFAULT_WIDTH;
this.canvas.height = DEFAULT_HEIGHT;
this.camera.aspect = this.canvas.width / this.canvas.height;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.canvas.width, this.canvas.height);
el.append(this.canvas);
}
onLoaded(cb: () => void) {
if (this.loaded) {
cb();
}
this.loadedCb = cb;
}
isLoaded(): boolean {
return this.loaded;
}
showPolycube(voxelSpace: VoxelSpaceBoolean) {
this.controls.disableFly();
this.clearScene();