three-cad-viewer 4.2.0 → 4.3.1
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/core/studio-manager.d.ts +0 -1
- package/dist/core/types.d.ts +1 -22
- package/dist/index.d.ts +1 -1
- package/dist/rendering/texture-cache.d.ts +11 -50
- package/dist/scene/nestedgroup.d.ts +1 -2
- package/dist/three-cad-viewer.esm.js +32 -650
- package/dist/three-cad-viewer.esm.js.map +1 -1
- package/dist/three-cad-viewer.esm.min.js +3 -3
- package/dist/three-cad-viewer.js +32 -650
- package/dist/three-cad-viewer.min.js +3 -3
- package/package.json +1 -1
- package/src/_version.ts +1 -1
- package/src/core/studio-manager.ts +4 -34
- package/src/core/types.ts +1 -27
- package/src/index.ts +0 -1
- package/src/rendering/material-factory.ts +12 -8
- package/src/rendering/material-presets.ts +0 -14
- package/src/rendering/texture-cache.ts +16 -712
- package/src/scene/nestedgroup.ts +0 -7
package/dist/three-cad-viewer.js
CHANGED
|
@@ -82551,19 +82551,6 @@ void main() {
|
|
|
82551
82551
|
// =============================================================================
|
|
82552
82552
|
// Constants
|
|
82553
82553
|
// =============================================================================
|
|
82554
|
-
/** Size of procedurally generated builtin textures (pixels) */
|
|
82555
|
-
const BUILTIN_SIZE = 256;
|
|
82556
|
-
/** Names of all supported builtin procedural textures */
|
|
82557
|
-
const BUILTIN_NAMES = [
|
|
82558
|
-
"brushed",
|
|
82559
|
-
"knurled",
|
|
82560
|
-
"sandblasted",
|
|
82561
|
-
"hammered",
|
|
82562
|
-
"checker",
|
|
82563
|
-
"wood-dark",
|
|
82564
|
-
"leather",
|
|
82565
|
-
"fabric-weave",
|
|
82566
|
-
];
|
|
82567
82554
|
/**
|
|
82568
82555
|
* Texture fields that carry sRGB color data.
|
|
82569
82556
|
*
|
|
@@ -82592,431 +82579,6 @@ void main() {
|
|
|
82592
82579
|
"specularColorMap",
|
|
82593
82580
|
]);
|
|
82594
82581
|
// =============================================================================
|
|
82595
|
-
// Builtin Procedural Texture Generators
|
|
82596
|
-
// =============================================================================
|
|
82597
|
-
/**
|
|
82598
|
-
* Create a 2D canvas context of the given size.
|
|
82599
|
-
*/
|
|
82600
|
-
function createCanvas(size) {
|
|
82601
|
-
// Prefer OffscreenCanvas when available (Web Workers, modern browsers)
|
|
82602
|
-
if (typeof OffscreenCanvas !== "undefined") {
|
|
82603
|
-
const canvas = new OffscreenCanvas(size, size);
|
|
82604
|
-
const ctx = canvas.getContext("2d");
|
|
82605
|
-
return { canvas, ctx };
|
|
82606
|
-
}
|
|
82607
|
-
const canvas = document.createElement("canvas");
|
|
82608
|
-
canvas.width = size;
|
|
82609
|
-
canvas.height = size;
|
|
82610
|
-
const ctx = canvas.getContext("2d");
|
|
82611
|
-
return { canvas, ctx };
|
|
82612
|
-
}
|
|
82613
|
-
/**
|
|
82614
|
-
* Simple pseudo-random number generator (mulberry32) for deterministic output.
|
|
82615
|
-
* Ensures builtin textures look identical across sessions.
|
|
82616
|
-
*/
|
|
82617
|
-
function mulberry32(seed) {
|
|
82618
|
-
return () => {
|
|
82619
|
-
seed |= 0;
|
|
82620
|
-
seed = (seed + 0x6d2b79f5) | 0;
|
|
82621
|
-
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
82622
|
-
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
82623
|
-
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
82624
|
-
};
|
|
82625
|
-
}
|
|
82626
|
-
/**
|
|
82627
|
-
* Generate a brushed-metal normal map.
|
|
82628
|
-
*
|
|
82629
|
-
* Creates horizontal noise streaks simulating directional brushing.
|
|
82630
|
-
* The streaks run along the X axis with slight vertical variation.
|
|
82631
|
-
*/
|
|
82632
|
-
function generateBrushed(size) {
|
|
82633
|
-
const { canvas, ctx } = createCanvas(size);
|
|
82634
|
-
const imageData = ctx.createImageData(size, size);
|
|
82635
|
-
const data = imageData.data;
|
|
82636
|
-
const rng = mulberry32(42);
|
|
82637
|
-
// Generate per-row intensity variation (horizontal streaks)
|
|
82638
|
-
const rowIntensity = new Float32Array(size);
|
|
82639
|
-
for (let y = 0; y < size; y++) {
|
|
82640
|
-
rowIntensity[y] = 0.3 + rng() * 0.7;
|
|
82641
|
-
}
|
|
82642
|
-
for (let y = 0; y < size; y++) {
|
|
82643
|
-
for (let x = 0; x < size; x++) {
|
|
82644
|
-
const idx = (y * size + x) * 4;
|
|
82645
|
-
// High-frequency horizontal noise for brush lines
|
|
82646
|
-
const noise = (rng() - 0.5) * 0.15 * rowIntensity[y];
|
|
82647
|
-
// Slight vertical gradient perturbation
|
|
82648
|
-
const yNoise = (rng() - 0.5) * 0.05;
|
|
82649
|
-
// Normal map encoding: (0.5, 0.5, 1.0) = flat
|
|
82650
|
-
data[idx] = Math.round((0.5 + noise) * 255); // R: tangent X
|
|
82651
|
-
data[idx + 1] = Math.round((0.5 + yNoise) * 255); // G: tangent Y
|
|
82652
|
-
data[idx + 2] = 255; // B: tangent Z (flat)
|
|
82653
|
-
data[idx + 3] = 255; // A: opaque
|
|
82654
|
-
}
|
|
82655
|
-
}
|
|
82656
|
-
ctx.putImageData(imageData, 0, 0);
|
|
82657
|
-
return canvas;
|
|
82658
|
-
}
|
|
82659
|
-
/**
|
|
82660
|
-
* Generate a diamond knurl pattern normal map.
|
|
82661
|
-
*
|
|
82662
|
-
* Creates a repeating diamond grid pattern typical of knurled metal surfaces.
|
|
82663
|
-
*/
|
|
82664
|
-
function generateKnurled(size) {
|
|
82665
|
-
const { canvas, ctx } = createCanvas(size);
|
|
82666
|
-
const imageData = ctx.createImageData(size, size);
|
|
82667
|
-
const data = imageData.data;
|
|
82668
|
-
const diamonds = 16; // Number of diamond repeats
|
|
82669
|
-
const step = size / diamonds;
|
|
82670
|
-
for (let y = 0; y < size; y++) {
|
|
82671
|
-
for (let x = 0; x < size; x++) {
|
|
82672
|
-
const idx = (y * size + x) * 4;
|
|
82673
|
-
// Diamond pattern using modular arithmetic
|
|
82674
|
-
const dx = ((x % step) / step) * 2 - 1; // -1 to 1 within cell
|
|
82675
|
-
const dy = ((y % step) / step) * 2 - 1;
|
|
82676
|
-
// Diamond distance (L1 norm)
|
|
82677
|
-
const dist = Math.abs(dx) + Math.abs(dy);
|
|
82678
|
-
// Gradient direction for normal
|
|
82679
|
-
const sx = dx > 0 ? 1 : -1;
|
|
82680
|
-
const sy = dy > 0 ? 1 : -1;
|
|
82681
|
-
// Smooth pyramid shape
|
|
82682
|
-
const strength = 0.3;
|
|
82683
|
-
const nx = dist < 1 ? sx * strength * (1 - dist) : 0;
|
|
82684
|
-
const ny = dist < 1 ? sy * strength * (1 - dist) : 0;
|
|
82685
|
-
data[idx] = Math.round((0.5 + nx) * 255);
|
|
82686
|
-
data[idx + 1] = Math.round((0.5 + ny) * 255);
|
|
82687
|
-
data[idx + 2] = 255;
|
|
82688
|
-
data[idx + 3] = 255;
|
|
82689
|
-
}
|
|
82690
|
-
}
|
|
82691
|
-
ctx.putImageData(imageData, 0, 0);
|
|
82692
|
-
return canvas;
|
|
82693
|
-
}
|
|
82694
|
-
/**
|
|
82695
|
-
* Generate a sandblasted surface normal map.
|
|
82696
|
-
*
|
|
82697
|
-
* Creates fine random grain using layered noise at different frequencies,
|
|
82698
|
-
* simulating a sandblasted or bead-blasted metal surface.
|
|
82699
|
-
*/
|
|
82700
|
-
function generateSandblasted(size) {
|
|
82701
|
-
const { canvas, ctx } = createCanvas(size);
|
|
82702
|
-
const imageData = ctx.createImageData(size, size);
|
|
82703
|
-
const data = imageData.data;
|
|
82704
|
-
const rng = mulberry32(137);
|
|
82705
|
-
// Generate a height field with multi-octave noise
|
|
82706
|
-
const heights = new Float32Array(size * size);
|
|
82707
|
-
for (let i = 0; i < heights.length; i++) {
|
|
82708
|
-
heights[i] = rng() * 0.5 + rng() * 0.3 + rng() * 0.2;
|
|
82709
|
-
}
|
|
82710
|
-
// Derive normals from height field via finite differences
|
|
82711
|
-
const strength = 0.2;
|
|
82712
|
-
for (let y = 0; y < size; y++) {
|
|
82713
|
-
for (let x = 0; x < size; x++) {
|
|
82714
|
-
const idx = (y * size + x) * 4;
|
|
82715
|
-
const xp = (x + 1) % size;
|
|
82716
|
-
const xm = (x - 1 + size) % size;
|
|
82717
|
-
const yp = (y + 1) % size;
|
|
82718
|
-
const ym = (y - 1 + size) % size;
|
|
82719
|
-
const dhdx = (heights[y * size + xp] - heights[y * size + xm]) * 0.5;
|
|
82720
|
-
const dhdy = (heights[yp * size + x] - heights[ym * size + x]) * 0.5;
|
|
82721
|
-
data[idx] = Math.round((0.5 - dhdx * strength) * 255);
|
|
82722
|
-
data[idx + 1] = Math.round((0.5 - dhdy * strength) * 255);
|
|
82723
|
-
data[idx + 2] = 255;
|
|
82724
|
-
data[idx + 3] = 255;
|
|
82725
|
-
}
|
|
82726
|
-
}
|
|
82727
|
-
ctx.putImageData(imageData, 0, 0);
|
|
82728
|
-
return canvas;
|
|
82729
|
-
}
|
|
82730
|
-
/**
|
|
82731
|
-
* Generate a hammered/peened surface normal map.
|
|
82732
|
-
*
|
|
82733
|
-
* Creates random crater bumps simulating a hand-hammered metal surface.
|
|
82734
|
-
*/
|
|
82735
|
-
function generateHammered(size) {
|
|
82736
|
-
const { canvas, ctx } = createCanvas(size);
|
|
82737
|
-
const imageData = ctx.createImageData(size, size);
|
|
82738
|
-
const data = imageData.data;
|
|
82739
|
-
const rng = mulberry32(314);
|
|
82740
|
-
// Generate a height field with random circular craters
|
|
82741
|
-
const heights = new Float32Array(size * size);
|
|
82742
|
-
const craterCount = 80;
|
|
82743
|
-
for (let c = 0; c < craterCount; c++) {
|
|
82744
|
-
const cx = rng() * size;
|
|
82745
|
-
const cy = rng() * size;
|
|
82746
|
-
const radius = 8 + rng() * 16;
|
|
82747
|
-
const depth = 0.3 + rng() * 0.7;
|
|
82748
|
-
// Stamp crater (with wrapping for tileable texture)
|
|
82749
|
-
const r2 = radius * radius;
|
|
82750
|
-
for (let dy = -radius; dy <= radius; dy++) {
|
|
82751
|
-
for (let dx = -radius; dx <= radius; dx++) {
|
|
82752
|
-
const d2 = dx * dx + dy * dy;
|
|
82753
|
-
if (d2 < r2) {
|
|
82754
|
-
const px = ((Math.round(cx + dx) % size) + size) % size;
|
|
82755
|
-
const py = ((Math.round(cy + dy) % size) + size) % size;
|
|
82756
|
-
// Smooth hemisphere shape
|
|
82757
|
-
const t = 1 - d2 / r2;
|
|
82758
|
-
heights[py * size + px] += depth * t * t;
|
|
82759
|
-
}
|
|
82760
|
-
}
|
|
82761
|
-
}
|
|
82762
|
-
}
|
|
82763
|
-
// Derive normals from height field
|
|
82764
|
-
const strength = 0.25;
|
|
82765
|
-
for (let y = 0; y < size; y++) {
|
|
82766
|
-
for (let x = 0; x < size; x++) {
|
|
82767
|
-
const idx = (y * size + x) * 4;
|
|
82768
|
-
const xp = (x + 1) % size;
|
|
82769
|
-
const xm = (x - 1 + size) % size;
|
|
82770
|
-
const yp = (y + 1) % size;
|
|
82771
|
-
const ym = (y - 1 + size) % size;
|
|
82772
|
-
const dhdx = (heights[y * size + xp] - heights[y * size + xm]) * 0.5;
|
|
82773
|
-
const dhdy = (heights[yp * size + x] - heights[ym * size + x]) * 0.5;
|
|
82774
|
-
data[idx] = Math.round(Math.max(0, Math.min(255, (0.5 - dhdx * strength) * 255)));
|
|
82775
|
-
data[idx + 1] = Math.round(Math.max(0, Math.min(255, (0.5 - dhdy * strength) * 255)));
|
|
82776
|
-
data[idx + 2] = 255;
|
|
82777
|
-
data[idx + 3] = 255;
|
|
82778
|
-
}
|
|
82779
|
-
}
|
|
82780
|
-
ctx.putImageData(imageData, 0, 0);
|
|
82781
|
-
return canvas;
|
|
82782
|
-
}
|
|
82783
|
-
/**
|
|
82784
|
-
* Generate a black/white checkerboard texture.
|
|
82785
|
-
*
|
|
82786
|
-
* Useful for UV debugging and as a base color texture for testing.
|
|
82787
|
-
*/
|
|
82788
|
-
function generateChecker(size) {
|
|
82789
|
-
const { canvas, ctx } = createCanvas(size);
|
|
82790
|
-
const squares = 8; // 8x8 checkerboard
|
|
82791
|
-
const step = size / squares;
|
|
82792
|
-
ctx.fillStyle = "#ffffff";
|
|
82793
|
-
ctx.fillRect(0, 0, size, size);
|
|
82794
|
-
ctx.fillStyle = "#000000";
|
|
82795
|
-
for (let row = 0; row < squares; row++) {
|
|
82796
|
-
for (let col = 0; col < squares; col++) {
|
|
82797
|
-
if ((row + col) % 2 === 0) {
|
|
82798
|
-
ctx.fillRect(col * step, row * step, step, step);
|
|
82799
|
-
}
|
|
82800
|
-
}
|
|
82801
|
-
}
|
|
82802
|
-
return canvas;
|
|
82803
|
-
}
|
|
82804
|
-
/**
|
|
82805
|
-
* Generate a dark wood grain base color texture.
|
|
82806
|
-
*
|
|
82807
|
-
* Creates concentric growth rings with noise perturbation,
|
|
82808
|
-
* in warm walnut/mahogany tones. Intended as a baseColorTexture (sRGB).
|
|
82809
|
-
*/
|
|
82810
|
-
function generateWoodDark(size) {
|
|
82811
|
-
const { canvas, ctx } = createCanvas(size);
|
|
82812
|
-
const imageData = ctx.createImageData(size, size);
|
|
82813
|
-
const data = imageData.data;
|
|
82814
|
-
const rng = mulberry32(73);
|
|
82815
|
-
// Pre-generate a noise field for grain perturbation
|
|
82816
|
-
const noise = new Float32Array(size * size);
|
|
82817
|
-
for (let i = 0; i < noise.length; i++) {
|
|
82818
|
-
noise[i] = rng();
|
|
82819
|
-
}
|
|
82820
|
-
// Smooth the noise (simple box blur, 2 passes)
|
|
82821
|
-
const tmp = new Float32Array(size * size);
|
|
82822
|
-
for (let pass = 0; pass < 2; pass++) {
|
|
82823
|
-
const src = pass === 0 ? noise : tmp;
|
|
82824
|
-
const dst = pass === 0 ? tmp : noise;
|
|
82825
|
-
const k = 3;
|
|
82826
|
-
for (let y = 0; y < size; y++) {
|
|
82827
|
-
for (let x = 0; x < size; x++) {
|
|
82828
|
-
let sum = 0;
|
|
82829
|
-
let count = 0;
|
|
82830
|
-
for (let dy = -k; dy <= k; dy++) {
|
|
82831
|
-
for (let dx = -k; dx <= k; dx++) {
|
|
82832
|
-
const px = (x + dx + size) % size;
|
|
82833
|
-
const py = (y + dy + size) % size;
|
|
82834
|
-
sum += src[py * size + px];
|
|
82835
|
-
count++;
|
|
82836
|
-
}
|
|
82837
|
-
}
|
|
82838
|
-
dst[y * size + x] = sum / count;
|
|
82839
|
-
}
|
|
82840
|
-
}
|
|
82841
|
-
}
|
|
82842
|
-
// Wood color palette (sRGB, will be decoded by Three.js)
|
|
82843
|
-
// Dark grain lines: ~[80, 45, 22] Light wood body: ~[145, 90, 48]
|
|
82844
|
-
const darkR = 80, darkG = 45, darkB = 22;
|
|
82845
|
-
const lightR = 145, lightG = 90, lightB = 48;
|
|
82846
|
-
// Ring center (offset from image center for asymmetry)
|
|
82847
|
-
const cx = size * 0.45;
|
|
82848
|
-
const cy = size * 0.52;
|
|
82849
|
-
const ringScale = 0.08; // Controls ring spacing
|
|
82850
|
-
for (let y = 0; y < size; y++) {
|
|
82851
|
-
for (let x = 0; x < size; x++) {
|
|
82852
|
-
const idx = (y * size + x) * 4;
|
|
82853
|
-
// Distance from center with noise distortion
|
|
82854
|
-
const n = noise[y * size + x];
|
|
82855
|
-
const dx = x - cx + (n - 0.5) * 30;
|
|
82856
|
-
const dy = y - cy + (n - 0.5) * 15;
|
|
82857
|
-
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
82858
|
-
// Growth ring pattern (sinusoidal)
|
|
82859
|
-
const ring = Math.sin(dist * ringScale * Math.PI * 2);
|
|
82860
|
-
// Remap from [-1,1] to [0,1]
|
|
82861
|
-
const t = ring * 0.5 + 0.5;
|
|
82862
|
-
// sqrt biases toward light — dark lines stay thin, brown dominates
|
|
82863
|
-
const ringFactor = Math.sqrt(t);
|
|
82864
|
-
// Add fine-grain noise for fiber texture
|
|
82865
|
-
const fineNoise = (rng() - 0.5) * 12;
|
|
82866
|
-
// Interpolate between dark and light
|
|
82867
|
-
const r = Math.round(darkR + (lightR - darkR) * ringFactor + fineNoise);
|
|
82868
|
-
const g = Math.round(darkG + (lightG - darkG) * ringFactor + fineNoise * 0.6);
|
|
82869
|
-
const b = Math.round(darkB + (lightB - darkB) * ringFactor + fineNoise * 0.3);
|
|
82870
|
-
data[idx] = Math.max(0, Math.min(255, r));
|
|
82871
|
-
data[idx + 1] = Math.max(0, Math.min(255, g));
|
|
82872
|
-
data[idx + 2] = Math.max(0, Math.min(255, b));
|
|
82873
|
-
data[idx + 3] = 255;
|
|
82874
|
-
}
|
|
82875
|
-
}
|
|
82876
|
-
ctx.putImageData(imageData, 0, 0);
|
|
82877
|
-
return canvas;
|
|
82878
|
-
}
|
|
82879
|
-
/**
|
|
82880
|
-
* Generate a leather pebble-grain normal map.
|
|
82881
|
-
*
|
|
82882
|
-
* Creates irregular rounded bumps (Voronoi-like cells) typical of
|
|
82883
|
-
* top-grain or pebbled leather. Each cell has a smooth dome shape
|
|
82884
|
-
* with slight creases between cells.
|
|
82885
|
-
*/
|
|
82886
|
-
function generateLeather(size) {
|
|
82887
|
-
const { canvas, ctx } = createCanvas(size);
|
|
82888
|
-
const imageData = ctx.createImageData(size, size);
|
|
82889
|
-
const data = imageData.data;
|
|
82890
|
-
const rng = mulberry32(217);
|
|
82891
|
-
// Scatter seed points for Voronoi cells (pebbles)
|
|
82892
|
-
const cellCount = 180;
|
|
82893
|
-
const seeds = [];
|
|
82894
|
-
for (let i = 0; i < cellCount; i++) {
|
|
82895
|
-
seeds.push({ x: rng() * size, y: rng() * size });
|
|
82896
|
-
}
|
|
82897
|
-
// Build a height field from Voronoi distance
|
|
82898
|
-
const heights = new Float32Array(size * size);
|
|
82899
|
-
for (let y = 0; y < size; y++) {
|
|
82900
|
-
for (let x = 0; x < size; x++) {
|
|
82901
|
-
// Find distance to nearest seed (with wrapping for tileability)
|
|
82902
|
-
let minDist = Infinity;
|
|
82903
|
-
for (const s of seeds) {
|
|
82904
|
-
let dx = Math.abs(x - s.x);
|
|
82905
|
-
let dy = Math.abs(y - s.y);
|
|
82906
|
-
if (dx > size / 2)
|
|
82907
|
-
dx = size - dx;
|
|
82908
|
-
if (dy > size / 2)
|
|
82909
|
-
dy = size - dy;
|
|
82910
|
-
const d = Math.sqrt(dx * dx + dy * dy);
|
|
82911
|
-
if (d < minDist)
|
|
82912
|
-
minDist = d;
|
|
82913
|
-
}
|
|
82914
|
-
// Invert: closer to seed = higher (dome center)
|
|
82915
|
-
// Normalize roughly by expected average cell radius
|
|
82916
|
-
const avgRadius = size / Math.sqrt(cellCount);
|
|
82917
|
-
const t = Math.min(minDist / avgRadius, 1.0);
|
|
82918
|
-
// Smooth dome falloff: 1 at center, 0 at edge
|
|
82919
|
-
heights[y * size + x] = (1 - t * t) * 0.8 + rng() * 0.05;
|
|
82920
|
-
}
|
|
82921
|
-
}
|
|
82922
|
-
// Derive normals from height field via finite differences
|
|
82923
|
-
const strength = 0.35;
|
|
82924
|
-
for (let y = 0; y < size; y++) {
|
|
82925
|
-
for (let x = 0; x < size; x++) {
|
|
82926
|
-
const idx = (y * size + x) * 4;
|
|
82927
|
-
const xp = (x + 1) % size;
|
|
82928
|
-
const xm = (x - 1 + size) % size;
|
|
82929
|
-
const yp = (y + 1) % size;
|
|
82930
|
-
const ym = (y - 1 + size) % size;
|
|
82931
|
-
const dhdx = (heights[y * size + xp] - heights[y * size + xm]) * 0.5;
|
|
82932
|
-
const dhdy = (heights[yp * size + x] - heights[ym * size + x]) * 0.5;
|
|
82933
|
-
data[idx] = Math.round(Math.max(0, Math.min(255, (0.5 - dhdx * strength) * 255)));
|
|
82934
|
-
data[idx + 1] = Math.round(Math.max(0, Math.min(255, (0.5 - dhdy * strength) * 255)));
|
|
82935
|
-
data[idx + 2] = 255;
|
|
82936
|
-
data[idx + 3] = 255;
|
|
82937
|
-
}
|
|
82938
|
-
}
|
|
82939
|
-
ctx.putImageData(imageData, 0, 0);
|
|
82940
|
-
return canvas;
|
|
82941
|
-
}
|
|
82942
|
-
/**
|
|
82943
|
-
* Generate a fabric twill-weave normal map.
|
|
82944
|
-
*
|
|
82945
|
-
* Creates a repeating over-under weave pattern with slightly raised
|
|
82946
|
-
* warp/weft threads and recessed gaps, typical of upholstery fabric.
|
|
82947
|
-
*/
|
|
82948
|
-
function generateFabricWeave(size) {
|
|
82949
|
-
const { canvas, ctx } = createCanvas(size);
|
|
82950
|
-
const imageData = ctx.createImageData(size, size);
|
|
82951
|
-
const data = imageData.data;
|
|
82952
|
-
const rng = mulberry32(159);
|
|
82953
|
-
// Thread parameters
|
|
82954
|
-
const threadCount = 32; // threads per axis
|
|
82955
|
-
const cellSize = size / threadCount;
|
|
82956
|
-
// Build height field: each cell is either warp-over or weft-over
|
|
82957
|
-
const heights = new Float32Array(size * size);
|
|
82958
|
-
for (let y = 0; y < size; y++) {
|
|
82959
|
-
for (let x = 0; x < size; x++) {
|
|
82960
|
-
const cx = Math.floor(x / cellSize);
|
|
82961
|
-
const cy = Math.floor(y / cellSize);
|
|
82962
|
-
// Position within the cell [0, 1]
|
|
82963
|
-
const lx = (x % cellSize) / cellSize;
|
|
82964
|
-
const ly = (y % cellSize) / cellSize;
|
|
82965
|
-
// Twill pattern: diagonal shift (2/1 twill)
|
|
82966
|
-
const isWarpOver = ((cx + cy) % 3) < 2;
|
|
82967
|
-
// Thread profile: rounded bump across the thread width
|
|
82968
|
-
// Warp threads run vertically (bump shape across x)
|
|
82969
|
-
// Weft threads run horizontally (bump shape across y)
|
|
82970
|
-
const warpProfile = Math.sin(lx * Math.PI);
|
|
82971
|
-
const weftProfile = Math.sin(ly * Math.PI);
|
|
82972
|
-
// Gap between threads (edges of cells are lower)
|
|
82973
|
-
const edgeFalloff = Math.min(Math.sin(lx * Math.PI), Math.sin(ly * Math.PI));
|
|
82974
|
-
let h;
|
|
82975
|
-
if (isWarpOver) {
|
|
82976
|
-
// Warp on top: height from warp profile
|
|
82977
|
-
h = 0.5 + warpProfile * 0.4 * edgeFalloff;
|
|
82978
|
-
}
|
|
82979
|
-
else {
|
|
82980
|
-
// Weft on top: height from weft profile
|
|
82981
|
-
h = 0.5 + weftProfile * 0.4 * edgeFalloff;
|
|
82982
|
-
}
|
|
82983
|
-
// Add subtle noise for fabric irregularity
|
|
82984
|
-
h += (rng() - 0.5) * 0.04;
|
|
82985
|
-
heights[y * size + x] = h;
|
|
82986
|
-
}
|
|
82987
|
-
}
|
|
82988
|
-
// Derive normals from height field
|
|
82989
|
-
const strength = 0.3;
|
|
82990
|
-
for (let y = 0; y < size; y++) {
|
|
82991
|
-
for (let x = 0; x < size; x++) {
|
|
82992
|
-
const idx = (y * size + x) * 4;
|
|
82993
|
-
const xp = (x + 1) % size;
|
|
82994
|
-
const xm = (x - 1 + size) % size;
|
|
82995
|
-
const yp = (y + 1) % size;
|
|
82996
|
-
const ym = (y - 1 + size) % size;
|
|
82997
|
-
const dhdx = (heights[y * size + xp] - heights[y * size + xm]) * 0.5;
|
|
82998
|
-
const dhdy = (heights[yp * size + x] - heights[ym * size + x]) * 0.5;
|
|
82999
|
-
data[idx] = Math.round(Math.max(0, Math.min(255, (0.5 - dhdx * strength) * 255)));
|
|
83000
|
-
data[idx + 1] = Math.round(Math.max(0, Math.min(255, (0.5 - dhdy * strength) * 255)));
|
|
83001
|
-
data[idx + 2] = 255;
|
|
83002
|
-
data[idx + 3] = 255;
|
|
83003
|
-
}
|
|
83004
|
-
}
|
|
83005
|
-
ctx.putImageData(imageData, 0, 0);
|
|
83006
|
-
return canvas;
|
|
83007
|
-
}
|
|
83008
|
-
/** Map of builtin texture names to their generator functions */
|
|
83009
|
-
const BUILTIN_GENERATORS = {
|
|
83010
|
-
brushed: generateBrushed,
|
|
83011
|
-
knurled: generateKnurled,
|
|
83012
|
-
sandblasted: generateSandblasted,
|
|
83013
|
-
hammered: generateHammered,
|
|
83014
|
-
checker: generateChecker,
|
|
83015
|
-
"wood-dark": generateWoodDark,
|
|
83016
|
-
leather: generateLeather,
|
|
83017
|
-
"fabric-weave": generateFabricWeave,
|
|
83018
|
-
};
|
|
83019
|
-
// =============================================================================
|
|
83020
82582
|
// TextureCache
|
|
83021
82583
|
// =============================================================================
|
|
83022
82584
|
/**
|
|
@@ -83027,52 +82589,36 @@ void main() {
|
|
|
83027
82589
|
* Only TextureCache.dispose() / disposeFull() disposes GPU texture resources.
|
|
83028
82590
|
*
|
|
83029
82591
|
* Resolution order for texture reference strings:
|
|
83030
|
-
* 1. `
|
|
83031
|
-
* 2.
|
|
83032
|
-
* 3. `data:` prefix -- treat as data URI, load directly
|
|
83033
|
-
* 4. Otherwise -- treat as URL, resolve relative to HTML page
|
|
82592
|
+
* 1. `data:` prefix -- treat as data URI, load directly
|
|
82593
|
+
* 2. Otherwise -- treat as URL, resolve relative to HTML page
|
|
83034
82594
|
*
|
|
83035
82595
|
* Features:
|
|
83036
|
-
* - Two-tier caching: user textures (disposed on clear) + builtin textures
|
|
83037
|
-
* (persistent, only disposed on viewer teardown)
|
|
83038
82596
|
* - In-flight promise deduplication (no duplicate loads for the same key)
|
|
83039
82597
|
* - Correct colorSpace assignment per texture semantic role
|
|
83040
82598
|
* - GPU resource tracking via gpuTracker
|
|
83041
82599
|
*/
|
|
83042
82600
|
class TextureCache {
|
|
83043
82601
|
constructor() {
|
|
83044
|
-
/**
|
|
82602
|
+
/** Textures cache (disposed on clear/dispose, rebuilt per shape data) */
|
|
83045
82603
|
this._cache = new Map();
|
|
83046
|
-
/** Built-in procedural textures (persistent, only disposed via disposeFull) */
|
|
83047
|
-
this._builtinCache = new Map();
|
|
83048
82604
|
/** In-flight load promises keyed by cache key */
|
|
83049
82605
|
this._inflight = new Map();
|
|
83050
|
-
/** Root-level textures table from shape data */
|
|
83051
|
-
this._texturesTable = {};
|
|
83052
82606
|
/** THREE.TextureLoader instance (created lazily) */
|
|
83053
82607
|
this._textureLoader = null;
|
|
83054
82608
|
/** Whether this cache has been fully disposed */
|
|
83055
82609
|
this._disposed = false;
|
|
82610
|
+
/** Max anisotropic filtering level.
|
|
82611
|
+
* Default 16 covers most GPUs; clamped by the driver if unsupported. */
|
|
82612
|
+
this.maxAnisotropy = 16;
|
|
83056
82613
|
}
|
|
83057
82614
|
// ---------------------------------------------------------------------------
|
|
83058
82615
|
// Public API
|
|
83059
82616
|
// ---------------------------------------------------------------------------
|
|
83060
|
-
/**
|
|
83061
|
-
* Set or update the root-level textures table.
|
|
83062
|
-
*
|
|
83063
|
-
* Called when new shape data is loaded. The table maps string keys to
|
|
83064
|
-
* TextureEntry objects (embedded base64 data or URL references).
|
|
83065
|
-
*
|
|
83066
|
-
* @param table - The textures table from root Shapes node, or undefined to clear
|
|
83067
|
-
*/
|
|
83068
|
-
setTexturesTable(table) {
|
|
83069
|
-
this._texturesTable = table ?? {};
|
|
83070
|
-
}
|
|
83071
82617
|
/**
|
|
83072
82618
|
* Resolve a texture reference string and return a cached or newly loaded
|
|
83073
82619
|
* THREE.Texture with the correct colorSpace set.
|
|
83074
82620
|
*
|
|
83075
|
-
* @param ref - Texture reference string (
|
|
82621
|
+
* @param ref - Texture reference string (table key, data URI, or URL)
|
|
83076
82622
|
* @param textureRole - The texture role name (MaterialAppearance field name or proxy role)
|
|
83077
82623
|
* (e.g. "baseColorTexture", "normalTexture"). Used to determine colorSpace.
|
|
83078
82624
|
* @returns The resolved THREE.Texture, or null if the reference is invalid
|
|
@@ -83090,148 +82636,41 @@ void main() {
|
|
|
83090
82636
|
const colorSpace = SRGB_TEXTURE_ROLES.has(textureRole)
|
|
83091
82637
|
? SRGBColorSpace
|
|
83092
82638
|
: LinearSRGBColorSpace;
|
|
83093
|
-
// 1.
|
|
83094
|
-
if (ref.startsWith("builtin:")) {
|
|
83095
|
-
return this._getBuiltin(ref, colorSpace);
|
|
83096
|
-
}
|
|
83097
|
-
// 2. Look up in textures table
|
|
83098
|
-
const tableEntry = this._texturesTable[ref];
|
|
83099
|
-
if (tableEntry) {
|
|
83100
|
-
return this._getFromTable(ref, tableEntry, colorSpace);
|
|
83101
|
-
}
|
|
83102
|
-
// 3. data: prefix (data URI)
|
|
82639
|
+
// 1. data: prefix (data URI)
|
|
83103
82640
|
if (ref.startsWith("data:")) {
|
|
83104
82641
|
return this._getFromDataUri(ref, colorSpace);
|
|
83105
82642
|
}
|
|
83106
|
-
//
|
|
82643
|
+
// 2. URL (relative to HTML page)
|
|
83107
82644
|
return this._getFromUrl(ref, colorSpace);
|
|
83108
82645
|
}
|
|
83109
82646
|
/**
|
|
83110
|
-
*
|
|
83111
|
-
*/
|
|
83112
|
-
isBuiltin(ref) {
|
|
83113
|
-
return ref.startsWith("builtin:");
|
|
83114
|
-
}
|
|
83115
|
-
/**
|
|
83116
|
-
* Dispose user textures (called on viewer.clear() when shape data is replaced).
|
|
82647
|
+
* Dispose textures (called on viewer.clear() when shape data is replaced).
|
|
83117
82648
|
*
|
|
83118
|
-
* Disposes all textures in the
|
|
83119
|
-
* The builtin procedural texture cache is preserved.
|
|
82649
|
+
* Disposes all textures in the cache and clears in-flight promises.
|
|
83120
82650
|
*/
|
|
83121
82651
|
dispose() {
|
|
83122
|
-
// Dispose user textures
|
|
83123
82652
|
for (const [key, texture] of this._cache) {
|
|
83124
82653
|
gpuTracker.untrack("texture", texture);
|
|
83125
82654
|
texture.dispose();
|
|
83126
|
-
logger.debug(`Disposed
|
|
82655
|
+
logger.debug(`Disposed texture: ${key}`);
|
|
83127
82656
|
}
|
|
83128
82657
|
this._cache.clear();
|
|
83129
82658
|
// Clear in-flight promises (they may resolve but won't be used)
|
|
83130
82659
|
this._inflight.clear();
|
|
83131
|
-
// Clear the textures table (will be set again with new shape data)
|
|
83132
|
-
this._texturesTable = {};
|
|
83133
82660
|
}
|
|
83134
82661
|
/**
|
|
83135
|
-
* Dispose all textures
|
|
82662
|
+
* Dispose all textures.
|
|
83136
82663
|
*
|
|
83137
82664
|
* Called on viewer.dispose() when the viewer is fully torn down.
|
|
83138
82665
|
* After this call, the TextureCache cannot be used again.
|
|
83139
82666
|
*/
|
|
83140
82667
|
disposeFull() {
|
|
83141
82668
|
this._disposed = true;
|
|
83142
|
-
// Dispose user textures first
|
|
83143
82669
|
this.dispose();
|
|
83144
|
-
// Dispose builtin textures
|
|
83145
|
-
for (const [key, texture] of this._builtinCache) {
|
|
83146
|
-
gpuTracker.untrack("texture", texture);
|
|
83147
|
-
texture.dispose();
|
|
83148
|
-
logger.debug(`Disposed builtin texture: ${key}`);
|
|
83149
|
-
}
|
|
83150
|
-
this._builtinCache.clear();
|
|
83151
|
-
// Null the texture loader reference
|
|
83152
82670
|
this._textureLoader = null;
|
|
83153
82671
|
logger.debug("TextureCache fully disposed");
|
|
83154
82672
|
}
|
|
83155
82673
|
// ---------------------------------------------------------------------------
|
|
83156
|
-
// Private: Builtin procedural textures
|
|
83157
|
-
// ---------------------------------------------------------------------------
|
|
83158
|
-
/**
|
|
83159
|
-
* Get or generate a builtin procedural texture.
|
|
83160
|
-
*
|
|
83161
|
-
* Builtin textures are cached in the persistent `_builtinCache` and survive
|
|
83162
|
-
* `dispose()` calls. They are only freed via `disposeFull()`.
|
|
83163
|
-
*/
|
|
83164
|
-
_getBuiltin(ref, colorSpace) {
|
|
83165
|
-
// Extract name after "builtin:" prefix
|
|
83166
|
-
const name = ref.slice(8);
|
|
83167
|
-
if (!BUILTIN_GENERATORS[name]) {
|
|
83168
|
-
logger.warn(`Unknown builtin texture: "${ref}". Available: ${BUILTIN_NAMES.join(", ")}`);
|
|
83169
|
-
return null;
|
|
83170
|
-
}
|
|
83171
|
-
// Check builtin cache
|
|
83172
|
-
const cached = this._builtinCache.get(ref);
|
|
83173
|
-
if (cached) {
|
|
83174
|
-
// TODO: If the same builtin is used in different roles (sRGB vs Linear),
|
|
83175
|
-
// mutating colorSpace in-place would affect other materials. This is
|
|
83176
|
-
// unlikely for builtins (almost always normal maps) but could be fixed
|
|
83177
|
-
// with a composite cache key (ref + colorSpace) if needed.
|
|
83178
|
-
cached.colorSpace = colorSpace;
|
|
83179
|
-
return cached;
|
|
83180
|
-
}
|
|
83181
|
-
// Generate the procedural texture
|
|
83182
|
-
const canvas = BUILTIN_GENERATORS[name](BUILTIN_SIZE);
|
|
83183
|
-
const texture = new CanvasTexture(canvas);
|
|
83184
|
-
texture.wrapS = RepeatWrapping;
|
|
83185
|
-
texture.wrapT = RepeatWrapping;
|
|
83186
|
-
texture.colorSpace = colorSpace;
|
|
83187
|
-
texture.needsUpdate = true;
|
|
83188
|
-
// Cache in the persistent builtin cache
|
|
83189
|
-
this._builtinCache.set(ref, texture);
|
|
83190
|
-
gpuTracker.trackTexture(texture, `Builtin procedural texture: ${ref}`);
|
|
83191
|
-
logger.debug(`Generated builtin texture: ${ref}`);
|
|
83192
|
-
return texture;
|
|
83193
|
-
}
|
|
83194
|
-
// ---------------------------------------------------------------------------
|
|
83195
|
-
// Private: Table entry loading
|
|
83196
|
-
// ---------------------------------------------------------------------------
|
|
83197
|
-
/**
|
|
83198
|
-
* Load a texture from the root-level textures table entry.
|
|
83199
|
-
*
|
|
83200
|
-
* Handles both embedded (base64 data) and URL-referenced entries.
|
|
83201
|
-
*/
|
|
83202
|
-
async _getFromTable(key, entry, colorSpace) {
|
|
83203
|
-
// Check user cache
|
|
83204
|
-
// TODO: If the same texture table entry is used in different roles
|
|
83205
|
-
// (sRGB vs Linear), mutating colorSpace in-place would affect other
|
|
83206
|
-
// materials. Could be fixed with a composite cache key (key + colorSpace).
|
|
83207
|
-
const cached = this._cache.get(key);
|
|
83208
|
-
if (cached) {
|
|
83209
|
-
cached.colorSpace = colorSpace;
|
|
83210
|
-
return cached;
|
|
83211
|
-
}
|
|
83212
|
-
// Check in-flight
|
|
83213
|
-
const inflight = this._inflight.get(key);
|
|
83214
|
-
if (inflight) {
|
|
83215
|
-
const texture = await inflight;
|
|
83216
|
-
texture.colorSpace = colorSpace;
|
|
83217
|
-
return texture;
|
|
83218
|
-
}
|
|
83219
|
-
// Resolve table entry to a loadable source
|
|
83220
|
-
if (entry.data && entry.format) {
|
|
83221
|
-
// Embedded: construct data URI from base64 data
|
|
83222
|
-
const mimeType = this._formatToMime(entry.format);
|
|
83223
|
-
const dataUri = `data:${mimeType};base64,${entry.data}`;
|
|
83224
|
-
return this._loadAndCache(key, dataUri, colorSpace);
|
|
83225
|
-
}
|
|
83226
|
-
if (entry.url) {
|
|
83227
|
-
// URL reference
|
|
83228
|
-
return this._loadAndCache(key, entry.url, colorSpace);
|
|
83229
|
-
}
|
|
83230
|
-
// Invalid entry (neither data nor url)
|
|
83231
|
-
logger.warn(`Texture table entry "${key}" has neither data nor url, skipping`);
|
|
83232
|
-
return null;
|
|
83233
|
-
}
|
|
83234
|
-
// ---------------------------------------------------------------------------
|
|
83235
82674
|
// Private: Data URI loading
|
|
83236
82675
|
// ---------------------------------------------------------------------------
|
|
83237
82676
|
/**
|
|
@@ -83332,6 +82771,7 @@ void main() {
|
|
|
83332
82771
|
texture.colorSpace = colorSpace;
|
|
83333
82772
|
texture.wrapS = RepeatWrapping;
|
|
83334
82773
|
texture.wrapT = RepeatWrapping;
|
|
82774
|
+
texture.anisotropy = this.maxAnisotropy;
|
|
83335
82775
|
resolve(texture);
|
|
83336
82776
|
}, undefined, // onProgress (not used)
|
|
83337
82777
|
(error) => {
|
|
@@ -83354,24 +82794,6 @@ void main() {
|
|
|
83354
82794
|
}
|
|
83355
82795
|
return this._textureLoader;
|
|
83356
82796
|
}
|
|
83357
|
-
/**
|
|
83358
|
-
* Convert a format string (e.g. "png", "jpg", "webp") to a MIME type.
|
|
83359
|
-
*/
|
|
83360
|
-
_formatToMime(format) {
|
|
83361
|
-
switch (format.toLowerCase()) {
|
|
83362
|
-
case "jpg":
|
|
83363
|
-
case "jpeg":
|
|
83364
|
-
return "image/jpeg";
|
|
83365
|
-
case "png":
|
|
83366
|
-
return "image/png";
|
|
83367
|
-
case "webp":
|
|
83368
|
-
return "image/webp";
|
|
83369
|
-
default:
|
|
83370
|
-
// Default to PNG for unknown formats
|
|
83371
|
-
logger.warn(`Unknown texture format "${format}", defaulting to image/png`);
|
|
83372
|
-
return "image/png";
|
|
83373
|
-
}
|
|
83374
|
-
}
|
|
83375
82797
|
}
|
|
83376
82798
|
/**
|
|
83377
82799
|
* Get the correct color space for a Three.js material map property name.
|
|
@@ -83705,12 +83127,10 @@ void main() {
|
|
|
83705
83127
|
}
|
|
83706
83128
|
}
|
|
83707
83129
|
// --- Anisotropy (brushed metal) ---
|
|
83708
|
-
|
|
83709
|
-
|
|
83710
|
-
|
|
83711
|
-
|
|
83712
|
-
material.anisotropyRotation = def.anisotropyRotation;
|
|
83713
|
-
}
|
|
83130
|
+
// Skipped: anisotropic reflections require tangent vectors on the mesh.
|
|
83131
|
+
// CAD tessellation never provides tangents, so Three.js falls back to
|
|
83132
|
+
// screen-space derivative tangents which produce visible diamond-shaped
|
|
83133
|
+
// facet artifacts on coarse meshes.
|
|
83714
83134
|
// --- Textures ---
|
|
83715
83135
|
// Resolve all texture references via TextureCache.
|
|
83716
83136
|
// The TextureCache determines colorSpace internally from the texture role name.
|
|
@@ -83753,11 +83173,17 @@ void main() {
|
|
|
83753
83173
|
// Skip displacement properties (not supported, would waste GPU memory)
|
|
83754
83174
|
if (key === "displacement" || key === "displacementScale" || key === "displacementBias")
|
|
83755
83175
|
continue;
|
|
83176
|
+
// Skip anisotropy — requires tangent vectors that CAD meshes don't have
|
|
83177
|
+
if (key === "anisotropy" || key === "anisotropyRotation")
|
|
83178
|
+
continue;
|
|
83756
83179
|
// Color arrays → THREE.Color (already linear, no sRGB conversion)
|
|
83757
83180
|
if (COLOR_ARRAY_KEYS.has(key) && Array.isArray(prop.value)) {
|
|
83758
83181
|
const [r, g, b] = prop.value;
|
|
83759
83182
|
matOptions[key] = new Color(r, g, b);
|
|
83760
83183
|
}
|
|
83184
|
+
else if ((key === "normalScale" || key === "clearcoatNormalScale") && Array.isArray(prop.value)) {
|
|
83185
|
+
matOptions[key] = new Vector2(prop.value[0], prop.value[1]);
|
|
83186
|
+
}
|
|
83761
83187
|
else if (key === "iridescenceThicknessRange" && Array.isArray(prop.value)) {
|
|
83762
83188
|
matOptions[key] = prop.value;
|
|
83763
83189
|
}
|
|
@@ -83920,9 +83346,9 @@ void main() {
|
|
|
83920
83346
|
const sheenRoughnessTex = await resolve(def.sheenRoughnessMap, "sheenRoughnessTexture");
|
|
83921
83347
|
if (sheenRoughnessTex)
|
|
83922
83348
|
material.sheenRoughnessMap = sheenRoughnessTex;
|
|
83923
|
-
|
|
83924
|
-
|
|
83925
|
-
|
|
83349
|
+
// Anisotropy texture skipped — CAD meshes lack tangent vectors.
|
|
83350
|
+
// const anisotropyTex = await resolve(def.anisotropyMap, "anisotropyTexture");
|
|
83351
|
+
// if (anisotropyTex) material.anisotropyMap = anisotropyTex;
|
|
83926
83352
|
}
|
|
83927
83353
|
/**
|
|
83928
83354
|
* Update global settings.
|
|
@@ -84202,18 +83628,6 @@ void main() {
|
|
|
84202
83628
|
// ---------------------------------------------------------------------------
|
|
84203
83629
|
// Natural / Other
|
|
84204
83630
|
// ---------------------------------------------------------------------------
|
|
84205
|
-
"wood-light": {
|
|
84206
|
-
name: "Wood (Light)",
|
|
84207
|
-
color: [0.89, 0.8, 0.68, 1],
|
|
84208
|
-
metalness: 0.0,
|
|
84209
|
-
roughness: 0.6,
|
|
84210
|
-
},
|
|
84211
|
-
"wood-dark": {
|
|
84212
|
-
name: "Wood (Dark)",
|
|
84213
|
-
color: [0.63, 0.51, 0.38, 1],
|
|
84214
|
-
metalness: 0.0,
|
|
84215
|
-
roughness: 0.55,
|
|
84216
|
-
},
|
|
84217
83631
|
"ceramic-white": {
|
|
84218
83632
|
name: "Ceramic (White)",
|
|
84219
83633
|
color: [0.98, 0.98, 0.97, 1],
|
|
@@ -84635,7 +84049,6 @@ float metalnessFactor = metalness;
|
|
|
84635
84049
|
this.bsphere = null;
|
|
84636
84050
|
this.groups = {};
|
|
84637
84051
|
this.clipPlanes = null;
|
|
84638
|
-
this.texturesTable = null;
|
|
84639
84052
|
this.materialsTable = null;
|
|
84640
84053
|
this.resolvedMaterials = new Map();
|
|
84641
84054
|
this.resolvedMaterialX = new Map();
|
|
@@ -84666,7 +84079,6 @@ float metalnessFactor = metalness;
|
|
|
84666
84079
|
this._disposeStudioResources();
|
|
84667
84080
|
this.resolvedMaterials.clear();
|
|
84668
84081
|
this.resolvedMaterialX.clear();
|
|
84669
|
-
this.texturesTable = null;
|
|
84670
84082
|
this.materialsTable = null;
|
|
84671
84083
|
}
|
|
84672
84084
|
/**
|
|
@@ -85121,7 +84533,6 @@ float metalnessFactor = metalness;
|
|
|
85121
84533
|
if (this.shapes.format == "GDS") {
|
|
85122
84534
|
this.instances = this.shapes.instances || null;
|
|
85123
84535
|
}
|
|
85124
|
-
this.texturesTable = this.shapes.textures || null;
|
|
85125
84536
|
this.materialsTable = this.shapes.materials || null;
|
|
85126
84537
|
this.resolvedMaterials.clear();
|
|
85127
84538
|
this.resolvedMaterialX.clear();
|
|
@@ -85323,7 +84734,6 @@ float metalnessFactor = metalness;
|
|
|
85323
84734
|
if (!this._textureCache) {
|
|
85324
84735
|
this._textureCache = new TextureCache();
|
|
85325
84736
|
}
|
|
85326
|
-
this._textureCache.setTexturesTable(this.texturesTable ?? undefined);
|
|
85327
84737
|
// Track material tags that failed to resolve
|
|
85328
84738
|
const unresolvedTags = new Set();
|
|
85329
84739
|
// Iterate all ObjectGroups with front meshes
|
|
@@ -94880,7 +94290,7 @@ float metalnessFactor = metalness;
|
|
|
94880
94290
|
}
|
|
94881
94291
|
}
|
|
94882
94292
|
|
|
94883
|
-
const version = "4.
|
|
94293
|
+
const version = "4.3.1";
|
|
94884
94294
|
|
|
94885
94295
|
/**
|
|
94886
94296
|
* Clean room environment for Studio mode PMREM generation.
|
|
@@ -104959,7 +104369,6 @@ void mainImage(const in vec4 inputColor, const in vec2 uv, const in float depth,
|
|
|
104959
104369
|
this._composer = null;
|
|
104960
104370
|
this._active = false;
|
|
104961
104371
|
this._savedClippingState = null;
|
|
104962
|
-
this._savedViewState = null;
|
|
104963
104372
|
this._shadowLights = [];
|
|
104964
104373
|
// -------------------------------------------------------------------------
|
|
104965
104374
|
// Mode enter/leave
|
|
@@ -104978,14 +104387,7 @@ void mainImage(const in vec4 inputColor, const in vec2 uv, const in float depth,
|
|
|
104978
104387
|
if (clipping.planeHelpers) {
|
|
104979
104388
|
clipping.planeHelpers.visible = false;
|
|
104980
104389
|
}
|
|
104981
|
-
// 2.
|
|
104982
|
-
// subscriber side-effects before the texture is ready)
|
|
104983
|
-
this._savedViewState = {
|
|
104984
|
-
ortho: state.get("ortho"),
|
|
104985
|
-
axes: state.get("axes"),
|
|
104986
|
-
grid: [...state.get("grid")],
|
|
104987
|
-
};
|
|
104988
|
-
// 3. Build/swap studio materials (async due to textures)
|
|
104390
|
+
// 2. Build/swap studio materials (async due to textures)
|
|
104989
104391
|
const nestedGroup = this._ctx.getNestedGroup();
|
|
104990
104392
|
const unresolvedTags = await nestedGroup.enterStudioMode(state.get("studioTextureMapping"));
|
|
104991
104393
|
if (!this._active)
|
|
@@ -104994,23 +104396,15 @@ void mainImage(const in vec4 inputColor, const in vec2 uv, const in float depth,
|
|
|
104994
104396
|
if (unresolvedTags.length > 0) {
|
|
104995
104397
|
this._ctx.dispatchEvent(new CustomEvent("tcv-material-warnings", { detail: unresolvedTags }));
|
|
104996
104398
|
}
|
|
104997
|
-
//
|
|
104399
|
+
// 3. Load environment map
|
|
104998
104400
|
const envName = state.get("studioEnvironment");
|
|
104999
104401
|
await this.envManager.loadEnvironment(envName, renderer);
|
|
105000
104402
|
if (!this._active)
|
|
105001
104403
|
return;
|
|
105002
|
-
//
|
|
104404
|
+
// 4. Apply ALL rendering changes atomically
|
|
105003
104405
|
const scene = this._ctx.getScene();
|
|
105004
104406
|
const camera = this._ctx.getCamera();
|
|
105005
104407
|
this.envManager.apply(scene, state.get("studioEnvIntensity"), state.get("studioBackground"), state.get("up") === "Z", camera.ortho, state.get("studioEnvRotation"));
|
|
105006
|
-
// 6. Override camera, axes, grid (after env is loaded so
|
|
105007
|
-
// ortho subscriber doesn't trigger reapplyEnv with null texture)
|
|
105008
|
-
if (this._savedViewState?.ortho)
|
|
105009
|
-
this._ctx.setOrtho(false);
|
|
105010
|
-
if (this._savedViewState?.axes)
|
|
105011
|
-
this._ctx.setAxes(false);
|
|
105012
|
-
if (state.get("grid").some(Boolean))
|
|
105013
|
-
this._ctx.setGrids([false, false, false]);
|
|
105014
104408
|
// Lighting: disable CAD lights; environment IBL provides all illumination
|
|
105015
104409
|
this._ctx.getAmbientLight().intensity = 0;
|
|
105016
104410
|
this._ctx.getDirectLight().intensity = 0;
|
|
@@ -105072,20 +104466,8 @@ void mainImage(const in vec4 inputColor, const in vec2 uv, const in float depth,
|
|
|
105072
104466
|
this._ctx.getClipping().restoreState(this._savedClippingState);
|
|
105073
104467
|
this._savedClippingState = null;
|
|
105074
104468
|
}
|
|
105075
|
-
// 7. Clear active flag
|
|
104469
|
+
// 7. Clear active flag; edges restored by ObjectGroup.leaveStudioMode()
|
|
105076
104470
|
this._active = false;
|
|
105077
|
-
// 8. Restore camera, axes, grid
|
|
105078
|
-
if (this._savedViewState) {
|
|
105079
|
-
const { ortho, axes, grid } = this._savedViewState;
|
|
105080
|
-
if (ortho)
|
|
105081
|
-
this._ctx.setOrtho(true);
|
|
105082
|
-
if (axes)
|
|
105083
|
-
this._ctx.setAxes(true);
|
|
105084
|
-
if (grid.some(Boolean))
|
|
105085
|
-
this._ctx.setGrids(grid);
|
|
105086
|
-
this._savedViewState = null;
|
|
105087
|
-
}
|
|
105088
|
-
// 9. Edges restored by ObjectGroup.leaveStudioMode()
|
|
105089
104471
|
this._ctx.update(true, false);
|
|
105090
104472
|
};
|
|
105091
104473
|
this.resetStudio = () => {
|