perf(ui): F8-12 — pixel-space planet sizing + single-copy label/outline layers (#55)
* Planet size formula moves to pixel-space: `pointRadiusBasePx = 2 + 2 * cbrt(size / SIZE_NORMALIZER)`. The on-screen disc now reads ~4-7 px at the reference zoom regardless of how large the world rectangle is — the previous `world-units` formulation blew up on small maps and made Source-class planets swallow their neighbours. * Labels + outlines live in the origin copy only. The 9× replication across torus copies was the dominant cost on a 100+ planet map (Pixi.Text creation + Graphics rebuilds on every zoom step); the origin-copy layout is what the camera-wrap listener guarantees the user actually sees. * `setPlanetLabels` and `setPlanetOutlines` skip Pixi-object rebuilds when the input fingerprint is unchanged — toggle flips and selection changes now keep the existing Text / Graphics instances alive and only repaint the affected pieces. * `renderer.md` updated to the new contract. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -26,11 +26,12 @@ export type WrapMode = "torus" | "no-wrap";
|
||||
// thickening that the old contract promised but never delivered is
|
||||
// gone.
|
||||
//
|
||||
// `pointRadiusWorld` is the opposite intent: a planet's known
|
||||
// `size` produces a base radius in world units, and the renderer
|
||||
// softens its growth with the camera scale through
|
||||
// `PLANET_SIZE_ZOOM_ALPHA` (F8-12 / #31). When `pointRadiusWorld`
|
||||
// is set on a `PointPrim`, `pointRadiusPx` is ignored.
|
||||
// `pointRadiusBasePx` is the opposite intent: a planet's known
|
||||
// `size` produces a base on-screen pixel radius at the "whole world
|
||||
// fits" reference zoom, and the renderer grows it sub-linearly with
|
||||
// the camera scale through `PLANET_SIZE_ZOOM_ALPHA` (F8-12 / #31).
|
||||
// When `pointRadiusBasePx` is set on a `PointPrim`, `pointRadiusPx`
|
||||
// is ignored.
|
||||
export interface Style {
|
||||
fillColor?: number; // 0xRRGGBB
|
||||
fillAlpha?: number; // 0..1
|
||||
@@ -38,7 +39,7 @@ export interface Style {
|
||||
strokeAlpha?: number; // 0..1
|
||||
strokeWidthPx?: number; // screen pixels at any zoom
|
||||
pointRadiusPx?: number; // screen pixels at any zoom (for kind === 'point')
|
||||
pointRadiusWorld?: number; // world units, softened by PLANET_SIZE_ZOOM_ALPHA
|
||||
pointRadiusBasePx?: number; // screen pixels at scaleRef, softened by PLANET_SIZE_ZOOM_ALPHA
|
||||
// strokeDashPx — when set on a `LinePrim`, the line is rendered as
|
||||
// a dashed pattern whose dash and gap are both this length. When
|
||||
// unset (or zero), the stroke is solid. Interpreted in world-unit
|
||||
@@ -231,12 +232,13 @@ export const PLANET_SIZE_ZOOM_ALPHA = 0.33;
|
||||
/**
|
||||
* displayPointRadiusWorld returns the world-space radius the renderer
|
||||
* should draw a `PointPrim` with at the current camera scale. When
|
||||
* `style.pointRadiusWorld` is set (known-size planets), the radius is
|
||||
* the base world radius softened by `PLANET_SIZE_ZOOM_ALPHA` relative
|
||||
* to `scaleRef` — at `scale = scaleRef` it equals the base radius;
|
||||
* zooming in grows it sub-linearly. Otherwise the radius collapses to
|
||||
* `pointRadiusPx / cameraScale` so the on-screen disc stays the same
|
||||
* pixel size regardless of zoom.
|
||||
* `style.pointRadiusBasePx` is set (known-size planets), the radius
|
||||
* is the base pixel size at `scaleRef`, grown by
|
||||
* `(scale / scaleRef)^α` and converted back into world units —
|
||||
* `α = PLANET_SIZE_ZOOM_ALPHA`. At `scale = scaleRef` the visible
|
||||
* pixel size equals the base; a 10× zoom-in only grows it ~2.15×.
|
||||
* Otherwise the radius collapses to `pointRadiusPx / cameraScale` so
|
||||
* the on-screen disc stays the same pixel size regardless of zoom.
|
||||
*
|
||||
* Used by both the renderer (`render.ts:drawPoint`) and the hit-test
|
||||
* (`hit-test.ts:matchPoint`) so the visible disc and the click zone
|
||||
@@ -247,12 +249,17 @@ export function displayPointRadiusWorld(
|
||||
cameraScale: number,
|
||||
scaleRef: number,
|
||||
): number {
|
||||
if (style.pointRadiusWorld !== undefined) {
|
||||
const softening = Math.pow(cameraScale / scaleRef, PLANET_SIZE_ZOOM_ALPHA - 1);
|
||||
return style.pointRadiusWorld * softening;
|
||||
if (cameraScale <= 0) {
|
||||
return style.pointRadiusBasePx ?? style.pointRadiusPx ?? DEFAULT_POINT_RADIUS_PX;
|
||||
}
|
||||
if (style.pointRadiusBasePx !== undefined) {
|
||||
const refScale = scaleRef > 0 ? scaleRef : cameraScale;
|
||||
const screenPx =
|
||||
style.pointRadiusBasePx *
|
||||
Math.pow(cameraScale / refScale, PLANET_SIZE_ZOOM_ALPHA);
|
||||
return screenPx / cameraScale;
|
||||
}
|
||||
const px = style.pointRadiusPx ?? DEFAULT_POINT_RADIUS_PX;
|
||||
if (cameraScale <= 0) return px;
|
||||
return px / cameraScale;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user