reze-engine 0.50.3 → 0.50.5
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/engine.d.ts +247 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +822 -64
- package/dist/model.d.ts +49 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +160 -4
- 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/physics.d.ts +35 -0
- package/dist/physics/physics.d.ts.map +1 -1
- package/dist/physics/physics.js +64 -0
- package/dist/physics/world.d.ts +4 -0
- package/dist/physics/world.d.ts.map +1 -1
- package/dist/physics/world.js +6 -0
- package/dist/shaders/cast-api.d.ts +1 -1
- package/dist/shaders/cast-api.d.ts.map +1 -1
- package/dist/shaders/cast-layout.d.ts +44 -1
- package/dist/shaders/cast-layout.d.ts.map +1 -1
- package/dist/shaders/cast-layout.js +44 -1
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +7 -1
- package/dist/shaders/materials/nodes.d.ts +1 -1
- package/dist/shaders/materials/nodes.d.ts.map +1 -1
- package/dist/shaders/materials/nodes.js +17 -9
- package/dist/shaders/passes/composite.d.ts +1 -1
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/depth-prepass.d.ts +1 -1
- package/dist/shaders/passes/depth-prepass.d.ts.map +1 -1
- package/dist/shaders/passes/depth-prepass.js +52 -12
- 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/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/ground.d.ts +16 -0
- package/dist/shaders/passes/ground.d.ts.map +1 -1
- package/dist/shaders/passes/ground.js +131 -27
- package/dist/shaders/passes/outline.d.ts +1 -1
- package/dist/shaders/passes/outline.d.ts.map +1 -1
- package/dist/shaders/passes/outline.js +12 -3
- package/dist/shaders/passes/particles.d.ts.map +1 -1
- package/dist/shaders/passes/particles.js +6 -2
- package/dist/shaders/passes/scene-contract.d.ts +38 -6
- package/dist/shaders/passes/scene-contract.d.ts.map +1 -1
- package/dist/shaders/passes/scene-contract.js +53 -16
- 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/passes/trails.d.ts.map +1 -1
- package/dist/shaders/passes/trails.js +36 -8
- 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/package.json +1 -1
- package/src/engine.ts +867 -56
- package/src/model.ts +163 -4
- package/src/physics/physics.ts +63 -0
- package/src/physics/world.ts +7 -0
- package/src/shaders/cast-layout.ts +44 -1
- package/src/shaders/materials/common.ts +7 -1
- package/src/shaders/materials/nodes.ts +17 -9
- package/src/shaders/passes/depth-prepass.ts +53 -12
- package/src/shaders/passes/ground.ts +133 -27
- package/src/shaders/passes/outline.ts +14 -3
- package/src/shaders/passes/particles.ts +6 -2
- package/src/shaders/passes/scene-contract.ts +55 -16
- package/src/shaders/passes/trails.ts +36 -8
- package/dist/physics-debug.d.ts +0 -30
- package/dist/physics-debug.d.ts.map +0 -1
- package/dist/physics-debug.js +0 -526
- package/dist/shaders/materials/body.d.ts +0 -2
- package/dist/shaders/materials/body.d.ts.map +0 -1
- package/dist/shaders/materials/body.js +0 -95
- package/dist/shaders/materials/cloth_rough.d.ts +0 -2
- package/dist/shaders/materials/cloth_rough.d.ts.map +0 -1
- package/dist/shaders/materials/cloth_rough.js +0 -69
- package/dist/shaders/materials/cloth_smooth.d.ts +0 -2
- package/dist/shaders/materials/cloth_smooth.d.ts.map +0 -1
- package/dist/shaders/materials/cloth_smooth.js +0 -61
- package/dist/shaders/materials/default.d.ts +0 -2
- package/dist/shaders/materials/default.d.ts.map +0 -1
- package/dist/shaders/materials/default.js +0 -43
- package/dist/shaders/materials/eye.d.ts +0 -2
- package/dist/shaders/materials/eye.d.ts.map +0 -1
- package/dist/shaders/materials/eye.js +0 -60
- package/dist/shaders/materials/face.d.ts +0 -2
- package/dist/shaders/materials/face.d.ts.map +0 -1
- package/dist/shaders/materials/face.js +0 -95
- package/dist/shaders/materials/hair.d.ts +0 -2
- package/dist/shaders/materials/hair.d.ts.map +0 -1
- package/dist/shaders/materials/hair.js +0 -90
- package/dist/shaders/materials/metal.d.ts +0 -2
- package/dist/shaders/materials/metal.d.ts.map +0 -1
- package/dist/shaders/materials/metal.js +0 -77
- package/dist/shaders/materials/mmd_classic.d.ts +0 -2
- package/dist/shaders/materials/mmd_classic.d.ts.map +0 -1
- package/dist/shaders/materials/mmd_classic.js +0 -66
- package/dist/shaders/materials/stockings.d.ts +0 -2
- package/dist/shaders/materials/stockings.d.ts.map +0 -1
- package/dist/shaders/materials/stockings.js +0 -122
- package/dist/shaders/passes/physics-debug.d.ts +0 -2
- package/dist/shaders/passes/physics-debug.d.ts.map +0 -1
- package/dist/shaders/passes/physics-debug.js +0 -69
package/src/model.ts
CHANGED
|
@@ -30,6 +30,10 @@ const _animSlerp = new Quat(0, 0, 0, 1)
|
|
|
30
30
|
const _animInterpT = new Vec3(0, 0, 0)
|
|
31
31
|
const _convOut = new Vec3(0, 0, 0)
|
|
32
32
|
const _convMat = new Float32Array(16)
|
|
33
|
+
// Scratch for the post-physics append recovery — see applyPhysicsAppend.
|
|
34
|
+
const _appendBasisX = new Vec3(0, 0, 0)
|
|
35
|
+
const _appendBasisY = new Vec3(0, 0, 0)
|
|
36
|
+
const _appendBasisZ = new Vec3(0, 0, 0)
|
|
33
37
|
// Blend-path scratch: per-entry sample target and the crossfade's two fixed entries.
|
|
34
38
|
const _blendQ = new Quat(0, 0, 0, 1)
|
|
35
39
|
const _blendT = new Vec3(0, 0, 0)
|
|
@@ -388,6 +392,15 @@ export class Model {
|
|
|
388
392
|
// visited-array + closure. Order depends only on parentIndex (static), so this
|
|
389
393
|
// reproduces the old recursion's finishing order exactly. See buildDeformOrder.
|
|
390
394
|
private deformOrder!: Int32Array
|
|
395
|
+
/** 1 where the simulation overwrites the bone's world matrix. See setPhysicsDrivenBones. */
|
|
396
|
+
private physicsDriven: Uint8Array | null = null
|
|
397
|
+
/** Bones to recompute after a step, in deform order — null when no rig needs it. */
|
|
398
|
+
private physicsAppendOrder: Int32Array | null = null
|
|
399
|
+
/** Simulated bones something inherits from; their local rotation is recovered. */
|
|
400
|
+
private physicsAppendSources: Int32Array | null = null
|
|
401
|
+
/** Recovered post-step local rotations, kept OUT of localRotations on purpose. */
|
|
402
|
+
private appendRotOverride: Quat[] | null = null
|
|
403
|
+
private appendRotOverrideSet: Uint8Array | null = null
|
|
391
404
|
|
|
392
405
|
// Bind-pose absolute (world) position per bone, xyz packed. Static (bindTranslation
|
|
393
406
|
// accumulated down the hierarchy). Precomputed once so convertVMDTranslationToLocal
|
|
@@ -2612,7 +2625,141 @@ export class Model {
|
|
|
2612
2625
|
}
|
|
2613
2626
|
}
|
|
2614
2627
|
|
|
2615
|
-
|
|
2628
|
+
/**
|
|
2629
|
+
* Which bones the physics simulation overwrites, and what that costs the
|
|
2630
|
+
* append (付与) pass — the 胸 problem.
|
|
2631
|
+
*
|
|
2632
|
+
* PMX lets a bone inherit a fraction of another bone's rotation (付与親 /
|
|
2633
|
+
* append parent). The pipeline computes that inheritance inside
|
|
2634
|
+
* computeWorldMatrices, which runs BEFORE physics — and it reads the
|
|
2635
|
+
* parent's LOCAL rotation, which physics never writes: the simulation
|
|
2636
|
+
* publishes world matrices only. So when a rig hangs a bone off a simulated
|
|
2637
|
+
* one, the inheritance saw the animated pose and nothing else, and the
|
|
2638
|
+
* dependent bone sat still no matter how much the parent swung. Rigs that
|
|
2639
|
+
* drive a chest this way — a simulated bone with the visible bones
|
|
2640
|
+
* appending from it — produced no motion at all.
|
|
2641
|
+
*
|
|
2642
|
+
* The engine calls this once, after building the simulation, and it
|
|
2643
|
+
* precomputes the whole answer: WHICH bones need revisiting after a step,
|
|
2644
|
+
* in deform order. Everything downstream is a walk over that list.
|
|
2645
|
+
*/
|
|
2646
|
+
setPhysicsDrivenBones(boneIndices: number[]): void {
|
|
2647
|
+
const bones = this.skeleton.bones
|
|
2648
|
+
const n = bones.length
|
|
2649
|
+
this.physicsDriven = new Uint8Array(n)
|
|
2650
|
+
for (const b of boneIndices) if (b >= 0 && b < n) this.physicsDriven[b] = 1
|
|
2651
|
+
|
|
2652
|
+
// Bones to recompute after a step: anything that INHERITS from a simulated
|
|
2653
|
+
// bone, everything under it, and anything inheriting from those in turn.
|
|
2654
|
+
// Simulated bones themselves are deliberately excluded — their world matrix
|
|
2655
|
+
// IS the simulation's output, and recomputing it from a local pose the
|
|
2656
|
+
// simulation never wrote would throw the step away.
|
|
2657
|
+
const affected = new Uint8Array(n)
|
|
2658
|
+
let changed = true
|
|
2659
|
+
while (changed) {
|
|
2660
|
+
changed = false
|
|
2661
|
+
for (let k = 0; k < n; k++) {
|
|
2662
|
+
const i = this.deformOrder[k]
|
|
2663
|
+
if (affected[i] || this.physicsDriven[i]) continue
|
|
2664
|
+
const b = bones[i]
|
|
2665
|
+
const ap = b.appendParentIndex
|
|
2666
|
+
const inheritsAffected =
|
|
2667
|
+
(b.appendRotate || b.appendMove) && ap !== undefined && ap >= 0 && ap < n && (this.physicsDriven[ap] || affected[ap])
|
|
2668
|
+
const parentAffected = b.parentIndex >= 0 && affected[b.parentIndex]
|
|
2669
|
+
if (inheritsAffected || parentAffected) {
|
|
2670
|
+
affected[i] = 1
|
|
2671
|
+
changed = true
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
const order: number[] = []
|
|
2677
|
+
for (let k = 0; k < n; k++) {
|
|
2678
|
+
const i = this.deformOrder[k]
|
|
2679
|
+
if (affected[i]) order.push(i)
|
|
2680
|
+
}
|
|
2681
|
+
this.physicsAppendOrder = order.length > 0 ? Int32Array.from(order) : null
|
|
2682
|
+
// The simulated bones something actually inherits from — the only ones
|
|
2683
|
+
// whose post-step local rotation has to be recovered below.
|
|
2684
|
+
const sources = new Set<number>()
|
|
2685
|
+
for (const i of order) {
|
|
2686
|
+
const ap = bones[i].appendParentIndex
|
|
2687
|
+
if (ap !== undefined && ap >= 0 && ap < n && this.physicsDriven[ap]) sources.add(ap)
|
|
2688
|
+
}
|
|
2689
|
+
this.physicsAppendSources = sources.size > 0 ? Int32Array.from(sources) : null
|
|
2690
|
+
if (this.appendRotOverride === null && this.physicsAppendOrder) {
|
|
2691
|
+
this.appendRotOverride = Array.from({ length: n }, () => Quat.identity())
|
|
2692
|
+
this.appendRotOverrideSet = new Uint8Array(n)
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
/** The simulated bones that visible bones INHERIT from — the chest rig, in
|
|
2697
|
+
* one list. Empty unless setPhysicsDrivenBones found such a relationship. */
|
|
2698
|
+
getAppendSourceBones(): number[] {
|
|
2699
|
+
return this.physicsAppendSources ? Array.from(this.physicsAppendSources) : []
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
/**
|
|
2703
|
+
* Re-run the append pass against the simulation's result. Call after a step.
|
|
2704
|
+
*
|
|
2705
|
+
* Two halves. First the simulated bones an append parent list names get their
|
|
2706
|
+
* post-step LOCAL rotation recovered — physics published only world matrices,
|
|
2707
|
+
* and the append math speaks local. The recovery is the ordinary change of
|
|
2708
|
+
* basis: local = parentWorld⁻¹ · world, read back as a quaternion off the
|
|
2709
|
+
* relative basis, which is exact for the rigid transforms these are.
|
|
2710
|
+
*
|
|
2711
|
+
* Then the dependent bones recompute, in deform order, reading that recovered
|
|
2712
|
+
* rotation instead of the animated one. The recovered value lives in its OWN
|
|
2713
|
+
* array rather than in localRotations, deliberately: localRotations is what
|
|
2714
|
+
* next frame's pose blends against and what the simulation reads to build
|
|
2715
|
+
* kinematic targets, and writing a simulation result back into it would make
|
|
2716
|
+
* the animation chase its own tail.
|
|
2717
|
+
*/
|
|
2718
|
+
applyPhysicsAppend(): void {
|
|
2719
|
+
const order = this.physicsAppendOrder
|
|
2720
|
+
const sources = this.physicsAppendSources
|
|
2721
|
+
if (!order || !sources || !this.appendRotOverride || !this.appendRotOverrideSet) return
|
|
2722
|
+
const bones = this.skeleton.bones
|
|
2723
|
+
const worldMats = this.runtimeSkeleton.worldMatrices
|
|
2724
|
+
|
|
2725
|
+
this.appendRotOverrideSet.fill(0)
|
|
2726
|
+
for (let s = 0; s < sources.length; s++) {
|
|
2727
|
+
const i = sources[s]
|
|
2728
|
+
const w = worldMats[i].values
|
|
2729
|
+
const p = bones[i].parentIndex
|
|
2730
|
+
// Columns of the bone's own basis, expressed in its parent's frame. With
|
|
2731
|
+
// no parent the world basis already IS the local one.
|
|
2732
|
+
let bx0 = w[0], bx1 = w[1], bx2 = w[2]
|
|
2733
|
+
let by0 = w[4], by1 = w[5], by2 = w[6]
|
|
2734
|
+
let bz0 = w[8], bz1 = w[9], bz2 = w[10]
|
|
2735
|
+
if (p >= 0) {
|
|
2736
|
+
const pm = worldMats[p].values
|
|
2737
|
+
// parentᵀ · child, the rotation half of parentWorld⁻¹ · world: the
|
|
2738
|
+
// parent basis is orthonormal, so its inverse is its transpose.
|
|
2739
|
+
const r0 = bx0, r1 = bx1, r2 = bx2
|
|
2740
|
+
bx0 = pm[0] * r0 + pm[1] * r1 + pm[2] * r2
|
|
2741
|
+
bx1 = pm[4] * r0 + pm[5] * r1 + pm[6] * r2
|
|
2742
|
+
bx2 = pm[8] * r0 + pm[9] * r1 + pm[10] * r2
|
|
2743
|
+
const g0 = by0, g1 = by1, g2 = by2
|
|
2744
|
+
by0 = pm[0] * g0 + pm[1] * g1 + pm[2] * g2
|
|
2745
|
+
by1 = pm[4] * g0 + pm[5] * g1 + pm[6] * g2
|
|
2746
|
+
by2 = pm[8] * g0 + pm[9] * g1 + pm[10] * g2
|
|
2747
|
+
const b0 = bz0, b1 = bz1, b2 = bz2
|
|
2748
|
+
bz0 = pm[0] * b0 + pm[1] * b1 + pm[2] * b2
|
|
2749
|
+
bz1 = pm[4] * b0 + pm[5] * b1 + pm[6] * b2
|
|
2750
|
+
bz2 = pm[8] * b0 + pm[9] * b1 + pm[10] * b2
|
|
2751
|
+
}
|
|
2752
|
+
_appendBasisX.setXYZ(bx0, bx1, bx2)
|
|
2753
|
+
_appendBasisY.setXYZ(by0, by1, by2)
|
|
2754
|
+
_appendBasisZ.setXYZ(bz0, bz1, bz2)
|
|
2755
|
+
Quat.fromBasisInto(_appendBasisX, _appendBasisY, _appendBasisZ, this.appendRotOverride[i])
|
|
2756
|
+
this.appendRotOverrideSet[i] = 1
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
this.computeWorldMatrices(order)
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
computeWorldMatrices(subset?: Int32Array): void {
|
|
2616
2763
|
const bones = this.skeleton.bones
|
|
2617
2764
|
const localRot = this.runtimeSkeleton.localRotations
|
|
2618
2765
|
const localTrans = this.runtimeSkeleton.localTranslations
|
|
@@ -2624,8 +2771,15 @@ export class Model {
|
|
|
2624
2771
|
// Flat traversal in precomputed order: every bone's parent is already done, so no
|
|
2625
2772
|
// per-bone visited check, no recursion, and no per-call allocation. Same per-bone
|
|
2626
2773
|
// math as before. Scratch slots are safe to reuse since there's no reentrancy now.
|
|
2627
|
-
|
|
2628
|
-
|
|
2774
|
+
//
|
|
2775
|
+
// A SUBSET is the post-physics pass (applyPhysicsAppend): the same walk over
|
|
2776
|
+
// the bones that inherit from a simulated one, in the same relative order,
|
|
2777
|
+
// leaving every other bone — the simulated ones above all — untouched.
|
|
2778
|
+
const order = subset ?? this.deformOrder
|
|
2779
|
+
const count = subset ? subset.length : boneCount
|
|
2780
|
+
const override = this.appendRotOverride
|
|
2781
|
+
const overrideSet = this.appendRotOverrideSet
|
|
2782
|
+
for (let k = 0; k < count; k++) {
|
|
2629
2783
|
const i = order[k]
|
|
2630
2784
|
const b = bones[i]
|
|
2631
2785
|
|
|
@@ -2643,7 +2797,12 @@ export class Model {
|
|
|
2643
2797
|
|
|
2644
2798
|
if (hasRatio) {
|
|
2645
2799
|
if (b.appendRotate) {
|
|
2646
|
-
|
|
2800
|
+
// The simulated parent's RECOVERED rotation when there is one — the
|
|
2801
|
+
// whole point of the post-physics pass. localRotations still holds
|
|
2802
|
+
// the animated pose for that bone, which is exactly what must not
|
|
2803
|
+
// be inherited here.
|
|
2804
|
+
const appendRot =
|
|
2805
|
+
override && overrideSet && overrideSet[appendParentIdx] ? override[appendParentIdx] : localRot[appendParentIdx]
|
|
2647
2806
|
let ax = appendRot.x, ay = appendRot.y, az = appendRot.z
|
|
2648
2807
|
const aw = appendRot.w
|
|
2649
2808
|
const absRatio = ratio < 0 ? -ratio : ratio
|
package/src/physics/physics.ts
CHANGED
|
@@ -706,6 +706,69 @@ export class RezePhysics {
|
|
|
706
706
|
// boneWorld = bodyWorld × bodyOffsetInverse.
|
|
707
707
|
// The body pose is the render-interpolated pose between the previous and current
|
|
708
708
|
// substep states (alpha = fraction into the next step), which removes fixed-step judder.
|
|
709
|
+
/**
|
|
710
|
+
* Let the bodies carrying these bones swing longer, by damping them less.
|
|
711
|
+
*
|
|
712
|
+
* DAMPING, and not solver iterations, and the difference is the whole point.
|
|
713
|
+
* Under-converging a joint does make it swing further — it also stops it ever
|
|
714
|
+
* reaching equilibrium, so the body hangs visibly low at rest. Sag and swing
|
|
715
|
+
* come as a pair there and no amount of tuning separates them. Damping does
|
|
716
|
+
* separate them: for m·x″ + c·x′ + k·x = mg the rest position is mg/k, which
|
|
717
|
+
* c does not appear in. Less damping is a longer, larger oscillation about
|
|
718
|
+
* exactly the same resting height.
|
|
719
|
+
*
|
|
720
|
+
* Scoped to the bones asked for, because it is a look and not a correction —
|
|
721
|
+
* rigs whose visible bones inherit from a simulated one (付与親) are authored
|
|
722
|
+
* against an MMD that lets them move more than a faithfully damped
|
|
723
|
+
* simulation does. Hair and skirt keep their authored damping.
|
|
724
|
+
*
|
|
725
|
+
* `scale` multiplies the AUTHORED damping: 1 restores it, 0.5 halves it,
|
|
726
|
+
* 0 leaves the body undamped and ringing. Idempotent — the authored values
|
|
727
|
+
* are snapshotted on first use, so repeated calls set rather than compound.
|
|
728
|
+
*/
|
|
729
|
+
setJiggleDamping(boneIndices: number[], scale: number): void {
|
|
730
|
+
const wanted = new Set(boneIndices.filter((b) => b >= 0))
|
|
731
|
+
if (wanted.size === 0) return
|
|
732
|
+
if (!this.authoredLinDamp || !this.authoredAngDamp) {
|
|
733
|
+
this.authoredLinDamp = Float32Array.from(this.store.linearDamping)
|
|
734
|
+
this.authoredAngDamp = Float32Array.from(this.store.angularDamping)
|
|
735
|
+
}
|
|
736
|
+
const s = Math.max(0, Math.min(1, scale))
|
|
737
|
+
const boneOf = this.store.boneIndex
|
|
738
|
+
for (let i = 0; i < this.store.count; i++) {
|
|
739
|
+
const b = boneOf[i]
|
|
740
|
+
if (b < 0 || !wanted.has(b)) continue
|
|
741
|
+
this.store.linearDamping[i] = this.authoredLinDamp[i] * s
|
|
742
|
+
this.store.angularDamping[i] = this.authoredAngDamp[i] * s
|
|
743
|
+
}
|
|
744
|
+
// The factors are cached against dt, which has not changed.
|
|
745
|
+
this.world.invalidateDampingCache()
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/** Authored damping, kept so setJiggleDamping sets rather than compounds. */
|
|
749
|
+
private authoredLinDamp: Float32Array | null = null
|
|
750
|
+
private authoredAngDamp: Float32Array | null = null
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Bones whose world matrix this simulation OVERWRITES each step.
|
|
754
|
+
*
|
|
755
|
+
* The same test applyDynamicsToBones runs — a Dynamic body bound to a real
|
|
756
|
+
* bone — exposed because the pose pipeline has to know. PMX lets a bone
|
|
757
|
+
* inherit rotation from an 付与親 (append parent), and when that parent is
|
|
758
|
+
* simulated the inheritance has to consume the SIMULATED result, not the
|
|
759
|
+
* animated pose the frame started with. Nothing else can answer which bones
|
|
760
|
+
* those are: the mapping lives in this store.
|
|
761
|
+
*/
|
|
762
|
+
getPhysicsDrivenBones(): number[] {
|
|
763
|
+
const out: number[] = []
|
|
764
|
+
for (let i = 0; i < this.store.count; i++) {
|
|
765
|
+
if (this.store.type[i] !== RigidbodyType.Dynamic) continue
|
|
766
|
+
const b = this.store.boneIndex[i]
|
|
767
|
+
if (b >= 0) out.push(b)
|
|
768
|
+
}
|
|
769
|
+
return out
|
|
770
|
+
}
|
|
771
|
+
|
|
709
772
|
private applyDynamicsToBones(boneWorldMatrices: Mat4[], alpha: number): void {
|
|
710
773
|
const N = this.store.count
|
|
711
774
|
const inv = this.store.bodyOffsetInverse
|
package/src/physics/world.ts
CHANGED
|
@@ -45,6 +45,13 @@ export class World {
|
|
|
45
45
|
// Per-body damping factors pow(1−damping, dt), cached because damping and
|
|
46
46
|
// the fixed dt never change — two Math.pow per body per substep otherwise.
|
|
47
47
|
private dampCacheDt = -1
|
|
48
|
+
|
|
49
|
+
/** Drop the cached damping factors. The cache is keyed on dt alone, because
|
|
50
|
+
* authored damping never changed — until a rig asked for softer jiggle (see
|
|
51
|
+
* RezePhysics.setJiggleDamping), which rewrites the store's values. */
|
|
52
|
+
invalidateDampingCache(): void {
|
|
53
|
+
this.dampCacheDt = -1
|
|
54
|
+
}
|
|
48
55
|
private linDampFactor: Float32Array | null = null
|
|
49
56
|
private angDampFactor: Float32Array | null = null
|
|
50
57
|
|
|
@@ -14,7 +14,50 @@
|
|
|
14
14
|
* the whole engine failing to start on an import order nobody chose.
|
|
15
15
|
*/
|
|
16
16
|
export const EFFECT_SUBJECTS = 4
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Bone anchors, for the WHOLE SCENE rather than per effect — this is an address
|
|
19
|
+
* space that every installed effect draws slots from, and an effect asking for
|
|
20
|
+
* one the table has already given away is told so and has it dropped.
|
|
21
|
+
*
|
|
22
|
+
* 8 was reachable, and quietly: two effects each wanting two hands, two feet and
|
|
23
|
+
* a head is ten, so the second one silently lost its ribbons to a diagnostic
|
|
24
|
+
* nobody was reading. 16 is double the headroom for 131KB of storage buffer,
|
|
25
|
+
* where 8 cost 66KB.
|
|
26
|
+
*
|
|
27
|
+
* The cost really is only that. The per-frame upload is bounded by the last
|
|
28
|
+
* TRAILED slot, not by this cap (see the writeBuffer in updateCastBuffer), so a
|
|
29
|
+
* scene using three anchors uploads three anchors' worth whatever this says.
|
|
30
|
+
* The CPU-side path rings are keyed by (model, slot) and allocated on use. And
|
|
31
|
+
* effects never index this directly — they loop to rzTrailCount / rzSubjectCount
|
|
32
|
+
* and read through rzAnchor/rzTrail, both of which bounds-check against it.
|
|
33
|
+
*
|
|
34
|
+
* It stays a compile-time constant because the accessors in cast-api.ts are
|
|
35
|
+
* interpolated into every effect module as WGSL literals; making it dynamic
|
|
36
|
+
* means resizing the buffer and recompiling every installed effect whenever the
|
|
37
|
+
* scene's anchor count grows, which is a different feature from raising a number
|
|
38
|
+
* that was never load-bearing.
|
|
39
|
+
*/
|
|
40
|
+
export const EFFECT_ANCHORS = 16
|
|
41
|
+
/**
|
|
42
|
+
* Path samples kept per trailed anchor — 128 at the 60Hz sampling rate is a
|
|
43
|
+
* ~2.1 second ribbon.
|
|
44
|
+
*
|
|
45
|
+
* Briefly 256, and reverted with the reason, because the reason is the useful
|
|
46
|
+
* part: a ribbon's cost is not its geometry, it is its FRAGMENTS. It is a wide
|
|
47
|
+
* translucent strip blended additively into the HDR target at the pass's sample
|
|
48
|
+
* count, and it overlaps itself — so its cost tracks the screen AREA it covers,
|
|
49
|
+
* and a twice-as-long ribbon covers roughly twice as much.
|
|
50
|
+
*
|
|
51
|
+
* That lands very differently on the two backends. Overdraw and blend bandwidth
|
|
52
|
+
* are what a tile-based GPU pays for most and what an immediate-mode desktop GPU
|
|
53
|
+
* absorbs, which is why ribbons were reported as costing far more on Safari than
|
|
54
|
+
* on Chrome for the same scene. Doubling this doubles the one thing already
|
|
55
|
+
* known to be the bottleneck there.
|
|
56
|
+
*
|
|
57
|
+
* Raising it is still SAFE — effects loop to rzTrailCount and nothing breaks —
|
|
58
|
+
* it is simply not cheap, and the cost shows up on the slower of the two
|
|
59
|
+
* browsers rather than the one it would be measured on.
|
|
60
|
+
*/
|
|
18
61
|
export const EFFECT_TRAIL_SAMPLES = 128
|
|
19
62
|
/** vec4 slot where the trails begin — after the subjects and the anchors. */
|
|
20
63
|
export const EFFECT_TRAIL_BASE = EFFECT_SUBJECTS * 3 + EFFECT_ANCHORS * EFFECT_SUBJECTS * 3
|
|
@@ -73,7 +73,13 @@ struct MaterialUniforms {
|
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
struct VertexOutput {
|
|
76
|
-
@
|
|
76
|
+
// @invariant: the opaque depth prepass rasterises this same skinned position
|
|
77
|
+
// through a DIFFERENT shader module, and the colour pass then depth-tests
|
|
78
|
+
// less-equal against what it wrote. Without invariance a backend is free to
|
|
79
|
+
// optimise the two position computations differently, and a one-ulp
|
|
80
|
+
// disagreement is a pixel of missing character. Invariance pins both to the
|
|
81
|
+
// same result; it costs only that freedom.
|
|
82
|
+
@builtin(position) @invariant position: vec4f,
|
|
77
83
|
@location(0) normal: vec3f,
|
|
78
84
|
@location(1) uv: vec2f,
|
|
79
85
|
@location(2) worldPos: vec3f,
|
|
@@ -216,18 +216,26 @@ fn curve5(t0: f32, y0: f32, y1: f32, y2: f32, y3: f32, y4: f32) -> f32 {
|
|
|
216
216
|
let t = clamp(t0, 0.0, 1.0) * 4.0;
|
|
217
217
|
let i = min(floor(t), 3.0);
|
|
218
218
|
let f = t - i;
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
|
|
219
|
+
// NO local array, deliberately. This used to build array<f32, 5> and index it
|
|
220
|
+
// with the runtime k — and a dynamic index on function-local memory is the one
|
|
221
|
+
// construct this codebase has already caught Metal lowering to a
|
|
222
|
+
// per-invocation copy plus a switch (the filmic LUT note in composite.ts).
|
|
223
|
+
// rgb_curve calls this three times per node per pixel, so on a graph-heavy
|
|
224
|
+
// close-up that lowering was paid in the hottest loop the frame has. Four
|
|
225
|
+
// segments select cleanly: the values below are the SAME subtractions the
|
|
226
|
+
// array indexing produced, chosen by k instead of loaded through it —
|
|
227
|
+
// bit-identical results, no local memory, nothing for the backend to spill.
|
|
224
228
|
let k = i32(i);
|
|
225
|
-
let
|
|
226
|
-
let
|
|
229
|
+
let s0 = y1 - y0;
|
|
230
|
+
let s1 = y2 - y1;
|
|
231
|
+
let s2 = y3 - y2;
|
|
232
|
+
let s3 = y4 - y3;
|
|
233
|
+
let pa = select(select(y0, y1, k == 1), select(y2, y3, k == 3), k >= 2);
|
|
234
|
+
let pb = select(select(y1, y2, k == 1), select(y3, y4, k == 3), k >= 2);
|
|
227
235
|
// Secants either side of each knot, clamped at the ends.
|
|
228
|
-
let dPrev = select(ys[max(k, 1)] - ys[max(k, 1) - 1], pb - pa, k == 0);
|
|
229
236
|
let dHere = pb - pa;
|
|
230
|
-
let
|
|
237
|
+
let dPrev = select(s0, select(s1, s2, k == 3), k >= 2);
|
|
238
|
+
let dNext = select(select(s1, s2, k == 1), s3, k >= 2);
|
|
231
239
|
let m0 = curve_slope(dPrev, dHere);
|
|
232
240
|
let m1 = curve_slope(dHere, dNext);
|
|
233
241
|
let f2 = f * f;
|
|
@@ -1,17 +1,36 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
1
|
+
// The depth-only prime, one module behind four pipelines.
|
|
2
|
+
//
|
|
3
|
+
// The oldest fps complaint the engine had — zoom close and the frame drops, in
|
|
4
|
+
// every material generation — was per-fragment shading TIMES layering: an MMD
|
|
5
|
+
// model at close-up is cloth over body over face under hair, author-order drawn,
|
|
6
|
+
// every buried layer fully shaded and then covered. This module lets depth go
|
|
7
|
+
// down FIRST so the colour passes shade each pixel once:
|
|
8
|
+
//
|
|
9
|
+
// · opaque prepass (CUTOFF 0.5) — plain auto-class opaque draws, before the
|
|
10
|
+
// opaque colour walk. See drawOpaqueDepthPrepass for who is in and why.
|
|
11
|
+
// · hair prime (CUTOFF 1.0, stencil not-equal) — between the non-hair and
|
|
12
|
+
// hair colour walks, fenced off the eye silhouette the see-through-hair
|
|
13
|
+
// stencil pass needs. See drawHairDepthPrime.
|
|
14
|
+
// · transparent solid prime (CUTOFF 1.0) — a translucent material's alpha-1
|
|
15
|
+
// texels, where over-blending is plain replacement and the buried work
|
|
16
|
+
// provably never shows. See drawTransparentSolidPrepass.
|
|
17
|
+
// · transparent depth prepass (CUTOFF 0.5, AFTER colour) — the original
|
|
18
|
+
// occupant, dormant: re-records sheer fabric's depth so outline hulls are
|
|
19
|
+
// occluded behind it. Kept for a future OIT path.
|
|
9
20
|
//
|
|
10
21
|
// Reuses mainPipelineLayout: camera g0b0, diffuseSampler g0b2, skinMats g1b0,
|
|
11
22
|
// diffuse texture g2b0, material uniforms g2b1 — the same bind groups the
|
|
12
23
|
// color draws already set, so drawing it costs no extra binding work.
|
|
24
|
+
//
|
|
25
|
+
// A FUNCTION rather than the constant it was, for the reason commonFsOutWgsl is
|
|
26
|
+
// one: the fragment outputs below depend on whether the device carries the id
|
|
27
|
+
// attachment, and that answer does not exist at import time. The constant could
|
|
28
|
+
// not have taken the outputs at all, which is most of why it did not have them.
|
|
13
29
|
|
|
14
|
-
|
|
30
|
+
import { sceneFsOutWgsl, sceneIdPadWgsl } from "./scene-contract"
|
|
31
|
+
|
|
32
|
+
export function transparentDepthPrepassWgsl(): string {
|
|
33
|
+
return /* wgsl */ `
|
|
15
34
|
struct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };
|
|
16
35
|
struct MaterialUniforms {
|
|
17
36
|
diffuseColor: vec3f,
|
|
@@ -25,7 +44,9 @@ struct MaterialUniforms {
|
|
|
25
44
|
@group(2) @binding(1) var<uniform> material: MaterialUniforms;
|
|
26
45
|
|
|
27
46
|
struct VSOut {
|
|
28
|
-
@
|
|
47
|
+
// @invariant, to the same end as the material VertexOutput: the colour pass
|
|
48
|
+
// must land on exactly the depths this wrote.
|
|
49
|
+
@builtin(position) @invariant position: vec4f,
|
|
29
50
|
@location(0) uv: vec2f,
|
|
30
51
|
};
|
|
31
52
|
|
|
@@ -50,8 +71,28 @@ struct VSOut {
|
|
|
50
71
|
return o;
|
|
51
72
|
}
|
|
52
73
|
|
|
53
|
-
|
|
74
|
+
// Every attachment the pass carries, declared and then not written: the
|
|
75
|
+
// pipeline takes all of them at writeMask 0 (see sceneTargets), so what this
|
|
76
|
+
// returns is discarded by the hardware and only the depth write survives — which
|
|
77
|
+
// is the entire purpose of the pass. Declaring them anyway is what keeps the
|
|
78
|
+
// pipeline valid on a browser that requires an output per target rather than
|
|
79
|
+
// per WRITTEN target. Costs one dead struct store on a fragment that already
|
|
80
|
+
// runs, because the alpha test below needs it to.
|
|
81
|
+
${sceneFsOutWgsl({ name: "PrepassOut", aux: "mask" })}
|
|
82
|
+
// The cutout threshold, per pipeline. 0.5 is the OPAQUE prime's "solid enough"
|
|
83
|
+
// — safe there because opaque colour replaces rather than blends. The
|
|
84
|
+
// TRANSPARENT prime overrides it to 1.0: a translucent fragment's blend reads
|
|
85
|
+
// what is behind it, so only a texel at EXACTLY alpha 1 — where over-blending
|
|
86
|
+
// collapses to plain replacement and the destination stops mattering — may
|
|
87
|
+
// claim depth ahead of its buried layers without changing the pixel.
|
|
88
|
+
override CUTOFF: f32 = 0.5;
|
|
89
|
+
@fragment fn fs(in: VSOut) -> PrepassOut {
|
|
54
90
|
let a = material.alpha * textureSample(diffuseTexture, diffuseSampler, in.uv).a;
|
|
55
|
-
if (a <
|
|
91
|
+
if (a < CUTOFF) { discard; }
|
|
92
|
+
var out: PrepassOut;
|
|
93
|
+
out.color = vec4f(0.0);
|
|
94
|
+
out.mask = vec4f(0.0);
|
|
95
|
+
${sceneIdPadWgsl("out")} return out;
|
|
56
96
|
}
|
|
57
97
|
`
|
|
98
|
+
}
|