Add support for custom box brush geometry in scene documents
This commit is contained in:
@@ -3,8 +3,8 @@ import { createModelInstanceCollisionSettings, createModelInstance, isModelInsta
|
|||||||
import { isProjectAssetKind } from "../assets/project-assets";
|
import { isProjectAssetKind } from "../assets/project-assets";
|
||||||
import { createPlayerStartColliderSettings, createInteractableEntity, normalizeEntityName, createPointLightEntity, createPlayerStartEntity, createSoundEmitterEntity, createSpotLightEntity, createTeleportTargetEntity, createTriggerVolumeEntity, isPlayerStartColliderMode } from "../entities/entity-instances";
|
import { createPlayerStartColliderSettings, createInteractableEntity, normalizeEntityName, createPointLightEntity, createPlayerStartEntity, createSoundEmitterEntity, createSpotLightEntity, createTeleportTargetEntity, createTriggerVolumeEntity, isPlayerStartColliderMode } from "../entities/entity-instances";
|
||||||
import { createPlayAnimationInteractionLink, createPlaySoundInteractionLink, createStopAnimationInteractionLink, createStopSoundInteractionLink, createTeleportPlayerInteractionLink, createToggleVisibilityInteractionLink, isInteractionTriggerKind } from "../interactions/interaction-links";
|
import { createPlayAnimationInteractionLink, createPlaySoundInteractionLink, createStopAnimationInteractionLink, createStopSoundInteractionLink, createTeleportPlayerInteractionLink, createToggleVisibilityInteractionLink, isInteractionTriggerKind } from "../interactions/interaction-links";
|
||||||
import { createBoxBrush, createDefaultFaceUvState, DEFAULT_BOX_BRUSH_ROTATION_DEGREES, isBoxFaceId, isFaceUvRotationQuarterTurns, normalizeBrushName } from "./brushes";
|
import { BOX_VERTEX_IDS, createBoxBrush, createDefaultBoxBrushGeometry, createDefaultFaceUvState, DEFAULT_BOX_BRUSH_ROTATION_DEGREES, isBoxFaceId, isFaceUvRotationQuarterTurns, normalizeBrushName } from "./brushes";
|
||||||
import { BOX_BRUSH_SCENE_DOCUMENT_VERSION, ANIMATION_PLAYBACK_SCENE_DOCUMENT_VERSION, ENTITY_NAMES_SCENE_DOCUMENT_VERSION, ENTITY_SYSTEM_FOUNDATION_SCENE_DOCUMENT_VERSION, FACE_MATERIALS_SCENE_DOCUMENT_VERSION, FIRST_ROOM_POLISH_SCENE_DOCUMENT_VERSION, FOUNDATION_SCENE_DOCUMENT_VERSION, IMPORTED_MODEL_COLLIDERS_SCENE_DOCUMENT_VERSION, LOCAL_LIGHTS_AND_SKYBOX_SCENE_DOCUMENT_VERSION, MODEL_ASSET_PIPELINE_SCENE_DOCUMENT_VERSION, PLAYER_START_COLLIDER_SETTINGS_SCENE_DOCUMENT_VERSION, RUNNER_V1_SCENE_DOCUMENT_VERSION, SPATIAL_AUDIO_SCENE_DOCUMENT_VERSION, SCENE_DOCUMENT_VERSION, TRIGGER_ACTION_TARGET_FOUNDATION_SCENE_DOCUMENT_VERSION, WHITEBOX_FLOAT_TRANSFORM_SCENE_DOCUMENT_VERSION, WORLD_ENVIRONMENT_SCENE_DOCUMENT_VERSION } from "./scene-document";
|
import { BOX_BRUSH_SCENE_DOCUMENT_VERSION, ANIMATION_PLAYBACK_SCENE_DOCUMENT_VERSION, ENTITY_NAMES_SCENE_DOCUMENT_VERSION, ENTITY_SYSTEM_FOUNDATION_SCENE_DOCUMENT_VERSION, FACE_MATERIALS_SCENE_DOCUMENT_VERSION, FIRST_ROOM_POLISH_SCENE_DOCUMENT_VERSION, FOUNDATION_SCENE_DOCUMENT_VERSION, IMPORTED_MODEL_COLLIDERS_SCENE_DOCUMENT_VERSION, LOCAL_LIGHTS_AND_SKYBOX_SCENE_DOCUMENT_VERSION, MODEL_ASSET_PIPELINE_SCENE_DOCUMENT_VERSION, PLAYER_START_COLLIDER_SETTINGS_SCENE_DOCUMENT_VERSION, RUNNER_V1_SCENE_DOCUMENT_VERSION, SPATIAL_AUDIO_SCENE_DOCUMENT_VERSION, SCENE_DOCUMENT_VERSION, TRIGGER_ACTION_TARGET_FOUNDATION_SCENE_DOCUMENT_VERSION, WHITEBOX_FLOAT_TRANSFORM_SCENE_DOCUMENT_VERSION, WHITEBOX_GEOMETRY_SCENE_DOCUMENT_VERSION, WORLD_ENVIRONMENT_SCENE_DOCUMENT_VERSION } from "./scene-document";
|
||||||
import { createDefaultAdvancedRenderingSettings, isAdvancedRenderingShadowMapSize, isAdvancedRenderingShadowType, isAdvancedRenderingToneMappingMode, isWorldBackgroundMode } from "./world-settings";
|
import { createDefaultAdvancedRenderingSettings, isAdvancedRenderingShadowMapSize, isAdvancedRenderingShadowType, isAdvancedRenderingToneMappingMode, isWorldBackgroundMode } from "./world-settings";
|
||||||
function isRecord(value) {
|
function isRecord(value) {
|
||||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||||
@@ -499,6 +499,33 @@ function readBoxBrushFaces(value, label, materials, allowMissingUvState) {
|
|||||||
negZ: readBrushFace(value.negZ, `${label}.negZ`, materials, allowMissingUvState)
|
negZ: readBrushFace(value.negZ, `${label}.negZ`, materials, allowMissingUvState)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function readBoxBrushGeometry(value, label, size) {
|
||||||
|
if (value === undefined) {
|
||||||
|
return createDefaultBoxBrushGeometry(size);
|
||||||
|
}
|
||||||
|
if (!isRecord(value)) {
|
||||||
|
throw new Error(`${label} must be an object.`);
|
||||||
|
}
|
||||||
|
if (!isRecord(value.vertices)) {
|
||||||
|
throw new Error(`${label}.vertices must be an object.`);
|
||||||
|
}
|
||||||
|
const extraVertexKeys = Object.keys(value.vertices).filter((vertexId) => !BOX_VERTEX_IDS.includes(vertexId));
|
||||||
|
if (extraVertexKeys.length > 0) {
|
||||||
|
throw new Error(`${label}.vertices contains unsupported vertex ids: ${extraVertexKeys.join(", ")}.`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
vertices: {
|
||||||
|
negX_negY_negZ: readVec3(value.vertices.negX_negY_negZ, `${label}.vertices.negX_negY_negZ`),
|
||||||
|
posX_negY_negZ: readVec3(value.vertices.posX_negY_negZ, `${label}.vertices.posX_negY_negZ`),
|
||||||
|
negX_posY_negZ: readVec3(value.vertices.negX_posY_negZ, `${label}.vertices.negX_posY_negZ`),
|
||||||
|
posX_posY_negZ: readVec3(value.vertices.posX_posY_negZ, `${label}.vertices.posX_posY_negZ`),
|
||||||
|
negX_negY_posZ: readVec3(value.vertices.negX_negY_posZ, `${label}.vertices.negX_negY_posZ`),
|
||||||
|
posX_negY_posZ: readVec3(value.vertices.posX_negY_posZ, `${label}.vertices.posX_negY_posZ`),
|
||||||
|
negX_posY_posZ: readVec3(value.vertices.negX_posY_posZ, `${label}.vertices.negX_posY_posZ`),
|
||||||
|
posX_posY_posZ: readVec3(value.vertices.posX_posY_posZ, `${label}.vertices.posX_posY_posZ`)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
function readBrushes(value, materials, allowMissingUvState) {
|
function readBrushes(value, materials, allowMissingUvState) {
|
||||||
if (!isRecord(value)) {
|
if (!isRecord(value)) {
|
||||||
throw new Error("brushes must be a record.");
|
throw new Error("brushes must be a record.");
|
||||||
@@ -522,6 +549,7 @@ function readBrushes(value, materials, allowMissingUvState) {
|
|||||||
center,
|
center,
|
||||||
rotationDegrees: readOptionalVec3(brushValue.rotationDegrees, `brushes.${brushId}.rotationDegrees`, DEFAULT_BOX_BRUSH_ROTATION_DEGREES),
|
rotationDegrees: readOptionalVec3(brushValue.rotationDegrees, `brushes.${brushId}.rotationDegrees`, DEFAULT_BOX_BRUSH_ROTATION_DEGREES),
|
||||||
size,
|
size,
|
||||||
|
geometry: readBoxBrushGeometry(brushValue.geometry, `brushes.${brushId}.geometry`, size),
|
||||||
faces: readBoxBrushFaces(brushValue.faces, `brushes.${brushId}.faces`, materials, allowMissingUvState),
|
faces: readBoxBrushFaces(brushValue.faces, `brushes.${brushId}.faces`, materials, allowMissingUvState),
|
||||||
layerId: expectOptionalString(brushValue.layerId, `brushes.${brushId}.layerId`),
|
layerId: expectOptionalString(brushValue.layerId, `brushes.${brushId}.layerId`),
|
||||||
groupId: expectOptionalString(brushValue.groupId, `brushes.${brushId}.groupId`)
|
groupId: expectOptionalString(brushValue.groupId, `brushes.${brushId}.groupId`)
|
||||||
@@ -1199,7 +1227,7 @@ export function migrateSceneDocument(source) {
|
|||||||
interactionLinks: readInteractionLinks(source.interactionLinks)
|
interactionLinks: readInteractionLinks(source.interactionLinks)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (source.version !== WHITEBOX_FLOAT_TRANSFORM_SCENE_DOCUMENT_VERSION) {
|
if (source.version !== WHITEBOX_FLOAT_TRANSFORM_SCENE_DOCUMENT_VERSION && source.version !== WHITEBOX_GEOMETRY_SCENE_DOCUMENT_VERSION) {
|
||||||
throw new Error(`Unsupported scene document version: ${String(source.version)}.`);
|
throw new Error(`Unsupported scene document version: ${String(source.version)}.`);
|
||||||
}
|
}
|
||||||
const materials = readMaterialRegistry(source.materials, "materials");
|
const materials = readMaterialRegistry(source.materials, "materials");
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { cloneMaterialRegistry, createStarterMaterialRegistry } from "../materials/starter-material-library";
|
import { cloneMaterialRegistry, createStarterMaterialRegistry } from "../materials/starter-material-library";
|
||||||
import { createDefaultWorldSettings } from "./world-settings";
|
import { createDefaultWorldSettings } from "./world-settings";
|
||||||
export const SCENE_DOCUMENT_VERSION = 18;
|
export const SCENE_DOCUMENT_VERSION = 19;
|
||||||
|
export const WHITEBOX_GEOMETRY_SCENE_DOCUMENT_VERSION = 19;
|
||||||
export const WHITEBOX_FLOAT_TRANSFORM_SCENE_DOCUMENT_VERSION = 18;
|
export const WHITEBOX_FLOAT_TRANSFORM_SCENE_DOCUMENT_VERSION = 18;
|
||||||
export const PLAYER_START_COLLIDER_SETTINGS_SCENE_DOCUMENT_VERSION = 17;
|
export const PLAYER_START_COLLIDER_SETTINGS_SCENE_DOCUMENT_VERSION = 17;
|
||||||
export const IMPORTED_MODEL_COLLIDERS_SCENE_DOCUMENT_VERSION = 16;
|
export const IMPORTED_MODEL_COLLIDERS_SCENE_DOCUMENT_VERSION = 16;
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import { getModelInstances } from "../assets/model-instances";
|
|||||||
import { cloneWorldSettings } from "../document/world-settings";
|
import { cloneWorldSettings } from "../document/world-settings";
|
||||||
import { getEntityInstances, getPrimaryPlayerStartEntity } from "../entities/entity-instances";
|
import { getEntityInstances, getPrimaryPlayerStartEntity } from "../entities/entity-instances";
|
||||||
import { getBoxBrushBounds } from "../geometry/box-brush";
|
import { getBoxBrushBounds } from "../geometry/box-brush";
|
||||||
|
import { buildBoxBrushDerivedMeshData } from "../geometry/box-brush-mesh";
|
||||||
import { buildGeneratedModelCollider } from "../geometry/model-instance-collider-generation";
|
import { buildGeneratedModelCollider } from "../geometry/model-instance-collider-generation";
|
||||||
import { cloneInteractionLink, getInteractionLinks } from "../interactions/interaction-links";
|
import { cloneInteractionLink, getInteractionLinks } from "../interactions/interaction-links";
|
||||||
import { cloneMaterialDef } from "../materials/starter-material-library";
|
import { cloneMaterialDef } from "../materials/starter-material-library";
|
||||||
import { cloneFaceUvState } from "../document/brushes";
|
import { cloneBoxBrushGeometry, cloneFaceUvState } from "../document/brushes";
|
||||||
import { assertRuntimeSceneBuildable } from "./runtime-scene-validation";
|
import { assertRuntimeSceneBuildable } from "./runtime-scene-validation";
|
||||||
import { FIRST_PERSON_PLAYER_SHAPE } from "./player-collision";
|
import { FIRST_PERSON_PLAYER_SHAPE } from "./player-collision";
|
||||||
function cloneVec3(vector) {
|
function cloneVec3(vector) {
|
||||||
@@ -32,6 +33,7 @@ function buildRuntimeBrush(brush, document) {
|
|||||||
center: cloneVec3(brush.center),
|
center: cloneVec3(brush.center),
|
||||||
rotationDegrees: cloneVec3(brush.rotationDegrees),
|
rotationDegrees: cloneVec3(brush.rotationDegrees),
|
||||||
size: cloneVec3(brush.size),
|
size: cloneVec3(brush.size),
|
||||||
|
geometry: cloneBoxBrushGeometry(brush.geometry),
|
||||||
faces: {
|
faces: {
|
||||||
posX: {
|
posX: {
|
||||||
materialId: brush.faces.posX.materialId,
|
materialId: brush.faces.posX.materialId,
|
||||||
@@ -68,13 +70,15 @@ function buildRuntimeBrush(brush, document) {
|
|||||||
}
|
}
|
||||||
function buildRuntimeCollider(brush) {
|
function buildRuntimeCollider(brush) {
|
||||||
const bounds = getBoxBrushBounds(brush);
|
const bounds = getBoxBrushBounds(brush);
|
||||||
|
const derivedMesh = buildBoxBrushDerivedMeshData(brush);
|
||||||
return {
|
return {
|
||||||
kind: "box",
|
kind: "trimesh",
|
||||||
source: "brush",
|
source: "brush",
|
||||||
brushId: brush.id,
|
brushId: brush.id,
|
||||||
center: cloneVec3(brush.center),
|
center: cloneVec3(brush.center),
|
||||||
rotationDegrees: cloneVec3(brush.rotationDegrees),
|
rotationDegrees: cloneVec3(brush.rotationDegrees),
|
||||||
size: cloneVec3(brush.size),
|
vertices: derivedMesh.colliderVertices,
|
||||||
|
indices: derivedMesh.colliderIndices,
|
||||||
worldBounds: {
|
worldBounds: {
|
||||||
min: cloneVec3(bounds.min),
|
min: cloneVec3(bounds.min),
|
||||||
max: cloneVec3(bounds.max)
|
max: cloneVec3(bounds.max)
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
import { getModelInstances } from "../assets/model-instances";
|
import { getModelInstances } from "../assets/model-instances";
|
||||||
import { assertSceneDocumentIsValid, createDiagnostic, formatSceneDiagnosticSummary } from "../document/scene-document-validation";
|
import { assertSceneDocumentIsValid, createDiagnostic, formatSceneDiagnosticSummary } from "../document/scene-document-validation";
|
||||||
import { getPrimaryPlayerStartEntity } from "../entities/entity-instances";
|
import { getPrimaryPlayerStartEntity } from "../entities/entity-instances";
|
||||||
|
import { validateBoxBrushGeometry } from "../geometry/box-brush-mesh";
|
||||||
import { buildGeneratedModelCollider, ModelColliderGenerationError } from "../geometry/model-instance-collider-generation";
|
import { buildGeneratedModelCollider, ModelColliderGenerationError } from "../geometry/model-instance-collider-generation";
|
||||||
|
function validateBrushGeometry(brush, path, diagnostics) {
|
||||||
|
for (const diagnostic of validateBoxBrushGeometry(brush)) {
|
||||||
|
diagnostics.push(createDiagnostic("error", diagnostic.code, diagnostic.message, `${path}.geometry`, "build"));
|
||||||
|
}
|
||||||
|
}
|
||||||
export function validateRuntimeSceneBuild(document, options) {
|
export function validateRuntimeSceneBuild(document, options) {
|
||||||
const diagnostics = [];
|
const diagnostics = [];
|
||||||
if (options.navigationMode === "firstPerson" && getPrimaryPlayerStartEntity(document.entities) === null) {
|
if (options.navigationMode === "firstPerson" && getPrimaryPlayerStartEntity(document.entities) === null) {
|
||||||
diagnostics.push(createDiagnostic("error", "missing-player-start", "First-person run requires an authored Player Start. Place one or switch to Orbit Visitor.", "entities", "build"));
|
diagnostics.push(createDiagnostic("error", "missing-player-start", "First-person run requires an authored Player Start. Place one or switch to Orbit Visitor.", "entities", "build"));
|
||||||
}
|
}
|
||||||
|
for (const brush of Object.values(document.brushes)) {
|
||||||
|
validateBrushGeometry(brush, `brushes.${brush.id}`, diagnostics);
|
||||||
|
}
|
||||||
for (const modelInstance of getModelInstances(document.modelInstances)) {
|
for (const modelInstance of getModelInstances(document.modelInstances)) {
|
||||||
const path = `modelInstances.${modelInstance.id}.collision.mode`;
|
const path = `modelInstances.${modelInstance.id}.collision.mode`;
|
||||||
const asset = document.assets[modelInstance.assetId];
|
const asset = document.assets[modelInstance.assetId];
|
||||||
|
|||||||
@@ -155,11 +155,6 @@ describe("scene document JSON", () => {
|
|||||||
y: -1.25,
|
y: -1.25,
|
||||||
z: -1.5
|
z: -1.5
|
||||||
};
|
};
|
||||||
brush.size = {
|
|
||||||
x: 2.5,
|
|
||||||
y: 2.25,
|
|
||||||
z: 2.75
|
|
||||||
};
|
|
||||||
|
|
||||||
const document = {
|
const document = {
|
||||||
...createEmptySceneDocument({ name: "Authored Geometry Scene" }),
|
...createEmptySceneDocument({ name: "Authored Geometry Scene" }),
|
||||||
|
|||||||
Reference in New Issue
Block a user