spoint 0.1.42 → 0.1.44
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/SKILL.md +22 -0
- package/client/animation.js +14 -3
- package/client/app.js +17 -7
- package/package.json +1 -1
- package/src/apps/AppContext.js +18 -0
- package/src/physics/World.js +7 -0
package/SKILL.md
CHANGED
|
@@ -239,6 +239,14 @@ ctx.physics.addCapsuleCollider(radius, fullHeight)
|
|
|
239
239
|
ctx.physics.addTrimeshCollider()
|
|
240
240
|
// Builds static mesh from entity.model path. Static only.
|
|
241
241
|
|
|
242
|
+
ctx.physics.addConvexCollider(points)
|
|
243
|
+
// points: flat Float32Array or Array of [x,y,z,x,y,z,...] vertex positions
|
|
244
|
+
// Builds a ConvexHullShape from the provided point cloud. Supports all motion types.
|
|
245
|
+
|
|
246
|
+
ctx.physics.addConvexFromModel(meshIndex = 0)
|
|
247
|
+
// Extracts vertex positions from entity.model GLB and builds ConvexHullShape.
|
|
248
|
+
// Simpler and faster than trimesh for dynamic objects like vehicles/crates.
|
|
249
|
+
|
|
242
250
|
ctx.physics.addForce([fx, fy, fz]) // Impulse: velocity += force / mass
|
|
243
251
|
ctx.physics.setVelocity([vx, vy, vz]) // Set velocity directly
|
|
244
252
|
```
|
|
@@ -544,6 +552,12 @@ ctx.physics.addCapsuleCollider(radius, fullHeight)
|
|
|
544
552
|
ctx.physics.addTrimeshCollider()
|
|
545
553
|
// Static trimesh from entity.model. Only for static bodies.
|
|
546
554
|
|
|
555
|
+
ctx.physics.addConvexCollider(points)
|
|
556
|
+
// points: flat array [x,y,z,x,y,z,...]. Supports all motion types (dynamic/kinematic/static).
|
|
557
|
+
|
|
558
|
+
ctx.physics.addConvexFromModel(meshIndex = 0)
|
|
559
|
+
// Extracts vertices from entity.model GLB and builds ConvexHullShape. Good for dynamic vehicles/crates.
|
|
560
|
+
|
|
547
561
|
ctx.physics.addForce([fx, fy, fz]) // velocity += force / mass
|
|
548
562
|
ctx.physics.setVelocity([vx, vy, vz])
|
|
549
563
|
```
|
|
@@ -1068,6 +1082,14 @@ The engine manually applies `gravity[1] * dt` to Y velocity. This is already han
|
|
|
1068
1082
|
|
|
1069
1083
|
`addTrimeshCollider()` creates a static mesh. No dynamic or kinematic trimesh support.
|
|
1070
1084
|
|
|
1085
|
+
### Convex hull for dynamic objects
|
|
1086
|
+
|
|
1087
|
+
Use `addConvexCollider(points)` or `addConvexFromModel()` for dynamic/kinematic bodies that need shape-accurate physics (vehicles, crates). Convex hulls support all motion types unlike trimesh. `addConvexFromModel()` reads vertices from the entity's GLB at setup time - call it after setting `entity.model`.
|
|
1088
|
+
|
|
1089
|
+
### Animation library is cached globally
|
|
1090
|
+
|
|
1091
|
+
`loadAnimationLibrary()` loads `/anim-lib.glb` only once and caches the result. All subsequent calls return the cached result immediately. The library is also pre-fetched in parallel with the VRM download during initialization.
|
|
1092
|
+
|
|
1071
1093
|
### Tick drops under load
|
|
1072
1094
|
|
|
1073
1095
|
TickSystem processes max 4 ticks per loop. If the server falls more than 4 ticks behind (31ms at 128 TPS), those ticks are dropped silently.
|
package/client/animation.js
CHANGED
|
@@ -123,12 +123,23 @@ function normalizeClips(gltf, vrmVersion, vrmHumanoid) {
|
|
|
123
123
|
return clips
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
let _gltfPromise = null
|
|
127
|
+
let _normalizedCache = null
|
|
128
|
+
|
|
129
|
+
export function preloadAnimationLibrary() {
|
|
130
|
+
if (_gltfPromise) return _gltfPromise
|
|
131
|
+
_gltfPromise = new GLTFLoader().loadAsync('/anim-lib.glb')
|
|
132
|
+
return _gltfPromise
|
|
133
|
+
}
|
|
134
|
+
|
|
126
135
|
export async function loadAnimationLibrary(vrmVersion, vrmHumanoid) {
|
|
127
|
-
|
|
128
|
-
const gltf = await
|
|
136
|
+
if (_normalizedCache) return _normalizedCache
|
|
137
|
+
const gltf = await preloadAnimationLibrary()
|
|
138
|
+
if (_normalizedCache) return _normalizedCache
|
|
129
139
|
const normalizedClips = normalizeClips(gltf, vrmVersion || '1', vrmHumanoid)
|
|
130
140
|
console.log(`[anim] Loaded animation library (${normalizedClips.size} clips):`, [...normalizedClips.keys()])
|
|
131
|
-
|
|
141
|
+
_normalizedCache = { normalizedClips }
|
|
142
|
+
return _normalizedCache
|
|
132
143
|
}
|
|
133
144
|
|
|
134
145
|
export function createPlayerAnimator(vrm, allClips, vrmVersion, animConfig = {}) {
|
package/client/app.js
CHANGED
|
@@ -9,7 +9,7 @@ import { VRMLoaderPlugin, VRMUtils } from '@pixiv/three-vrm'
|
|
|
9
9
|
import { PhysicsNetworkClient, InputHandler, MSG } from '/src/index.client.js'
|
|
10
10
|
import { createElement, applyDiff } from 'webjsx'
|
|
11
11
|
import { createCameraController } from './camera.js'
|
|
12
|
-
import { loadAnimationLibrary, createPlayerAnimator } from './animation.js'
|
|
12
|
+
import { loadAnimationLibrary, preloadAnimationLibrary, createPlayerAnimator } from './animation.js'
|
|
13
13
|
import { initFacialSystem } from './facial-animation.js'
|
|
14
14
|
import { VRButton } from 'three/addons/webxr/VRButton.js'
|
|
15
15
|
import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js'
|
|
@@ -713,12 +713,11 @@ function detectVrmVersion(buffer) {
|
|
|
713
713
|
|
|
714
714
|
function initAssets(playerModelUrl) {
|
|
715
715
|
loadingMgr.setStage('DOWNLOAD')
|
|
716
|
-
|
|
716
|
+
preloadAnimationLibrary()
|
|
717
|
+
assetsReady = loadingMgr.fetchWithProgress(playerModelUrl).then(async b => {
|
|
717
718
|
vrmBuffer = b
|
|
718
719
|
loadingMgr.setStage('PROCESS')
|
|
719
|
-
|
|
720
|
-
}).then(result => {
|
|
721
|
-
animAssets = result
|
|
720
|
+
animAssets = await loadAnimationLibrary(detectVrmVersion(b), null)
|
|
722
721
|
assetsLoaded = true
|
|
723
722
|
checkAllLoaded()
|
|
724
723
|
}).catch(err => {
|
|
@@ -840,7 +839,16 @@ function evaluateAppModule(code) {
|
|
|
840
839
|
try {
|
|
841
840
|
let stripped = code.replace(/^import\s+.*$/gm, '')
|
|
842
841
|
stripped = stripped.replace(/const\s+__dirname\s*=.*import\.meta\.url.*$/gm, 'const __dirname = "/"')
|
|
843
|
-
|
|
842
|
+
stripped = stripped.replace(/export\s+/g, '')
|
|
843
|
+
const exportDefaultIdx = stripped.search(/\bdefault\s*[\{(]/)
|
|
844
|
+
let wrapped
|
|
845
|
+
if (exportDefaultIdx !== -1) {
|
|
846
|
+
const before = stripped.slice(0, exportDefaultIdx)
|
|
847
|
+
const after = stripped.slice(exportDefaultIdx + 'default'.length).trimStart()
|
|
848
|
+
wrapped = before + '\nreturn ' + after + '\n//# sourceURL=app-module.js'
|
|
849
|
+
} else {
|
|
850
|
+
wrapped = stripped.replace(/\bdefault\s*/, 'return ') + '\n//# sourceURL=app-module.js'
|
|
851
|
+
}
|
|
844
852
|
const join = (...parts) => parts.filter(Boolean).join('/')
|
|
845
853
|
const readdirSync = () => []
|
|
846
854
|
const statSync = () => ({ isDirectory: () => false })
|
|
@@ -969,6 +977,7 @@ function loadEntityModel(entityId, entityState) {
|
|
|
969
977
|
fitShadowFrustum()
|
|
970
978
|
pendingLoads.delete(entityId)
|
|
971
979
|
if (!environmentLoaded) { environmentLoaded = true; checkAllLoaded() }
|
|
980
|
+
if (loadingScreenHidden) renderer.compileAsync(scene, camera).catch(() => renderer.compile(scene, camera))
|
|
972
981
|
}, (progress) => { if (progress.total > 0) console.log('[gltf]', url, Math.round(progress.loaded / progress.total * 100) + '%') }, (err) => { console.error('[gltf]', url, err); pendingLoads.delete(entityId) })
|
|
973
982
|
}
|
|
974
983
|
|
|
@@ -1117,7 +1126,8 @@ function checkAllLoaded() {
|
|
|
1117
1126
|
loadingMgr.setStage('INIT')
|
|
1118
1127
|
loadingMgr.complete()
|
|
1119
1128
|
loadingScreenHidden = true
|
|
1120
|
-
|
|
1129
|
+
loadingScreen.hide()
|
|
1130
|
+
warmupShaders().catch(() => {})
|
|
1121
1131
|
}
|
|
1122
1132
|
|
|
1123
1133
|
function initInputHandler() {
|
package/package.json
CHANGED
package/src/apps/AppContext.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CliDebugger } from '../debug/CliDebugger.js'
|
|
2
|
+
import { extractMeshFromGLB } from '../physics/GLBLoader.js'
|
|
2
3
|
|
|
3
4
|
export class AppContext {
|
|
4
5
|
constructor(entity, runtime) {
|
|
@@ -75,6 +76,23 @@ export class AppContext {
|
|
|
75
76
|
ent._physicsBodyId = bodyId
|
|
76
77
|
}
|
|
77
78
|
},
|
|
79
|
+
addConvexCollider: (points) => {
|
|
80
|
+
ent.collider = { type: 'convex', points }
|
|
81
|
+
if (runtime._physics) {
|
|
82
|
+
const mt = ent.bodyType === 'dynamic' ? 'dynamic' : ent.bodyType === 'kinematic' ? 'kinematic' : 'static'
|
|
83
|
+
ent._physicsBodyId = runtime._physics.addBody('convex', points, ent.position, mt, { rotation: ent.rotation, mass: ent.mass })
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
addConvexFromModel: (meshIndex = 0) => {
|
|
87
|
+
if (!ent.model) return
|
|
88
|
+
const mesh = extractMeshFromGLB(runtime.resolveAssetPath(ent.model), meshIndex)
|
|
89
|
+
const points = Array.from(mesh.vertices)
|
|
90
|
+
ent.collider = { type: 'convex', points }
|
|
91
|
+
if (runtime._physics) {
|
|
92
|
+
const mt = ent.bodyType === 'dynamic' ? 'dynamic' : ent.bodyType === 'kinematic' ? 'kinematic' : 'static'
|
|
93
|
+
ent._physicsBodyId = runtime._physics.addBody('convex', points, ent.position, mt, { rotation: ent.rotation, mass: ent.mass })
|
|
94
|
+
}
|
|
95
|
+
},
|
|
78
96
|
addForce: (f) => {
|
|
79
97
|
const mass = ent.mass || 1
|
|
80
98
|
ent.velocity[0] += f[0] / mass
|
package/src/physics/World.js
CHANGED
|
@@ -59,6 +59,13 @@ export class PhysicsWorld {
|
|
|
59
59
|
if (shapeType === 'box') shape = new J.BoxShape(new J.Vec3(params[0], params[1], params[2]), 0.05, null)
|
|
60
60
|
else if (shapeType === 'sphere') shape = new J.SphereShape(params)
|
|
61
61
|
else if (shapeType === 'capsule') shape = new J.CapsuleShape(params[1], params[0])
|
|
62
|
+
else if (shapeType === 'convex') {
|
|
63
|
+
const pts = new J.VertexList()
|
|
64
|
+
for (let i = 0; i < params.length; i += 3) pts.push_back(new J.Float3(params[i], params[i + 1], params[i + 2]))
|
|
65
|
+
const cvx = new J.ConvexHullShapeSettings(pts)
|
|
66
|
+
shape = cvx.Create().Get()
|
|
67
|
+
J.destroy(pts); J.destroy(cvx)
|
|
68
|
+
}
|
|
62
69
|
else return null
|
|
63
70
|
const mt = motionType === 'dynamic' ? J.EMotionType_Dynamic : motionType === 'kinematic' ? J.EMotionType_Kinematic : J.EMotionType_Static
|
|
64
71
|
layer = motionType === 'static' ? LAYER_STATIC : LAYER_DYNAMIC
|