spoint 0.1.28 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/animation.js +7 -6
- package/client/app.js +58 -49
- package/package.json +1 -1
package/client/animation.js
CHANGED
|
@@ -46,15 +46,15 @@ function filterUpperBodyTracks(clip) {
|
|
|
46
46
|
return new THREE.AnimationClip(clip.name, clip.duration, filteredTracks)
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function
|
|
50
|
-
// Get all bone/mesh names that exist in target
|
|
49
|
+
function buildValidBoneSet(targetObj) {
|
|
51
50
|
const validBones = new Set()
|
|
52
51
|
targetObj.traverse(child => {
|
|
53
|
-
if (child.isBone || child.isSkinnedMesh)
|
|
54
|
-
validBones.add(child.name)
|
|
55
|
-
}
|
|
52
|
+
if (child.isBone || child.isSkinnedMesh) validBones.add(child.name)
|
|
56
53
|
})
|
|
54
|
+
return validBones
|
|
55
|
+
}
|
|
57
56
|
|
|
57
|
+
function filterValidClipTracks(clip, validBones) {
|
|
58
58
|
const validTracks = clip.tracks.filter(track => {
|
|
59
59
|
const boneName = extractBoneName(track.name)
|
|
60
60
|
if (!validBones.has(boneName)) {
|
|
@@ -140,6 +140,7 @@ export function createPlayerAnimator(vrm, allClips, vrmVersion, animConfig = {})
|
|
|
140
140
|
const additiveActions = new Map()
|
|
141
141
|
|
|
142
142
|
const clips = allClips.normalizedClips || allClips.rawClips || allClips
|
|
143
|
+
const validBones = buildValidBoneSet(root)
|
|
143
144
|
|
|
144
145
|
for (const [name, clip] of clips) {
|
|
145
146
|
if (!STATES[name]) continue
|
|
@@ -149,7 +150,7 @@ export function createPlayerAnimator(vrm, allClips, vrmVersion, animConfig = {})
|
|
|
149
150
|
console.log(`[anim] ${name} tracks:`, clip.tracks.map(t => extractBoneName(t.name)))
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
let playClip = filterValidClipTracks(clip,
|
|
153
|
+
let playClip = filterValidClipTracks(clip, validBones)
|
|
153
154
|
|
|
154
155
|
if (cfg.upperBody) {
|
|
155
156
|
const upperBodyClip = filterUpperBodyTracks(playClip)
|
package/client/app.js
CHANGED
|
@@ -659,6 +659,7 @@ scene.add(ground)
|
|
|
659
659
|
|
|
660
660
|
const loadingManager = new THREE.LoadingManager()
|
|
661
661
|
loadingManager.onError = (url) => console.warn('[THREE] Failed to load:', url)
|
|
662
|
+
THREE.Cache.enabled = true
|
|
662
663
|
const gltfLoader = new GLTFLoader(loadingManager)
|
|
663
664
|
const dracoLoader = new DRACOLoader(loadingManager)
|
|
664
665
|
dracoLoader.setDecoderPath('/draco/')
|
|
@@ -771,13 +772,15 @@ function initVRMFeatures(id, vrm) {
|
|
|
771
772
|
playerExpressions.set(id, features)
|
|
772
773
|
}
|
|
773
774
|
|
|
775
|
+
const _lookTargetVec = new THREE.Vector3()
|
|
776
|
+
|
|
774
777
|
function updateVRMFeatures(id, dt, targetPosition) {
|
|
775
778
|
const features = playerExpressions.get(id)
|
|
776
779
|
if (!features) return
|
|
777
780
|
if (features.springBone) features.springBone.update(dt)
|
|
778
781
|
if (features.lookAt && targetPosition) {
|
|
779
|
-
|
|
780
|
-
features.lookAt.lookAt(
|
|
782
|
+
_lookTargetVec.set(targetPosition.x, targetPosition.y + 1.6, targetPosition.z)
|
|
783
|
+
features.lookAt.lookAt(_lookTargetVec)
|
|
781
784
|
}
|
|
782
785
|
if (features.expressions) {
|
|
783
786
|
features.blinkTimer += dt
|
|
@@ -891,18 +894,13 @@ function rebuildEntityHierarchy(entities) {
|
|
|
891
894
|
if (!mesh) continue
|
|
892
895
|
|
|
893
896
|
const parentId = entityParentMap.get(e.id)
|
|
894
|
-
const currentParent = mesh.parent
|
|
895
|
-
const currentParentId = Array.from(entityParentMap.entries()).find(([k, v]) => v === null || entityMeshes.get(k) === currentParent)?.[0]
|
|
897
|
+
const currentParent = mesh.parent !== scene ? mesh.parent : null
|
|
896
898
|
|
|
897
899
|
if (parentId === null) {
|
|
898
|
-
if (currentParent
|
|
899
|
-
scene.add(mesh)
|
|
900
|
-
}
|
|
900
|
+
if (currentParent) scene.add(mesh)
|
|
901
901
|
} else {
|
|
902
902
|
const parentMesh = entityMeshes.get(parentId)
|
|
903
|
-
if (parentMesh && parentMesh !== currentParent)
|
|
904
|
-
parentMesh.add(mesh)
|
|
905
|
-
}
|
|
903
|
+
if (parentMesh && parentMesh !== currentParent) parentMesh.add(mesh)
|
|
906
904
|
}
|
|
907
905
|
}
|
|
908
906
|
}
|
|
@@ -921,8 +919,8 @@ function loadEntityModel(entityId, entityState) {
|
|
|
921
919
|
} else {
|
|
922
920
|
group = buildEntityMesh(entityId, entityState.custom)
|
|
923
921
|
}
|
|
924
|
-
group.position.set(
|
|
925
|
-
|
|
922
|
+
const ep = entityState.position; group.position.set(ep[0], ep[1], ep[2])
|
|
923
|
+
const er = entityState.rotation; if (er) group.quaternion.set(er[0], er[1], er[2], er[3])
|
|
926
924
|
scene.add(group)
|
|
927
925
|
entityMeshes.set(entityId, group)
|
|
928
926
|
pendingLoads.delete(entityId)
|
|
@@ -933,13 +931,20 @@ function loadEntityModel(entityId, entityState) {
|
|
|
933
931
|
const url = entityState.model.startsWith('./') ? '/' + entityState.model.slice(2) : entityState.model
|
|
934
932
|
gltfLoader.load(url, (gltf) => {
|
|
935
933
|
const model = gltf.scene
|
|
936
|
-
model.position.set(
|
|
937
|
-
|
|
938
|
-
|
|
934
|
+
const mp = entityState.position; model.position.set(mp[0], mp[1], mp[2])
|
|
935
|
+
const mr = entityState.rotation; if (mr) model.quaternion.set(mr[0], mr[1], mr[2], mr[3])
|
|
936
|
+
const colliders = []
|
|
937
|
+
model.traverse(c => {
|
|
938
|
+
if (c.isMesh) {
|
|
939
|
+
c.castShadow = true
|
|
940
|
+
c.receiveShadow = true
|
|
941
|
+
if (!c.isSkinnedMesh) { c.matrixAutoUpdate = false; colliders.push(c) }
|
|
942
|
+
if (c.material) { c.material.shadowSide = THREE.DoubleSide; c.material.roughness = 1; c.material.metalness = 0; if (c.material.specularIntensity !== undefined) c.material.specularIntensity = 0 }
|
|
943
|
+
}
|
|
944
|
+
})
|
|
945
|
+
model.updateMatrixWorld(true)
|
|
939
946
|
scene.add(model)
|
|
940
947
|
entityMeshes.set(entityId, model)
|
|
941
|
-
const colliders = []
|
|
942
|
-
model.traverse(c => { if (c.isMesh && !c.isSkinnedMesh) colliders.push(c) })
|
|
943
948
|
cam.setEnvironment(colliders)
|
|
944
949
|
scene.remove(ground)
|
|
945
950
|
fitShadowFrustum()
|
|
@@ -978,15 +983,17 @@ const client = new PhysicsNetworkClient({
|
|
|
978
983
|
const mesh = playerMeshes.get(p.id)
|
|
979
984
|
const feetOff = mesh?.userData?.feetOffset ?? 1.3
|
|
980
985
|
const tx = p.position[0], ty = p.position[1] - feetOff, tz = p.position[2]
|
|
981
|
-
playerTargets.
|
|
986
|
+
const existingTarget = playerTargets.get(p.id)
|
|
987
|
+
if (existingTarget) { existingTarget.x = tx; existingTarget.y = ty; existingTarget.z = tz }
|
|
988
|
+
else playerTargets.set(p.id, { x: tx, y: ty, z: tz })
|
|
982
989
|
playerStates.set(p.id, p)
|
|
983
990
|
const dx = tx - mesh.position.x, dy = ty - mesh.position.y, dz = tz - mesh.position.z
|
|
984
991
|
if (!mesh.userData.initialized || dx * dx + dy * dy + dz * dz > 100) { mesh.position.set(tx, ty, tz); mesh.userData.initialized = true }
|
|
985
992
|
}
|
|
986
993
|
for (const e of smoothState.entities) {
|
|
987
994
|
const mesh = entityMeshes.get(e.id)
|
|
988
|
-
if (mesh && e.position) mesh.position.set(
|
|
989
|
-
if (mesh && e.rotation) mesh.quaternion.set(
|
|
995
|
+
if (mesh && e.position) mesh.position.set(e.position[0], e.position[1], e.position[2])
|
|
996
|
+
if (mesh && e.rotation) mesh.quaternion.set(e.rotation[0], e.rotation[1], e.rotation[2], e.rotation[3])
|
|
990
997
|
if (!entityMeshes.has(e.id)) loadEntityModel(e.id, e)
|
|
991
998
|
}
|
|
992
999
|
rebuildEntityHierarchy(smoothState.entities)
|
|
@@ -1015,12 +1022,13 @@ const client = new PhysicsNetworkClient({
|
|
|
1015
1022
|
const a = evaluateAppModule(d.code)
|
|
1016
1023
|
if (a?.client) {
|
|
1017
1024
|
appModules.set(d.app, a.client)
|
|
1025
|
+
_appModuleList = [...appModules.values()]
|
|
1018
1026
|
if (a.client.setup) try { a.client.setup(engineCtx) } catch (e) { console.error('[app-setup]', d.app, e.message) }
|
|
1019
1027
|
}
|
|
1020
1028
|
},
|
|
1021
1029
|
onAssetUpdate: () => {},
|
|
1022
1030
|
onAppEvent: (payload) => {
|
|
1023
|
-
for (
|
|
1031
|
+
for (let _i = 0; _i < _appModuleList.length; _i++) { const mod = _appModuleList[_i]; if (mod.onEvent) try { mod.onEvent(payload, engineCtx) } catch (e) { console.error('[app-event]', e.message) } }
|
|
1024
1032
|
},
|
|
1025
1033
|
onHotReload: () => { sessionStorage.setItem('cam', JSON.stringify(cam.save())); location.reload() },
|
|
1026
1034
|
debug: false
|
|
@@ -1091,7 +1099,7 @@ function initInputHandler() {
|
|
|
1091
1099
|
setTimeout(() => {
|
|
1092
1100
|
xrBaseReferenceSpace = renderer.xr.getReferenceSpace()
|
|
1093
1101
|
if (!xrBaseReferenceSpace) return
|
|
1094
|
-
const local =
|
|
1102
|
+
const local = playerStates.get(client.playerId)
|
|
1095
1103
|
if (local?.position) {
|
|
1096
1104
|
const headHeight = local.crouch ? 1.1 : 1.6
|
|
1097
1105
|
const pos = { x: -local.position[0], y: -(local.position[1] + headHeight), z: -local.position[2] }
|
|
@@ -1220,7 +1228,7 @@ function startInputLoop() {
|
|
|
1220
1228
|
inputHandler.pulse('right', 0.5, 100)
|
|
1221
1229
|
}
|
|
1222
1230
|
lastShootState = input.shoot
|
|
1223
|
-
const local =
|
|
1231
|
+
const local = playerStates.get(client.playerId)
|
|
1224
1232
|
if (local) {
|
|
1225
1233
|
if (local.health < lastHealth) {
|
|
1226
1234
|
inputHandler.pulse('left', 0.8, 200)
|
|
@@ -1228,7 +1236,7 @@ function startInputLoop() {
|
|
|
1228
1236
|
}
|
|
1229
1237
|
lastHealth = local.health
|
|
1230
1238
|
}
|
|
1231
|
-
for (
|
|
1239
|
+
for (let _i = 0; _i < _appModuleList.length; _i++) { const mod = _appModuleList[_i]; if (mod.onInput) try { mod.onInput(input, engineCtx) } catch (e) { console.error('[app-input]', e.message) } }
|
|
1232
1240
|
client.sendInput(input)
|
|
1233
1241
|
}, 1000 / 60)
|
|
1234
1242
|
}
|
|
@@ -1241,8 +1249,8 @@ document.addEventListener('pointerlockchange', () => {
|
|
|
1241
1249
|
else document.removeEventListener('mousemove', cam.onMouseMove)
|
|
1242
1250
|
})
|
|
1243
1251
|
renderer.domElement.addEventListener('wheel', cam.onWheel, { passive: false })
|
|
1244
|
-
renderer.domElement.addEventListener('mousedown', (e) => { for (
|
|
1245
|
-
renderer.domElement.addEventListener('mouseup', (e) => { for (
|
|
1252
|
+
renderer.domElement.addEventListener('mousedown', (e) => { for (let _i = 0; _i < _appModuleList.length; _i++) { const mod = _appModuleList[_i]; if (mod.onMouseDown) try { mod.onMouseDown(e, engineCtx) } catch (ex) {} } })
|
|
1253
|
+
renderer.domElement.addEventListener('mouseup', (e) => { for (let _i = 0; _i < _appModuleList.length; _i++) { const mod = _appModuleList[_i]; if (mod.onMouseUp) try { mod.onMouseUp(e, engineCtx) } catch (ex) {} } })
|
|
1246
1254
|
renderer.domElement.addEventListener('contextmenu', (e) => e.preventDefault())
|
|
1247
1255
|
window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight) })
|
|
1248
1256
|
|
|
@@ -1283,7 +1291,7 @@ function loadQueuedModels() {
|
|
|
1283
1291
|
try {
|
|
1284
1292
|
const buffer = e.target.result
|
|
1285
1293
|
gltfLoader.parse(buffer, '', (gltf) => {
|
|
1286
|
-
const local =
|
|
1294
|
+
const local = playerStates.get(client.playerId)
|
|
1287
1295
|
if (!local) return
|
|
1288
1296
|
const sy = Math.sin(cam.yaw), cy = Math.cos(cam.yaw)
|
|
1289
1297
|
const spawnDist = 1.0
|
|
@@ -1354,6 +1362,7 @@ document.addEventListener('drop', (e) => {
|
|
|
1354
1362
|
})
|
|
1355
1363
|
|
|
1356
1364
|
let smoothDt = 1 / 60
|
|
1365
|
+
let _appModuleList = []
|
|
1357
1366
|
function animate(timestamp) {
|
|
1358
1367
|
const now = timestamp || performance.now()
|
|
1359
1368
|
const rawDt = Math.min((now - lastFrameTime) / 1000, 0.1)
|
|
@@ -1363,53 +1372,53 @@ function animate(timestamp) {
|
|
|
1363
1372
|
fpsFrames++
|
|
1364
1373
|
if (now - fpsLast >= 1000) { fpsDisplay = fpsFrames; fpsFrames = 0; fpsLast = now }
|
|
1365
1374
|
const lerpFactor = 1.0 - Math.exp(-16.0 * frameDt)
|
|
1366
|
-
|
|
1375
|
+
playerTargets.forEach((target, id) => {
|
|
1367
1376
|
const mesh = playerMeshes.get(id)
|
|
1368
|
-
if (!mesh)
|
|
1377
|
+
if (!mesh) return
|
|
1369
1378
|
const ps = playerStates.get(id)
|
|
1370
1379
|
const vx = ps?.velocity?.[0] || 0, vy = ps?.velocity?.[1] || 0, vz = ps?.velocity?.[2] || 0
|
|
1371
1380
|
const goalX = target.x + vx * frameDt, goalY = target.y + vy * frameDt, goalZ = target.z + vz * frameDt
|
|
1372
1381
|
mesh.position.x += (goalX - mesh.position.x) * lerpFactor
|
|
1373
1382
|
mesh.position.y += (goalY - mesh.position.y) * lerpFactor
|
|
1374
1383
|
mesh.position.z += (goalZ - mesh.position.z) * lerpFactor
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1384
|
+
})
|
|
1385
|
+
playerAnimators.forEach((animator, id) => {
|
|
1377
1386
|
const ps = playerStates.get(id)
|
|
1378
|
-
if (!ps)
|
|
1387
|
+
if (!ps) return
|
|
1379
1388
|
animator.update(frameDt, ps.velocity, ps.onGround, ps.health, ps._aiming || false, ps.crouch || 0)
|
|
1380
1389
|
const mesh = playerMeshes.get(id)
|
|
1381
|
-
if (!mesh)
|
|
1390
|
+
if (!mesh) return
|
|
1382
1391
|
const vx = ps.velocity?.[0] || 0, vz = ps.velocity?.[2] || 0
|
|
1383
|
-
if (
|
|
1392
|
+
if (vx * vx + vz * vz > 0.25) mesh.userData.lastYaw = Math.atan2(vx, vz)
|
|
1384
1393
|
if (mesh.userData.lastYaw !== undefined) {
|
|
1385
1394
|
let diff = mesh.userData.lastYaw - mesh.rotation.y
|
|
1386
|
-
|
|
1387
|
-
while (diff < -Math.PI) diff += Math.PI * 2
|
|
1395
|
+
diff = diff - Math.PI * 2 * Math.round(diff / (Math.PI * 2))
|
|
1388
1396
|
mesh.rotation.y += diff * lerpFactor
|
|
1389
1397
|
}
|
|
1390
1398
|
const target = playerTargets.get(id)
|
|
1391
1399
|
updateVRMFeatures(id, frameDt, target)
|
|
1392
1400
|
if (id !== client.playerId && ps.lookPitch !== undefined) {
|
|
1393
|
-
const
|
|
1394
|
-
if (
|
|
1395
|
-
const
|
|
1396
|
-
if (
|
|
1401
|
+
const features = playerExpressions.get(id)
|
|
1402
|
+
if (features && !features._headBone) {
|
|
1403
|
+
const vrm = playerVrms.get(id)
|
|
1404
|
+
if (vrm?.humanoid) features._headBone = vrm.humanoid.getNormalizedBoneNode('head')
|
|
1397
1405
|
}
|
|
1406
|
+
if (features?._headBone) features._headBone.rotation.x = -(ps.lookPitch || 0) * 0.6
|
|
1398
1407
|
}
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1408
|
+
})
|
|
1409
|
+
entityMeshes.forEach((mesh) => {
|
|
1401
1410
|
if (mesh.userData.spin) mesh.rotation.y += mesh.userData.spin * frameDt
|
|
1402
1411
|
if (mesh.userData.hover) {
|
|
1403
1412
|
mesh.userData.hoverTime = (mesh.userData.hoverTime || 0) + frameDt
|
|
1404
1413
|
const child = mesh.children[0]
|
|
1405
1414
|
if (child) child.position.y = Math.sin(mesh.userData.hoverTime * 2) * mesh.userData.hover
|
|
1406
1415
|
}
|
|
1407
|
-
}
|
|
1408
|
-
for (
|
|
1416
|
+
})
|
|
1417
|
+
for (let _i = 0; _i < _appModuleList.length; _i++) { const mod = _appModuleList[_i]; if (mod.onFrame) try { mod.onFrame(frameDt, engineCtx) } catch (e) {} }
|
|
1409
1418
|
if (engineCtx.facial) engineCtx.facial.update(frameDt)
|
|
1410
1419
|
uiTimer += frameDt
|
|
1411
1420
|
if (latestState && uiTimer >= 0.25) { uiTimer = 0; renderAppUI(latestState) }
|
|
1412
|
-
const local =
|
|
1421
|
+
const local = playerStates.get(client.playerId)
|
|
1413
1422
|
const inVR = renderer.xr.isPresenting
|
|
1414
1423
|
if (!inVR || cam.getEditMode()) {
|
|
1415
1424
|
cam.update(local, playerMeshes.get(client.playerId), frameDt, latestInput)
|
|
@@ -1435,15 +1444,15 @@ function animate(timestamp) {
|
|
|
1435
1444
|
const speed = Math.sqrt(local.velocity[0] ** 2 + local.velocity[2] ** 2)
|
|
1436
1445
|
isMoving = speed > 0.5
|
|
1437
1446
|
}
|
|
1438
|
-
updateVignette(frameDt, isMoving)
|
|
1447
|
+
updateVignette(frameDt, isMoving)
|
|
1439
1448
|
|
|
1440
1449
|
if (arEnabled) {
|
|
1441
1450
|
const xrFrame = renderer.xr.getFrame()
|
|
1442
1451
|
if (xrFrame) {
|
|
1443
1452
|
arControls.update(xrFrame, camera, scene)
|
|
1444
|
-
const
|
|
1445
|
-
if (
|
|
1446
|
-
arControls.setInitialFPSPosition(
|
|
1453
|
+
const arLocal = playerStates.get(client.playerId)
|
|
1454
|
+
if (arLocal?.position && !arControls.anchorPlaced) {
|
|
1455
|
+
arControls.setInitialFPSPosition(arLocal.position, cam.yaw)
|
|
1447
1456
|
}
|
|
1448
1457
|
}
|
|
1449
1458
|
}
|