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