cube-parameters
Version:
A sophisticated 3D model viewer built with React, TypeScript, and Three.js, featuring advanced visualization tools, measurement capabilities, and lighting controls.
338 lines (296 loc) • 10.9 kB
text/typescript
import * as THREE from 'three';
import { ResourceManager } from './ResourceManager';
export type MaterialType = 'wood' | 'matPaint' | 'metal' | 'plastic' | 'glass' | 'default';
export interface MaterialParameters {
diffusion: number;
opacity: number;
reflection: number;
refraction: number;
edge: number;
thickness: number;
edgeLinePipe: number;
}
export interface MaterialState {
type: MaterialType;
parameters: MaterialParameters;
isHovered: boolean;
isSelected: boolean;
}
export class EnhancedMaterialManager {
private originalMaterials = new WeakMap<THREE.Object3D, THREE.Material | THREE.Material[]>();
private objectStates = new WeakMap<THREE.Object3D, MaterialState>();
private resourceManager = ResourceManager.getInstance();
private hoverMaterial: THREE.MeshStandardMaterial;
private selectionMaterial: THREE.MeshStandardMaterial;
private trackedObjects = new Set<THREE.Object3D>();
private isDisposed = false;
constructor() {
try {
this.hoverMaterial = this.resourceManager.getMaterial('hover', () =>
new THREE.MeshStandardMaterial({
color: 0xffaa00,
emissive: 0x442200,
emissiveIntensity: 0.4,
transparent: false
})
) as THREE.MeshStandardMaterial;
this.selectionMaterial = this.resourceManager.getMaterial('selection', () =>
new THREE.MeshStandardMaterial({
color: 0x00ff00,
emissive: 0x004400,
emissiveIntensity: 0.3,
transparent: false
})
) as THREE.MeshStandardMaterial;
} catch (error) {
console.error('Error initializing EnhancedMaterialManager:', error);
// Fallback materials
this.hoverMaterial = new THREE.MeshStandardMaterial({ color: 0xffaa00 });
this.selectionMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
}
}
setObjectState(object: THREE.Object3D, updates: Partial<MaterialState>) {
if (this.isDisposed || !object) {
console.warn('Material manager is disposed or object is null');
return;
}
try {
// Store original material if not already stored
if (!this.originalMaterials.has(object)) {
this.storeOriginalMaterial(object);
}
// Track this object
this.trackedObjects.add(object);
// Get or create current state
const currentState = this.objectStates.get(object) || {
type: 'default',
parameters: this.getDefaultParameters(),
isHovered: false,
isSelected: false
};
// Update state
const newState = { ...currentState, ...updates };
this.objectStates.set(object, newState);
// Apply material based on priority: selection > hover > custom > original
this.applyMaterialByPriority(object, newState);
} catch (error) {
console.error('Error setting object state:', error);
}
}
setHoverEffect(object: THREE.Object3D, hover: boolean) {
if (this.isDisposed || !object) return;
try {
// Only store state, don't apply material changes for hover/selection
const currentState = this.objectStates.get(object) || {
type: 'default',
parameters: this.getDefaultParameters(),
isHovered: false,
isSelected: false
};
this.objectStates.set(object, { ...currentState, isHovered: hover });
} catch (error) {
console.error('Error setting hover effect:', error);
}
}
setSelectionEffect(object: THREE.Object3D, selected: boolean) {
if (this.isDisposed || !object) return;
try {
// Only store state, don't apply material changes for hover/selection
const currentState = this.objectStates.get(object) || {
type: 'default',
parameters: this.getDefaultParameters(),
isHovered: false,
isSelected: false
};
this.objectStates.set(object, { ...currentState, isSelected: selected });
} catch (error) {
console.error('Error setting selection effect:', error);
}
}
setMaterialType(object: THREE.Object3D, type: MaterialType, parameters?: Partial<MaterialParameters>) {
if (this.isDisposed || !object) return;
try {
const finalParameters = {
...this.getDefaultParameters(),
...parameters
};
this.setObjectState(object, { type, parameters: finalParameters });
} catch (error) {
console.error('Error setting material type:', error);
}
}
updateMaterialParameters(object: THREE.Object3D, parameters: Partial<MaterialParameters>) {
if (this.isDisposed || !object) return;
try {
const currentState = this.objectStates.get(object);
if (currentState) {
const newParameters = { ...currentState.parameters, ...parameters };
this.setObjectState(object, { parameters: newParameters });
}
} catch (error) {
console.error('Error updating material parameters:', error);
}
}
private storeOriginalMaterial(object: THREE.Object3D) {
try {
if (object instanceof THREE.Mesh && object.material) {
this.originalMaterials.set(object, object.material);
}
object.children.forEach(child => {
if (child instanceof THREE.Mesh && !child.userData.isHelper && child.material) {
this.originalMaterials.set(child, child.material);
}
});
} catch (error) {
console.error('Error storing original material:', error);
}
}
private applyMaterialByPriority(object: THREE.Object3D, state: MaterialState) {
try {
let materialToApply: THREE.Material;
// Only apply custom materials, not hover/selection effects
if (state.type !== 'default') {
materialToApply = this.createCustomMaterial(state.type, state.parameters);
this.applyMaterialToObject(object, materialToApply);
} else {
// Restore original material
this.restoreOriginalMaterial(object);
}
} catch (error) {
console.error('Error applying material by priority:', error);
}
}
private createCustomMaterial(type: MaterialType, parameters: MaterialParameters): THREE.Material {
const cacheKey = `${type}_${JSON.stringify(parameters)}`;
try {
return this.resourceManager.getMaterial(cacheKey, () => {
switch (type) {
case 'wood':
return new THREE.MeshStandardMaterial({
color: 0x8B4513,
roughness: Math.max(0, Math.min(1, 0.8 - parameters.reflection * 0.5)),
metalness: 0.1,
transparent: parameters.opacity < 1,
opacity: Math.max(0, Math.min(1, parameters.opacity)),
emissiveIntensity: Math.max(0, parameters.edge * 0.2)
});
case 'matPaint':
return new THREE.MeshLambertMaterial({
color: 0x666666,
transparent: parameters.opacity < 1,
opacity: Math.max(0, Math.min(1, parameters.opacity)),
reflectivity: Math.max(0, Math.min(1, parameters.reflection * 0.3))
});
case 'metal':
return new THREE.MeshStandardMaterial({
color: 0xC0C0C0,
roughness: Math.max(0, Math.min(1, 0.2 - parameters.reflection * 0.15)),
metalness: 0.9,
transparent: parameters.opacity < 1,
opacity: Math.max(0, Math.min(1, parameters.opacity)),
emissiveIntensity: Math.max(0, parameters.edge * 0.1)
});
case 'plastic':
return new THREE.MeshPhongMaterial({
color: 0xFFFFFF,
shininess: Math.max(0, 100 * parameters.reflection),
transparent: parameters.opacity < 1,
opacity: Math.max(0, Math.min(1, parameters.opacity)),
specular: 0x222222
});
case 'glass':
return new THREE.MeshPhysicalMaterial({
color: 0xFFFFFF,
metalness: 0,
roughness: 0.1,
transparent: true,
opacity: Math.max(0, Math.min(1, parameters.opacity * 0.3)),
transmission: 0.9,
ior: Math.max(1, Math.min(2.5, parameters.refraction)),
thickness: Math.max(0, parameters.thickness)
});
default:
return new THREE.MeshStandardMaterial({ color: 0x888888 });
}
}) as THREE.Material;
} catch (error) {
console.error('Error creating custom material:', error);
return new THREE.MeshStandardMaterial({ color: 0x888888 });
}
}
private applyMaterialToObject(object: THREE.Object3D, material: THREE.Material) {
try {
if (object instanceof THREE.Mesh) {
object.material = material;
}
object.children.forEach(child => {
if (child instanceof THREE.Mesh && !child.userData.isHelper) {
child.material = material;
}
});
} catch (error) {
console.error('Error applying material to object:', error);
}
}
private restoreOriginalMaterial(object: THREE.Object3D) {
try {
if (object instanceof THREE.Mesh) {
const originalMaterial = this.originalMaterials.get(object);
if (originalMaterial) {
object.material = originalMaterial;
}
}
object.children.forEach(child => {
if (child instanceof THREE.Mesh && !child.userData.isHelper) {
const originalMaterial = this.originalMaterials.get(child);
if (originalMaterial) {
child.material = originalMaterial;
}
}
});
} catch (error) {
console.error('Error restoring original material:', error);
}
}
private getDefaultParameters(): MaterialParameters {
return {
diffusion: 0.5,
opacity: 1.0,
reflection: 0.3,
refraction: 1.4,
edge: 0.1,
thickness: 1.0,
edgeLinePipe: 0.0
};
}
clearAllEffects() {
if (this.isDisposed) return;
try {
// Clear all states and restore original materials for tracked objects
this.trackedObjects.forEach(object => {
try {
this.restoreOriginalMaterial(object);
} catch (error) {
console.warn('Error restoring material during clear:', error);
}
});
this.objectStates = new WeakMap();
this.trackedObjects.clear();
} catch (error) {
console.error('Error clearing all effects:', error);
}
}
getObjectState(object: THREE.Object3D): MaterialState | null {
if (this.isDisposed || !object) return null;
return this.objectStates.get(object) || null;
}
dispose() {
try {
this.isDisposed = true;
this.clearAllEffects();
this.originalMaterials = new WeakMap();
} catch (error) {
console.error('Error disposing material manager:', error);
}
}
}