Add support for model and image assets in App component
This commit is contained in:
121
src/app/App.tsx
121
src/app/App.tsx
@@ -634,6 +634,7 @@ export function App({ store, initialStatusMessage }: AppProps) {
|
|||||||
const selectedTeleportTarget = selectedEntity?.kind === "teleportTarget" ? selectedEntity : null;
|
const selectedTeleportTarget = selectedEntity?.kind === "teleportTarget" ? selectedEntity : null;
|
||||||
const selectedInteractable = selectedEntity?.kind === "interactable" ? selectedEntity : null;
|
const selectedInteractable = selectedEntity?.kind === "interactable" ? selectedEntity : null;
|
||||||
const projectAssetList = Object.values(editorState.document.assets);
|
const projectAssetList = Object.values(editorState.document.assets);
|
||||||
|
const modelAssetList = projectAssetList.filter(isModelAsset);
|
||||||
const imageAssetList = projectAssetList.filter(isImageAsset);
|
const imageAssetList = projectAssetList.filter(isImageAsset);
|
||||||
const selectedPointLight = selectedEntity?.kind === "pointLight" ? selectedEntity : null;
|
const selectedPointLight = selectedEntity?.kind === "pointLight" ? selectedEntity : null;
|
||||||
const selectedSpotLight = selectedEntity?.kind === "spotLight" ? selectedEntity : null;
|
const selectedSpotLight = selectedEntity?.kind === "spotLight" ? selectedEntity : null;
|
||||||
@@ -658,6 +659,14 @@ export function App({ store, initialStatusMessage }: AppProps) {
|
|||||||
const [uvOffsetDraft, setUvOffsetDraft] = useState(createVec2Draft(createDefaultFaceUvState().offset));
|
const [uvOffsetDraft, setUvOffsetDraft] = useState(createVec2Draft(createDefaultFaceUvState().offset));
|
||||||
const [uvScaleDraft, setUvScaleDraft] = useState(createVec2Draft(createDefaultFaceUvState().scale));
|
const [uvScaleDraft, setUvScaleDraft] = useState(createVec2Draft(createDefaultFaceUvState().scale));
|
||||||
const [entityPositionDraft, setEntityPositionDraft] = useState(createVec3Draft(DEFAULT_ENTITY_POSITION));
|
const [entityPositionDraft, setEntityPositionDraft] = useState(createVec3Draft(DEFAULT_ENTITY_POSITION));
|
||||||
|
const [pointLightColorDraft, setPointLightColorDraft] = useState(DEFAULT_POINT_LIGHT_COLOR_HEX);
|
||||||
|
const [pointLightIntensityDraft, setPointLightIntensityDraft] = useState(String(DEFAULT_POINT_LIGHT_INTENSITY));
|
||||||
|
const [pointLightDistanceDraft, setPointLightDistanceDraft] = useState(String(DEFAULT_POINT_LIGHT_DISTANCE));
|
||||||
|
const [spotLightColorDraft, setSpotLightColorDraft] = useState(DEFAULT_SPOT_LIGHT_COLOR_HEX);
|
||||||
|
const [spotLightIntensityDraft, setSpotLightIntensityDraft] = useState(String(DEFAULT_SPOT_LIGHT_INTENSITY));
|
||||||
|
const [spotLightDistanceDraft, setSpotLightDistanceDraft] = useState(String(DEFAULT_SPOT_LIGHT_DISTANCE));
|
||||||
|
const [spotLightAngleDraft, setSpotLightAngleDraft] = useState(String(DEFAULT_SPOT_LIGHT_ANGLE_DEGREES));
|
||||||
|
const [spotLightDirectionDraft, setSpotLightDirectionDraft] = useState(createVec3Draft(DEFAULT_SPOT_LIGHT_DIRECTION));
|
||||||
const [playerStartYawDraft, setPlayerStartYawDraft] = useState("0");
|
const [playerStartYawDraft, setPlayerStartYawDraft] = useState("0");
|
||||||
const [soundEmitterRadiusDraft, setSoundEmitterRadiusDraft] = useState(String(DEFAULT_SOUND_EMITTER_RADIUS));
|
const [soundEmitterRadiusDraft, setSoundEmitterRadiusDraft] = useState(String(DEFAULT_SOUND_EMITTER_RADIUS));
|
||||||
const [soundEmitterGainDraft, setSoundEmitterGainDraft] = useState(String(DEFAULT_SOUND_EMITTER_GAIN));
|
const [soundEmitterGainDraft, setSoundEmitterGainDraft] = useState(String(DEFAULT_SOUND_EMITTER_GAIN));
|
||||||
@@ -752,6 +761,14 @@ export function App({ store, initialStatusMessage }: AppProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedEntity === null) {
|
if (selectedEntity === null) {
|
||||||
setEntityPositionDraft(createVec3Draft(DEFAULT_ENTITY_POSITION));
|
setEntityPositionDraft(createVec3Draft(DEFAULT_ENTITY_POSITION));
|
||||||
|
setPointLightColorDraft(DEFAULT_POINT_LIGHT_COLOR_HEX);
|
||||||
|
setPointLightIntensityDraft(String(DEFAULT_POINT_LIGHT_INTENSITY));
|
||||||
|
setPointLightDistanceDraft(String(DEFAULT_POINT_LIGHT_DISTANCE));
|
||||||
|
setSpotLightColorDraft(DEFAULT_SPOT_LIGHT_COLOR_HEX);
|
||||||
|
setSpotLightIntensityDraft(String(DEFAULT_SPOT_LIGHT_INTENSITY));
|
||||||
|
setSpotLightDistanceDraft(String(DEFAULT_SPOT_LIGHT_DISTANCE));
|
||||||
|
setSpotLightAngleDraft(String(DEFAULT_SPOT_LIGHT_ANGLE_DEGREES));
|
||||||
|
setSpotLightDirectionDraft(createVec3Draft(DEFAULT_SPOT_LIGHT_DIRECTION));
|
||||||
setPlayerStartYawDraft("0");
|
setPlayerStartYawDraft("0");
|
||||||
setSoundEmitterRadiusDraft(String(DEFAULT_SOUND_EMITTER_RADIUS));
|
setSoundEmitterRadiusDraft(String(DEFAULT_SOUND_EMITTER_RADIUS));
|
||||||
setSoundEmitterGainDraft(String(DEFAULT_SOUND_EMITTER_GAIN));
|
setSoundEmitterGainDraft(String(DEFAULT_SOUND_EMITTER_GAIN));
|
||||||
@@ -770,6 +787,18 @@ export function App({ store, initialStatusMessage }: AppProps) {
|
|||||||
setEntityPositionDraft(createVec3Draft(selectedEntity.position));
|
setEntityPositionDraft(createVec3Draft(selectedEntity.position));
|
||||||
|
|
||||||
switch (selectedEntity.kind) {
|
switch (selectedEntity.kind) {
|
||||||
|
case "pointLight":
|
||||||
|
setPointLightColorDraft(selectedEntity.colorHex);
|
||||||
|
setPointLightIntensityDraft(String(selectedEntity.intensity));
|
||||||
|
setPointLightDistanceDraft(String(selectedEntity.distance));
|
||||||
|
break;
|
||||||
|
case "spotLight":
|
||||||
|
setSpotLightColorDraft(selectedEntity.colorHex);
|
||||||
|
setSpotLightIntensityDraft(String(selectedEntity.intensity));
|
||||||
|
setSpotLightDistanceDraft(String(selectedEntity.distance));
|
||||||
|
setSpotLightAngleDraft(String(selectedEntity.angleDegrees));
|
||||||
|
setSpotLightDirectionDraft(createVec3Draft(selectedEntity.direction));
|
||||||
|
break;
|
||||||
case "playerStart":
|
case "playerStart":
|
||||||
setPlayerStartYawDraft(String(selectedEntity.yawDegrees));
|
setPlayerStartYawDraft(String(selectedEntity.yawDegrees));
|
||||||
break;
|
break;
|
||||||
@@ -820,6 +849,10 @@ export function App({ store, initialStatusMessage }: AppProps) {
|
|||||||
setSunDirectionDraft(createVec3Draft(editorState.document.world.sunLight.direction));
|
setSunDirectionDraft(createVec3Draft(editorState.document.world.sunLight.direction));
|
||||||
}, [editorState.document.world.sunLight.direction]);
|
}, [editorState.document.world.sunLight.direction]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadedImageAssetsRef.current = loadedImageAssets;
|
||||||
|
}, [loadedImageAssets]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadedModelAssetsRef.current = loadedModelAssets;
|
loadedModelAssetsRef.current = loadedModelAssets;
|
||||||
}, [loadedModelAssets]);
|
}, [loadedModelAssets]);
|
||||||
@@ -858,71 +891,113 @@ export function App({ store, initialStatusMessage }: AppProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
const previousLoadedAssets = loadedModelAssetsRef.current;
|
|
||||||
const currentAssets = editorState.document.assets;
|
const currentAssets = editorState.document.assets;
|
||||||
const previousLoadedAssetIds = new Set(Object.keys(previousLoadedAssets));
|
const previousLoadedModelAssets = loadedModelAssetsRef.current;
|
||||||
const nextLoadedAssets: Record<string, LoadedModelAsset> = {};
|
const previousLoadedImageAssets = loadedImageAssetsRef.current;
|
||||||
|
const previousLoadedModelAssetIds = new Set(Object.keys(previousLoadedModelAssets));
|
||||||
|
const previousLoadedImageAssetIds = new Set(Object.keys(previousLoadedImageAssets));
|
||||||
|
const nextLoadedModelAssets: Record<string, LoadedModelAsset> = {};
|
||||||
|
const nextLoadedImageAssets: Record<string, LoadedImageAsset> = {};
|
||||||
const syncErrorMessages: string[] = [];
|
const syncErrorMessages: string[] = [];
|
||||||
|
|
||||||
const syncModelAssets = async () => {
|
const syncAssets = async () => {
|
||||||
if (projectAssetStorage === null) {
|
if (projectAssetStorage === null) {
|
||||||
for (const loadedAsset of Object.values(previousLoadedAssets)) {
|
for (const loadedAsset of Object.values(previousLoadedModelAssets)) {
|
||||||
disposeModelTemplate(loadedAsset.template);
|
disposeModelTemplate(loadedAsset.template);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const loadedAsset of Object.values(previousLoadedImageAssets)) {
|
||||||
|
disposeLoadedImageAsset(loadedAsset);
|
||||||
|
}
|
||||||
|
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
loadedModelAssetsRef.current = {};
|
loadedModelAssetsRef.current = {};
|
||||||
|
loadedImageAssetsRef.current = {};
|
||||||
setLoadedModelAssets({});
|
setLoadedModelAssets({});
|
||||||
|
setLoadedImageAssets({});
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const asset of Object.values(currentAssets)) {
|
for (const asset of Object.values(currentAssets)) {
|
||||||
if (!isModelAsset(asset)) {
|
if (isModelAsset(asset)) {
|
||||||
|
previousLoadedModelAssetIds.delete(asset.id);
|
||||||
|
|
||||||
|
const cachedLoadedAsset = previousLoadedModelAssets[asset.id];
|
||||||
|
|
||||||
|
if (cachedLoadedAsset !== undefined && cachedLoadedAsset.storageKey === asset.storageKey) {
|
||||||
|
nextLoadedModelAssets[asset.id] = cachedLoadedAsset;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
nextLoadedModelAssets[asset.id] = await loadModelAssetFromStorage(projectAssetStorage, asset);
|
||||||
|
} catch (error) {
|
||||||
|
syncErrorMessages.push(`Model asset ${asset.sourceName} could not be restored: ${getErrorMessage(error)}`);
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
previousLoadedAssetIds.delete(asset.id);
|
if (isImageAsset(asset)) {
|
||||||
|
previousLoadedImageAssetIds.delete(asset.id);
|
||||||
|
|
||||||
const cachedLoadedAsset = previousLoadedAssets[asset.id];
|
const cachedLoadedAsset = previousLoadedImageAssets[asset.id];
|
||||||
|
|
||||||
if (cachedLoadedAsset !== undefined && cachedLoadedAsset.storageKey === asset.storageKey) {
|
if (cachedLoadedAsset !== undefined && cachedLoadedAsset.storageKey === asset.storageKey) {
|
||||||
nextLoadedAssets[asset.id] = cachedLoadedAsset;
|
nextLoadedImageAssets[asset.id] = cachedLoadedAsset;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
nextLoadedAssets[asset.id] = await loadModelAssetFromStorage(projectAssetStorage, asset);
|
nextLoadedImageAssets[asset.id] = await loadImageAssetFromStorage(projectAssetStorage, asset);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
syncErrorMessages.push(`Model asset ${asset.sourceName} could not be restored: ${getErrorMessage(error)}`);
|
syncErrorMessages.push(`Image asset ${asset.sourceName} could not be restored: ${getErrorMessage(error)}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
for (const loadedAsset of Object.values(nextLoadedAssets)) {
|
for (const loadedAsset of Object.values(nextLoadedModelAssets)) {
|
||||||
if (previousLoadedAssets[loadedAsset.assetId] !== loadedAsset) {
|
if (previousLoadedModelAssets[loadedAsset.assetId] !== loadedAsset) {
|
||||||
disposeModelTemplate(loadedAsset.template);
|
disposeModelTemplate(loadedAsset.template);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const loadedAsset of Object.values(nextLoadedImageAssets)) {
|
||||||
|
if (previousLoadedImageAssets[loadedAsset.assetId] !== loadedAsset) {
|
||||||
|
disposeLoadedImageAsset(loadedAsset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const assetId of previousLoadedAssetIds) {
|
for (const assetId of previousLoadedModelAssetIds) {
|
||||||
const removedAsset = previousLoadedAssets[assetId];
|
const removedAsset = previousLoadedModelAssets[assetId];
|
||||||
|
|
||||||
if (removedAsset !== undefined) {
|
if (removedAsset !== undefined) {
|
||||||
disposeModelTemplate(removedAsset.template);
|
disposeModelTemplate(removedAsset.template);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadedModelAssetsRef.current = nextLoadedAssets;
|
for (const assetId of previousLoadedImageAssetIds) {
|
||||||
setLoadedModelAssets(nextLoadedAssets);
|
const removedAsset = previousLoadedImageAssets[assetId];
|
||||||
|
|
||||||
|
if (removedAsset !== undefined) {
|
||||||
|
disposeLoadedImageAsset(removedAsset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedModelAssetsRef.current = nextLoadedModelAssets;
|
||||||
|
loadedImageAssetsRef.current = nextLoadedImageAssets;
|
||||||
|
setLoadedModelAssets(nextLoadedModelAssets);
|
||||||
|
setLoadedImageAssets(nextLoadedImageAssets);
|
||||||
setAssetStatusMessage(syncErrorMessages.length === 0 ? null : syncErrorMessages.join(" | "));
|
setAssetStatusMessage(syncErrorMessages.length === 0 ? null : syncErrorMessages.join(" | "));
|
||||||
};
|
};
|
||||||
|
|
||||||
void syncModelAssets();
|
void syncAssets();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user