Add starter environment assets TypeScript file

This commit is contained in:
2026-04-15 04:24:40 +02:00
parent 651265a4d4
commit 2d47ba89a1

View File

@@ -0,0 +1,279 @@
import type { ProjectDocument } from "../document/scene-document";
import {
areWorldBackgroundSettingsEqual,
cloneWorldSettings,
createDefaultWorldSettings,
createDefaultWorldTimeOfDaySettings,
DEFAULT_NIGHT_IMAGE_ENVIRONMENT_INTENSITY,
type WorldBackgroundSettings
} from "../document/world-settings";
import {
cloneProjectAssetRecord,
type ImageAssetRecord,
type ProjectAssetRecord
} from "./project-assets";
const STARTER_ENVIRONMENT_STORAGE_KEY_PREFIX = "starter-image:";
const STARTER_ENVIRONMENT_DIRECTORY = "/starter-environments";
const STARTER_ENVIRONMENT_WIDTH = 1024;
const STARTER_ENVIRONMENT_HEIGHT = 512;
const DEFAULT_DAY_ENVIRONMENT_INTENSITY = 0.85;
export const STARTER_DAY_ENVIRONMENT_ASSET_ID =
"starter-image-winter-soccer-field-day" as const;
export const STARTER_NIGHT_ENVIRONMENT_ASSET_ID =
"starter-image-snow-mountains-night" as const;
interface StarterEnvironmentAssetDefinition {
asset: ImageAssetRecord;
folderName: string;
}
const STARTER_ENVIRONMENT_LIBRARY: readonly StarterEnvironmentAssetDefinition[] =
[
{
asset: {
id: STARTER_DAY_ENVIRONMENT_ASSET_ID,
kind: "image",
sourceName: "Starter Winter Soccer Field Day.hdr",
mimeType: "image/vnd.radiance",
storageKey: `${STARTER_ENVIRONMENT_STORAGE_KEY_PREFIX}${STARTER_DAY_ENVIRONMENT_ASSET_ID}`,
byteLength: 1_474_206,
metadata: {
kind: "image",
width: STARTER_ENVIRONMENT_WIDTH,
height: STARTER_ENVIRONMENT_HEIGHT,
hasAlpha: false,
warnings: []
}
},
folderName: "winter_soccer_field_day"
},
{
asset: {
id: "starter-image-sunset-open-sky",
kind: "image",
sourceName: "Starter Sunset Open Sky.hdr",
mimeType: "image/vnd.radiance",
storageKey: `${STARTER_ENVIRONMENT_STORAGE_KEY_PREFIX}starter-image-sunset-open-sky`,
byteLength: 551_007,
metadata: {
kind: "image",
width: STARTER_ENVIRONMENT_WIDTH,
height: STARTER_ENVIRONMENT_HEIGHT,
hasAlpha: false,
warnings: []
}
},
folderName: "sunset_open_sky"
},
{
asset: {
id: "starter-image-evening-blue-sky",
kind: "image",
sourceName: "Starter Evening Blue Sky.hdr",
mimeType: "image/vnd.radiance",
storageKey: `${STARTER_ENVIRONMENT_STORAGE_KEY_PREFIX}starter-image-evening-blue-sky`,
byteLength: 509_472,
metadata: {
kind: "image",
width: STARTER_ENVIRONMENT_WIDTH,
height: STARTER_ENVIRONMENT_HEIGHT,
hasAlpha: false,
warnings: []
}
},
folderName: "evening_blue_sky"
},
{
asset: {
id: STARTER_NIGHT_ENVIRONMENT_ASSET_ID,
kind: "image",
sourceName: "Starter Snow Mountains Night.hdr",
mimeType: "image/vnd.radiance",
storageKey: `${STARTER_ENVIRONMENT_STORAGE_KEY_PREFIX}${STARTER_NIGHT_ENVIRONMENT_ASSET_ID}`,
byteLength: 1_394_352,
metadata: {
kind: "image",
width: STARTER_ENVIRONMENT_WIDTH,
height: STARTER_ENVIRONMENT_HEIGHT,
hasAlpha: false,
warnings: []
}
},
folderName: "snow_mountains_night"
}
] as const;
function cloneStarterEnvironmentAsset(
definition: StarterEnvironmentAssetDefinition
): ImageAssetRecord {
return cloneProjectAssetRecord(definition.asset) as ImageAssetRecord;
}
function cloneWorldBackground(background: WorldBackgroundSettings) {
if (background.mode === "image") {
return {
mode: "image" as const,
assetId: background.assetId,
environmentIntensity: background.environmentIntensity
};
}
if (background.mode === "solid") {
return {
mode: "solid" as const,
colorHex: background.colorHex
};
}
return {
mode: "verticalGradient" as const,
topColorHex: background.topColorHex,
bottomColorHex: background.bottomColorHex
};
}
function createStarterEnvironmentBackground(
assetId: string,
environmentIntensity: number
) {
return {
mode: "image" as const,
assetId,
environmentIntensity
};
}
function isDefaultDayBackground(background: WorldBackgroundSettings): boolean {
return areWorldBackgroundSettingsEqual(
background,
createDefaultWorldSettings().background
);
}
function isDefaultNightBackground(background: WorldBackgroundSettings): boolean {
return areWorldBackgroundSettingsEqual(
background,
createDefaultWorldTimeOfDaySettings().night.background
);
}
export function createStarterEnvironmentAssetRegistry(): Record<
string,
ImageAssetRecord
> {
return Object.fromEntries(
STARTER_ENVIRONMENT_LIBRARY.map((definition) => [
definition.asset.id,
cloneStarterEnvironmentAsset(definition)
])
);
}
export function isStarterEnvironmentImageAsset(
asset: ProjectAssetRecord | ImageAssetRecord
): asset is ImageAssetRecord {
return (
asset.kind === "image" &&
asset.storageKey.startsWith(STARTER_ENVIRONMENT_STORAGE_KEY_PREFIX)
);
}
function getStarterEnvironmentDefinitionById(assetId: string) {
return (
STARTER_ENVIRONMENT_LIBRARY.find((definition) => definition.asset.id === assetId) ??
null
);
}
function getStarterEnvironmentDefinition(asset: ImageAssetRecord) {
return getStarterEnvironmentDefinitionById(asset.id);
}
export function getStarterEnvironmentTextureUrl(
asset: ImageAssetRecord
): string {
const definition = getStarterEnvironmentDefinition(asset);
if (definition === null) {
throw new Error(`Unknown starter environment image asset ${asset.id}.`);
}
return `${STARTER_ENVIRONMENT_DIRECTORY}/${definition.folderName}/environment.hdr`;
}
export function getStarterEnvironmentPreviewUrl(
asset: ImageAssetRecord
): string {
const definition = getStarterEnvironmentDefinition(asset);
if (definition === null) {
throw new Error(`Unknown starter environment image asset ${asset.id}.`);
}
return `${STARTER_ENVIRONMENT_DIRECTORY}/${definition.folderName}/preview.jpg`;
}
export function mergeStarterEnvironmentAssets(
assets: Record<string, ProjectAssetRecord>
): Record<string, ProjectAssetRecord> {
const mergedAssets: Record<string, ProjectAssetRecord> = {};
for (const definition of STARTER_ENVIRONMENT_LIBRARY) {
mergedAssets[definition.asset.id] = cloneStarterEnvironmentAsset(definition);
}
for (const [assetId, asset] of Object.entries(assets)) {
mergedAssets[assetId] = cloneProjectAssetRecord(asset);
}
return mergedAssets;
}
export function applyStarterEnvironmentAssetsToProjectDocument(
document: ProjectDocument
): ProjectDocument {
const mergedAssets = mergeStarterEnvironmentAssets(document.assets);
const nextScenes = Object.fromEntries(
Object.entries(document.scenes).map(([sceneId, scene]) => {
const nextWorld = cloneWorldSettings(scene.world);
if (isDefaultDayBackground(nextWorld.background)) {
nextWorld.background = createStarterEnvironmentBackground(
STARTER_DAY_ENVIRONMENT_ASSET_ID,
DEFAULT_DAY_ENVIRONMENT_INTENSITY
);
} else {
nextWorld.background = cloneWorldBackground(nextWorld.background);
}
if (isDefaultNightBackground(nextWorld.timeOfDay.night.background)) {
nextWorld.timeOfDay.night.background = createStarterEnvironmentBackground(
STARTER_NIGHT_ENVIRONMENT_ASSET_ID,
DEFAULT_NIGHT_IMAGE_ENVIRONMENT_INTENSITY
);
} else {
nextWorld.timeOfDay.night.background = cloneWorldBackground(
nextWorld.timeOfDay.night.background
);
}
return [
sceneId,
{
...scene,
world: nextWorld
}
];
})
);
return {
...document,
assets: mergedAssets,
scenes: nextScenes
};
}