Skip to content

Commit

Permalink
canvasを領域内いっぱいに広げて表示するようにした (#71)
Browse files Browse the repository at this point in the history
* ひとまずリサイズはできるようになった

* ちょっと反応が遅く感じるので250msに

* いったん丸ごと再描画する方法で乗り切る

* 完璧にリサイズできた

* 初期値から可変にしておく

* ここサイズ変えるのめんどくさいし中央に出すようにしよう

* maxCanvasSizeで最大幅を制限する

* maxCanvasSizeをUIで設定できるようにした
  • Loading branch information
k-chop authored Jan 13, 2025
1 parent 0b784d2 commit c7f75ca
Show file tree
Hide file tree
Showing 11 changed files with 145 additions and 5 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"culori": "4.0.1",
"d3-color": "3.1.0",
"d3-scale-chromatic": "3.1.0",
"es-toolkit": "1.31.0",
"eventmit": "2.0.4",
"idb-keyval": "6.2.1",
"lodash.throttle": "4.1.1",
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

84 changes: 81 additions & 3 deletions src/camera/camera.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { getCurrentParams } from "@/mandelbrot-state/mandelbrot-state";
import { getStore } from "@/store/store";
import type { IterationBuffer } from "@/types";
import p5 from "p5";
import { getIterationCache } from "../iteration-buffer/iteration-buffer";
import {
getIterationCache,
scaleIterationCacheAroundPoint,
setIterationCache,
translateRectInIterationCache,
} from "../iteration-buffer/iteration-buffer";
import { Rect } from "../math/rect";
import { renderIterationsToPixel } from "../rendering/rendering";
import { getCurrentPalette, markAsRendered, needsRerender } from "./palette";
import {
getCurrentPalette,
markAsRendered,
markNeedsRerender,
needsRerender,
} from "./palette";

let mainBuffer: p5.Graphics;

Expand All @@ -16,7 +27,16 @@ let bufferRect: Rect;
/**
* FIXME: responsiveにするときに任意の値で初期化できるようにする
*/
export const initializeCanvasSize = (w: number = 800, h: number = 800) => {
export const initializeCanvasSize = () => {
const elm = document.getElementById("canvas-wrapper");
let w = 800;
let h = 800;

if (elm) {
w = elm.clientWidth;
h = elm.clientHeight;
}

width = w;
height = h;

Expand All @@ -34,6 +54,64 @@ export const setupCamera = (p: p5, w: number, h: number) => {
console.log("Camera setup done", { width, height });
};

/**
* 画面サイズが変わったときに呼ぶ
*
* やること
* - canvasのリサイズ
* - mainBufferのリサイズ
* - cacheの位置変更(できれば)
*/
export const resizeCamera = (
p: p5,
requestWidth: number,
requestHeight: number,
) => {
const from = getCanvasSize();
console.debug(
`Request resize canvas to w=${requestWidth} h=${requestHeight}, from w=${from.width} h=${from.height}`,
);

const maxSize = getStore("maxCanvasSize");

const w = maxSize === -1 ? requestWidth : Math.min(requestWidth, maxSize);
const h = maxSize === -1 ? requestHeight : Math.min(requestHeight, maxSize);

console.debug(`Resize to: w=${w}, h=${h} (maxCanvasSize=${maxSize})`);

p.resizeCanvas(w, h);

width = w;
height = h;
bufferRect = { x: 0, y: 0, width: w, height: h };

mainBuffer.resizeCanvas(width, height);
clearMainBuffer();

const scaleFactor =
Math.min(width, height) / Math.min(from.width, from.height);

console.debug("Resize scale factor", scaleFactor);

// サイズ差の分trasnlateしてからscale

const offsetX = Math.round((width - from.width) / 2);
const offsetY = Math.round((height - from.height) / 2);
translateRectInIterationCache(-offsetX, -offsetY);

const translated = scaleIterationCacheAroundPoint(
width / 2,
height / 2,
scaleFactor,
width,
height,
);
setIterationCache(translated);
renderToMainBuffer();

markNeedsRerender();
};

export const nextBuffer = (_p: p5): p5.Graphics => {
if (needsRerender()) {
markAsRendered();
Expand Down
2 changes: 1 addition & 1 deletion src/camera/palette.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ let renderNext = true;
/**
* 次に再renderするようマークする
*/
const markNeedsRerender = () => {
export const markNeedsRerender = () => {
renderNext = true;
};

Expand Down
7 changes: 7 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { debounce } from "es-toolkit";
import p5 from "p5";
import React from "react";
import ReactDOMClient from "react-dom/client";
Expand All @@ -8,6 +9,7 @@ import {
p5Draw,
p5MouseReleased,
p5Setup,
resizeTo,
zoomTo,
} from "./p5-adapter/p5-adapter";
import { isInside } from "./p5-adapter/utils";
Expand Down Expand Up @@ -71,6 +73,11 @@ const sketch = (p: p5) => {
p.draw = () => {
p5Draw(p);
};

const debouncedResizeFunc = debounce(() => resizeTo(p), 250);
p.windowResized = () => {
debouncedResizeFunc();
};
};

const entrypoint = () => {
Expand Down
3 changes: 3 additions & 0 deletions src/mandelbrot-state/mandelbrot-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ export const markAsRenderedWithCurrentParams = () => {
export const needsRenderForCurrentParams = () => {
return !isSameParams(lastCalc, currentParams);
};
export const requireNextRender = () => {
lastCalc = { ...currentParams, N: 0 };
};
const isSameParams = (a: MandelbrotParams, b: MandelbrotParams) =>
a.x === b.x && a.y === b.y && a.r === b.r && a.N === b.N && a.mode === b.mode;

Expand Down
10 changes: 10 additions & 0 deletions src/p5-adapter/p5-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
getCanvasSize,
initializeCanvasSize,
nextBuffer,
resizeCamera,
setupCamera,
} from "@/camera/camera";
import {
Expand Down Expand Up @@ -279,6 +280,15 @@ export const zoomTo = (isZoomOut: boolean) => {
});
};

/** wrapper elementの高さを取得してcameraのサイズを変える */
export const resizeTo = (p: p5 = UNSAFE_p5Instance) => {
const elm = document.getElementById("canvas-wrapper");

if (elm) {
resizeCamera(p, elm.clientWidth, elm.clientHeight);
}
};

// ================================================================================================
// 以下、p5.jsのcallback関数
// ================================================================================================
Expand Down
3 changes: 3 additions & 0 deletions src/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type Store = {
workerCount: number;
animationTime: number;
refOrbitWorkerCount: number;
/** -1なら無制限。値があればcanvasの縦横幅はこの値以上にならない */
maxCanvasSize: number;
canvasLocked: boolean;
shouldReuseRefOrbit: boolean;
paletteLength: number;
Expand All @@ -46,6 +48,7 @@ const store: Store = {
workerCount: 2,
animationTime: 0,
refOrbitWorkerCount: 1, // 仮
maxCanvasSize: -1,
// UI state
canvasLocked: false,
// mandelbrot state
Expand Down
2 changes: 2 additions & 0 deletions src/store/sync-storage/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type Settings = {
workerCount: number;
animationTime: number;
animationCycleStep?: number;
maxCanvasSize?: number;
};

export const DEFAULT_WORKER_COUNT =
Expand All @@ -15,6 +16,7 @@ const defaultSettings = {
workerCount: DEFAULT_WORKER_COUNT,
animationTime: 0,
animationCycleStep: 1,
maxCanvasSize: -1,
} satisfies Settings;

export const isSettingField = (key: string): key is keyof Settings =>
Expand Down
2 changes: 1 addition & 1 deletion src/view/canvas-overlay/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const CanvasOverlay = () => {
const progress = useStoreValue("progress");

return (
<div className="size-[800px] p-1">
<div className="p-1">
{typeof progress === "string" && <LoadingSpinner />}
</div>
);
Expand Down
28 changes: 28 additions & 0 deletions src/view/right-sidebar/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ValueSlider } from "@/components/slider-wrapper";
import { resizeTo } from "@/p5-adapter/p5-adapter";
import { Button } from "@/shadcn/components/ui/button";
import { useToast } from "@/shadcn/hooks/use-toast";
import { readPOIListFromClipboard } from "@/store/sync-storage/poi-list";
Expand Down Expand Up @@ -30,6 +31,7 @@ export const Settings = () => {

const zoomRate = useStoreValue("zoomRate");
const workerCount = useStoreValue("workerCount");
const maxCanvasSize = useStoreValue("maxCanvasSize");

const zoomRateValues = ["1.2", "1.5", "2.0", "4.0", "6.0", "10", "50", "100"];
const workerCountValues = createWorkerCountValues();
Expand Down Expand Up @@ -62,6 +64,15 @@ export const Settings = () => {
"43",
"47",
];
const maxCanvasSizeValues = [
"-1",
"128",
"256",
"512",
"800",
"1024",
"2048",
];

const [zoomRatePreview, setZoomRatePreview] = useState(zoomRate);
const [workerCountPreview, setWorkerCountPreview] = useState(workerCount);
Expand All @@ -71,6 +82,8 @@ export const Settings = () => {
const [animationCycleStep, setAnimationCycleStep] = useState(() =>
getStore("animationCycleStep"),
);
const [maxCanvasSizePreview, setMaxCanvasSizePreview] =
useState(maxCanvasSize);

return (
<div className="flex max-w-80 flex-col gap-6">
Expand Down Expand Up @@ -127,6 +140,21 @@ export const Settings = () => {
onValueCommit={(value) => updateStore("animationCycleStep", value)}
/>
</div>
<div>
<div className="mb-1 ml-2">Max Canvas Size: {maxCanvasSizePreview}</div>
<ValueSlider<number>
values={maxCanvasSizeValues}
defaultValue={maxCanvasSize}
valueConverter={(value) => parseInt(value)}
onValueChange={(value) => {
setMaxCanvasSizePreview(value);
}}
onValueCommit={(value) => {
updateStore("maxCanvasSize", value);
resizeTo();
}}
/>
</div>
<div>
<div className="mb-1 ml-2">Import POI List</div>
<Button
Expand Down

0 comments on commit c7f75ca

Please sign in to comment.