auto-git:

[add] src/geometry/model-instance-collider-generation.ts
 [add] src/runtime-three/rapier-collision-world.ts
 [change] AGENTS.md
 [change] CHAT_CONTEXT.md
 [change] architecture.md
 [change] package.json
 [change] prompts-lite.txt
 [change] prompts.txt
 [change] roadmap.md
 [change] src/assets/model-instances.ts
 [change] src/document/migrate-scene-document.ts
 [change] src/document/scene-document-validation.ts
 [change] src/document/scene-document.ts
 [change] src/runtime-three/first-person-navigation-controller.ts
 [change] src/runtime-three/navigation-controller.ts
 [change] src/runtime-three/runtime-host.ts
 [change] src/runtime-three/runtime-scene-build.ts
 [change] src/runtime-three/runtime-scene-validation.ts
 [change] src/viewport-three/viewport-host.ts
 [change] testing.md
This commit is contained in:
2026-04-04 07:51:38 +02:00
parent d82e37861e
commit 3d1dd3fe63
20 changed files with 1553 additions and 31 deletions

View File

@@ -1,3 +1,4 @@
import type { LoadedModelAsset } from "../assets/gltf-model-import";
import type { Vec3 } from "../core/vector";
import { getModelInstances } from "../assets/model-instances";
import type { BoxBrush, BoxFaceId, FaceUvState } from "../document/brushes";
@@ -5,6 +6,7 @@ import type { SceneDocument } from "../document/scene-document";
import { cloneWorldSettings, type WorldSettings } from "../document/world-settings";
import { getEntityInstances, getPrimaryPlayerStartEntity, type EntityInstance } from "../entities/entity-instances";
import { getBoxBrushBounds } from "../geometry/box-brush";
import { buildGeneratedModelCollider, type GeneratedColliderBounds, type GeneratedModelCollider } from "../geometry/model-instance-collider-generation";
import { cloneInteractionLink, getInteractionLinks, type InteractionLink } from "../interactions/interaction-links";
import { cloneMaterialDef, type MaterialDef } from "../materials/starter-material-library";
import { cloneFaceUvState } from "../document/brushes";
@@ -28,11 +30,14 @@ export interface RuntimeBoxBrushInstance {
export interface RuntimeBoxCollider {
kind: "box";
source: "brush";
brushId: string;
min: Vec3;
max: Vec3;
}
export type RuntimeSceneCollider = RuntimeBoxCollider | GeneratedModelCollider;
export interface RuntimeSceneBounds {
min: Vec3;
max: Vec3;
@@ -132,7 +137,7 @@ export interface RuntimeSceneDefinition {
world: WorldSettings;
localLights: RuntimeLocalLightCollection;
brushes: RuntimeBoxBrushInstance[];
colliders: RuntimeBoxCollider[];
colliders: RuntimeSceneCollider[];
sceneBounds: RuntimeSceneBounds | null;
modelInstances: RuntimeModelInstance[];
entities: RuntimeEntityCollection;
@@ -143,6 +148,7 @@ export interface RuntimeSceneDefinition {
interface BuildRuntimeSceneOptions {
navigationMode?: RuntimeNavigationMode;
loadedModelAssets?: Record<string, LoadedModelAsset>;
}
function cloneVec3(vector: Vec3): Vec3 {
@@ -213,6 +219,7 @@ function buildRuntimeCollider(brush: BoxBrush): RuntimeBoxCollider {
return {
kind: "box",
source: "brush",
brushId: brush.id,
min: cloneVec3(bounds.min),
max: cloneVec3(bounds.max)
@@ -232,21 +239,37 @@ function buildRuntimeModelInstance(modelInstance: SceneDocument["modelInstances"
};
}
function combineColliderBounds(colliders: RuntimeBoxCollider[]): RuntimeSceneBounds | null {
function getColliderBounds(collider: RuntimeSceneCollider): GeneratedColliderBounds {
if (collider.source === "brush") {
return {
min: cloneVec3(collider.min),
max: cloneVec3(collider.max)
};
}
return {
min: cloneVec3(collider.worldBounds.min),
max: cloneVec3(collider.worldBounds.max)
};
}
function combineColliderBounds(colliders: RuntimeSceneCollider[]): RuntimeSceneBounds | null {
if (colliders.length === 0) {
return null;
}
const min = cloneVec3(colliders[0].min);
const max = cloneVec3(colliders[0].max);
const firstBounds = getColliderBounds(colliders[0]);
const min = cloneVec3(firstBounds.min);
const max = cloneVec3(firstBounds.max);
for (const collider of colliders.slice(1)) {
min.x = Math.min(min.x, collider.min.x);
min.y = Math.min(min.y, collider.min.y);
min.z = Math.min(min.z, collider.min.z);
max.x = Math.max(max.x, collider.max.x);
max.y = Math.max(max.y, collider.max.y);
max.z = Math.max(max.z, collider.max.z);
const bounds = getColliderBounds(collider);
min.x = Math.min(min.x, bounds.min.x);
min.y = Math.min(min.y, bounds.min.y);
min.z = Math.min(min.z, bounds.min.z);
max.x = Math.max(max.x, bounds.max.x);
max.y = Math.max(max.y, bounds.max.y);
max.z = Math.max(max.z, bounds.max.z);
}
return {
@@ -396,15 +419,33 @@ function assertNever(value: never): never {
}
export function buildRuntimeSceneFromDocument(document: SceneDocument, options: BuildRuntimeSceneOptions = {}): RuntimeSceneDefinition {
assertRuntimeSceneBuildable(document, options.navigationMode ?? "orbitVisitor");
assertRuntimeSceneBuildable(document, {
navigationMode: options.navigationMode ?? "orbitVisitor",
loadedModelAssets: options.loadedModelAssets
});
const brushes = Object.values(document.brushes).map((brush) => buildRuntimeBrush(brush, document));
const colliders = Object.values(document.brushes).map((brush) => buildRuntimeCollider(brush));
const sceneBounds = combineColliderBounds(colliders);
const colliders: RuntimeSceneCollider[] = Object.values(document.brushes).map((brush) => buildRuntimeCollider(brush));
const modelInstances = getModelInstances(document.modelInstances).map(buildRuntimeModelInstance);
const collections = buildRuntimeSceneCollections(document);
const interactionLinks = getInteractionLinks(document.interactionLinks).map((link) => cloneInteractionLink(link));
const playerStartEntity = getPrimaryPlayerStartEntity(document.entities);
for (const modelInstance of getModelInstances(document.modelInstances)) {
const asset = document.assets[modelInstance.assetId];
if (asset === undefined || asset.kind !== "model") {
continue;
}
const generatedCollider = buildGeneratedModelCollider(modelInstance, asset, options.loadedModelAssets?.[modelInstance.assetId]);
if (generatedCollider !== null) {
colliders.push(generatedCollider);
}
}
const combinedSceneBounds = combineColliderBounds(colliders);
const playerStart =
playerStartEntity === null
? null
@@ -419,14 +460,14 @@ export function buildRuntimeSceneFromDocument(document: SceneDocument, options:
localLights: collections.localLights,
brushes,
colliders,
sceneBounds,
sceneBounds: combinedSceneBounds,
modelInstances,
entities: collections.entities,
interactionLinks,
playerStart,
spawn:
playerStart === null
? buildFallbackSpawn(sceneBounds)
? buildFallbackSpawn(combinedSceneBounds)
: {
source: "playerStart",
entityId: playerStart.entityId,