reze-engine 0.36.0 → 0.37.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/README.md +4 -3
- package/dist/camera.d.ts +17 -0
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +25 -0
- package/dist/engine.d.ts +94 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +250 -14
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/model.d.ts +81 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +192 -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 +12 -3
- package/dist/physics/contact.d.ts.map +1 -1
- package/dist/physics/contact.js +5 -2
- package/dist/physics/manifold.d.ts +32 -11
- package/dist/physics/manifold.d.ts.map +1 -1
- package/dist/physics/manifold.js +137 -57
- package/dist/physics/solver.d.ts +7 -1
- package/dist/physics/solver.d.ts.map +1 -1
- package/dist/physics/solver.js +536 -182
- package/dist/physics/world.d.ts +2 -0
- package/dist/physics/world.d.ts.map +1 -1
- package/dist/physics/world.js +16 -43
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +84 -44
- package/package.json +1 -1
- package/src/camera.ts +26 -0
- package/src/engine.ts +305 -18
- package/src/index.ts +11 -1
- package/src/model.ts +231 -1
- package/src/physics/contact.ts +17 -7
- package/src/physics/manifold.ts +151 -0
- package/src/physics/solver.ts +520 -184
- package/src/physics/world.ts +17 -43
- package/src/pmx-loader.ts +84 -44
package/dist/physics/world.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export interface WindOptions {
|
|
|
29
29
|
export declare class World {
|
|
30
30
|
readonly gravity: Vec3;
|
|
31
31
|
solverIterations: number;
|
|
32
|
+
/** Per-pair contact impulse history behind warm starting. */
|
|
33
|
+
private manifolds;
|
|
32
34
|
private dampCacheDt;
|
|
33
35
|
private linDampFactor;
|
|
34
36
|
private angDampFactor;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"world.d.ts","sourceRoot":"","sources":["../../src/physics/world.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAE5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAC1D,OAAO,
|
|
1
|
+
{"version":3,"file":"world.d.ts","sourceRoot":"","sources":["../../src/physics/world.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAA;AAE5C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAA;AAC1D,OAAO,EAAgE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAA;AAEzG,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAO1D;;;;;;;;;GASG;AACH,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,SAAS,EAAE,IAAI,CAAA;IACf,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAA;IAChB;;;kEAG8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,qBAAa,KAAK;IAChB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;IACtB,gBAAgB,SAAK;IAErB,6DAA6D;IAC7D,OAAO,CAAC,SAAS,CAAsB;IAIvC,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,aAAa,CAA4B;IAIjD,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,aAAa,CAAO;IAC5B;gDAC4C;IAC5C,OAAO,CAAC,SAAS,CAAI;gBAET,OAAO,EAAE,IAAI;IAIzB,UAAU,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI;IAMzB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI;IAoBvC,OAAO,IAAI,WAAW,GAAG,IAAI;IAW7B,IAAI,CACF,KAAK,EAAE,cAAc,EACrB,WAAW,EAAE,sBAAsB,EAAE,EACrC,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,WAAW,EACrB,EAAE,EAAE,MAAM,GACT,IAAI;CA4IR"}
|
package/dist/physics/world.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { Vec3 } from "../math";
|
|
2
2
|
import { RigidbodyType } from "./types";
|
|
3
|
-
import { solveConstraints } from "./solver";
|
|
3
|
+
import { solveConstraints, saveContactImpulses, applySplitImpulsePush } from "./solver";
|
|
4
|
+
import { ManifoldCache } from "./manifold";
|
|
4
5
|
import { findContacts } from "./contact";
|
|
5
6
|
export class World {
|
|
6
7
|
constructor(gravity) {
|
|
7
8
|
this.solverIterations = 10;
|
|
9
|
+
/** Per-pair contact impulse history behind warm starting. */
|
|
10
|
+
this.manifolds = new ManifoldCache();
|
|
8
11
|
// Per-body damping factors pow(1−damping, dt), cached because damping and
|
|
9
12
|
// the fixed dt never change — two Math.pow per body per substep otherwise.
|
|
10
13
|
this.dampCacheDt = -1;
|
|
@@ -121,50 +124,20 @@ export class World {
|
|
|
121
124
|
findContacts(store, contacts);
|
|
122
125
|
// 3. Solve joint + contact constraints (velocity-only).
|
|
123
126
|
if (constraints.length > 0 || contacts.count > 0) {
|
|
124
|
-
solveConstraints(store, constraints, cache, contacts, dt, this.solverIterations);
|
|
127
|
+
solveConstraints(store, constraints, cache, contacts, dt, this.solverIterations, this.manifolds);
|
|
128
|
+
applySplitImpulsePush(store, dt);
|
|
129
|
+
saveContactImpulses(store, contacts, this.manifolds);
|
|
125
130
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// it because it doesn't go through the velocity channel. Inverse-mass
|
|
129
|
-
// weighted so a kinematic body stays put and only the dynamic one
|
|
130
|
-
// translates.
|
|
131
|
-
// Softer and later than a rigid-body engine would use, deliberately. MMD
|
|
132
|
-
// cloth is a dense chain of small bodies resting on the character, and
|
|
133
|
-
// aggressive penetration recovery feeds those contacts energy that the
|
|
134
|
-
// joint springs hand straight back — the tips never settle. Measured on a
|
|
135
|
-
// 688-body model: resting peak velocity 1.79 → 1.10, and 12.9 → 6.6 under
|
|
136
|
-
// an idle animation, for a slop of two hundredths of a unit (under 2 mm at
|
|
137
|
-
// MMD scale) that no eye finds.
|
|
138
|
-
const POS_CORRECTION_FACTOR = 0.15;
|
|
139
|
-
const POS_SLOP = 0.02;
|
|
140
|
-
for (let ci = 0; ci < contacts.count; ci++) {
|
|
141
|
-
const c = contacts.get(ci);
|
|
142
|
-
if (c.depth <= POS_SLOP)
|
|
143
|
-
continue;
|
|
144
|
-
const imA = invMass[c.bodyA];
|
|
145
|
-
const imB = invMass[c.bodyB];
|
|
146
|
-
const total = imA + imB;
|
|
147
|
-
if (total <= 0)
|
|
148
|
-
continue;
|
|
149
|
-
const correction = (c.depth - POS_SLOP) * POS_CORRECTION_FACTOR;
|
|
150
|
-
const dx = correction * c.nx;
|
|
151
|
-
const dy = correction * c.ny;
|
|
152
|
-
const dz = correction * c.nz;
|
|
153
|
-
const ai = c.bodyA * 3;
|
|
154
|
-
const bi = c.bodyB * 3;
|
|
155
|
-
if (imA > 0) {
|
|
156
|
-
const fA = imA / total;
|
|
157
|
-
pos[ai + 0] -= dx * fA;
|
|
158
|
-
pos[ai + 1] -= dy * fA;
|
|
159
|
-
pos[ai + 2] -= dz * fA;
|
|
160
|
-
}
|
|
161
|
-
if (imB > 0) {
|
|
162
|
-
const fB = imB / total;
|
|
163
|
-
pos[bi + 0] += dx * fB;
|
|
164
|
-
pos[bi + 1] += dy * fB;
|
|
165
|
-
pos[bi + 2] += dz * fB;
|
|
166
|
-
}
|
|
131
|
+
else {
|
|
132
|
+
this.manifolds.clear();
|
|
167
133
|
}
|
|
134
|
+
// 4. Penetration recovery happens in the contact velocity row (Baumgarte,
|
|
135
|
+
// CONTACT_ERP), not here. Bullet 2.75 — the build MMD's physics runs —
|
|
136
|
+
// defaults m_splitImpulse to false and folds positionalError straight
|
|
137
|
+
// into m_rhs, so a separate position-translation pass is a divergence
|
|
138
|
+
// from what PMX rigs are authored against. It also could not be made to
|
|
139
|
+
// behave: every contact translated the body by its own full share, so a
|
|
140
|
+
// panel carrying dozens of rows was shoved out dozens of times.
|
|
168
141
|
// 5. Integrate. Cap angular velocity at π/2 per step — a high-impulse
|
|
169
142
|
// contact spike on a low-inertia body would otherwise spin past π
|
|
170
143
|
// in one step and trash the quaternion integration. Linear velocity
|
package/dist/pmx-loader.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pmx-loader.d.ts","sourceRoot":"","sources":["../src/pmx-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,
|
|
1
|
+
{"version":3,"file":"pmx-loader.d.ts","sourceRoot":"","sources":["../src/pmx-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAcN,MAAM,SAAS,CAAA;AAGhB,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEzE,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,mBAAmB,CAAI;IAC/B,OAAO,CAAC,eAAe,CAAI;IAC3B,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,kBAAkB,CAAI;IAC9B,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,mBAAmB,CAA4B;IACvD,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,WAAW,CAAY;IAC/B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,QAAQ,CAAe;IAE/B,OAAO;IAQP,OAAO,CAAC,IAAI;WAMC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAI9C,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK;WAIpC,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAKxF,OAAO,CAAC,KAAK;IAiBb,OAAO,CAAC,WAAW;IAwDnB,OAAO,CAAC,aAAa;IA+FrB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,cAAc;IAqEtB,OAAO,CAAC,UAAU;IAsKlB,OAAO,CAAC,WAAW;IAgMnB,OAAO,CAAC,iBAAiB;IAgDzB,OAAO,CAAC,gBAAgB;IA+FxB,OAAO,CAAC,WAAW;IAmGnB,OAAO,CAAC,kBAAkB;IAmC1B,OAAO,CAAC,OAAO;IAkJf,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,OAAO;IAwBf,OAAO,CAAC,QAAQ;CAIjB"}
|
package/dist/pmx-loader.js
CHANGED
|
@@ -457,6 +457,14 @@ export class PmxLoader {
|
|
|
457
457
|
vertexOffsets: [],
|
|
458
458
|
groupReferences: [],
|
|
459
459
|
};
|
|
460
|
+
// Allocated only for the type that uses them, so `morph.boneOffsets`
|
|
461
|
+
// being undefined means "not that kind of morph" rather than "empty".
|
|
462
|
+
if (morphType === 2)
|
|
463
|
+
morph.boneOffsets = [];
|
|
464
|
+
else if (morphType >= 3 && morphType <= 7)
|
|
465
|
+
morph.uvOffsets = [];
|
|
466
|
+
else if (morphType === 8)
|
|
467
|
+
morph.materialOffsets = [];
|
|
460
468
|
// Parse vertex morphs (type 1)
|
|
461
469
|
if (morphType === 1) {
|
|
462
470
|
for (let j = 0; j < offsetCount; j++) {
|
|
@@ -492,62 +500,94 @@ export class PmxLoader {
|
|
|
492
500
|
}
|
|
493
501
|
}
|
|
494
502
|
else {
|
|
495
|
-
//
|
|
503
|
+
// Bone, UV and material morphs are kept; flip and impulse (PMX 2.1)
|
|
504
|
+
// are still read past — nothing in the wild uses them, and impulse
|
|
505
|
+
// would need to drive rigidbodies.
|
|
496
506
|
for (let j = 0; j < offsetCount; j++) {
|
|
497
507
|
if (this.offset >= this.view.buffer.byteLength) {
|
|
498
508
|
break;
|
|
499
509
|
}
|
|
500
510
|
if (morphType === 2) {
|
|
501
511
|
// Bone morph
|
|
502
|
-
this.getNonVertexIndex(this.boneIndexSize);
|
|
503
|
-
this.getFloat32();
|
|
504
|
-
this.getFloat32();
|
|
505
|
-
this.getFloat32();
|
|
506
|
-
this.getFloat32();
|
|
507
|
-
this.getFloat32();
|
|
508
|
-
this.getFloat32();
|
|
509
|
-
this.getFloat32();
|
|
512
|
+
const boneIndex = this.getNonVertexIndex(this.boneIndexSize);
|
|
513
|
+
const tx = this.getFloat32();
|
|
514
|
+
const ty = this.getFloat32();
|
|
515
|
+
const tz = this.getFloat32();
|
|
516
|
+
const rx = this.getFloat32();
|
|
517
|
+
const ry = this.getFloat32();
|
|
518
|
+
const rz = this.getFloat32();
|
|
519
|
+
const rw = this.getFloat32();
|
|
520
|
+
if (boneIndex >= 0) {
|
|
521
|
+
const offset = {
|
|
522
|
+
boneIndex,
|
|
523
|
+
translation: [tx, ty, tz],
|
|
524
|
+
rotation: [rx, ry, rz, rw],
|
|
525
|
+
};
|
|
526
|
+
morph.boneOffsets.push(offset);
|
|
527
|
+
}
|
|
510
528
|
}
|
|
511
529
|
else if (morphType >= 3 && morphType <= 7) {
|
|
512
530
|
// UV morph + additional UV channels 1-4; offset is always a Float4 per spec
|
|
513
|
-
this.getIndex(this.vertexIndexSize);
|
|
514
|
-
this.getFloat32();
|
|
515
|
-
this.getFloat32();
|
|
516
|
-
this.getFloat32();
|
|
517
|
-
this.getFloat32();
|
|
531
|
+
const vertexIndex = this.getIndex(this.vertexIndexSize);
|
|
532
|
+
const x = this.getFloat32();
|
|
533
|
+
const y = this.getFloat32();
|
|
534
|
+
const z = this.getFloat32();
|
|
535
|
+
const w = this.getFloat32();
|
|
536
|
+
if (vertexIndex >= 0 && vertexIndex < this.vertexCount) {
|
|
537
|
+
const offset = { vertexIndex, offset: [x, y, z, w] };
|
|
538
|
+
morph.uvOffsets.push(offset);
|
|
539
|
+
}
|
|
518
540
|
}
|
|
519
541
|
else if (morphType === 8) {
|
|
520
542
|
// Material morph
|
|
521
|
-
this.getNonVertexIndex(this.materialIndexSize);
|
|
522
|
-
this.getUint8();
|
|
523
|
-
this.getFloat32();
|
|
524
|
-
this.getFloat32();
|
|
525
|
-
this.getFloat32();
|
|
526
|
-
this.getFloat32();
|
|
527
|
-
this.getFloat32();
|
|
528
|
-
this.getFloat32();
|
|
529
|
-
this.getFloat32();
|
|
530
|
-
this.getFloat32();
|
|
531
|
-
this.getFloat32();
|
|
532
|
-
this.getFloat32();
|
|
533
|
-
this.getFloat32();
|
|
534
|
-
this.getFloat32();
|
|
535
|
-
this.getFloat32();
|
|
536
|
-
this.getFloat32();
|
|
537
|
-
this.getFloat32();
|
|
538
|
-
this.getFloat32();
|
|
539
|
-
this.getFloat32();
|
|
540
|
-
this.getFloat32();
|
|
541
|
-
this.getFloat32();
|
|
542
|
-
this.getFloat32();
|
|
543
|
-
this.getFloat32();
|
|
544
|
-
this.getFloat32();
|
|
545
|
-
this.getFloat32();
|
|
546
|
-
this.getFloat32();
|
|
547
|
-
this.getFloat32();
|
|
548
|
-
this.getFloat32();
|
|
549
|
-
this.getFloat32();
|
|
550
|
-
this.getFloat32();
|
|
543
|
+
const materialIndex = this.getNonVertexIndex(this.materialIndexSize);
|
|
544
|
+
const offsetType = this.getUint8();
|
|
545
|
+
const diffuseR = this.getFloat32();
|
|
546
|
+
const diffuseG = this.getFloat32();
|
|
547
|
+
const diffuseB = this.getFloat32();
|
|
548
|
+
const diffuseA = this.getFloat32();
|
|
549
|
+
const specularR = this.getFloat32();
|
|
550
|
+
const specularG = this.getFloat32();
|
|
551
|
+
const specularB = this.getFloat32();
|
|
552
|
+
const shininess = this.getFloat32();
|
|
553
|
+
const ambientR = this.getFloat32();
|
|
554
|
+
const ambientG = this.getFloat32();
|
|
555
|
+
const ambientB = this.getFloat32();
|
|
556
|
+
const edgeR = this.getFloat32();
|
|
557
|
+
const edgeG = this.getFloat32();
|
|
558
|
+
const edgeB = this.getFloat32();
|
|
559
|
+
const edgeA = this.getFloat32();
|
|
560
|
+
const edgeSize = this.getFloat32();
|
|
561
|
+
const texR = this.getFloat32();
|
|
562
|
+
const texG = this.getFloat32();
|
|
563
|
+
const texB = this.getFloat32();
|
|
564
|
+
const texA = this.getFloat32();
|
|
565
|
+
const sphR = this.getFloat32();
|
|
566
|
+
const sphG = this.getFloat32();
|
|
567
|
+
const sphB = this.getFloat32();
|
|
568
|
+
const sphA = this.getFloat32();
|
|
569
|
+
const toonR = this.getFloat32();
|
|
570
|
+
const toonG = this.getFloat32();
|
|
571
|
+
const toonB = this.getFloat32();
|
|
572
|
+
const toonA = this.getFloat32();
|
|
573
|
+
// -1 is legal and means "every material" — kept as-is so the
|
|
574
|
+
// applier can fan it out without a second convention.
|
|
575
|
+
if (materialIndex >= -1) {
|
|
576
|
+
const offset = {
|
|
577
|
+
materialIndex,
|
|
578
|
+
offsetType,
|
|
579
|
+
diffuse: [diffuseR, diffuseG, diffuseB, diffuseA],
|
|
580
|
+
specular: [specularR, specularG, specularB],
|
|
581
|
+
shininess,
|
|
582
|
+
ambient: [ambientR, ambientG, ambientB],
|
|
583
|
+
edgeColor: [edgeR, edgeG, edgeB, edgeA],
|
|
584
|
+
edgeSize,
|
|
585
|
+
textureCoeff: [texR, texG, texB, texA],
|
|
586
|
+
sphereCoeff: [sphR, sphG, sphB, sphA],
|
|
587
|
+
toonCoeff: [toonR, toonG, toonB, toonA],
|
|
588
|
+
};
|
|
589
|
+
morph.materialOffsets.push(offset);
|
|
590
|
+
}
|
|
551
591
|
}
|
|
552
592
|
else if (morphType === 9) {
|
|
553
593
|
// Flip morph (PMX 2.1): same layout as group morph
|
package/package.json
CHANGED
package/src/camera.ts
CHANGED
|
@@ -4,6 +4,11 @@ import type { CameraPose } from "./camera-animation"
|
|
|
4
4
|
/** Far cap / zoom limit; large enough for wide shots without clipping distant ground */
|
|
5
5
|
const FAR_CAP = 8000
|
|
6
6
|
const FAR_MIN = 200
|
|
7
|
+
/** The character-framing value the cloth z-fighting fix settled on — never go below it. */
|
|
8
|
+
const NEAR_MIN = 0.5
|
|
9
|
+
/** ~40cm in MMD units. Past this the near plane would start clipping geometry a
|
|
10
|
+
* user has deliberately pushed the camera up against. */
|
|
11
|
+
const NEAR_MAX = 5
|
|
7
12
|
|
|
8
13
|
export class Camera {
|
|
9
14
|
alpha: number
|
|
@@ -226,8 +231,29 @@ export class Camera {
|
|
|
226
231
|
this.far = Math.min(FAR_CAP, Math.max(FAR_MIN, this.radius * 12 + margin))
|
|
227
232
|
}
|
|
228
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Near plane scales with how far out you are framing.
|
|
236
|
+
*
|
|
237
|
+
* A 24-bit non-reversed depth buffer spends most of its precision just past
|
|
238
|
+
* the near plane, so what survives at distance is governed by the far/near
|
|
239
|
+
* ratio. 0.5 was chosen for character framing and fixed coplanar cloth there
|
|
240
|
+
* (see `near` above) — but a stage puts the floor 5–20× further out, where the
|
|
241
|
+
* same ratio leaves neighbouring surfaces sharing a depth value. They then win
|
|
242
|
+
* and lose per pixel as the camera turns, which reads as flickering bands and
|
|
243
|
+
* cracks across the floor.
|
|
244
|
+
*
|
|
245
|
+
* Tying near to the framing keeps character shots byte-identical (radius ~26
|
|
246
|
+
* still lands on the 0.5 floor) and buys back an order of magnitude once you
|
|
247
|
+
* pull out to see a whole stage. Capped so it can never clip something the
|
|
248
|
+
* user is deliberately close to.
|
|
249
|
+
*/
|
|
250
|
+
private updateNearFromRadius(): void {
|
|
251
|
+
this.near = Math.min(NEAR_MAX, Math.max(NEAR_MIN, this.radius / 50))
|
|
252
|
+
}
|
|
253
|
+
|
|
229
254
|
getProjectionMatrix(): Mat4 {
|
|
230
255
|
this.updateFarFromRadius()
|
|
256
|
+
this.updateNearFromRadius()
|
|
231
257
|
Mat4.perspectiveInto(this._projMat.values, this.fov, this.aspect, this.near, this.far)
|
|
232
258
|
return this._projMat
|
|
233
259
|
}
|