spoint 0.1.28 → 0.1.29

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)
@@ -1380,21 +1387,21 @@ function animate(timestamp) {
1380
1387
  const mesh = playerMeshes.get(id)
1381
1388
  if (!mesh) continue
1382
1389
  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)
1390
+ if (vx * vx + vz * vz > 0.25) mesh.userData.lastYaw = Math.atan2(vx, vz)
1384
1391
  if (mesh.userData.lastYaw !== undefined) {
1385
1392
  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
1393
+ diff = diff - Math.PI * 2 * Math.round(diff / (Math.PI * 2))
1388
1394
  mesh.rotation.y += diff * lerpFactor
1389
1395
  }
1390
1396
  const target = playerTargets.get(id)
1391
1397
  updateVRMFeatures(id, frameDt, target)
1392
1398
  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
1399
+ const features = playerExpressions.get(id)
1400
+ if (features && !features._headBone) {
1401
+ const vrm = playerVrms.get(id)
1402
+ if (vrm?.humanoid) features._headBone = vrm.humanoid.getNormalizedBoneNode('head')
1397
1403
  }
1404
+ if (features?._headBone) features._headBone.rotation.x = -(ps.lookPitch || 0) * 0.6
1398
1405
  }
1399
1406
  }
1400
1407
  for (const [eid, mesh] of entityMeshes) {
@@ -1409,7 +1416,7 @@ function animate(timestamp) {
1409
1416
  if (engineCtx.facial) engineCtx.facial.update(frameDt)
1410
1417
  uiTimer += frameDt
1411
1418
  if (latestState && uiTimer >= 0.25) { uiTimer = 0; renderAppUI(latestState) }
1412
- const local = client.state?.players?.find(p => p.id === client.playerId)
1419
+ const local = playerStates.get(client.playerId)
1413
1420
  const inVR = renderer.xr.isPresenting
1414
1421
  if (!inVR || cam.getEditMode()) {
1415
1422
  cam.update(local, playerMeshes.get(client.playerId), frameDt, latestInput)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spoint",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "Physics and netcode SDK for multiplayer game servers",
5
5
  "type": "module",
6
6
  "main": "src/index.js",