@@ -75,6 +75,7 @@ let liveApplyTimer = null;
7575const featureModules = {
7676 genetic : null ,
7777 perturb : null ,
78+ gradient : null ,
7879} ;
7980
8081async function ensureFeatureModule ( name ) {
@@ -103,6 +104,19 @@ async function ensureFeatureModule(name) {
103104 }
104105 return featureModules [ name ] ;
105106 }
107+ if ( name === "gradient" ) {
108+ const { initializeGradientEditor } = await import ( "./features/gradient-editor.mjs" ) ;
109+ featureModules [ name ] = initializeGradientEditor ( {
110+ getConfig : ( ) => state . config ,
111+ setConfig : ( config ) => {
112+ state . config = config ;
113+ render ( ) ;
114+ } ,
115+ render,
116+ } ) ;
117+ featureModules [ name ] . init ( ) ;
118+ return featureModules [ name ] ;
119+ }
106120 return null ;
107121}
108122
@@ -121,6 +135,9 @@ function setActiveInspectorPanel(panelId) {
121135 if ( panelId === "perturb-panel" ) {
122136 void ensureFeatureModule ( "perturb" ) ;
123137 }
138+ if ( panelId === "gradients-panel" ) {
139+ void ensureFeatureModule ( "gradient" ) ;
140+ }
124141}
125142
126143function controlsToConfig ( ) {
@@ -217,10 +234,131 @@ function colorFor(value, config, channels) {
217234 const alphabet = config . alphabet_sortie || config . alphabet_entree || [ 0 , 1 ] ;
218235 let index = alphabet . findIndex ( ( item ) => String ( item ) === String ( visualValue ) ) ;
219236 if ( index < 0 ) index = Math . abs ( Number ( visualValue ) || Number ( value ) || 0 ) % alphabet . length ;
237+
238+ // Check if gradient mode is enabled
239+ const gradient = config . rendu ?. gradient ;
240+ if ( gradient && gradient . mode === "gradient" ) {
241+ return interpolateGradientColor ( index , alphabet . length , gradient ) ;
242+ }
243+
244+ // Fall back to discrete palette
220245 const palette = config . rendu . palette || getDefaultPalette ( alphabet . length ) ;
221246 return palette [ index % palette . length ] || palette [ 0 ] ;
222247}
223248
249+ function interpolateGradientColor ( stateIndex , stateCount , gradientConfig ) {
250+ const t = stateCount > 1 ? stateIndex / ( stateCount - 1 ) : 0 ;
251+ const anchors = gradientConfig . ancres || { } ;
252+ const method = gradientConfig . methode || "lineaire" ;
253+
254+ if ( method === "lineaire" && stateCount >= 2 ) {
255+ const c0 = anchors [ "0" ] || "#0066ff" ;
256+ const c1 = anchors [ "1" ] || "#ff6b35" ;
257+ return lerpColor ( t , c0 , c1 ) ;
258+ }
259+
260+ if ( method === "barycentric" && stateCount >= 3 ) {
261+ const c0 = anchors [ "0" ] || "#0066ff" ;
262+ const c1 = anchors [ "1" ] || "#00ff41" ;
263+ const c2 = anchors [ "2" ] || "#ff6b35" ;
264+ const lambda0 = Math . max ( 0 , 1 - t ) ;
265+ const lambda1 = t < 0.5 ? t * 2 : ( 1 - t ) * 2 ;
266+ const lambda2 = t ;
267+ return barycentricColor ( lambda0 , lambda1 , lambda2 , c0 , c1 , c2 ) ;
268+ }
269+
270+ if ( method === "bilinear" && stateCount >= 4 ) {
271+ const c0 = anchors [ "0" ] || "#0066ff" ;
272+ const c1 = anchors [ "1" ] || "#00aaff" ;
273+ const c2 = anchors [ "2" ] || "#00ff41" ;
274+ const c3 = anchors [ "3" ] || "#ff6b35" ;
275+ const u = t * 2 ;
276+ const v = t * 2 ;
277+ return bilinearColor ( u < 1 ? u : 2 - u , v < 1 ? v : 2 - v , c0 , c1 , c2 , c3 ) ;
278+ }
279+
280+ // IDW fallback
281+ const colors = Object . values ( anchors ) ;
282+ return idwColor ( t , stateCount , colors ) ;
283+ }
284+
285+ function lerpColor ( t , color1 , color2 ) {
286+ const c1 = hexToRgb ( color1 ) ;
287+ const c2 = hexToRgb ( color2 ) ;
288+ const r = Math . round ( c1 . r + ( c2 . r - c1 . r ) * t ) ;
289+ const g = Math . round ( c1 . g + ( c2 . g - c1 . g ) * t ) ;
290+ const b = Math . round ( c1 . b + ( c2 . b - c1 . b ) * t ) ;
291+ return `rgb(${ r } , ${ g } , ${ b } )` ;
292+ }
293+
294+ function hexToRgb ( hex ) {
295+ const h = hex . replace ( "#" , "" ) ;
296+ return {
297+ r : parseInt ( h . substr ( 0 , 2 ) , 16 ) ,
298+ g : parseInt ( h . substr ( 2 , 2 ) , 16 ) ,
299+ b : parseInt ( h . substr ( 4 , 2 ) , 16 ) ,
300+ } ;
301+ }
302+
303+ function barycentricColor ( l0 , l1 , l2 , color1 , color2 , color3 ) {
304+ const c1 = hexToRgb ( color1 ) ;
305+ const c2 = hexToRgb ( color2 ) ;
306+ const c3 = hexToRgb ( color3 ) ;
307+ const sum = l0 + l1 + l2 || 1 ;
308+ const n0 = l0 / sum ;
309+ const n1 = l1 / sum ;
310+ const n2 = l2 / sum ;
311+ const r = Math . round ( c1 . r * n0 + c2 . r * n1 + c3 . r * n2 ) ;
312+ const g = Math . round ( c1 . g * n0 + c2 . g * n1 + c3 . g * n2 ) ;
313+ const b = Math . round ( c1 . b * n0 + c2 . b * n1 + c3 . b * n2 ) ;
314+ return `rgb(${ r } , ${ g } , ${ b } )` ;
315+ }
316+
317+ function bilinearColor ( u , v , color1 , color2 , color3 , color4 ) {
318+ const uc = Math . max ( 0 , Math . min ( 1 , u ) ) ;
319+ const vc = Math . max ( 0 , Math . min ( 1 , v ) ) ;
320+ const c1 = hexToRgb ( color1 ) ;
321+ const c2 = hexToRgb ( color2 ) ;
322+ const c3 = hexToRgb ( color3 ) ;
323+ const c4 = hexToRgb ( color4 ) ;
324+ const bottom = {
325+ r : c1 . r + ( c2 . r - c1 . r ) * uc ,
326+ g : c1 . g + ( c2 . g - c1 . g ) * uc ,
327+ b : c1 . b + ( c2 . b - c1 . b ) * uc ,
328+ } ;
329+ const top = {
330+ r : c4 . r + ( c3 . r - c4 . r ) * uc ,
331+ g : c4 . g + ( c3 . g - c4 . g ) * uc ,
332+ b : c4 . b + ( c3 . b - c4 . b ) * uc ,
333+ } ;
334+ const r = Math . round ( bottom . r + ( top . r - bottom . r ) * vc ) ;
335+ const g = Math . round ( bottom . g + ( top . g - bottom . g ) * vc ) ;
336+ const b = Math . round ( bottom . b + ( top . b - bottom . b ) * vc ) ;
337+ return `rgb(${ r } , ${ g } , ${ b } )` ;
338+ }
339+
340+ function idwColor ( t , stateCount , colors ) {
341+ if ( colors . length === 0 ) return "#808080" ;
342+ if ( colors . length === 1 ) return colors [ 0 ] ;
343+
344+ let weights = [ ] ;
345+ for ( let i = 0 ; i < stateCount ; i ++ ) {
346+ const pointPos = stateCount > 1 ? i / ( stateCount - 1 ) : 0.5 ;
347+ const diff = Math . abs ( t - pointPos ) ;
348+ if ( diff < 0.001 ) {
349+ return colors [ i ] || "#808080" ;
350+ }
351+ weights . push ( 1 / ( diff * diff ) ) ;
352+ }
353+
354+ const sumWeights = weights . reduce ( ( a , b ) => a + b , 0 ) ;
355+ const rgbs = colors . map ( hexToRgb ) ;
356+ const r = Math . round ( rgbs . reduce ( ( sum , rgb , i ) => sum + rgb . r * weights [ i ] , 0 ) / sumWeights ) ;
357+ const g = Math . round ( rgbs . reduce ( ( sum , rgb , i ) => sum + rgb . g * weights [ i ] , 0 ) / sumWeights ) ;
358+ const b = Math . round ( rgbs . reduce ( ( sum , rgb , i ) => sum + rgb . b * weights [ i ] , 0 ) / sumWeights ) ;
359+ return `rgb(${ r } , ${ g } , ${ b } )` ;
360+ }
361+
224362function renderTransitionSignals ( config ) {
225363 if ( ! transitionSignals ) return ;
226364 const entries = window . AutomaginariumCore . transitionSignalEntries ( config , 6 ) ;
@@ -358,6 +496,9 @@ function applyConfig(config, { source = "Configuration" } = {}) {
358496 describe ( normalized ) ;
359497 updateRuleSpaceDisplay ( normalized ) ;
360498 updatePaletteEditor ( ) ;
499+ if ( featureModules . gradient && typeof featureModules . gradient . updateOnConfigChange === "function" ) {
500+ featureModules . gradient . updateOnConfigChange ( ) ;
501+ }
361502 updateHudRule ( ) ;
362503 render ( ) ;
363504 setSyncState ( "ok" , `${ source } chargee` ) ;
0 commit comments