Files
websperiments/standalone/goldberg-sphere/paint_state.mjs
mikael-lovqvists-claude-agent 9600a2bc2a Add Goldberg Polyhedron Paint experiment
Interactive WebGL Goldberg polyhedron viewer and painter with PBR
shading, adjustable environment lighting, paint tools (pen, brush,
circle, fill, line, pick), undo/redo, colour palettes, and mesh
relaxation. Added to the standalone experiments index.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 16:00:19 +00:00

89 lines
3.3 KiB
JavaScript

export const PAINT_BG = [0.10, 0.13, 0.17];
export const PAINT_BG_PBR = [0.0, 0.8];
export const PAINT_BG_NOISE = [0.0, 0.0, 4.0, 0.5]; // scale, strength, octaves, gain
export class Paint_State {
constructor() {
this.enabled = false;
this.tool = 'pen'; // 'pen' | 'fill' | 'line'
this.face_colors = new Map(); // fi → [r, g, b]
this.face_pbr = new Map(); // fi → [metallic, roughness]
this.face_noise = new Map(); // fi → [scale, strength, octaves, gain]
this.face_key = new Map(); // fi → material key string (pre-computed at paint time)
this.color_left = [0.85, 0.25, 0.12];
this.color_right = [0.18, 0.45, 0.85];
this.metallic_left = 0.0;
this.metallic_right = 0.0;
this.roughness_left = 0.6;
this.roughness_right = 0.6;
this.noise_left = [0.0, 0.0, 4.0, 0.5]; // scale, strength, octaves, gain
this.noise_right = [0.0, 0.0, 4.0, 0.5];
this.preview = null; // { faces: Set<fi>, color, pbr, noise } | null
}
_parse_hex(hex) {
return [parseInt(hex.slice(1,3),16)/255, parseInt(hex.slice(3,5),16)/255, parseInt(hex.slice(5,7),16)/255];
}
_to_hex(c) {
const h = v => Math.round(v*255).toString(16).padStart(2,'0');
return `#${h(c[0])}${h(c[1])}${h(c[2])}`;
}
set_hex_left(hex) { this.color_left = this._parse_hex(hex); }
set_hex_right(hex) { this.color_right = this._parse_hex(hex); }
hex_left() { return this._to_hex(this.color_left); }
hex_right() { return this._to_hex(this.color_right); }
hex(side) { return side === 'right' ? this.hex_right() : this.hex_left(); }
_make_key(color, pbr, noise) {
return `${color.map(v=>v.toFixed(5)).join(',')}|${pbr.map(v=>v.toFixed(5)).join(',')}|${noise.map(v=>v.toFixed(3)).join(',')}`;
}
paint(fi, button) {
const color = button === 2 ? this.color_right : this.color_left;
const metallic = button === 2 ? this.metallic_right : this.metallic_left;
const roughness= button === 2 ? this.roughness_right: this.roughness_left;
const noise = button === 2 ? this.noise_right : this.noise_left;
const pbr = [metallic, roughness];
this.face_colors.set(fi, [...color]);
this.face_pbr.set(fi, pbr);
this.face_noise.set(fi, [...noise]);
this.face_key.set(fi, this._make_key(color, pbr, noise));
}
clear() {
this.face_colors.clear();
this.face_pbr.clear();
this.face_noise.clear();
this.face_key.clear();
}
set_preview(faces, button) {
const color = button === 2 ? this.color_right : this.color_left;
const metallic = button === 2 ? this.metallic_right : this.metallic_left;
const roughness= button === 2 ? this.roughness_right: this.roughness_left;
const noise = button === 2 ? this.noise_right : this.noise_left;
this.preview = { faces: new Set(faces), color: [...color], pbr: [metallic, roughness], noise: [...noise] };
}
commit_preview() {
if (!this.preview) { return; }
const color = this.preview.color;
const pbr = this.preview.pbr ?? PAINT_BG_PBR;
const noise = this.preview.noise ?? PAINT_BG_NOISE;
const key = this._make_key(color, pbr, noise);
for (const fi of this.preview.faces) {
this.face_colors.set(fi, [...color]);
this.face_pbr.set(fi, [...pbr]);
this.face_noise.set(fi, [...noise]);
this.face_key.set(fi, key);
}
this.preview = null;
}
clear_preview() {
this.preview = null;
}
}