1 /* $RCSfile$ 2 * $Author: hansonr $ 3 * $Date: 2015-01-12 22:32:00 -0600 (Mon, 12 Jan 2015) $ 4 * $Revision: 20187 $ 5 * 6 * Copyright (C) 2003-2005 The Jmol Development Team 7 * 8 * Contact: jmol-developers@lists.sf.net 9 * 10 * This library is free software; you can redistribute it and/or 11 * modify it under the terms of the GNU Lesser General Public 12 * License as published by the Free Software Foundation; either 13 * version 2.1 of the License, or (at your option) any later version. 14 * 15 * This library is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 * Lesser General Public License for more details. 19 * 20 * You should have received a copy of the GNU Lesser General Public 21 * License along with this library; if not, write to the Free Software 22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 23 */ 24 25 package org.jmol.util; 26 27 import javajs.util.AU; 28 import javajs.util.CU; 29 import javajs.util.M4; 30 import javajs.util.V3; 31 32 33 /** 34 *<p> 35 * All functions. 36 * Implements the shading of RGB values to support shadow and lighting 37 * highlights. 38 *</p> 39 *<p> 40 * Each RGB value has 64 shades. shade[0] represents ambient lighting. 41 * shade[63] is white ... a full specular highlight. 42 *</p> 43 * 44 * @author Miguel, miguel@jmol.org 45 * @author Bob Hanson, hansonr@stolaf.edu 46 * @author N David Brown -- cel shading 47 * 48 */ 49 public class Shader { 50 51 // there are 64 shades of a given color 52 // 0 = ambient 53 // 52 = normal 54 // 56 = max for 55 // 63 = specular 56 private final static int SHADE_INDEX_MAX = 64; 57 public final static int SHADE_INDEX_LAST = SHADE_INDEX_MAX - 1; 58 public final static byte SHADE_INDEX_NORMAL = 52; 59 public final static byte SHADE_INDEX_NOISY_LIMIT = 56; 60 61 // the vwr vector is always {0 0 1} 62 // the light source vector normalized 63 64 private float xLight, yLight, zLight; 65 Shader()66 public Shader() { 67 setLightSource(-1f, -1f, 2.5f); 68 } 69 70 V3 lightSource = new V3(); 71 setLightSource(float x, float y, float z)72 private void setLightSource(float x, float y, float z) { 73 lightSource.set(x, y, z); 74 lightSource.normalize(); 75 xLight = lightSource.x; 76 yLight = lightSource.y; 77 zLight = lightSource.z; 78 } 79 80 boolean specularOn = true; 81 boolean usePhongExponent = false; 82 83 //fractional distance from black for ambient color 84 int ambientPercent = 45; 85 86 // df in I = df * (N dot L) + sf * (R dot V)^p 87 int diffusePercent = 84; 88 89 // log_2(p) in I = df * (N dot L) + sf * (R dot V)^p 90 // for faster calculation of shades 91 int specularExponent = 6; 92 93 // sf in I = df * (N dot L) + sf * (R dot V)^p 94 // not a percent of anything, really 95 int specularPercent = 22; 96 97 // fractional distance to white for specular dot 98 int specularPower = 40; 99 100 // p in I = df * (N dot L) + sf * (R dot V)^p 101 int phongExponent = 64; 102 103 float ambientFraction = ambientPercent / 100f; 104 float diffuseFactor = diffusePercent / 100f; 105 float intenseFraction = specularPower / 100f; 106 float specularFactor = specularPercent / 100f; 107 108 private int[][] ashades = AU.newInt2(128); 109 private int[][] ashadesGreyscale; 110 boolean celOn; 111 int celPower = 10; 112 private int celRGB; 113 private float celZ; 114 private boolean useLight; 115 setCel(boolean celShading, int celShadingPower, int argb)116 void setCel(boolean celShading, int celShadingPower, int argb) { 117 celShading = celShading && celShadingPower != 0; 118 argb = C.getArgb(C.getBgContrast(argb)); 119 // problem here is with antialiasDisplay on white background 120 argb = (argb == 0xFF000000 ? 0xFF040404 121 : argb == -1 ? -2 122 : argb + 1); 123 if (celOn == celShading && celRGB == argb && celPower == celShadingPower) 124 return; 125 celOn = celShading; 126 celPower = celShadingPower; 127 useLight = (!celOn || celShadingPower > 0); 128 celZ = 1 - (float) Math.pow(2, -Math.abs(celShadingPower)/10f); 129 celRGB = argb; 130 flushCaches(); 131 } 132 flushCaches()133 void flushCaches() { 134 checkShades(C.colixMax); 135 for (int i = C.colixMax; --i >= 0; ) 136 ashades[i] = null; 137 calcSphereShading(); 138 for (int i = maxSphereCache; --i >= 0;) 139 sphereShapeCache[i] = null; 140 ellipsoidShades = null; 141 } 142 setLastColix(int argb, boolean asGrey)143 public void setLastColix(int argb, boolean asGrey) { 144 C.allocateColix(argb, true); 145 checkShades(C.LAST_AVAILABLE_COLIX); 146 if (asGrey) 147 C.setLastGrey(argb); 148 ashades[C.LAST_AVAILABLE_COLIX] = getShades2(argb, false); 149 } 150 getShades(short colix)151 int[] getShades(short colix) { 152 checkShades(C.colixMax); 153 colix &= C.OPAQUE_MASK; 154 int[] shades = ashades[colix]; 155 if (shades == null) 156 shades = ashades[colix] = getShades2(C.argbs[colix], false); 157 return shades; 158 } 159 getShadesG(short colix)160 int[] getShadesG(short colix) { 161 checkShades(C.colixMax); 162 colix &= C.OPAQUE_MASK; 163 if (ashadesGreyscale == null) 164 ashadesGreyscale = AU.newInt2(ashades.length); 165 int[] shadesGreyscale = ashadesGreyscale[colix]; 166 if (shadesGreyscale == null) 167 shadesGreyscale = ashadesGreyscale[colix] = 168 getShades2(C.argbs[colix], true); 169 return shadesGreyscale; 170 } 171 checkShades(int n)172 private void checkShades(int n) { 173 if (ashades != null && ashades.length >= n) 174 return; 175 if (n == C.LAST_AVAILABLE_COLIX) 176 n++; 177 ashades = AU.arrayCopyII(ashades, n); 178 if (ashadesGreyscale != null) 179 ashadesGreyscale = AU.arrayCopyII(ashadesGreyscale, n); 180 } 181 182 /* 183 * intensity calculation: 184 * 185 * af ambientFraction (from ambient percent) 186 * if intenseFraction (from specular power) 187 * 188 * given a color rr gg bb, consider one of these components x: 189 * 190 * int[0:63] shades [0 .......... 52(normal) ........ 63] 191 * af*x........ x ..............x+(255-x)*if 192 * black <---ambient%--x---specular power----> white 193 */ 194 getShades2(int rgb, boolean greyScale)195 private int[] getShades2(int rgb, boolean greyScale) { 196 int[] shades = new int[SHADE_INDEX_MAX]; 197 if (rgb == 0) 198 return shades; 199 200 float red0 = ((rgb >> 16) & 0xFF); 201 float grn0 = ((rgb >> 8) & 0xFF); 202 float blu0 = (rgb & 0xFF); 203 204 float red = 0; 205 float grn = 0; 206 float blu = 0; 207 208 float f = ambientFraction; 209 210 while (true) { 211 red = red0 * f + 0.5f; 212 grn = grn0 * f + 0.5f; 213 blu = blu0 * f + 0.5f; 214 if (f > 0 && red < 4 && grn < 4 && blu < 4) { 215 // with antialiasing, black shades with all 216 // components less than 4 will be considered 0 217 // so we must adjust things just a bit. 218 red0++; 219 grn0++; 220 blu0++; 221 if (f < 0.1f) 222 f += 0.1f; 223 rgb = CU.rgb((int) Math.floor(red0), (int) Math.floor(grn0), 224 (int) Math.floor(blu0)); 225 continue; 226 } 227 break; 228 } 229 230 int i = 0; 231 232 f = (1 - f) / SHADE_INDEX_NORMAL; 233 234 float redStep = red0 * f; 235 float grnStep = grn0 * f; 236 float bluStep = blu0 * f; 237 238 if (celOn) { 239 240 int max = SHADE_INDEX_MAX / 2; 241 242 int _rgb = CU.rgb((int) Math.floor(red), (int) Math.floor(grn), 243 (int) Math.floor(blu)); 244 if (celPower >= 0) 245 for (; i < max; ++i) 246 shades[i] = _rgb; 247 248 red += redStep * max; 249 grn += grnStep * max; 250 blu += bluStep * max; 251 _rgb = CU.rgb((int) Math.floor(red), (int) Math.floor(grn), 252 (int) Math.floor(blu)); 253 for (; i < SHADE_INDEX_MAX; i++) 254 shades[i] = _rgb; 255 256 // Min r,g,b is 4,4,4 or else antialiasing bleeds background colour into edges. 257 // 0 and 1 here prevents dithering of the outline 258 shades[0] = shades[1] = celRGB; 259 } else { 260 261 for (; i < SHADE_INDEX_NORMAL; ++i) { 262 shades[i] = CU.rgb((int) Math.floor(red), (int) Math.floor(grn), 263 (int) Math.floor(blu)); 264 red += redStep; 265 grn += grnStep; 266 blu += bluStep; 267 } 268 269 shades[i++] = rgb; 270 271 f = intenseFraction / (SHADE_INDEX_MAX - i); 272 redStep = (255.5f - red) * f; 273 grnStep = (255.5f - grn) * f; 274 bluStep = (255.5f - blu) * f; 275 276 for (; i < SHADE_INDEX_MAX; i++) { 277 red += redStep; 278 grn += grnStep; 279 blu += bluStep; 280 shades[i] = CU.rgb((int) Math.floor(red), (int) Math.floor(grn), 281 (int) Math.floor(blu)); 282 } 283 } 284 if (greyScale) 285 for (; --i >= 0;) 286 shades[i] = CU.toFFGGGfromRGB(shades[i]); 287 return shades; 288 } 289 getShadeIndex(float x, float y, float z)290 public int getShadeIndex(float x, float y, float z) { 291 // from Cylinder3D.calcArgbEndcap and renderCone 292 // from GData.getShadeIndex and getShadeIndex 293 double magnitude = Math.sqrt(x * x + y * y + z * z); 294 return Math.round(getShadeF((float) (x / magnitude), 295 (float) (y / magnitude), (float) (z / magnitude)) 296 * SHADE_INDEX_LAST); 297 } 298 getShadeB(float x, float y, float z)299 public byte getShadeB(float x, float y, float z) { 300 //from Normix3D.setRotationMatrix 301 return (byte) Math.round (getShadeF(x, y, z) 302 * SHADE_INDEX_LAST); 303 } 304 getShadeFp8(float x, float y, float z)305 public int getShadeFp8(float x, float y, float z) { 306 //from calcDitheredNoisyShadeIndex (not utilized) 307 //and Cylinder.calcRotatedPoint 308 double magnitude = Math.sqrt(x*x + y*y + z*z); 309 return (int) Math.floor(getShadeF((float)(x/magnitude), 310 (float)(y/magnitude), 311 (float)(z/magnitude)) 312 * SHADE_INDEX_LAST * (1 << 8)); 313 } 314 getShadeF(float x, float y, float z)315 private float getShadeF(float x, float y, float z) { 316 float NdotL = (useLight ? x * xLight + y * yLight + z * zLight : z); 317 if (NdotL <= 0) 318 return 0; 319 // I = k_diffuse * f_diffuse + k_specular * f_specular 320 // where 321 // k_diffuse = (N dot L) 322 // k_specular = {[(2(N dot L)N - L] dot V}^p 323 // 324 // and in our case V = {0 0 1} so the z component of that is: 325 // 326 // k_specular = ( 2 * NdotL * z - zLight )^p 327 // 328 // HOWEVER -- Jmol's "specularExponent is log_2(phongExponent) 329 // 330 // "specularExponent" phong_exponent 331 // 0 1 332 // 1 2 333 // 2 4 334 // 3 8 335 // 4 16 336 // 5 32 337 // 5.322 40 338 // 6 64 339 // 7 128 340 // 8 256 341 // 9 512 342 // 10 1024 343 float intensity = NdotL * diffuseFactor; 344 if (specularOn) { 345 float k_specular = 2 * NdotL * z - zLight; 346 if (k_specular > 0) { 347 if (usePhongExponent) { 348 k_specular = (float) Math.pow(k_specular, phongExponent); 349 } else { 350 for (int n = specularExponent; --n >= 0 351 && k_specular > .0001f;) 352 k_specular *= k_specular; 353 } 354 intensity += k_specular * specularFactor; 355 } 356 } 357 return (celOn && z < celZ ? 0f : intensity > 1 ? 1f : intensity); 358 } 359 360 /* 361 byte getDitheredShadeIndex(float x, float y, float z) { 362 //not utilized 363 // add some randomness to prevent banding 364 int fp8ShadeIndex = getFp8ShadeIndex(x, y, z); 365 int shadeIndex = fp8ShadeIndex >> 8; 366 // this cannot overflow because the if the float shadeIndex is 1.0 367 // then shadeIndex will be == shadeLast 368 // but there will be no fractional component, so the next test will fail 369 if ((fp8ShadeIndex & 0xFF) > nextRandom8Bit()) 370 ++shadeIndex; 371 int random16bit = seed & 0xFFFF; 372 if (random16bit < 65536 / 3 && shadeIndex > 0) 373 --shadeIndex; 374 else if (random16bit > 65536 * 2 / 3 && shadeIndex < shadeLast) 375 ++shadeIndex; 376 return (byte)shadeIndex; 377 } 378 */ 379 getShadeN(float x, float y, float z, float r)380 public byte getShadeN(float x, float y, float z, float r) { 381 // from Sphere3D only 382 // add some randomness to prevent banding 383 int fp8ShadeIndex = (int) Math.floor(getShadeF(x / r, y / r, z / r) 384 * SHADE_INDEX_LAST * (1 << 8)); 385 int shadeIndex = fp8ShadeIndex >> 8; 386 // this cannot overflow because the if the float shadeIndex is 1.0 387 // then shadeIndex will be == shadeLast 388 // but there will be no fractional component, so the next test will fail 389 if (!useLight) 390 return (byte) shadeIndex; 391 if ((fp8ShadeIndex & 0xFF) > nextRandom8Bit()) 392 ++shadeIndex; 393 int random16bit = seed & 0xFFFF; 394 if (random16bit < 65536 / 3 && shadeIndex > 0) 395 --shadeIndex; 396 else if (random16bit > 65536 * 2 / 3 && shadeIndex < SHADE_INDEX_LAST) 397 ++shadeIndex; 398 return (byte) shadeIndex; 399 } 400 401 /* 402 This is a linear congruential pseudorandom number generator, 403 as defined by D. H. Lehmer and described by Donald E. Knuth in 404 The Art of Computer Programming, 405 Volume 2: Seminumerical Algorithms, section 3.2.1. 406 407 long seed = 1; 408 int nextRandom8Bit() { 409 seed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1); 410 // return (int)(seed >>> (48 - bits)); 411 return (int)(seed >>> 40); 412 } 413 */ 414 415 416 //////////////////////////////////////////////////////////////// 417 // Sphere shading cache for Large spheres 418 //////////////////////////////////////////////////////////////// 419 420 public byte[] sphereShadeIndexes = new byte[256 * 256]; 421 calcSphereShading()422 private synchronized void calcSphereShading() { 423 float xF = -127.5f; 424 float r2 = 130 * 130; 425 for (int i = 0; i < 256; ++xF, ++i) { 426 float yF = -127.5f; 427 float xF2 = xF * xF; 428 for (int j = 0; j < 256; ++yF, ++j) { 429 byte shadeIndex = 0; 430 float z2 = r2 - xF2 - yF * yF; 431 if (z2 > 0) { 432 float z = (float) Math.sqrt(z2); 433 shadeIndex = getShadeN(xF, yF, z, 130); 434 } 435 sphereShadeIndexes[(j << 8) + i] = shadeIndex; 436 } 437 } 438 } 439 440 /* 441 byte getSphereshadeIndex(int x, int y, int r) { 442 int d = 2*r + 1; 443 x += r; 444 if (x < 0) 445 x = 0; 446 int x8 = (x << 8) / d; 447 if (x8 > 0xFF) 448 x8 = 0xFF; 449 y += r; 450 if (y < 0) 451 y = 0; 452 int y8 = (y << 8) / d; 453 if (y8 > 0xFF) 454 y8 = 0xFF; 455 return sphereShadeIndexes[(y8 << 8) + x8]; 456 } 457 */ 458 459 // this doesn't really need to be synchronized 460 // no serious harm done if two threads write seed at the same time 461 private int seed = 0x12345679; // turn lo bit on 462 /** 463 *<p> 464 * Implements RANDU algorithm for random noise in lighting/shading. 465 *</p> 466 *<p> 467 * RANDU is the classic example of a poor random number generator. 468 * But it is very cheap to calculate and is good enough for our purposes. 469 *</p> 470 * 471 * @return Next random 472 */ nextRandom8Bit()473 public int nextRandom8Bit() { 474 int t = seed; 475 seed = t = ((t << 16) + (t << 1) + t) & 0x7FFFFFFF; 476 return t >> 23; 477 } 478 479 private final static int SLIM = 20; 480 private final static int SDIM = SLIM * 2; 481 public final static int maxSphereCache = 128; 482 public int[][] sphereShapeCache = AU.newInt2(maxSphereCache); 483 public byte[][][] ellipsoidShades; 484 public int nOut; 485 public int nIn; 486 487 // Cel shading. 488 // @author N David Brown 489 getEllipsoidShade(float x, float y, float z, int radius, M4 mDeriv)490 public int getEllipsoidShade(float x, float y, float z, int radius, 491 M4 mDeriv) { 492 float tx = mDeriv.m00 * x + mDeriv.m01 * y + mDeriv.m02 * z + mDeriv.m03; 493 float ty = mDeriv.m10 * x + mDeriv.m11 * y + mDeriv.m12 * z + mDeriv.m13; 494 float tz = mDeriv.m20 * x + mDeriv.m21 * y + mDeriv.m22 * z + mDeriv.m23; 495 float f = Math.min(radius/2f, 45) / 496 (float) Math.sqrt(tx * tx + ty * ty + tz * tz); 497 // optimized for about 30-100% inclusion 498 int i = (int) (-tx * f); 499 int j = (int) (-ty * f); 500 int k = (int) (tz * f); 501 boolean outside = i < -SLIM || i >= SLIM || j < -SLIM || j >= SLIM 502 || k < 0 || k >= SDIM; 503 if (outside) { 504 while (i % 2 == 0 && j % 2 == 0 && k % 2 == 0 && i + j + k > 0) { 505 i >>= 1; 506 j >>= 1; 507 k >>= 1; 508 } 509 outside = i < -SLIM || i >= SLIM || j < -SLIM || j >= SLIM || k < 0 510 || k >= SDIM; 511 } 512 513 if (outside) 514 nOut++; 515 else 516 nIn++; 517 518 return (outside ? getShadeIndex(i, j, k) 519 : ellipsoidShades[i + SLIM][j + SLIM][k]); 520 } 521 createEllipsoidShades()522 public void createEllipsoidShades() { 523 524 // we don't need to cache rear-directed normals (kk < 0) 525 526 ellipsoidShades = new byte[SDIM][SDIM][SDIM]; 527 for (int ii = 0; ii < SDIM; ii++) 528 for (int jj = 0; jj < SDIM; jj++) 529 for (int kk = 0; kk < SDIM; kk++) 530 ellipsoidShades[ii][jj][kk] = (byte) getShadeIndex(ii - SLIM, jj 531 - SLIM, kk); 532 } 533 534 // /** 535 // * not implemented; just experimenting here 536 // * @param pbuf 537 // * @param zbuf 538 // * @param aobuf 539 // * @param width 540 // * @param height 541 // * @param ambientOcclusion 542 // */ 543 // public void occludePixels(int[] pbuf, int[] zbuf, int[] aobuf, int width, 544 // int height, int ambientOcclusion) { 545 // int n = zbuf.length; 546 // for (int x = 0, y = 0, offset = 0; offset < n; offset++) { 547 // int z = zbuf[offset]; 548 // int xymax = Math.min(z >> 5, 0); 549 // if (xymax == 0) 550 // continue; 551 // int r2max = xymax * xymax; 552 // int pxmax = Math.min(width, x + xymax); 553 // int pymax = Math.min(height, y + xymax); 554 // for (int px = Math.max(0, x - xymax); px < pxmax; px++) { 555 // for (int py = Math.max(0, y - xymax); py < pymax; py++) { 556 // int dx = px - x; 557 // int dy = py - y; 558 // int r2 = dx * dx + dy * dy; 559 // if (r2 > r2max) 560 // continue; 561 // int pt = offset + width * dy + dx; 562 // int dz = zbuf[pt] - z; 563 // if(dz <= z || dz * dz > r2) 564 // continue; 565 // // TODO 566 // } 567 // } 568 // 569 // 570 // if (++x == width) { 571 // x = 0; 572 // y++; 573 // } 574 // } 575 // } 576 577 } 578