@@ -1870,63 +1870,100 @@ export const ProteinViewer = forwardRef<ProteinViewerRef, ProteinViewerProps>(({
18701870 }
18711871 } ) ;
18721872
1873- // Helper: blend two colors
1874- const blendColors = ( c1 : number , c2 : number , factor : number ) => {
1875- const r = Math . round ( ( ( c1 >> 16 ) & 0xFF ) + ( ( ( c2 >> 16 ) & 0xFF ) - ( ( c1 >> 16 ) & 0xFF ) ) * factor ) ;
1876- const g = Math . round ( ( ( c1 >> 8 ) & 0xFF ) + ( ( ( c2 >> 8 ) & 0xFF ) - ( ( c1 >> 8 ) & 0xFF ) ) * factor ) ;
1877- const b = Math . round ( ( c1 & 0xFF ) + ( ( c2 & 0xFF ) - ( c1 & 0xFF ) ) * factor ) ;
1878- return ( r << 16 ) | ( g << 8 ) | b ;
1879- } ;
18801873
1881- // Apply gradient transitions at boundaries
1882- const gradWidth = 2 ; // Number of residues to blend on each side
1874+ // --- GLOBAL CHAIN SMOOTHING ALGORITHM ---
1875+ // 1. Linearize chain residues to array
1876+ // 2. Assign base vs custom colors
1877+ // 3. Apply floating-point moving average smoothing
1878+ // 4. Map back to atoms
18831879
18841880 try {
1885- component . structure . eachResidue ( ( res : any ) => {
1886- const key = `${ res . chainname } :${ res . resno } ` ;
1887- const customCol = residueColorMap . get ( key ) ;
1888-
1889- // Get element color for this residue
1890- let baseCol = 0xCCCCCC ;
1891- try {
1892- const firstAtom = Array . from ( res . iterateAtom ( ) ) [ 0 ] ;
1893- const ElementScheme = window . NGL . ColormakerRegistry . getScheme ( 'element' ) ;
1894- if ( ElementScheme ) {
1895- const es = new ElementScheme ( { } ) ;
1896- baseCol = es . atomColor ( firstAtom ) ;
1881+ component . structure . eachChain ( ( chain : any ) => {
1882+ // Collection phase
1883+ const residues : any [ ] = [ ] ;
1884+ const bgColors : number [ ] = [ ] ; // Base colors (element/custom)
1885+
1886+ chain . eachResidue ( ( res : any ) => {
1887+ residues . push ( res ) ;
1888+ const key = `${ res . chainname } :${ res . resno } ` ;
1889+ let col = residueColorMap . get ( key ) ;
1890+
1891+ if ( col === undefined ) {
1892+ // Default to element color
1893+ col = 0xCCCCCC ;
1894+ try {
1895+ const firstAtom = Array . from ( res . iterateAtom ( ) ) [ 0 ] ;
1896+ const ElementScheme = window . NGL . ColormakerRegistry . getScheme ( 'element' ) ;
1897+ if ( ElementScheme ) {
1898+ const es = new ElementScheme ( { } ) ;
1899+ col = es . atomColor ( firstAtom ) ;
1900+ }
1901+ } catch ( e ) { /* ignore */ }
18971902 }
1898- } catch ( e ) { /* ignore */ }
1899-
1900- let finalCol = customCol !== undefined ? customCol : baseCol ;
1901-
1902- // If not custom colored, check for nearby custom residues for gradient
1903- if ( customCol === undefined ) {
1904- let nearestCustom = null ;
1905- let nearestDist = gradWidth + 1 ;
1906-
1907- for ( let d = - gradWidth ; d <= gradWidth ; d ++ ) {
1908- if ( d === 0 ) continue ;
1909- const nKey = `${ res . chainname } :${ res . resno + d } ` ;
1910- const nCol = residueColorMap . get ( nKey ) ;
1911- if ( nCol !== undefined && Math . abs ( d ) < nearestDist ) {
1912- nearestDist = Math . abs ( d ) ;
1913- nearestCustom = nCol ;
1903+ bgColors . push ( col || 0xCCCCCC ) ;
1904+ } ) ;
1905+
1906+ // Smoothing Phase (RGB separation)
1907+ // We use a floating point buffer for precision accumulation
1908+ const len = bgColors . length ;
1909+ let rBuffer = new Float32Array ( len ) ;
1910+ let gBuffer = new Float32Array ( len ) ;
1911+ let bBuffer = new Float32Array ( len ) ;
1912+
1913+ // Initialize
1914+ for ( let i = 0 ; i < len ; i ++ ) {
1915+ const c = bgColors [ i ] || 0xCCCCCC ;
1916+ rBuffer [ i ] = ( c >> 16 ) & 0xFF ;
1917+ gBuffer [ i ] = ( c >> 8 ) & 0xFF ;
1918+ bBuffer [ i ] = c & 0xFF ;
1919+ }
1920+
1921+ // Multi-pass smoothing (3 passes of [0.25, 0.5, 0.25] kernel approx)
1922+ const passes = 3 ;
1923+ for ( let p = 0 ; p < passes ; p ++ ) {
1924+ const newR = new Float32Array ( len ) ;
1925+ const newG = new Float32Array ( len ) ;
1926+ const newB = new Float32Array ( len ) ;
1927+
1928+ for ( let i = 0 ; i < len ; i ++ ) {
1929+ let sumR = 0 , sumG = 0 , sumB = 0 ;
1930+ let count = 0 ;
1931+
1932+ // Previous
1933+ if ( i > 0 ) {
1934+ sumR += rBuffer [ i - 1 ] ; sumG += gBuffer [ i - 1 ] ; sumB += bBuffer [ i - 1 ] ;
1935+ count ++ ;
1936+ }
1937+ // Current (weight x2 for stability)
1938+ sumR += rBuffer [ i ] * 2 ; sumG += gBuffer [ i ] * 2 ; sumB += bBuffer [ i ] * 2 ;
1939+ count += 2 ;
1940+ // Next
1941+ if ( i < len - 1 ) {
1942+ sumR += rBuffer [ i + 1 ] ; sumG += gBuffer [ i + 1 ] ; sumB += bBuffer [ i + 1 ] ;
1943+ count ++ ;
19141944 }
1915- }
19161945
1917- if ( nearestCustom !== null ) {
1918- const blend = 1 - ( nearestDist / ( gradWidth + 1 ) ) ;
1919- finalCol = blendColors ( baseCol , nearestCustom , blend ) ;
1946+ newR [ i ] = sumR / count ;
1947+ newG [ i ] = sumG / count ;
1948+ newB [ i ] = sumB / count ;
19201949 }
1950+ rBuffer = newR ; gBuffer = newG ; bBuffer = newB ;
19211951 }
19221952
1923- // Apply color to all atoms in residue
1924- res . eachAtom ( ( atom : any ) => {
1925- atomColorMap . set ( atom . index , finalCol ) ;
1953+ // Assignment Phase
1954+ residues . forEach ( ( res , idx ) => {
1955+ const r = Math . round ( rBuffer [ idx ] ) ;
1956+ const g = Math . round ( gBuffer [ idx ] ) ;
1957+ const b = Math . round ( bBuffer [ idx ] ) ;
1958+ const finalColor = ( r << 16 ) | ( g << 8 ) | b ;
1959+
1960+ res . eachAtom ( ( atom : any ) => {
1961+ atomColorMap . set ( atom . index , finalColor ) ;
1962+ } ) ;
19261963 } ) ;
19271964 } ) ;
19281965 } catch ( e ) {
1929- console . warn ( "Error in gradient processing " , e ) ;
1966+ console . warn ( "Global smoothing failed " , e ) ;
19301967 }
19311968
19321969 // Fallback: if atomColorMap is still empty (e.g. error above), force populate
0 commit comments