reze-engine 0.67.0 → 0.68.0
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/dist/camera.d.ts +1 -0
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +9 -5
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +8 -11
- package/dist/graph/presets/pool_floor.d.ts +3 -0
- package/dist/graph/presets/pool_floor.d.ts.map +1 -0
- package/dist/graph/presets/pool_floor.js +42 -0
- package/dist/graph/presets/water.d.ts +3 -0
- package/dist/graph/presets/water.d.ts.map +1 -0
- package/dist/graph/presets/water.js +31 -0
- package/dist/physics/autofit.d.ts +147 -0
- package/dist/physics/autofit.d.ts.map +1 -0
- package/dist/physics/autofit.js +501 -0
- package/dist/physics/contact.d.ts.map +1 -1
- package/dist/physics/contact.js +4 -1
- package/dist/physics/physics.d.ts +2 -0
- package/dist/physics/physics.d.ts.map +1 -1
- package/dist/physics/physics.js +14 -3
- package/dist/physics/solver.d.ts.map +1 -1
- package/dist/physics/solver.js +13 -3
- package/dist/shaders/agx-lut.d.ts +11 -0
- package/dist/shaders/agx-lut.d.ts.map +1 -0
- package/dist/shaders/agx-lut.js +29 -0
- package/dist/shaders/dfg_lut.d.ts +3 -0
- package/dist/shaders/dfg_lut.d.ts.map +1 -0
- package/dist/shaders/dfg_lut.js +129 -0
- package/dist/shaders/ltc_mag_lut.d.ts +3 -0
- package/dist/shaders/ltc_mag_lut.d.ts.map +1 -0
- package/dist/shaders/ltc_mag_lut.js +1033 -0
- package/dist/shaders/passes/field-blit.d.ts +26 -0
- package/dist/shaders/passes/field-blit.d.ts.map +1 -0
- package/dist/shaders/passes/field-blit.js +65 -0
- package/dist/shaders/passes/ghosts.d.ts +22 -0
- package/dist/shaders/passes/ghosts.d.ts.map +1 -0
- package/dist/shaders/passes/ghosts.js +75 -0
- package/dist/shaders/passes/ground-noise.d.ts +7 -0
- package/dist/shaders/passes/ground-noise.d.ts.map +1 -0
- package/dist/shaders/passes/ground-noise.js +88 -0
- package/dist/shaders/passes/sim.d.ts +34 -0
- package/dist/shaders/passes/sim.d.ts.map +1 -0
- package/dist/shaders/passes/sim.js +169 -0
- package/dist/shaders/score-api.d.ts +10 -0
- package/dist/shaders/score-api.d.ts.map +1 -0
- package/dist/shaders/score-api.js +114 -0
- package/dist/shaders/shadow-api.d.ts +29 -0
- package/dist/shaders/shadow-api.d.ts.map +1 -0
- package/dist/shaders/shadow-api.js +112 -0
- package/package.json +1 -1
- package/src/camera.ts +9 -5
- package/src/engine.ts +8 -11
- package/src/physics/contact.ts +4 -1
- package/src/physics/physics.ts +14 -3
- package/src/physics/solver.ts +13 -3
- package/dist/_dbg-loader.mjs +0 -1100
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 3×3 PCF over one cascade's map, for a point already in that cascade's NDC.
|
|
3
|
+
*
|
|
4
|
+
* Shared with the materials, so an effect's shadow and her own shadow soften
|
|
5
|
+
* alike. The sampler is linear-filtered, which makes each tap a 2×2 bilinear
|
|
6
|
+
* compare. Unrolled — Safari's Metal backend doesn't unroll nested shadow
|
|
7
|
+
* loops reliably. The body expects `ndc` in scope and returns.
|
|
8
|
+
*/
|
|
9
|
+
export declare const pcf9: (map: string, ts: string, sampler?: string) => string;
|
|
10
|
+
/** Bindings the real accessor takes, from `binding` upward. */
|
|
11
|
+
export declare const SHADOW_API_BINDINGS = 5;
|
|
12
|
+
/**
|
|
13
|
+
* `rzShadow(p)`: how much of the sun reaches world point `p` — 1 lit, 0 in a
|
|
14
|
+
* caster's shadow, soft in between.
|
|
15
|
+
*
|
|
16
|
+
* With `on`, declares five bindings from `binding`:
|
|
17
|
+
* +0 the light uniform (the sun's direction, and in .w the shadow switch)
|
|
18
|
+
* +1 the cascade view-projections, then the caster sphere
|
|
19
|
+
* +2 the near cascade's map +3 the far cascade's map
|
|
20
|
+
* +4 the comparison sampler
|
|
21
|
+
*
|
|
22
|
+
* It takes a POINT and no normal. The materials' sampleShadow also rejects a
|
|
23
|
+
* surface facing away from the sun, which is right for skin and wrong for a
|
|
24
|
+
* blade of grass or a petal: those are thin, lit through, and shaded by their
|
|
25
|
+
* own N·L. The point is nudged toward the sun instead, which lifts a blade's
|
|
26
|
+
* root off a stage floor that casts into the same map.
|
|
27
|
+
*/
|
|
28
|
+
export declare function shadowApi(on: boolean, group: number, binding: number): string;
|
|
29
|
+
//# sourceMappingURL=shadow-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shadow-api.d.ts","sourceRoot":"","sources":["../../src/shaders/shadow-api.ts"],"names":[],"mappings":"AAcA;;;;;;;GAOG;AACH,eAAO,MAAM,IAAI,GAAI,KAAK,MAAM,EAAE,IAAI,MAAM,EAAE,gBAAyB,WActE,CAAA;AAED,+DAA+D;AAC/D,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAyD7E"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { SHADOW_CASCADES } from "../shadow-cascades";
|
|
2
|
+
// The sun's shadow, as an effect author sees it.
|
|
3
|
+
//
|
|
4
|
+
// A lawn standing in a character's shadow has to darken where the floor under
|
|
5
|
+
// it does, or the figure floats: the ground catcher draws her shadow, and the
|
|
6
|
+
// blades drawn over it hide it. The map is already there — the materials and
|
|
7
|
+
// the ground read it every frame — so this hands the same lookup to effects.
|
|
8
|
+
//
|
|
9
|
+
// Bound for real in the particle SHADING stage only. That is where geometry an
|
|
10
|
+
// effect draws inside the scene pass is shaded, and the shadow pass has already
|
|
11
|
+
// run by then. Every other module that compiles the author's file gets the
|
|
12
|
+
// stub, which answers 1: lit is what a point with no occlusion information is,
|
|
13
|
+
// the same answer sampleShadow gives outside every cascade.
|
|
14
|
+
/**
|
|
15
|
+
* 3×3 PCF over one cascade's map, for a point already in that cascade's NDC.
|
|
16
|
+
*
|
|
17
|
+
* Shared with the materials, so an effect's shadow and her own shadow soften
|
|
18
|
+
* alike. The sampler is linear-filtered, which makes each tap a 2×2 bilinear
|
|
19
|
+
* compare. Unrolled — Safari's Metal backend doesn't unroll nested shadow
|
|
20
|
+
* loops reliably. The body expects `ndc` in scope and returns.
|
|
21
|
+
*/
|
|
22
|
+
export const pcf9 = (map, ts, sampler = "shadowSampler") => /* wgsl */ `
|
|
23
|
+
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
24
|
+
let cmpZ = ndc.z - 0.001;
|
|
25
|
+
let ts = ${ts};
|
|
26
|
+
let s00 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(-ts, -ts), cmpZ);
|
|
27
|
+
let s10 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(0.0, -ts), cmpZ);
|
|
28
|
+
let s20 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f( ts, -ts), cmpZ);
|
|
29
|
+
let s01 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(-ts, 0.0), cmpZ);
|
|
30
|
+
let s11 = textureSampleCompareLevel(${map}, ${sampler}, suv, cmpZ);
|
|
31
|
+
let s21 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f( ts, 0.0), cmpZ);
|
|
32
|
+
let s02 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(-ts, ts), cmpZ);
|
|
33
|
+
let s12 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f(0.0, ts), cmpZ);
|
|
34
|
+
let s22 = textureSampleCompareLevel(${map}, ${sampler}, suv + vec2f( ts, ts), cmpZ);
|
|
35
|
+
return (s00 + s10 + s20 + s01 + s11 + s21 + s02 + s12 + s22) * (1.0 / 9.0);
|
|
36
|
+
`;
|
|
37
|
+
/** Bindings the real accessor takes, from `binding` upward. */
|
|
38
|
+
export const SHADOW_API_BINDINGS = 5;
|
|
39
|
+
/**
|
|
40
|
+
* `rzShadow(p)`: how much of the sun reaches world point `p` — 1 lit, 0 in a
|
|
41
|
+
* caster's shadow, soft in between.
|
|
42
|
+
*
|
|
43
|
+
* With `on`, declares five bindings from `binding`:
|
|
44
|
+
* +0 the light uniform (the sun's direction, and in .w the shadow switch)
|
|
45
|
+
* +1 the cascade view-projections, then the caster sphere
|
|
46
|
+
* +2 the near cascade's map +3 the far cascade's map
|
|
47
|
+
* +4 the comparison sampler
|
|
48
|
+
*
|
|
49
|
+
* It takes a POINT and no normal. The materials' sampleShadow also rejects a
|
|
50
|
+
* surface facing away from the sun, which is right for skin and wrong for a
|
|
51
|
+
* blade of grass or a petal: those are thin, lit through, and shaded by their
|
|
52
|
+
* own N·L. The point is nudged toward the sun instead, which lifts a blade's
|
|
53
|
+
* root off a stage floor that casts into the same map.
|
|
54
|
+
*/
|
|
55
|
+
export function shadowApi(on, group, binding) {
|
|
56
|
+
if (!on) {
|
|
57
|
+
return /* wgsl */ `
|
|
58
|
+
fn rzShadow(p: vec3f) -> f32 { return 1.0; }
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
const n = SHADOW_CASCADES.length;
|
|
62
|
+
return /* wgsl */ `
|
|
63
|
+
// The first two vec4s of the light uniform: world ambient, then the sun.
|
|
64
|
+
struct _RzShadowSun { ambient: vec4f, direction: vec4f, }
|
|
65
|
+
// The cascades' matrices, inner to outer, then every caster in one sphere
|
|
66
|
+
// (x, y, z, radius): radius 0 = nothing casts, negative = do not test.
|
|
67
|
+
struct _RzShadowBlock { viewProj: array<mat4x4f, ${n}>, casters: vec4f, }
|
|
68
|
+
@group(${group}) @binding(${binding}) var<uniform> _rzShadowSun: _RzShadowSun;
|
|
69
|
+
@group(${group}) @binding(${binding + 1}) var<uniform> _rzShadowVP: _RzShadowBlock;
|
|
70
|
+
@group(${group}) @binding(${binding + 2}) var _rzShadowNear: texture_depth_2d;
|
|
71
|
+
@group(${group}) @binding(${binding + 3}) var _rzShadowFar: texture_depth_2d;
|
|
72
|
+
@group(${group}) @binding(${binding + 4}) var _rzShadowCmp: sampler_comparison;
|
|
73
|
+
|
|
74
|
+
fn _rzShadowNearTaps(ndc: vec3f) -> f32 {
|
|
75
|
+
${pcf9("_rzShadowNear", `1.0 / ${SHADOW_CASCADES[0].mapSize}.0`, "_rzShadowCmp")}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fn _rzShadowFarTaps(ndc: vec3f) -> f32 {
|
|
79
|
+
${pcf9("_rzShadowFar", `1.0 / ${SHADOW_CASCADES[n - 1].mapSize}.0`, "_rzShadowCmp")}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn rzShadow(p: vec3f) -> f32 {
|
|
83
|
+
// The scene's one shadow switch, on the sun — see sampleShadow.
|
|
84
|
+
let castAmt = _rzShadowSun.direction.w;
|
|
85
|
+
if (castAmt <= 0.0) { return 1.0; }
|
|
86
|
+
let toSun = normalize(-_rzShadowSun.direction.xyz);
|
|
87
|
+
// Can anything cast onto this point at all? The ground's test, for the same
|
|
88
|
+
// two reasons: most of a lawn is nowhere near her, and a scene with no
|
|
89
|
+
// models never renders the map, whose untouched zeroes compare as shadowed.
|
|
90
|
+
let cs = _rzShadowVP.casters;
|
|
91
|
+
if (cs.w == 0.0) { return 1.0; }
|
|
92
|
+
if (cs.w > 0.0) {
|
|
93
|
+
let toCaster = cs.xyz - p;
|
|
94
|
+
let along = dot(toCaster, toSun);
|
|
95
|
+
let perp = length(toCaster - toSun * along);
|
|
96
|
+
if (along <= -cs.w || perp > cs.w) { return 1.0; }
|
|
97
|
+
}
|
|
98
|
+
let q = p + toSun * 0.08;
|
|
99
|
+
let c0 = _rzShadowVP.viewProj[0] * vec4f(q, 1.0);
|
|
100
|
+
let n0 = c0.xyz / max(c0.w, 1e-6);
|
|
101
|
+
if (all(abs(n0.xy) < vec2f(0.98)) && n0.z > 0.0 && n0.z < 1.0) {
|
|
102
|
+
return mix(1.0, _rzShadowNearTaps(n0), castAmt);
|
|
103
|
+
}
|
|
104
|
+
let c1 = _rzShadowVP.viewProj[${n - 1}] * vec4f(q, 1.0);
|
|
105
|
+
let n1 = c1.xyz / max(c1.w, 1e-6);
|
|
106
|
+
if (all(abs(n1.xy) < vec2f(0.98)) && n1.z > 0.0 && n1.z < 1.0) {
|
|
107
|
+
return mix(1.0, _rzShadowFarTaps(n1), castAmt);
|
|
108
|
+
}
|
|
109
|
+
return 1.0;
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
}
|
package/package.json
CHANGED
package/src/camera.ts
CHANGED
|
@@ -74,7 +74,8 @@ export class Camera {
|
|
|
74
74
|
|
|
75
75
|
// Camera settings
|
|
76
76
|
angularSensitivity: number = 0.005
|
|
77
|
-
panSensitivity: number = 0.
|
|
77
|
+
panSensitivity: number = 0.0003 // Sensitivity for right-click panning
|
|
78
|
+
static readonly PAN_REF_RADIUS = 33
|
|
78
79
|
wheelPrecision: number = 0.01
|
|
79
80
|
pinchPrecision: number = 0.05
|
|
80
81
|
minZ: number = 0.05
|
|
@@ -335,10 +336,13 @@ export class Camera {
|
|
|
335
336
|
private panCamera(deltaX: number, deltaY: number) {
|
|
336
337
|
const { right, up } = this.getCameraVectors()
|
|
337
338
|
|
|
338
|
-
//
|
|
339
|
-
//
|
|
340
|
-
//
|
|
341
|
-
|
|
339
|
+
// Pan scales with camera distance, so a drag covers the same share of the
|
|
340
|
+
// frame at every zoom from PAN_REF_RADIUS out. Closer in it shrinks slower
|
|
341
|
+
// than the distance (power 0.7): a close-up pans a little faster across the
|
|
342
|
+
// frame — 1.4x at distance 10, 1.8x at 5 — instead of crawling.
|
|
343
|
+
const r = this.radius
|
|
344
|
+
const ref = Camera.PAN_REF_RADIUS
|
|
345
|
+
const panDistance = (r >= ref ? r : ref * Math.pow(Math.max(r, 0) / ref, 0.7)) * this.panSensitivity
|
|
342
346
|
|
|
343
347
|
// Horizontal movement: drag right pans left (opposite direction)
|
|
344
348
|
// Vertical movement: drag up pans up (positive up vector)
|
package/src/engine.ts
CHANGED
|
@@ -17144,28 +17144,25 @@ export class Engine {
|
|
|
17144
17144
|
py += pos.y
|
|
17145
17145
|
pz += pos.z
|
|
17146
17146
|
}
|
|
17147
|
-
px += this.cameraTargetOffset.x
|
|
17148
|
-
py += this.cameraTargetOffset.y
|
|
17149
|
-
pz += this.cameraTargetOffset.z
|
|
17150
17147
|
const tau = this.cameraFollowSmoothing
|
|
17148
|
+
const f = this.cameraFollowPos
|
|
17151
17149
|
if (tau > 0 && this.cameraFollowSeeded) {
|
|
17152
17150
|
// Exponential lag toward the bone: the handheld-camera feel. The
|
|
17153
17151
|
// orbit pivot trails the target and eases in, never snapping.
|
|
17154
17152
|
const k = 1 - Math.exp(-deltaTime / tau)
|
|
17155
|
-
const f = this.cameraFollowPos
|
|
17156
17153
|
f.x += (px - f.x) * k
|
|
17157
17154
|
f.y += (py - f.y) * k
|
|
17158
17155
|
f.z += (pz - f.z) * k
|
|
17159
|
-
this.camera.target.x = f.x
|
|
17160
|
-
this.camera.target.y = f.y
|
|
17161
|
-
this.camera.target.z = f.z
|
|
17162
17156
|
} else {
|
|
17163
|
-
|
|
17157
|
+
f.setXYZ(px, py, pz)
|
|
17164
17158
|
this.cameraFollowSeeded = true
|
|
17165
|
-
this.camera.target.x = px
|
|
17166
|
-
this.camera.target.y = py
|
|
17167
|
-
this.camera.target.z = pz
|
|
17168
17159
|
}
|
|
17160
|
+
// The offset goes on AFTER the lag. A pan writes the offset (panSink),
|
|
17161
|
+
// and the lag is for the bone's motion: eased with it, a pan glided
|
|
17162
|
+
// on after the hand let go while an orbit stopped dead.
|
|
17163
|
+
this.camera.target.x = f.x + this.cameraTargetOffset.x
|
|
17164
|
+
this.camera.target.y = f.y + this.cameraTargetOffset.y
|
|
17165
|
+
this.camera.target.z = f.z + this.cameraTargetOffset.z
|
|
17169
17166
|
}
|
|
17170
17167
|
}
|
|
17171
17168
|
|
package/src/physics/contact.ts
CHANGED
|
@@ -1397,7 +1397,10 @@ function flipOneNormal(c: Contact): void {
|
|
|
1397
1397
|
// see RigidBodyStore.getCollisionPairs. SAP / dynamic AABB tree pay off
|
|
1398
1398
|
// above ~500 bodies; below that this flat sweep wins on cache locality.
|
|
1399
1399
|
export function findContacts(store: RigidBodyStore, pool: ContactPool): void {
|
|
1400
|
-
|
|
1400
|
+
// Inflated by the contact margin and no more: a narrowphase only ever makes
|
|
1401
|
+
// a contact within CONTACT_MARGIN, so a fatter box only bought SAT tests on
|
|
1402
|
+
// pairs that could never touch — at 0.5 that was most of the collision cost.
|
|
1403
|
+
store.updateAabbs(CONTACT_MARGIN + 0.01)
|
|
1401
1404
|
const pairs = store.getCollisionPairs()
|
|
1402
1405
|
for (let p = 0; p < pairs.length; p += 2) {
|
|
1403
1406
|
const i = pairs[p]
|
package/src/physics/physics.ts
CHANGED
|
@@ -24,8 +24,18 @@ export class RezePhysics {
|
|
|
24
24
|
private contacts: ContactPool
|
|
25
25
|
private firstFrame = true
|
|
26
26
|
private timeAccum = 0
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
// Two 120 Hz steps of 5 iterations where MMD takes one 60 Hz step of 10.
|
|
28
|
+
// Contacts and joints converge against each other far better in two small
|
|
29
|
+
// steps: a thin skirt lattice pressed on the legs shook 58% less and sat
|
|
30
|
+
// inside them a third as often. Collision runs every step — reusing one
|
|
31
|
+
// contact set across steps missed contacts that began between detections,
|
|
32
|
+
// and the late push-out shook worse than the single step ever did.
|
|
33
|
+
private readonly fixedTimeStep = 1 / 120
|
|
34
|
+
private readonly maxSubSteps = 12
|
|
35
|
+
private static readonly ITERATIONS = 5
|
|
36
|
+
// One 60 Hz frame of simulated time — what load shedding may never cut
|
|
37
|
+
// below, the guarantee the single 1/60 step always gave.
|
|
38
|
+
private static readonly MIN_STEPS = 2
|
|
29
39
|
/** EMA of one world.step's CPU cost; drives catch-up load shedding. */
|
|
30
40
|
private stepCostEmaMs = 0.5
|
|
31
41
|
/**
|
|
@@ -131,6 +141,7 @@ export class RezePhysics {
|
|
|
131
141
|
this.groundBody = gi
|
|
132
142
|
this.store.groundIndex = gi
|
|
133
143
|
this.world = new World(new Vec3(0, -98, 0))
|
|
144
|
+
this.world.solverIterations = RezePhysics.ITERATIONS
|
|
134
145
|
this.constraints = buildConstraints(rigidbodies, joints)
|
|
135
146
|
this.solverCache = new SolverCache(this.constraints)
|
|
136
147
|
this.contacts = new ContactPool()
|
|
@@ -307,7 +318,7 @@ export class RezePhysics {
|
|
|
307
318
|
this.stepBudgetMaxMs,
|
|
308
319
|
Math.max(this.stepBudgetMinMs, dt * 1000 * this.stepBudgetFraction),
|
|
309
320
|
)
|
|
310
|
-
const affordable = Math.max(
|
|
321
|
+
const affordable = Math.max(RezePhysics.MIN_STEPS, Math.floor(budgetMs / Math.max(0.05, this.stepCostEmaMs)))
|
|
311
322
|
if (nSub > affordable) nSub = affordable
|
|
312
323
|
for (let k = 0; k < nSub; k++) {
|
|
313
324
|
this.savePrevState()
|
package/src/physics/solver.ts
CHANGED
|
@@ -37,7 +37,12 @@ const BOUNCE_THRESHOLD = 2.0
|
|
|
37
37
|
// pushes apart; separated (depth < 0, a speculative row) it ALLOWS approach at
|
|
38
38
|
// the rate that closes the gap, which is what keeps those rows inert until the
|
|
39
39
|
// body would really arrive.
|
|
40
|
-
|
|
40
|
+
//
|
|
41
|
+
// 0.1, half Bullet's 0.2: at 120 Hz each step closes a tenth of the overlap,
|
|
42
|
+
// the same correction per second as MMD's 0.2 at 60 Hz, and a light cloth body
|
|
43
|
+
// pressed between its joints and a collider stops being kicked out harder than
|
|
44
|
+
// the joints can take back (skirt shake −20% on its own).
|
|
45
|
+
const CONTACT_ERP = 0.1
|
|
41
46
|
|
|
42
47
|
// btContactSolverInfo's SOLVER_RANDMIZE_ORDER. Gauss-Seidel is order-biased —
|
|
43
48
|
// rows solved first win — which on a cross-linked lattice shows up as a lean or
|
|
@@ -119,6 +124,11 @@ const LIMIT_SOFTNESS_ANGULAR = 0.5
|
|
|
119
124
|
// ratio (ζ = 0.7); ported in isolation the motor form measured 0.0015 mean
|
|
120
125
|
// reversal at jerk 0.5, the calmest joint-side result recorded.
|
|
121
126
|
const SPRING_DAMPING = 1.0
|
|
127
|
+
// The motor's velocity gain at the rate PMX rigs are authored against — MMD's
|
|
128
|
+
// Bullet at 60 Hz with 10 iterations. Bullet's form divides by the step's fps
|
|
129
|
+
// and iteration count; at any other substep rate that retunes every spring,
|
|
130
|
+
// so the gain is pinned here and the substep rate stays a solver choice.
|
|
131
|
+
const SPRING_VEL_FACTOR = (60 * SPRING_DAMPING) / 10
|
|
122
132
|
// btConstraintInfo2::erp, from infoGlobal.m_erp — used only by getMotorFactor.
|
|
123
133
|
const INFO_ERP = 0.2
|
|
124
134
|
|
|
@@ -543,7 +553,7 @@ function setupConstraint(
|
|
|
543
553
|
const k = con.springStiffness[i]
|
|
544
554
|
const serr = curr - con.equilibriumPoint[i]
|
|
545
555
|
const force = serr * k
|
|
546
|
-
const velFactor =
|
|
556
|
+
const velFactor = SPRING_VEL_FACTOR
|
|
547
557
|
const target = -velFactor * force
|
|
548
558
|
// getMotorFactor's `vel` is Bullet's tag_vel: −targetVelocity for a
|
|
549
559
|
// linear axis, which in our sign convention is the target itself.
|
|
@@ -625,7 +635,7 @@ function setupConstraint(
|
|
|
625
635
|
const k = con.springStiffness[idx]
|
|
626
636
|
const serr = _angDiffScratch[i] - con.equilibriumPoint[idx]
|
|
627
637
|
const force = -serr * k
|
|
628
|
-
const velFactor =
|
|
638
|
+
const velFactor = SPRING_VEL_FACTOR
|
|
629
639
|
const target = -velFactor * force
|
|
630
640
|
// tag_vel for a rotational axis is Bullet's targetVelocity, i.e. −ours.
|
|
631
641
|
const motFact = getMotorFactor(_angDiffScratch[i], con.angularMin[i], con.angularMax[i], -target, invDt * INFO_ERP)
|