PageSourceSearch

https://vrcmods.com/js/item-media-viewer/materials.js?viewer=materials-v45

js vrcmods.com collected 2026-10-01 12:09:32 UTC 9,531 bytes, 207 lines download raw bytes

1import * as THREE from "three";
2import { colorLuminance, textureHasImage, prepareTexture } from "./three-helpers.js?viewer=materials-v45";
3import { hashString } from "./utils.js?viewer=materials-v45";
4
5export function fallbackMaterialColor(material) {
6  const name = String(material?.name || "").toLowerCase();
7  if (/(hair|bang|tail|fur|fluff)/.test(name)) return new THREE.Color(0x2f3744);
8  if (/(eye|iris|pupil)/.test(name)) return new THREE.Color(0x75c7e8);
9  if (/(fang|tooth|teeth)/.test(name)) return new THREE.Color(0xe9dfd0);
10  if (/(nail|claw)/.test(name)) return new THREE.Color(0xc89ea8);
11  if (/(face|body|skin|arm|hand|leg|ear)/.test(name)) return new THREE.Color(0xd7a997);
12  if (/(short|fabric|cloth|shirt|dress|sock|fishnet|warmer)/.test(name)) return new THREE.Color(0x39425c);
13  if (/(metal|ring|zip|chain|buckle)/.test(name)) return new THREE.Color(0x8c9299);
14  const palette = [0xb68f7d, 0x6f8fb0, 0x9376a8, 0x7c9a8e, 0x9c7d61, 0x5f7088, 0x886b78];
15  return new THREE.Color(palette[hashString(name || "material") % palette.length]);
16}
17
18export function shouldUseFallbackMaterialColor(material, hasColorMap, missingColorMap) {
19  const color = material?.color ? material.color.clone() : new THREE.Color(0xffffff);
20  if (missingColorMap) return true;
21  return !hasColorMap && colorLuminance(color) > 0.88;
22}
23
24export function materialColor(material, hasColorMap, missingColorMap) {
25  const color = material?.color ? material.color.clone() : new THREE.Color(0xffffff);
26  if (hasColorMap && colorLuminance(color) < 0.08) return new THREE.Color(0xffffff);
27  if (shouldUseFallbackMaterialColor(material, hasColorMap, missingColorMap)) return fallbackMaterialColor(material);
28  if (!hasColorMap && colorLuminance(color) < 0.035) return new THREE.Color(0x9da8b6);
29  return color;
30}
31
32export function applyMaterialTransparencyState(material, { transparent = material.transparent === true, alphaTest = material.alphaTest || 0, opacity = material.opacity ?? 1 } = {}) {
33  material.transparent = transparent === true;
34  material.opacity = opacity;
35  material.alphaTest = alphaTest;
36  material.depthTest = true;
37  material.depthWrite = !material.transparent;
38  material.premultipliedAlpha = material.premultipliedAlpha === true;
39  if ("forceSinglePass" in material) material.forceSinglePass = true;
40  material.needsUpdate = true;
41  return material;
42}
43
44export function applyTransparentSorting(object) {
45  object.traverse((node) => {
46    if (!node.isMesh || !node.material) return;
47    const mats = Array.isArray(node.material) ? node.material : [node.material];
48    const hasTransparent = mats.some((mat) => mat?.transparent === true);
49    const hasAlphaTest = mats.some((mat) => (mat?.alphaTest || 0) > 0);
50    node.renderOrder = hasTransparent ? 20 : hasAlphaTest ? 10 : 0;
51  });
52}
53
54export function createUnlitMaterial(material) {
55  const unlit = new THREE.MeshBasicMaterial({
56    name: material.name || "",
57    color: material.color ? material.color.clone() : new THREE.Color(0xffffff),
58    map: material.map || null,
59    alphaMap: material.alphaMap || null,
60    aoMap: material.aoMap || null,
61    transparent: material.transparent === true,
62    opacity: material.opacity ?? 1,
63    alphaTest: material.alphaTest || 0,
64    depthWrite: material.depthWrite !== false,
65    depthTest: material.depthTest !== false,
66    premultipliedAlpha: material.premultipliedAlpha === true,
67    side: material.side || THREE.DoubleSide,
68    wireframe: material.wireframe === true,
69    vertexColors: material.vertexColors === true,
70  });
71  unlit.userData.shadedMaterial = material.userData?.shadedMaterial || material;
72  unlit.userData.unlitMaterial = unlit;
73  unlit.userData.previewColor = material.userData?.previewColor;
74  material.userData.unlitMaterial = unlit;
75  return unlit;
76}
77
78export function attachMaterialModes(material) {
79  const unlit = createUnlitMaterial(material);
80  material.userData.shadedMaterial = material;
81  material.userData.unlitMaterial = unlit;
82  return material;
83}
84
85export function applyPreviewMaterials(object) {
86  const summary = { meshes: 0, materials: 0, mapped: 0, missingMaps: 0, fallbacked: 0, materialNames: [], fallbackColors: [] };
87  object.traverse((node) => {
88    if (!node.isMesh) return;
89    summary.meshes += 1;
90    node.frustumCulled = false;
91    node.castShadow = false;
92    node.receiveShadow = false;
93    const materials = Array.isArray(node.material) ? node.material : [node.material];
94    const mapped = materials.map((material) => {
95      summary.materials += 1;
96      if (material?.name) summary.materialNames.push(material.name);
97      if (!material) {
98        return attachMaterialModes(new THREE.MeshStandardMaterial({
99          color: 0xb8c7db,
100          roughness: 0.88,
101          metalness: 0,
102          side: THREE.DoubleSide,
103        }));
104      }
105      const map = prepareTexture(material.map, true);
106      const emissiveMap = prepareTexture(material.emissiveMap, true);
107      const alphaMap = prepareTexture(material.alphaMap, false);
108      const normalMap = prepareTexture(material.normalMap, false);
109      const bumpMap = prepareTexture(material.bumpMap, false);
110      const roughnessMap = prepareTexture(material.roughnessMap, false);
111      const aoMap = prepareTexture(material.aoMap, false);
112      const missingColorMap = !!material.map && !map;
113      if (missingColorMap) summary.missingMaps += 1;
114      if (map) summary.mapped += 1;
115
116      const usedFallbackColor = shouldUseFallbackMaterialColor(material, !!map, missingColorMap);
117      const previewColor = materialColor(material, !!map, missingColorMap);
118      if (usedFallbackColor) {
119        summary.fallbacked += 1;
120        summary.fallbackColors.push({ name: material.name || "", color: `#${previewColor.getHexString()}` });
121      }
122      const rawOpacity = material.opacity ?? 1;
123      const initialAlphaMap = rawOpacity < 0.05 && !map ? null : alphaMap;
124      const opacityLooksMisencoded = rawOpacity < 0.05 && !initialAlphaMap;
125      const safeOpacity = opacityLooksMisencoded ? 1 : rawOpacity;
126      const sourceAlphaTest = Number(material.alphaTest || 0);
127      const colorMapMayCarryAlpha = !!map && (material.transparent === true || sourceAlphaTest > 0);
128      const hasAlphaSource = !!initialAlphaMap || colorMapMayCarryAlpha;
129      const transparent = !opacityLooksMisencoded && sourceAlphaTest <= 0 && (safeOpacity < 1 || (hasAlphaSource && material.transparent === true));
130      const alphaTest = sourceAlphaTest > 0 ? sourceAlphaTest : (!transparent && hasAlphaSource ? 0.28 : 0);
131      const standard = new THREE.MeshStandardMaterial({
132        name: material.name || "",
133        color: previewColor,
134        map,
135        emissive: material.emissive ? material.emissive.clone() : new THREE.Color(0x000000),
136        emissiveMap,
137        emissiveIntensity: emissiveMap ? 0.45 : 0.0,
138        alphaMap: initialAlphaMap,
139        normalMap,
140        bumpMap,
141        roughnessMap,
142        aoMap,
143        transparent,
144        opacity: transparent || safeOpacity < 1 ? safeOpacity : 1,
145        alphaTest,
146        depthWrite: !transparent,
147        depthTest: true,
148        premultipliedAlpha: material.premultipliedAlpha === true,
149        side: THREE.DoubleSide,
150        roughness: map ? Math.min(Math.max(material.roughness ?? 0.6, 0.45), 0.78) : 0.62,
151        metalness: Math.min(material.metalness ?? 0, 0.05),
152        envMapIntensity: map ? 0.85 : 0.55,
153        vertexColors: !!node.geometry?.attributes?.color || material.vertexColors === true,
154      });
155      standard.userData.previewColor = `#${previewColor.getHexString()}`;
156      applyMaterialTransparencyState(standard, { transparent, alphaTest, opacity: transparent || safeOpacity < 1 ? safeOpacity : 1 });
157      return attachMaterialModes(standard);
158    });
159    node.material = Array.isArray(node.material) ? mapped : mapped[0];
160  });
161  applyTransparentSorting(object);
162  summary.materialNames = [...new Set(summary.materialNames)].slice(0, 80);
163  window.__vrcModelViewerDebug = summary;
164  object.userData.viewerDebug = summary;
165}
166
167// Apply toon-family default shading (task 4.2): materials tagged isToonFamily by
168// applyDiscoveredTextures default to their unlit (albedo-dominant) variant, matching
169// how Unity's scene view displays Poiyomi/lilToon/UTS/MToon. Standard-family materials
170// keep the shaded PBR path. Called after texture discovery settles.
171export function applyToonFamilyDefaults(object) {
172  let swapped = 0;
173  object.traverse((node) => {
174    if (!node.isMesh || !node.material) return;
175    const swap = (material) => {
176      if (!material?.userData?.isToonFamily) return material;
177      const unlit = material.userData?.unlitMaterial;
178      if (!unlit) return material;
179      const wireframe = material.wireframe === true;
180      unlit.wireframe = wireframe;
181      swapped += 1;
182      return unlit;
183    };
184    node.material = Array.isArray(node.material)
185      ? node.material.map(swap)
186      : swap(node.material);
187  });
188  return swapped;
189}
190
191export function setObjectShading(object, enabled) {
192  object.traverse((node) => {
193    if (!node.isMesh || !node.material) return;
194    const swap = (material) => {
195      if (!material) return material;
196      const wireframe = material.wireframe === true;
197      const next = enabled
198        ? (material.userData?.shadedMaterial || material)
199        : (material.userData?.unlitMaterial || createUnlitMaterial(material));
200      next.wireframe = wireframe;
201      return next;
202    };
203    node.material = Array.isArray(node.material)
204      ? node.material.map(swap)
205      : swap(node.material);
206  });
207}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.