Skip to content

Commit

Permalink
add better model for simulations
Browse files Browse the repository at this point in the history
  • Loading branch information
nknapp committed Nov 6, 2024
1 parent 40cc0fd commit 1428e31
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 168 deletions.
82 changes: 37 additions & 45 deletions docs/src/components/Showcase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,79 +5,71 @@ import {
For,
onCleanup,
} from "solid-js";
import { PopupSimulator } from "popup-card-js";
import type { FoldedPaperSpec } from "popup-card-js";
import { type PopupSimulation, PopupSimulator } from "popup-card-js";

export interface ShowcaseProps {
shapes: Record<string, FoldedPaperSpec<string, string, string, string>>;
glues: [
from: { shape: string; segment: string },
to: { shape: string; segment: string },
][];
simulation: PopupSimulation;
}

interface Motors {
label: string;

setAngle(angle: number): void;
interface Motor {
shape: string;
motor: string;
}

export const angles = [-180, -135, -90, -45, 0, 45, 90, 135, 180] as const;

export const Showcase: Component<ShowcaseProps> = (props) => {
const [container, setContainer] = createSignal<HTMLDivElement>();
const [motors, setMotors] = createSignal<Motors[]>([]);

let simulator: PopupSimulator | null = null

createEffect(async () => {
const containerEl = container();
if (containerEl != null) {
console.log("creating simulator");
const simulator = PopupSimulator.createPopupSimulator(containerEl, {
simulator = PopupSimulator.createPopupSimulator(containerEl, {
gravity: 0,
});
for (const [id, shape] of Object.entries(props.shapes)) {
const handle = simulator.addFoldedPaper(id, shape);
for (const motorId of shape.motors ?? []) {
setMotors((motors) => {
return [
...motors,
{
label: `${id}-${motorId}`,
setAngle: (angle) => handle.setFoldAngle(motorId, angle),
},
];
});
}
}
for (const glue of props.glues) {
simulator.addGlue(glue[0], glue[1]);
}
simulator.load(props.simulation);

onCleanup(() => {
simulator.dispose();
simulator?.dispose();
});
}
});

return (
<div>
<For each={motors()}>
<For each={getMotors(props.simulation)}>
{(motor) => (
<div class={"flex items-center gap-2"}>
<label>{motor.label}</label>
<input
min="-180"
max="180"
value="0"
type={"range"}
onInput={(event) =>
motor.setAngle(
(Number(event.currentTarget.value) * Math.PI) / 180,
)
}
/>
<div class={""}>
<div>
{motor.shape} - {motor.motor}
</div>
<div>
<For each={angles}>
{(angle) => <button class={"!m-0 border-white border bg-white/10 px-4"} onClick={() => {
simulator?.fold(motor.shape, motor.motor, angle / 180 * Math.PI )
}}>{angle}</button>}
</For>
</div>
</div>
)}
</For>
<div ref={setContainer} class={"w-full h-96"}></div>
</div>
);
};

function getMotors(simulation: PopupSimulation): Motor[] {
const motors: Motor[] = [];
for (const command of simulation.commands) {
if (command.type !== "addShape" || command.shape.motors == null) {
continue;
}
for (const motor of command.shape.motors) {
motors.push({ shape: command.id, motor });
}
}
return motors;
}
2 changes: 1 addition & 1 deletion docs/src/content/docs/guides/card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function createBaseCard(cardThickness = 0.1) {
// },
fixedSegments: ["b"],
folds: {
one: ["a", "b"],
one: ["b", "a"],
},
motors: ["one"],
color: "green",
Expand Down
21 changes: 12 additions & 9 deletions docs/src/content/docs/guides/example.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ title: Example Guide
description: A guide in my new Starlight docs site.
---

import { Showcase } from "../../../components/Showcase";
import { createVFold } from "./vfold.js";
import { createBaseCard } from "./card.js";
import {Showcase} from "../../../components/Showcase";
import {createVFold} from "./vfold.js";
import {createBaseCard} from "./card.js";

<Showcase
client:load
shapes={{ card: createBaseCard(0.01), vfold: createVFold(0.01) }}
glues={[
[{shape: "card", segment: "b"}, {shape: "vfold", segment: "glueLeft" }],
[{shape: "card", segment: "a"}, {shape: "vfold", segment: "glueRight" }]
]}
client:load
simulation={{
commands: [
{type: "addShape", id: "card", shape: createBaseCard(0.01)},
{type: "addShape", id: "vfold", shape: createVFold(0.01)},
{type: "glue", from: {shape: "card", segment: "b"}, to: {shape: "vfold", segment: "glueLeft"}},
{type: "glue", from: {shape: "card", segment: "a"}, to: {shape: "vfold", segment: "glueRight"}}
]
}}
/>
8 changes: 4 additions & 4 deletions src/FoldedPaper/FoldedPaper.types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
export type Point3d = [x: number, y: number, y: number];

export interface FoldedPaperSpec<
PointId extends string,
PlaneId extends string,
FoldId extends string,
MotorId extends FoldId,
PointId extends string = string,
PlaneId extends string = string,
FoldId extends string = string,
MotorId extends FoldId = FoldId,
> {
/**
* A list of point position of the 3d unfolded shape
Expand Down
188 changes: 88 additions & 100 deletions src/index.manual-test.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,101 @@
import { PopupSimulator } from "./index.ts";
import { PopupSimulation, PopupSimulator } from "./index.ts";

export async function manualTest(container: HTMLDivElement) {
const simulator = PopupSimulator.createPopupSimulator(container, {
gravity: 0,
});
const cardController = simulator.addFoldedPaper("card", {
points3d: {
p1: [-0.5, 0, 0.5],
p2: [-0.5, 0, 0],
p3: [-0.5, 0, -0.5],
p4: [0.5, 0, -0.5],
p5: [0.5, 0, 0],
p6: [0.5, 0, 0.5],
p7: [0, 0, 0.8],
},
segments: {
a: ["p1", "p2", "p5", "p6"],
b: ["p2", "p3", "p4", "p5"],
c: ["p1", "p6", "p7"],
},
fixedSegments: ["b"],
folds: {
one: ["a", "b"],
two: ["a", "c"],
},
motors: ["one", "two"],
color: "green",
thickness: 0.001,
});

simulator.addFoldedPaper("parallelFold", {
points3d: {
f1a: [-0.1, 0.001, 0.05],
f2a: [0.1, 0.001, 0.05],
f1: [-0.1, 0.001, 0.1],
f2: [0.1, 0.001, 0.1],
f3: [-0.1, 0.1, 0.0],
f4: [0.1, 0.1, 0.0],
f5: [-0.1, 0.001, -0.1],
f6: [0.1, 0.001, -0.1],
f5a: [-0.1, 0.001, -0.05],
f6a: [0.1, 0.001, -0.05],
},
segments: {
glueA: ["f1", "f2", "f2a", "f1a"],
glueB: ["f5", "f6", "f6a", "f5a"],
a: ["f1", "f2", "f3", "f4"],
b: ["f5", "f6", "f3", "f4"],
},
folds: {
glueA: ["glueA", "a"],
ab: ["a", "b"],
glueB: ["b", "glueB"],
},
motors: [],
color: "red",
});
simulator.addGlue(
const model: PopupSimulation = {
commands: [
{
shape: "card",
segment: "a",
type: "addShape",
id: "card",
shape: {
points3d: {
p1: [-0.5, 0, 0.5],
p2: [-0.5, 0, 0],
p3: [-0.5, 0, -0.5],
p4: [0.5, 0, -0.5],
p5: [0.5, 0, 0],
p6: [0.5, 0, 0.5],
},
segments: {
a: ["p1", "p2", "p5", "p6"],
b: ["p2", "p3", "p4", "p5"],
},
fixedSegments: ["b"],
folds: {
one: ["a", "b"],
},
motors: ["one", "two"],
color: "green",
thickness: 0.001,
},
},
{
shape: "parallelFold",
segment: "glueA",
type: "addShape",
id: "parallelFold",
shape: {
points3d: {
f1a: [-0.1, 0.001, 0.05],
f2a: [0.1, 0.001, 0.05],
f1: [-0.1, 0.001, 0.1],
f2: [0.1, 0.001, 0.1],
f3: [-0.1, 0.1, 0.0],
f4: [0.1, 0.1, 0.0],
f5: [-0.1, 0.001, -0.1],
f6: [0.1, 0.001, -0.1],
f5a: [-0.1, 0.001, -0.05],
f6a: [0.1, 0.001, -0.05],
},
segments: {
glueA: ["f1", "f2", "f2a", "f1a"],
glueB: ["f5", "f6", "f6a", "f5a"],
a: ["f1", "f2", "f3", "f4"],
b: ["f5", "f6", "f3", "f4"],
},
folds: {
glueA: ["glueA", "a"],
ab: ["a", "b"],
glueB: ["b", "glueB"],
},
motors: [],
color: "red",
},
},
);
simulator.addGlue(
{
shape: "card",
segment: "b",
type: "glue",
from: {
shape: "card",
segment: "a",
},
to: {
shape: "parallelFold",
segment: "glueA",
},
},
{
shape: "parallelFold",
segment: "glueB",
type: "glue",
from: {
shape: "card",
segment: "b",
},
to: {
shape: "parallelFold",
segment: "glueB",
},
},
);
],
};

export async function manualTest(container: HTMLDivElement) {
const simulator = PopupSimulator.createPopupSimulator(container, {
gravity: 0,
});
simulator.load(model);

const controls = document.createElement("div");
controls.innerHTML = `
<button class="border border-black p-1 rounded hover:bg-gray-400">one=0 two=0</button>
<button class="border border-black p-1 rounded hover:bg-gray-400">one=-90 two=90</button>
<button class="border border-black p-1 rounded hover:bg-gray-400">one=-60 two=60</button>
<button class="border border-black p-1 rounded hover:bg-gray-400">one=-60</button>
<button class="border border-black p-1 rounded hover:bg-gray-400">one=-160</button>
<button class="border border-black p-1 rounded hover:bg-gray-400">two=90</button>
<button class="border border-black p-1 rounded hover:bg-gray-400">0</button>
<button class="border border-black p-1 rounded hover:bg-gray-400">-60</button>
<button class="border border-black p-1 rounded hover:bg-gray-400">-90</button>
<button class="border border-black p-1 rounded hover:bg-gray-400">-160</button>
<button class="border border-black p-1 rounded hover:bg-gray-400">-180</button>
`;

controls.style.position = "absolute";
Expand All @@ -97,35 +108,12 @@ export async function manualTest(container: HTMLDivElement) {
container.appendChild(controls);
for (const button of controls.querySelectorAll("button")) {
button.addEventListener("click", () => {
const one = getNumberMatch(button, /one=([-\d]+)/);
if (one != null) {
cardController.setFoldAngle("one", (Number(one) * Math.PI) / 180);
}
const two = getNumberMatch(button, /two=([-\d]+)/);
if (two != null) {
cardController.setFoldAngle("two", (Number(two) * Math.PI) / 180);
}
const angle = Number(button.textContent);
simulator.fold("card", "one", (angle * Math.PI) / 180);
});
}

return () => {
console.log("cleanup");
};
}

function getNumberMatch(
button: HTMLButtonElement,
regex: RegExp,
): number | null {
const match = button.textContent?.match(regex);
if (match == null) {
console.log(`No match:`, button.textContent, regex);
return null;
}
const number = Number(match[1]);
if (isNaN(number)) {
console.log(`Not a number`, button.textContent, regex, match[1]);
return null;
}
return number;
}
Loading

0 comments on commit 1428e31

Please sign in to comment.