@@ -5,11 +5,11 @@ import { mat4_mul, mat4_perspective, mat4_translate_z, quat_identity, quat_from_
import { create _program , make _buffer } from './gl_utils.mjs' ;
import { PAINT _BG , PAINT _BG _PBR , PAINT _BG _NOISE , Paint _State } from './paint_state.mjs' ;
import { Spin _State } from './spin_state.mjs' ;
import { shape _distortion , build _triangle _geo , build _ goldberg _geo } from './render_geo.mjs' ;
import { shape _distortion , build _goldberg _geo } from './render_geo.mjs' ;
import { build _goldberg _adjacency } from './topology.mjs' ;
import { Undo _State } from './undo_state.mjs' ;
import { Env _State } from './env_state.mjs' ;
import { PALETTES , DEFAULT_PALETTE } from './palettes.mjs' ;
import { DEFAULT _PALETTE } from './palettes.mjs' ;
// ---------------------------------------------------------------------------
// Load shaders
@@ -31,8 +31,8 @@ const spin = new Spin_State();
const paint = new Paint _State ( ) ;
const undo _state = new Undo _State ( ) ;
const env = new Env _State ( ) ;
const grid _state = { color : [ 0.05 , 0.05 , 0.05 ] , opacity : 1.0 } ;
let current _geo = null ;
let current _stage = 'relaxed' ;
let current _palette = DEFAULT _PALETTE ;
let building = false ;
let active _brush _side = 'left' ;
@@ -42,12 +42,7 @@ let paint_dirty = false;
let debounce _timer = null ;
function get _params ( ) {
return {
depth : parseInt ( document . getElementById ( 'depth' ) . value , 10 ) ,
iters : parseInt ( document . getElementById ( 'iters' ) . value , 10 ) ,
alpha _edge : parseFloat ( document . getElementById ( 'alpha-edge' ) . value ) ,
alpha _centroid : parseFloat ( document . getElementById ( 'alpha-centroid' ) . value ) ,
} ;
return { depth : 5 , iters : 500 , alpha _edge : 0 , alpha _centroid : 0.04 } ;
}
@@ -61,9 +56,6 @@ function set_stats(geo, stage_name, ms) {
if ( s . vertices !== undefined ) { lines += ` Verts: ${ s . vertices } ` ; }
if ( s . pentagons !== undefined ) { lines += ` ( ${ s . pentagons } pent) ` ; }
lines += '\n' ;
if ( s . worst _shape !== null ) {
lines += ` Worst shape_r: ${ s . worst _shape . toFixed ( 4 ) } \n ` ;
}
lines += ` Build: ${ ms } ms ` ;
stats _el . textContent = lines ;
}
@@ -109,7 +101,7 @@ function upload_geo() {
function rebuild _face _colors ( ) {
if ( ! cache . goldberg || ! face _col _buf ) { return ; }
const goldberg = cache . goldberg ;
const use _relaxed = current _stage === 'relaxed' ;
const use _relaxed = true ;
const fcol = [ ] , fpbr = [ ] , fnoise = [ ] ;
for ( let fi = 0 ; fi < goldberg . faces . length ; fi ++ ) {
const face = goldberg . faces [ fi ] ;
@@ -205,8 +197,6 @@ function apply_history(side) {
async function build ( ) {
if ( building ) { return ; }
building = true ;
const btn = document . getElementById ( 'build-btn' ) ;
btn . disabled = true ;
const p = get _params ( ) ;
const t0 = Date . now ( ) ;
@@ -220,14 +210,6 @@ async function build() {
cache . poly = null ;
cache . goldberg = null ;
}
if ( current _stage === 'ico' ) {
current _geo = build _triangle _geo ( cache . ico , current _palette ) ;
upload _geo ( ) ;
cache . depth = p . depth ;
set _stats ( current _geo , 'Icosahedron' , Date . now ( ) - t0 ) ;
set _status ( '' ) ;
return ;
}
if ( depth _changed || ! cache . poly ) {
let poly = cache . ico ;
@@ -238,14 +220,6 @@ async function build() {
cache . poly = poly ;
cache . goldberg = null ;
}
if ( current _stage === 'subdiv' ) {
current _geo = build _triangle _geo ( cache . poly , current _palette ) ;
upload _geo ( ) ;
cache . depth = p . depth ;
set _stats ( current _geo , ` Subdivided × ${ p . depth } ` , Date . now ( ) - t0 ) ;
set _status ( '' ) ;
return ;
}
if ( depth _changed || ! cache . goldberg ) {
set _status ( 'Building Goldberg dual…' ) ; await yield _ui ( 30 ) ;
@@ -253,16 +227,8 @@ async function build() {
cache . adj = null ;
undo _state . invalidate ( ) ;
}
if ( current _stage === 'goldberg' ) {
current _geo = build _goldberg _geo ( cache . goldberg , false , paint , current _palette ) ;
upload _geo ( ) ;
cache . depth = p . depth ;
set _stats ( current _geo , 'Goldberg (no relax)' , Date . now ( ) - t0 ) ;
set _status ( '' ) ;
return ;
}
set _status ( ` Relaxing ( ${ p . iters } iters, edge= ${ p . alpha _edge } , centroid= ${ p . alpha _centroid } )… ` ) ;
set _status ( ` Relaxing ( ${ p . iters } iters)… ` ) ;
await yield _ui ( 50 ) ;
cache . goldberg . relax _sphere ( p . iters , p . alpha _edge , p . alpha _centroid ) ;
current _geo = build _goldberg _geo ( cache . goldberg , true , paint , current _palette ) ;
@@ -273,7 +239,6 @@ async function build() {
} finally {
building = false ;
btn . disabled = false ;
snapshot _now ( ) ;
}
}
@@ -305,7 +270,7 @@ function pick_face(px, py) {
const hx = ro [ 0 ] + t * rd [ 0 ] , hy = ro [ 1 ] + t * rd [ 1 ] , hz = ro [ 2 ] + t * rd [ 2 ] ;
const goldberg = cache . goldberg ;
const use _relaxed = current _stage === 'relaxed' ;
const use _relaxed = true ;
let best _fi = - 1 , best _dot = - Infinity ;
for ( let fi = 0 ; fi < goldberg . faces . length ; fi ++ ) {
const face = goldberg . faces [ fi ] ;
@@ -412,7 +377,7 @@ function faces_at_radius(center_fi, radius) {
// ---------------------------------------------------------------------------
function save _paint ( ) {
const depth = parseInt ( document . getElementById ( 'depth' ) . value ) ;
const depth = 5 ;
const faces = [ ] ;
for ( const [ fi , c ] of paint . face _colors ) {
const h = v => Math . round ( v * 255 ) . toString ( 16 ) . padStart ( 2 , '0' ) ;
@@ -435,10 +400,7 @@ function load_paint(file) {
reader . onload = e => {
try {
const data = JSON . parse ( e . target . result ) ;
const depth = parseInt ( document . getElementById ( 'depth' ) . value ) ;
if ( data . depth !== depth ) {
document . getElementById ( 'depth' ) . value = data . depth ;
document . getElementById ( 'depth-val' ) . textContent = data . depth ;
if ( data . depth !== 5 ) {
cache . poly = null ; cache . goldberg = null ; cache . adj = null ;
}
paint . face _colors . clear ( ) ;
@@ -720,10 +682,15 @@ function render_frame(ts) {
gl . disable ( gl . POLYGON _OFFSET _FILL ) ;
gl . useProgram ( edge _prog ) ;
gl . uniformMatrix4fv ( gl . getUniformLocation ( edge _prog , 'u_mvp' ) , false , mvp ) ;
gl . uniform3fv ( gl . getUniformLocation ( edge _prog , 'u_edge_color' ) , grid _state . color ) ;
gl . uniform1f ( gl . getUniformLocation ( edge _prog , 'u_edge_opacity' ) , grid _state . opacity ) ;
gl . enable ( gl . BLEND ) ;
gl . blendFunc ( gl . SRC _ALPHA , gl . ONE _MINUS _SRC _ALPHA ) ;
const ep = gl . getAttribLocation ( edge _prog , 'a_pos' ) ;
gl . bindBuffer ( gl . ARRAY _BUFFER , edge _pos _buf ) ; gl . enableVertexAttribArray ( ep ) ; gl . vertexAttribPointer ( ep , 3 , gl . FLOAT , false , 0 , 0 ) ;
gl . drawArrays ( gl . LINES , 0 , edge _verts ) ;
gl . disableVertexAttribArray ( ep ) ;
gl . disable ( gl . BLEND ) ;
gl . enable ( gl . POLYGON _OFFSET _FILL ) ;
@@ -733,50 +700,6 @@ function render_frame(ts) {
// UI wiring
// ---------------------------------------------------------------------------
// Palette buttons (built dynamically from PALETTES array).
{
const grid = document . getElementById ( 'palette-grid' ) ;
for ( const p of PALETTES ) {
const btn = document . createElement ( 'button' ) ;
btn . className = 'sb' + ( p === DEFAULT _PALETTE ? ' active' : '' ) ;
btn . textContent = p . name ;
btn . addEventListener ( 'click' , ( ) => {
current _palette = p ;
grid . querySelectorAll ( '.sb' ) . forEach ( b => b . classList . remove ( 'active' ) ) ;
btn . classList . add ( 'active' ) ;
rebuild _face _colors ( ) ;
// Triangle stages need a full rebuild since positions are shared.
if ( current _stage === 'ico' || current _stage === 'subdiv' ) { build ( ) ; }
} ) ;
grid . appendChild ( btn ) ;
}
}
document . querySelectorAll ( '.sb' ) . forEach ( btn => {
btn . addEventListener ( 'click' , ( ) => {
document . querySelectorAll ( '.sb' ) . forEach ( b => b . classList . remove ( 'active' ) ) ;
btn . classList . add ( 'active' ) ;
current _stage = btn . dataset . stage ;
build ( ) ;
} ) ;
} ) ;
document . getElementById ( 'depth' ) . addEventListener ( 'input' , ( ) => {
const d = parseInt ( document . getElementById ( 'depth' ) . value ) ;
document . getElementById ( 'depth-val' ) . textContent = d ;
document . getElementById ( 'iters' ) . value = d * 8 ;
cache . poly = null ;
cache . goldberg = null ;
cache . adj = null ;
} ) ;
document . getElementById ( 'build-btn' ) . addEventListener ( 'click' , ( ) => {
if ( current _stage === 'relaxed' ) {
cache . goldberg = null ;
cache . adj = null ;
}
build ( ) ;
} ) ;
document . getElementById ( 'spin-btn' ) . addEventListener ( 'click' , ( ) => {
@@ -805,12 +728,8 @@ const update_paint_buttons = () => {
} ;
const make _tool _handler = tool => ( ) => {
if ( ! paint . enabled || paint . tool !== tool ) {
paint . enabled = true ;
paint . tool = tool ;
} else {
paint . enabled = false ;
}
paint . enabled = true ;
paint . tool = tool ;
update _paint _buttons ( ) ;
} ;
@@ -1160,6 +1079,42 @@ function _hsl_to_rgb(h,s,l) {
sync _env _ui ( ) ;
}
// ---------------------------------------------------------------------------
// Grid UI wiring
// ---------------------------------------------------------------------------
{
const btn = document . getElementById ( 'grid-color-btn' ) ;
const opacity = document . getElementById ( 'grid-opacity' ) ;
const opacity _val = document . getElementById ( 'grid-opacity-val' ) ;
const hex _from = c => {
const h = v => Math . round ( Math . min ( v , 1 ) * 255 ) . toString ( 16 ) . padStart ( 2 , '0' ) ;
return ` # ${ h ( c [ 0 ] ) } ${ h ( c [ 1 ] ) } ${ h ( c [ 2 ] ) } ` ;
} ;
btn . style . background = hex _from ( grid _state . color ) ;
btn . addEventListener ( 'click' , ( ) => {
color _dialog _open ( {
get : ( ) => hex _from ( grid _state . color ) ,
set : hex => {
const r = parseInt ( hex . slice ( 1 , 3 ) , 16 ) / 255 ;
const g = parseInt ( hex . slice ( 3 , 5 ) , 16 ) / 255 ;
const b = parseInt ( hex . slice ( 5 , 7 ) , 16 ) / 255 ;
grid _state . color = [ r , g , b ] ;
btn . style . background = hex ;
request _render ( ) ;
} ,
} ) ;
} ) ;
opacity . addEventListener ( 'input' , ( ) => {
grid _state . opacity = parseFloat ( opacity . value ) ;
opacity _val . textContent = grid _state . opacity . toFixed ( 2 ) ;
request _render ( ) ;
} ) ;
}
// ---------------------------------------------------------------------------
// Keyboard shortcuts
// ---------------------------------------------------------------------------
@@ -1179,5 +1134,7 @@ window.addEventListener('keydown', e => {
// Boot
// ---------------------------------------------------------------------------
paint . enabled = true ;
update _paint _buttons ( ) ;
resize ( ) ;
build ( ) ;