| |
| |
| |
| |
| |
| |
| |
|
|
| import * as THREE from "three"; |
|
|
| |
| |
| |
| const AMBER_YELLOW = { color: [1.0, 0.413, 0.007], roughness: 0.4, metalness: 0.0 }; |
| const BRIGHT_ORANGE = { color: [1.0, 0.144, 0.008], roughness: 0.45, metalness: 0.0 }; |
| const AMBER = { color: [0.847, 0.339, 0.022], roughness: 0.45, metalness: 0.0 }; |
| const CLEAN_YELLOW = { color: [1.0, 0.608, 0.021], roughness: 0.4, metalness: 0.0 }; |
| const CREAM = { color: [0.888, 0.86, 0.798], roughness: 0.35, metalness: 0.0 }; |
| const DARK = { color: [0.012, 0.012, 0.014], roughness: 0.55, metalness: 0.3 }; |
| const GRAY = { color: [0.256, 0.256, 0.279], roughness: 0.5, metalness: 0.35 }; |
| |
| const LENS = { color: [0.01, 0.012, 0.02], roughness: 0.05, metalness: 0.0 }; |
|
|
| |
| const WARM_GRAY = { color: [0.328, 0.312, 0.283], roughness: 0.35, metalness: 0.0 }; |
| const AMBER_ORANGE = { color: [0.815, 0.321, 0.019], roughness: 0.5, metalness: 0.0 }; |
| const CHARCOAL = { color: [0.028, 0.028, 0.033], roughness: 0.5, metalness: 0.0 }; |
| const CHARCOAL_FACE = { color: [0.044, 0.044, 0.051], roughness: 0.5, metalness: 0.0 }; |
| const CHARCOAL_BODY = { color: [0.017, 0.017, 0.019], roughness: 0.5, metalness: 0.0 }; |
| const LIGHT_WARM_GRAY = { color: [0.485, 0.459, 0.416], roughness: 0.35, metalness: 0.0 }; |
| const PURPLE_RING = { color: [0.114, 0.058, 0.578], roughness: 0.4, metalness: 0.0 }; |
| const PURPLE_FEET = { color: [0.164, 0.085, 0.584], roughness: 0.45, metalness: 0.0 }; |
| const PALE_BLUE = { color: [0.441, 0.613, 0.723], roughness: 0.35, metalness: 0.0 }; |
| const MEDIUM_BLUE = { color: [0.13, 0.321, 0.552], roughness: 0.35, metalness: 0.0 }; |
| const BLUE_GRAY = { color: [0.183, 0.379, 0.565], roughness: 0.35, metalness: 0.0 }; |
| const FEET_YELLOW = { color: [0.784, 0.455, 0.023], roughness: 0.5, metalness: 0.0 }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export const VARIANTS = { |
| |
| |
| classic: { |
| headDome: CREAM, |
| facePlate: WARM_GRAY, |
| trim: BRIGHT_ORANGE, |
| beakUpper: BRIGHT_ORANGE, |
| beakLower: BRIGHT_ORANGE, |
| tongue: AMBER_YELLOW, |
| eyeRing: AMBER_YELLOW, |
| lens: LENS, |
| bodyShell: CREAM, |
| sideShells: CREAM, |
| legShells: CREAM, |
| feet: BRIGHT_ORANGE, |
| soles: AMBER_ORANGE, |
| hips: GRAY, |
| mechDark: DARK, |
| mechGray: GRAY, |
| }, |
| |
| |
| charcoal: { |
| headDome: CHARCOAL, |
| facePlate: CHARCOAL_FACE, |
| trim: BRIGHT_ORANGE, |
| beakUpper: BRIGHT_ORANGE, |
| beakLower: BRIGHT_ORANGE, |
| tongue: AMBER_YELLOW, |
| eyeRing: AMBER_YELLOW, |
| lens: LENS, |
| bodyShell: CHARCOAL_BODY, |
| sideShells: CHARCOAL_BODY, |
| legShells: CHARCOAL_BODY, |
| feet: BRIGHT_ORANGE, |
| soles: AMBER_ORANGE, |
| hips: GRAY, |
| mechDark: DARK, |
| mechGray: GRAY, |
| }, |
| |
| |
| purple: { |
| headDome: LIGHT_WARM_GRAY, |
| facePlate: WARM_GRAY, |
| trim: CLEAN_YELLOW, |
| beakUpper: CLEAN_YELLOW, |
| beakLower: CLEAN_YELLOW, |
| tongue: AMBER_YELLOW, |
| eyeRing: PURPLE_RING, |
| lens: LENS, |
| bodyShell: CREAM, |
| sideShells: CREAM, |
| legShells: CREAM, |
| feet: CLEAN_YELLOW, |
| soles: PURPLE_RING, |
| hips: GRAY, |
| mechDark: DARK, |
| mechGray: GRAY, |
| |
| |
| swatch: PURPLE_FEET, |
| }, |
| |
| |
| blue: { |
| headDome: PALE_BLUE, |
| facePlate: MEDIUM_BLUE, |
| trim: BRIGHT_ORANGE, |
| beakUpper: BRIGHT_ORANGE, |
| beakLower: BRIGHT_ORANGE, |
| tongue: AMBER_YELLOW, |
| eyeRing: AMBER_YELLOW, |
| lens: LENS, |
| bodyShell: PALE_BLUE, |
| sideShells: PALE_BLUE, |
| legShells: PALE_BLUE, |
| feet: BRIGHT_ORANGE, |
| soles: AMBER_ORANGE, |
| hips: MEDIUM_BLUE, |
| mechDark: DARK, |
| mechGray: GRAY, |
| }, |
| }; |
|
|
| export const VARIANT_NAMES = Object.keys(VARIANTS); |
| export const DEFAULT_VARIANT = "classic"; |
|
|
| export const randomVariantName = () => |
| VARIANT_NAMES[Math.floor(Math.random() * VARIANT_NAMES.length)]; |
|
|
| |
| |
| |
| export function meshMaterialsFor(v) { |
| return { |
| |
| "top_head_shell.stl": v.headDome, |
| "bottom_head_shell.stl": v.trim, |
| "face_part.stl": v.facePlate, |
| |
| "noenoeil.stl": v.eyeRing, |
| "lens.stl": v.lens, |
| "m12_lens_holder.stl": v.mechDark, |
| |
| |
| "soft_mouth_top.stl": v.beakUpper, |
| "jaw.stl": v.beakLower, |
| "jaw_soft.stl": v.tongue, |
| |
| "trunk_base.stl": v.bodyShell, |
| "left_shell.stl": v.sideShells, |
| "right_shell.stl": v.sideShells, |
| "upper_leg_left.stl": v.legShells, |
| "upper_leg_right.stl": v.legShells, |
| "hip_l.stl": v.hips, |
| |
| "foot_left.stl": v.feet, |
| "foot_right.stl": v.feet, |
| "ankle_left.stl": v.feet, |
| "ankle_right.stl": v.feet, |
| "sole_left.stl": v.soles, |
| "sole_right.stl": v.soles, |
| |
| |
| "ankle_l_v1.stl": v.feet, |
| "ankle_r_v1.stl": v.feet, |
| "roller_blade.stl": v.feet, |
| "rim.stl": v.soles, |
| "tire.stl": v.mechDark, |
| |
| |
| |
| "xl330.stl": v.mechDark, |
| "leg.stl": v.mechDark, |
| "seeed_bearing__configuration_default.stl": v.mechDark, |
| |
| |
| |
| "yaw2roll.stl": v.mechDark, |
| "bearing_roll.stl": v.mechDark, |
| "neck.stl": v.mechDark, |
| "np_f970.stl": v.mechDark, |
| "pcb__raspberry_pi_zero_2_w.stl": v.mechDark, |
| "elec_rpi_robot_hat_pcb.stl": v.mechDark, |
| "banana_pcb_locker.stl": v.mechDark, |
| "speaker.stl": v.mechDark, |
| |
| "upper_leg_rigidity_plate.stl": v.mechGray, |
| "yaw_roll_motion.stl": v.mechGray, |
| "neck_pitch.stl": v.mechGray, |
| "motor_support.stl": v.mechGray, |
| "power_support.stl": v.mechGray, |
| "seeed_bearing__configuration__22x16x4.stl": v.mechGray, |
| }; |
| } |
|
|
| |
| export const materialHookFor = (v) => { |
| const map = meshMaterialsFor(v); |
| return (mesh) => map[mesh] ?? v.mechGray; |
| }; |
|
|
| |
| |
| |
| |
| |
| const matCache = new Map(); |
| function matFor(spec) { |
| const key = `${spec.color.join(",")}|${spec.roughness ?? 0.5}|${spec.metalness ?? 0}`; |
| let m = matCache.get(key); |
| if (!m) { |
| m = new THREE.MeshStandardMaterial({ |
| color: new THREE.Color(...spec.color), |
| roughness: spec.roughness ?? 0.5, |
| metalness: spec.metalness ?? 0.0, |
| }); |
| matCache.set(key, m); |
| } |
| return m; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const FADE_S = 0.35; |
| const easeInOut = (x) => (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2); |
| const fades = new Map(); |
| let fadeRaf = 0; |
|
|
| function driveFades() { |
| const t = performance.now() / 1000; |
| for (const [mesh, f] of fades) { |
| const x = Math.min(1, (t - f.start) / FADE_S); |
| const e = easeInOut(x); |
| f.mat.color.lerpColors(f.fromColor, f.toColor, e); |
| f.mat.roughness = f.fromRough + (f.toRough - f.fromRough) * e; |
| f.mat.metalness = f.fromMetal + (f.toMetal - f.fromMetal) * e; |
| const cur = mesh.material; |
| if (cur !== f.mat && cur?.color) { |
| cur.color.copy(f.mat.color); |
| cur.roughness = f.mat.roughness; |
| cur.metalness = f.mat.metalness; |
| } |
| if (x >= 1) { |
| fades.delete(mesh); |
| if (cur === f.mat) { |
| mesh.material = matFor(f.spec); |
| f.mat.dispose(); |
| } |
| } |
| } |
| fadeRaf = fades.size ? requestAnimationFrame(driveFades) : 0; |
| } |
|
|
| export function applyVariant(rig, variant) { |
| const v = typeof variant === "string" ? VARIANTS[variant] : variant; |
| const map = meshMaterialsFor(v); |
| const fade = !!rig.placer?.parent; |
| rig.root.traverse((o) => { |
| if (!o.isMesh || !o.userData.meshName) return; |
| const spec = map[o.userData.meshName] ?? v.mechGray; |
| const target = matFor(spec); |
| if (!fade) { |
| fades.delete(o); |
| o.material = target; |
| return; |
| } |
| |
| if (o.material === target && !fades.has(o)) return; |
| const from = o.material; |
| const m = target.clone(); |
| m.color.copy(from.color); |
| m.roughness = from.roughness; |
| m.metalness = from.metalness; |
| fades.set(o, { |
| mat: m, |
| spec, |
| fromColor: from.color.clone(), |
| toColor: target.color.clone(), |
| fromRough: from.roughness, |
| toRough: target.roughness, |
| fromMetal: from.metalness, |
| toMetal: target.metalness, |
| start: performance.now() / 1000, |
| }); |
| o.material = m; |
| }); |
| if (fades.size && !fadeRaf) fadeRaf = requestAnimationFrame(driveFades); |
| } |
|
|
| |
| export function specToHex(spec) { |
| return `#${new THREE.Color(...spec.color).getHexString()}`; |
| } |
|
|