UNPKG

prendy

Version:

Make games with prerendered backdrops using babylonjs and repond

232 lines (231 loc) 9.83 kB
import { Vector3 } from "@babylonjs/core"; import { keyBy } from "chootils/dist/arrays"; import { breakableForEach, forEach } from "chootils/dist/loops"; import { subtractPoints } from "chootils/dist/points2d"; import { getSpeedAndAngleFromVector } from "chootils/dist/speedAngleDistance2d"; import { getPointDistanceQuick } from "chootils/dist/speedAngleDistance3d"; import { getRefs, getState, setState } from "repond"; import { meta } from "../../meta"; import { checkPointIsInsideSlate, convertPointOnSlateToPointOnScreen, getPositionOnSlate } from "../babylonjs/slate"; import { getSpotPosition } from "./spots"; export function getModelNameFromDoll(dollName) { return getState().dolls[dollName].modelName; } export function get2DAngleFromDollToSpot(dollA, place, spot) { const spotPosition = getSpotPosition(place, spot); if (!dollA || !spotPosition) return 0; const dollPos = getState().dolls[dollA].position; const dollPos2D = { x: dollPos.z, y: dollPos.x }; const spotPos2D = { x: spotPosition.z, y: spotPosition.x }; return getSpeedAndAngleFromVector(subtractPoints(dollPos2D, spotPos2D)).angle; } export function get2DAngleBetweenDolls(dollA, dollB) { if (!dollA || !dollB) return 0; const dollAPos = getState().dolls[dollA].position; const dollBPos = getState().dolls[dollB].position; const dollAPos2D = { x: dollAPos.z, y: dollAPos.x }; const dollBPos2D = { x: dollBPos.z, y: dollBPos.x }; return getSpeedAndAngleFromVector(subtractPoints(dollAPos2D, dollBPos2D)).angle; } // export function stickDollToFloor() { // // Sticking on ground // if (raycaster.current && charRefs.groundRef) { // raycaster.current.set( // new THREE.Vector3( // charRefs.meshRef.position.x, // charRefs.meshRef.position.y + 200, // charRefs.meshRef.position.z // ), // new THREE.Vector3(0, -1) // ); // const intersects = raycaster.current.intersectObject(charRefs.groundRef); // const foundIntersect = intersects[0]; // if (foundIntersect) { // charRefs.meshRef.position.y = foundIntersect.point.y + 1.1; // } // } // } // export function doWhenPlaceAndModelsLoaded(callback: () => void) { // const initialIsLoadingBetweenPlaces = getGlobalState().isLoadingBetweenPlaces; // if (initialIsLoadingBetweenPlaces === false) { // callback(); // return null; // } // const effectId = "doWhenModelsLoaded" + Math.random(); // startItemEffect({ // id: effectId, // run: ({ newValue: newNowSegmentName }) => { // if (newNowSegmentName !== checkingSegmentName) return; // stopEffect(effectId); // callback(); // }, // check: { type: "global", prop: "nowSegmentName", id: "main" }, // step: "", // atStepEnd: true, // }); // return effectId; // } // { // getModelNameFromDoll, // get2DAngleFromDollToSpot, // get2DAngleBetweenDolls, // }; // } // -------------------------------------------- // internal doll utils function inRangeForDollMatches(inRangeForDollA, inRangeForDollB) { return (inRangeForDollA.see === inRangeForDollB.see && inRangeForDollA.talk === inRangeForDollB.talk && inRangeForDollA.touch === inRangeForDollB.touch); } export function enableCollisions(theMesh) { // Enable collision detection on player theMesh.ellipsoid = new Vector3(0.6, 1.2, 0.6); theMesh.ellipsoidOffset = new Vector3(0, 1.2, 0); // theMesh.showBoundingBox = true; theMesh.checkCollisions = true; theMesh.collisionGroup = 11; theMesh.useOctreeForCollisions = true; theMesh.rotationQuaternion = null; // allow euler rotation again } export function setDollAnimWeight(dollName, newWeights) { setState({ dolls: { [dollName]: { animWeightsGoal: { ...getState().dolls[dollName].animWeightsGoal, ...newWeights, }, }, }, }); } export function getQuickDistanceBetweenDolls(dollA, dollB) { const dollPositonA = getState().dolls[dollA].position; const dollPositonB = getState().dolls[dollB].position; return getPointDistanceQuick(dollPositonA, dollPositonB); } export function inRangesAreTheSame(inRangePropA, inRangePropB) { let bothMatch = true; breakableForEach(meta.assets.dollNames, (dollName) => { const bothMatchInLoop = inRangeForDollMatches(inRangePropA[dollName], inRangePropB[dollName]); if (!bothMatchInLoop) { bothMatch = false; return true; // to break out of inner loop (not returning from whole function) } }); return bothMatch; } // export function inRangeStatesAreTheSame(inRangeStateA :PartialDollsStateWithInRange , inRangeStateB :PartialDollsStateWithInRange); export function setupLightMaterial(theMaterial) { const placesRefs = getRefs().places; const globalState = getState().global.main; const { nowPlaceName } = globalState; const { nowCamName } = getState().global.main; const placeRefs = placesRefs[nowPlaceName]; if (theMaterial) { theMaterial.enableSpecularAntiAliasing = true; theMaterial.roughness = 0.95; theMaterial.environmentIntensity = 2; theMaterial.reflectionTexture = placeRefs.camsRefs[nowCamName].probeTexture; // theMaterial.enableSpecularAntiAliasing = false; // theMaterial.cameraToneMappingEnabled = true; // theMaterial.metallic = 0.25; } } export function saveModelStuffToDoll({ modelName, dollName, }) { const dollRefs = getRefs().dolls[dollName]; const modelRefs = getRefs().models[modelName]; const dollState = getState().dolls[dollName]; if (!modelRefs.container) return; const namePrefix = `clone_${dollName}_${modelName}_`; // console.log("namePrefix", namePrefix); let entries = modelRefs.container.instantiateModelsToScene((sourceName) => { const naeName = `${namePrefix}${sourceName}`; return `${namePrefix}${sourceName}`; }, false, { doNotInstantiate: true, }); dollRefs.entriesRef = entries; const { modelInfoByName } = meta.assets; const { meshNames, boneNames, animationNames, materialNames } = modelInfoByName[modelName]; const rootNode = entries.rootNodes[0]; const removePrefix = (name) => name.replace(namePrefix, ""); const meshArray = rootNode.getChildMeshes(); const meshes = keyBy(meshArray, "name", removePrefix); const skeleton = entries.skeletons[0]; const bones = (skeleton?.bones ? keyBy(skeleton.bones, "name", removePrefix) : {}); const aniGroups = keyBy(entries.animationGroups, "name", (name) => name.replace(namePrefix, "")); // NOTE This references the original material, and not duplicated for each doll const materials = keyBy(modelRefs.container.materials); const assetRefs = { meshes, skeleton, bones, aniGroups, materials, }; // clone_walker_walker___root__ const rootMesh = rootNode; dollRefs.meshRef = rootMesh; const loadedMeshNames = Object.keys(meshes); loadedMeshNames.forEach((loopedMeshName) => { // TODO // this keeps meshes rendering outside of the main camera view (ideally the 'frustrum' // could change so it works for the scene rendered to texture, but for now it's okay cause there's not many meshes // meshes[loopedMeshName].alwaysSelectAsActiveMesh = true; }); // NOTE Maybe temporary fix to make sure all child meshes are rendered ( but otherMeshes might still be typed to only first level sub meshes) // could also possibly loop through children when adding meshes to renderTargetTexture, but this seems faster // forEach(meshNames, (meshName) => { // used to use the typed meshNames forEach(loadedMeshNames, (meshName) => { dollRefs.otherMeshes[meshName] = meshes[meshName]; }); dollRefs.assetRefs = assetRefs; dollRefs.aniGroupsRef = aniGroups; dollRefs.aniGroupsRef?.[dollState.nowAnimation]?.start(true); // start looping the current animation enableCollisions(dollRefs.meshRef); dollRefs.meshRef.setEnabled(dollState.isVisible); // Once the models loaded, update the animation based on the dolls state // console.log("Running animations for doll after it loaded!", dollName, dollState.nowAnimation); // dollRules?.run("whenNowAnimationChanged"); } export function updateDollScreenPosition(dollName) { // Update screen positions :) const { meshRef } = getRefs().dolls[dollName]; const modelName = getState().dolls[dollName].modelName; if (!meshRef || !modelName) return; const { slatePos, focusedDoll, focusedDollIsInView, slateZoom, zoomMultiplier } = getState().global.main; const characterPointOnSlate = getPositionOnSlate(meshRef, modelName); const dollPointOnScreen = convertPointOnSlateToPointOnScreen({ pointOnSlate: characterPointOnSlate, slatePos: slatePos, slateZoom: slateZoom * zoomMultiplier, }); const newFocusedDollIsInView = dollName === focusedDoll ? checkPointIsInsideSlate(characterPointOnSlate) : focusedDollIsInView; setState({ dolls: { [dollName]: { positionOnScreen: dollPointOnScreen } }, global: { main: { focusedDollIsInView: newFocusedDollIsInView } }, }); } export function defaultInRangeForDoll() { return { touch: false, talk: false, see: false, }; } export function getDefaultInRangeFunction(dollNames) { function defaultInRange() { const untypedInRangeObject = {}; forEach(dollNames, (dollName) => { untypedInRangeObject[dollName] = defaultInRangeForDoll(); }); return untypedInRangeObject; } return defaultInRange; }