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.
@@ -46,15 +46,15 @@ function filterUpperBodyTracks(clip) {
46
46
  return new THREE.AnimationClip(clip.name, clip.duration, filteredTracks)
47
47
  }
48
48
 
49
- function filterValidClipTracks(clip, targetObj) {
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, root)
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
- const lookTarget = new THREE.Vector3(targetPosition.x, targetPosition.y + 1.6, targetPosition.z)
780
- features.lookAt.lookAt(lookTarget)
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 && mesh.parent !== scene ? mesh.parent : null
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 && currentParent !== scene) {
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(...entityState.position)
925
- if (entityState.rotation) group.quaternion.set(...entityState.rotation)
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(...entityState.position)
937
- if (entityState.rotation) model.quaternion.set(...entityState.rotation)
938
- model.traverse(c => { if (c.isMesh) { c.castShadow = true; c.receiveShadow = true; 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 } } })
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.set(p.id, { x: tx, y: ty, z: tz })
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(...e.position)
989
- if (mesh && e.rotation) mesh.quaternion.set(...e.rotation)
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 (const [, mod] of appModules) { if (mod.onEvent) try { mod.onEvent(payload, engineCtx) } catch (e) { console.error('[app-event]', e.message) } }
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 = client.state?.players?.find(p => p.id === client.playerId)
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 = client.state?.players?.find(p => p.id === client.playerId)
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 (const [, mod] of appModules) { if (mod.onInput) try { mod.onInput(input, engineCtx) } catch (e) { console.error('[app-input]', e.message) } }
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 (const [, mod] of appModules) { if (mod.onMouseDown) try { mod.onMouseDown(e, engineCtx) } catch (ex) {} } })
1245
- renderer.domElement.addEventListener('mouseup', (e) => { for (const [, mod] of appModules) { if (mod.onMouseUp) try { mod.onMouseUp(e, engineCtx) } catch (ex) {} } })
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 = client.state?.players?.find(p => p.id === client.playerId)
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
- for (const [id, target] of playerTargets) {
1375
+ playerTargets.forEach((target, id) => {
1367
1376
  const mesh = playerMeshes.get(id)
1368
- if (!mesh) continue
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
- for (const [id, animator] of playerAnimators) {
1384
+ })
1385
+ playerAnimators.forEach((animator, id) => {
1377
1386
  const ps = playerStates.get(id)
1378
- if (!ps) continue
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) continue
1390
+ if (!mesh) return
1382
1391
  const vx = ps.velocity?.[0] || 0, vz = ps.velocity?.[2] || 0
1383
- if (Math.sqrt(vx * vx + vz * vz) > 0.5) mesh.userData.lastYaw = Math.atan2(vx, vz)
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
- while (diff > Math.PI) diff -= Math.PI * 2
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 vrm = playerVrms.get(id)
1394
- if (vrm?.humanoid) {
1395
- const head = vrm.humanoid.getNormalizedBoneNode('head')
1396
- if (head) head.rotation.x = -(ps.lookPitch || 0) * 0.6
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
- for (const [eid, mesh] of entityMeshes) {
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 (const [, mod] of appModules) { if (mod.onFrame) try { mod.onFrame(frameDt, engineCtx) } catch (e) {} }
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 = client.state?.players?.find(p => p.id === client.playerId)
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 local = client.state?.players?.find(p => p.id === client.playerId)
1445
- if (local?.position && !arControls.anchorPlaced) {
1446
- arControls.setInitialFPSPosition(local.position, cam.yaw)
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spoint",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "description": "Physics and netcode SDK for multiplayer game servers",
5
5
  "type": "module",
6
6
  "main": "src/index.js",