Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions packages/polycss/src/api/createPolyScene.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2368,6 +2368,30 @@ describe("createPolyScene", () => {
expect(initialSvg.querySelector("path")?.getAttribute("d")).toBe(initialPathD);
});

it("baked preview can skip shadow rewrites", () => {
scene = makeScene(host, {
textureLighting: "baked",
directionalLight: { direction: [0, 0, 1] },
});
scene.add(makeParseResult([triangle()]), { castShadow: true });
const initialSvg = host.querySelector(".polycss-shadow") as SVGSVGElement;
const initialTransform = initialSvg.style.transform;
const initialPathD = initialSvg.querySelector("path")?.getAttribute("d");
const previewScene = scene as PolySceneHandle & {
previewBakedSolidLighting(next: Pick<PolySceneOptions, "directionalLight" | "ambientLight"> & {
skipShadows?: boolean;
}): boolean;
};

expect(previewScene.previewBakedSolidLighting({
directionalLight: { direction: [1, 0, 1] },
skipShadows: true,
})).toBe(true);

expect(initialSvg.style.transform).toBe(initialTransform);
expect(initialSvg.querySelector("path")?.getAttribute("d")).toBe(initialPathD);
});

it("non-shadow helper movement does not overwrite baked preview shadows", () => {
scene = makeScene(host, {
textureLighting: "baked",
Expand Down
8 changes: 6 additions & 2 deletions packages/polycss/src/api/createPolyScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1548,11 +1548,15 @@ export function createPolyScene(
}

function previewBakedSolidLighting(
next: Pick<Omit<PolySceneOptions, "camera">, "directionalLight" | "ambientLight">,
next: Pick<Omit<PolySceneOptions, "camera">, "directionalLight" | "ambientLight"> & {
skipShadows?: boolean;
},
): boolean {
if ((currentOptions.textureLighting ?? "baked") !== "baked") return false;
applyLightingVars(sceneEl, { ...currentOptions, ...next });
if (next.directionalLight?.direction) emitSceneShadows(next.directionalLight.direction as Vec3);
if (!next.skipShadows && next.directionalLight?.direction) {
emitSceneShadows(next.directionalLight.direction as Vec3);
}
let installed = false;
for (const entry of meshes) {
applyPreviewMeshLightVars(entry, next);
Expand Down
41 changes: 35 additions & 6 deletions website/src/components/GalleryWorkbench/GalleryWorkbench.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ const RESPONSIVE_ZOOM_BOTTOM_RESERVE = 72;
const RESPONSIVE_ZOOM_MIN_SCALE = 0.42;
const RESPONSIVE_SHADOW_EXTEND_BASE = 3200;
const RESPONSIVE_SHADOW_EXTEND_MIN = 2000;
const RESPONSIVE_SHADOW_PREVIEW_EXTEND_BASE = 1800;
const RESPONSIVE_SHADOW_PREVIEW_EXTEND_MIN = 800;

function clamp(value: number, min: number, max: number): number {
if (!Number.isFinite(value)) return min;
Expand All @@ -186,15 +188,38 @@ function responsiveZoomScaleForViewport(width: number, height: number): number {
return clamp(Math.min(widthScale, heightScale), RESPONSIVE_ZOOM_MIN_SCALE, 1);
}

function responsiveShadowMaxExtend(value: number, viewportScale: number): number {
function responsiveCappedShadowMaxExtend(
value: number,
viewportScale: number,
base: number,
min: number,
): number {
if (viewportScale >= 0.995) return value;
const cap = Math.max(
RESPONSIVE_SHADOW_EXTEND_MIN,
Math.round(RESPONSIVE_SHADOW_EXTEND_BASE * viewportScale),
min,
Math.round(base * viewportScale),
);
return Math.min(value, cap);
}

function responsiveShadowMaxExtend(value: number, viewportScale: number): number {
return responsiveCappedShadowMaxExtend(
value,
viewportScale,
RESPONSIVE_SHADOW_EXTEND_BASE,
RESPONSIVE_SHADOW_EXTEND_MIN,
);
}

function responsiveShadowPreviewMaxExtend(value: number, viewportScale: number): number {
return responsiveCappedShadowMaxExtend(
value,
viewportScale,
RESPONSIVE_SHADOW_PREVIEW_EXTEND_BASE,
RESPONSIVE_SHADOW_PREVIEW_EXTEND_MIN,
);
}

function initialResponsiveZoomScale(): number {
if (typeof window === "undefined") return 1;
return responsiveZoomScaleForViewport(window.innerWidth, window.innerHeight);
Expand Down Expand Up @@ -805,17 +830,21 @@ export default function GalleryWorkbench() {
markSceneRouteDirty();
setSceneOptions((current) => ({ ...current, ...partial }));
}, [markSceneRouteDirty]);
const responsiveZoomScale = useResponsiveViewportZoomScale(viewportRef);
const canPreviewSceneOptions = useCallback(
(options: SceneOptionsState) =>
options.renderer === "vanilla" && transientSceneHandleRef.current !== null,
[],
);
const previewSceneOptions = useCallback((options: SceneOptionsState) => {
transientSceneHandleRef.current?.applyLightOptions(options);
}, []);
const previewShadow = responsiveZoomScale >= 0.995 || options.textureLighting === "dynamic";
transientSceneHandleRef.current?.applyLightOptions({
...options,
shadowMaxExtend: responsiveShadowPreviewMaxExtend(options.shadowMaxExtend, responsiveZoomScale),
}, { shadow: previewShadow });
}, [responsiveZoomScale]);

const { handleCameraChange } = useGuiCameraSync({ setSceneOptions });
const responsiveZoomScale = useResponsiveViewportZoomScale(viewportRef);
const renderSceneOptions = useMemo<SceneOptionsState>(() => {
const shadowMaxExtend = responsiveShadowMaxExtend(sceneOptions.shadowMaxExtend, responsiveZoomScale);
if (responsiveZoomScale === 1 && shadowMaxExtend === sceneOptions.shadowMaxExtend) return sceneOptions;
Expand Down
14 changes: 11 additions & 3 deletions website/src/components/VanillaScene/VanillaScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -283,13 +283,14 @@ function bakedLightingSignature(

export interface VanillaSceneTransientHandle {
applySceneOptions(options: SceneOptionsState): void;
applyLightOptions(options: SceneOptionsState): void;
applyLightOptions(options: SceneOptionsState, preview?: { shadow?: boolean }): void;
}

type BakedSolidLightingPreviewSceneHandle = PolySceneHandle & {
previewBakedSolidLighting?: (next: {
directionalLight?: PolyDirectionalLight;
ambientLight?: PolyAmbientLight;
skipShadows?: boolean;
}) => boolean;
commitBakedSolidLighting?: () => boolean;
};
Expand Down Expand Up @@ -411,16 +412,23 @@ export function VanillaScene({
}
}, []);

const applyTransientLightOptions = useCallback((nextOptions: SceneOptionsState): void => {
const applyTransientLightOptions = useCallback((nextOptions: SceneOptionsState, preview?: { shadow?: boolean }): void => {
const scene = sceneRef.current;
if (!scene) return;
const nextDirectionalLight = directionalFromOptions(nextOptions);
const nextShadow = { maxExtend: nextOptions.shadowMaxExtend, lift: GALLERY_SHADOW_LIFT };
const previewShadow = preview?.shadow !== false;
if (nextOptions.textureLighting === "dynamic") {
scene.setOptions({ directionalLight: nextDirectionalLight });
scene.setOptions({
directionalLight: nextDirectionalLight,
...(previewShadow ? { shadow: nextShadow } : {}),
});
} else {
if (previewShadow) scene.setOptions({ shadow: nextShadow });
(scene as BakedSolidLightingPreviewSceneHandle).previewBakedSolidLighting?.({
directionalLight: nextDirectionalLight,
ambientLight: ambientFromOptions(nextOptions),
skipShadows: !previewShadow,
});
}
lightHandleRef.current?.setTransform({
Expand Down
Loading