Improve mouth geometry performance by precomputing all shapes
Optimize mouth geometry generation by precomputing a cache of shapes, eliminating runtime allocations and reducing garbage collection pressure during animations. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 92d6d2fb-91ca-4dea-98b6-ff5053cb5ac7 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/Q83Uqvu Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -38,11 +38,16 @@ const MOOD_ALIASES = {
|
||||
working: 'working',
|
||||
};
|
||||
|
||||
// ── Mouth arc geometry helpers ────────────────────────────────────────────────
|
||||
// ── Mouth arc geometry — precomputed cache (no runtime allocation) ────────────
|
||||
// 21 steps from smileAmount -1.0 → +1.0 (step 0.1). All geometries built once
|
||||
// at module init; updateAgents() just swaps references → zero GC pressure.
|
||||
|
||||
const _MOUTH_GEO_STEPS = 21;
|
||||
const _MOUTH_GEO_MIN = -1.0;
|
||||
const _MOUTH_GEO_MAX = 1.0;
|
||||
|
||||
function _buildMouthGeo(smileAmount) {
|
||||
// smileAmount: +1 = smile (arc bows down), 0 = flat, -1 = frown (arc bows up)
|
||||
const ctrlY = -smileAmount * 0.065; // control point moves down for smile
|
||||
const ctrlY = -smileAmount * 0.065; // control point: down for smile, up for frown
|
||||
const curve = new THREE.QuadraticBezierCurve3(
|
||||
new THREE.Vector3(-0.115, 0.000, 0.000),
|
||||
new THREE.Vector3( 0.000, ctrlY, 0.020),
|
||||
@@ -51,6 +56,17 @@ function _buildMouthGeo(smileAmount) {
|
||||
return new THREE.TubeGeometry(curve, 12, 0.013, 5, false);
|
||||
}
|
||||
|
||||
const _MOUTH_GEO_CACHE = Array.from({ length: _MOUTH_GEO_STEPS }, (_, i) => {
|
||||
const t = i / (_MOUTH_GEO_STEPS - 1);
|
||||
return _buildMouthGeo(_MOUTH_GEO_MIN + t * (_MOUTH_GEO_MAX - _MOUTH_GEO_MIN));
|
||||
});
|
||||
|
||||
function _pickMouthGeo(smileAmount) {
|
||||
const clamped = Math.max(_MOUTH_GEO_MIN, Math.min(_MOUTH_GEO_MAX, smileAmount));
|
||||
const idx = Math.round((clamped - _MOUTH_GEO_MIN) / (_MOUTH_GEO_MAX - _MOUTH_GEO_MIN) * (_MOUTH_GEO_STEPS - 1));
|
||||
return _MOUTH_GEO_CACHE[idx];
|
||||
}
|
||||
|
||||
// ── Build Timmy ───────────────────────────────────────────────────────────────
|
||||
|
||||
export function initAgents(sceneRef) {
|
||||
@@ -115,8 +131,7 @@ function buildTimmy(sc) {
|
||||
roughness: 0.7,
|
||||
metalness: 0.0,
|
||||
});
|
||||
const mouthInitialGeo = _buildMouthGeo(0.08); // start at idle smile
|
||||
const mouth = new THREE.Mesh(mouthInitialGeo, mouthMat);
|
||||
const mouth = new THREE.Mesh(_pickMouthGeo(0.08), mouthMat); // start at idle smile
|
||||
// Position in head-local space: lower face (y=-0.18), on the face surface (z≈0.30)
|
||||
mouth.position.set(0, -0.18, 0.30);
|
||||
head.add(mouth);
|
||||
@@ -218,8 +233,6 @@ function buildTimmy(sc) {
|
||||
faceTarget: { lidScale: 0.44, pupilScale: 0.90, smileAmount: 0.08 },
|
||||
// External override: when non-null, setFaceEmotion() takes precedence over derived state
|
||||
_overrideMood: null,
|
||||
// Track last geometry rebuild to throttle TubeGeometry updates
|
||||
_lastMouthSmile: 0.08,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -322,12 +335,10 @@ export function updateAgents(time) {
|
||||
timmy.pupilL.scale.setScalar(ps);
|
||||
timmy.pupilR.scale.setScalar(ps);
|
||||
|
||||
// Rebuild TubeGeometry mouth arc when smile value changes enough
|
||||
const smileDelta = Math.abs(timmy.faceParams.smileAmount - timmy._lastMouthSmile);
|
||||
if (smileDelta > 0.016) {
|
||||
timmy.mouth.geometry.dispose();
|
||||
timmy.mouth.geometry = _buildMouthGeo(timmy.faceParams.smileAmount);
|
||||
timmy._lastMouthSmile = timmy.faceParams.smileAmount;
|
||||
// Swap precomputed mouth geometry when cached index changes (zero runtime allocation)
|
||||
const nextMouthGeo = _pickMouthGeo(timmy.faceParams.smileAmount);
|
||||
if (timmy.mouth.geometry !== nextMouthGeo) {
|
||||
timmy.mouth.geometry = nextMouthGeo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,7 +436,8 @@ export function disposeAgents() {
|
||||
[timmy.eyeL, timmy.eyeR].forEach(m => {
|
||||
if (m) { m.geometry?.dispose(); m.material?.dispose(); }
|
||||
});
|
||||
timmy.mouth?.geometry?.dispose();
|
||||
// Cached mouth geometries are shared; dispose the cache here
|
||||
_MOUTH_GEO_CACHE.forEach(g => g.dispose());
|
||||
timmy.mouth?.material?.dispose();
|
||||
timmy.bubbleTex?.dispose();
|
||||
timmy.bubbleMat?.dispose();
|
||||
|
||||
Reference in New Issue
Block a user