prendy
Version:
Make games with prerendered backdrops using babylonjs and repond
130 lines (129 loc) • 6.97 kB
JavaScript
import { Matrix, Sound, Vector3 } from "@babylonjs/core";
import { forEach } from "chootils/dist/loops";
import { useEffect } from "react";
import { getRefs, getState, setState } from "repond";
import { getProjectionMatrixCustomSize, slateSize } from "../../../helpers/babylonjs/slate";
import { meta } from "../../../meta";
import { setGlobalState } from "../../prendyUtils/global";
import { getAbsoluteRotation } from "../getAbsoluteRotation";
import { getScene } from "../getSceneOrEngineUtils";
import { useModelFile } from "../useModelFile";
import { loadBackdropTexturesForPlace, loadProbeImagesForPlace, makeCameraFromModel } from "./utils";
function getPositionOnSlate(position, camera) {
const positionOnSlate = Vector3.Project(position, Matrix.Identity(), camera
.getViewMatrix()
// .multiply(currentCamera.getProjectionMatrix()),
.multiply(getProjectionMatrixCustomSize(camera, slateSize)), camera.viewport.toGlobal(slateSize.x, slateSize.y));
return positionOnSlate;
}
export function usePlace(placeName) {
const { placeInfoByName, soundFiles, prendyOptions } = meta.assets;
const placesRefs = getRefs().places;
const placeInfo = placeInfoByName[placeName];
const { modelFile, cameraNames, floorNames, triggerNames, spotNames, soundspotNames, wallNames } = placeInfo;
const placeRefs = placesRefs[placeName];
const scene = getScene();
const { container, meshes, cameras, transformNodes } = useModelFile(modelFile);
// this runs after useModelFile finished
useEffect(() => {
setGlobalState({ newPlaceModelLoaded: true });
if (!scene)
return;
forEach(cameraNames, (cameraName) => {
const camRef = placeRefs.camsRefs[cameraName];
camRef.camera = makeCameraFromModel(cameras[cameraName], scene);
// Find the focus nodes
const focusYNode = transformNodes[`${cameraName}_focus_y`];
const focusXNode = transformNodes[`${cameraName}_focus_x`];
const focusNode = transformNodes[`${cameraName}_focus`];
const focusNodePosition = focusNode?.getAbsolutePosition();
const focusXNodePosition = focusXNode?.getAbsolutePosition();
const focusYNodePosition = focusYNode?.getAbsolutePosition();
// find the position on the slate
const focusPositionOnSlate = focusNodePosition && getPositionOnSlate(focusNodePosition, camRef.camera);
const focusXPositionOnSlate = focusXNodePosition && getPositionOnSlate(focusXNodePosition, camRef.camera);
const focusYPositionOnSlate = focusYNodePosition && getPositionOnSlate(focusYNodePosition, camRef.camera);
const finalSlateFocusX = focusXPositionOnSlate?.x ?? focusPositionOnSlate?.x ?? null;
const finalSlateFocusY = focusYPositionOnSlate?.y ?? focusPositionOnSlate?.y ?? null;
camRef.focusPointOnSlate = { x: finalSlateFocusX, y: finalSlateFocusY };
});
// Load any models for this place that weren't already loaded
const { modelNamesLoaded } = getState().global.main;
forEach(prendyOptions.modelNamesByPlace[placeName], (modelName) => {
if (!modelNamesLoaded.includes(modelName)) {
setState({ models: { [modelName]: { wantToLoad: true } } });
}
});
loadBackdropTexturesForPlace(placeName)
.then(() => {
setGlobalState({ newPlaceVideosLoaded: true });
})
.catch((error) => console.warn("error loading backdrops", error));
loadProbeImagesForPlace(placeName)
.then(() => setGlobalState({ newPlaceProbesLoaded: true }))
.catch((error) => console.warn("error loading probes", error));
placeRefs.rootMesh = meshes["__root__"];
function setupWallOrFloor(mesh) {
mesh.checkCollisions = true;
mesh.collisionGroup = 11;
mesh.useOctreeForCollisions = true;
mesh.isVisible = false;
mesh.freezeWorldMatrix();
mesh.doNotSyncBoundingInfo = true;
}
forEach(floorNames, (name) => setupWallOrFloor(meshes[name]));
forEach(wallNames, (name) => {
setupWallOrFloor(meshes[name]);
placeRefs.wallMeshes[name] = meshes[name];
});
forEach(triggerNames, (name) => {
meshes[name].isVisible = false;
meshes[name].freezeWorldMatrix();
meshes[name].doNotSyncBoundingInfo = true;
// const { material } = meshes[name];
// if (material) material.alpha = 0.5;
placeRefs.triggerMeshes[name] = meshes[name];
});
forEach(spotNames, (name) => {
const spotNode = transformNodes[name];
placeRefs.spotPositions[name] = spotNode.getAbsolutePosition();
spotNode.computeWorldMatrix(true);
placeRefs.spotRotations[name] = getAbsoluteRotation(spotNode);
spotNode.freezeWorldMatrix();
});
forEach(soundspotNames, (name) => {
const soundspotBaseName = name.split(".")[0];
const newSound = new Sound(name, soundFiles[soundspotBaseName], // `${soundspotBaseName}.mp3`,
scene, null, { loop: true, autoplay: true, spatialSound: true });
newSound.setPosition(transformNodes[name].getAbsolutePosition());
placeRefs.soundspotSounds[name] = newSound;
});
forEach(container.meshes, (loopedMesh) => {
// NOTE this used to check for "camBox_" but now it checks for "cambox_"
// Also, it checked for a "." but now will cehck for an underscore with a number after it
if (loopedMesh.name.includes("cambox_") || loopedMesh.name.includes("camBox_")) {
let loopedCamNameWithNumber = loopedMesh.name.replace("cambox_", "");
loopedCamNameWithNumber = loopedCamNameWithNumber.replace("camBox_", "");
// split at the last underscore with a number after it
let cameraName = loopedCamNameWithNumber.replace(/_\d+$/, "");
// If the camBox has a "." in it, get the number after the last "."
if (cameraName.includes(".")) {
cameraName = cameraName.split(".")[0];
}
loopedMesh.isVisible = false;
loopedMesh.freezeWorldMatrix();
loopedMesh.doNotSyncBoundingInfo = true;
const camRef = placeRefs.camsRefs[cameraName];
if (camRef)
camRef.camCubeMeshes.push(loopedMesh);
}
});
return () => {
forEach(soundspotNames, (loopedName) => {
placeRefs.soundspotSounds[loopedName]?.dispose();
});
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [placeName]);
return { container, meshes, cameras };
}