1 /* $RCSfile$ 2 * $Author: hansonr $ 3 * $Date: 2006-02-25 11:44:18 -0600 (Sat, 25 Feb 2006) $ 4 * $Revision: 4528 $ 5 * 6 * Copyright (C) 2005 Miguel, Jmol Development 7 * 8 * Contact: jmol-developers@lists.sf.net, jmol-developers@lists.sourceforge.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 package org.jmol.renderspecial; 25 26 27 28 29 import javajs.util.BS; 30 import org.jmol.render.MeshRenderer; 31 import org.jmol.script.T; 32 import org.jmol.shape.Mesh; 33 import org.jmol.shapespecial.Draw; 34 import org.jmol.shapespecial.DrawMesh; 35 import org.jmol.shapespecial.Draw.EnumDrawType; 36 import org.jmol.util.C; 37 import org.jmol.util.GData; 38 import javajs.util.Lst; 39 40 import javajs.util.A4; 41 import javajs.util.M3; 42 import javajs.util.Measure; 43 import javajs.util.P3; 44 import javajs.util.P3i; 45 import javajs.util.T3; 46 import javajs.util.V3; 47 import org.jmol.viewer.ActionManager; 48 49 public class DrawRenderer extends MeshRenderer { 50 51 private EnumDrawType drawType; 52 protected DrawMesh dmesh; 53 54 private P3[] controlHermites; 55 protected P3 pt0 = new P3(); 56 protected P3 pt1 = new P3(); 57 protected P3 pt2 = new P3(); 58 protected final V3 vTemp = new V3(); 59 protected final V3 vTemp2 = new V3(); 60 61 @Override render()62 protected boolean render() { 63 /* 64 * Each drawn object, draw.meshes[i], may consist of several polygons, one 65 * for each MODEL FRAME. Or, it may be "fixed" and only contain one single 66 * polygon. 67 * 68 */ 69 needTranslucent = false; 70 imageFontScaling = vwr.imageFontScaling; 71 Draw draw = (Draw) shape; 72 //isPrecision = true;//vwr.tm.perspectiveDepth; 73 for (int i = draw.meshCount; --i >= 0;) { 74 Mesh mesh = dmesh = (DrawMesh) draw.meshes[i]; 75 if (mesh == null) { 76 System.out.println("DrawRenderer mesh is null?"); 77 return false; 78 } 79 if (mesh.connectedAtoms != null) { 80 if (mesh.connectedAtoms[0] < 0) 81 continue; 82 // bond-bond [ a b c d ] 83 // bond-atom [ a b c -1 ] 84 // atom-bond [ a -1 c d ] 85 // atom-atom [ a -1 c -1 ] 86 87 mesh.vs = new P3[4]; 88 mesh.vc = 4; 89 int[] c = mesh.connectedAtoms; 90 for (int j = 0; j < 4; j++) 91 mesh.vs[j] = (c[j] < 0 ? mesh.vs[j - 1] : vwr.ms.at[c[j]]); 92 mesh.recalcAltVertices = true; 93 } 94 if (renderMesh2(mesh)) 95 renderInfo(); 96 if (!isExport 97 && mesh.visibilityFlags != 0 98 && vwr.getPickingMode() == ActionManager.PICKING_DRAW) { 99 if (!g3d.setC(C.getColixTranslucent3(C.GOLD, true, 0.5f))) 100 needTranslucent = true; 101 else 102 renderHandles(); 103 } 104 } 105 return needTranslucent; 106 } 107 108 @Override isPolygonDisplayable(int i)109 protected boolean isPolygonDisplayable(int i) { 110 return Draw.isPolygonDisplayable(dmesh, i) 111 && (dmesh.modelFlags == null || dmesh.bsMeshesVisible.get(i)); 112 } 113 114 @Override render2(boolean isExport)115 protected void render2(boolean isExport) { 116 drawType = dmesh.drawType; 117 diameter = dmesh.diameter; 118 width = dmesh.width; 119 if (mesh.connectedAtoms != null) 120 getConnectionPoints(); 121 if (mesh.lineData != null) { 122 drawLineData(mesh.lineData); 123 return; 124 } 125 int nPoints = vertexCount; 126 boolean isCurved = ((drawType == EnumDrawType.CURVE 127 || drawType == EnumDrawType.ARROW || drawType == EnumDrawType.ARC) && vertexCount > 2); 128 if (width > 0 && isCurved || drawType == EnumDrawType.ARROW) { 129 pt1f.set(0, 0, 0); 130 int n = (drawType == EnumDrawType.ARC ? 2 : vertexCount); 131 for (int i = 0; i < n; i++) 132 pt1f.add(vertices[i]); 133 pt1f.scale(1f / n); 134 tm.transformPtScr(pt1f, pt1i); 135 diameter = (int) vwr.tm.scaleToScreen(pt1i.z, 136 (int) Math.floor(width * 1000)); 137 if (diameter == 0) 138 diameter = 1; 139 } 140 if (dmesh.haveXyPoints) { 141 if (dmesh.isVector) { 142 int ptXY = 0; 143 // [x y] or [x,y] refers to an xy point on the screen 144 // just a Point3f with z = Float.MAX_VALUE 145 // [x y %] or [x,y %] refers to an xy point on the screen 146 // as a percent 147 // just a Point3f with z = -Float.MAX_VALUE 148 for (int i = 0; i < 2; i++) 149 if (vertices[i].z == Float.MAX_VALUE 150 || vertices[i].z == -Float.MAX_VALUE) 151 ptXY += i + 1; 152 if (--ptXY < 2) { 153 renderXyArrow(ptXY); 154 return; 155 } 156 } else if (drawType == Draw.EnumDrawType.POINT){ 157 renderXyPoint(); 158 } 159 } 160 int tension = 5; 161 switch (drawType) { 162 default: 163 render2b(false); 164 return; 165 case CIRCULARPLANE: 166 if (dmesh.scale > 0) 167 width *= dmesh.scale; 168 render2b(false); 169 return; 170 case CIRCLE: 171 tm.transformPtScr(vertices[0], pt1i); 172 if (diameter == 0 && width == 0) 173 width = 1.0f; 174 if (dmesh.scale > 0) 175 width *= dmesh.scale; 176 if (width > 0) 177 diameter = (int) vwr.tm.scaleToScreen(pt1i.z, 178 (int) Math.floor(width * 1000)); 179 if (diameter > 0 && (mesh.drawTriangles || mesh.fillTriangles)) { 180 g3d.addRenderer(T.circle); 181 g3d.drawFilledCircle(colix, mesh.fillTriangles ? colix : 0, diameter, 182 pt1i.x, pt1i.y, pt1i.z); 183 } 184 return; 185 case LINE_SEGMENT: 186 for (int i = 0; i < nPoints - 1; i++) 187 drawEdge(i, i + 1, true, vertices[i], vertices[i + 1], screens[i], 188 screens[i + 1]); 189 return; 190 case CURVE: 191 break; 192 case ARC: 193 //renderArrowHead(controlHermites[nHermites - 2], controlHermites[nHermites - 1], false); 194 // 195 // {pt1} {pt2} {ptref} {nDegreesOffset, theta, fractionalOffset} 196 T3 ptRef = (vertexCount > 2 ? vertices[2] : Draw.randomPoint()); 197 float nDegreesOffset = (vertexCount > 3 ? vertices[3].x : 0); 198 float theta = (vertexCount > 3 ? vertices[3].y : 360); 199 if (theta == 0) 200 return; 201 float fractionalOffset = (vertexCount > 3 ? vertices[3].z : 0); 202 nPoints = setArc(vertices[0], vertices[1], ptRef, nDegreesOffset, theta, 203 fractionalOffset, dmesh.scale); 204 if (dmesh.isVector && !dmesh.noHead) { 205 renderArrowHead(pt0, pt1, 0.3f, false, false, dmesh.isBarb); 206 tm.transformPtScr(pt1f, screens[nPoints - 1]); 207 tm.transformPtScrT3(pt1f, p3Screens[nPoints - 1]); 208 } 209 pt1f.setT(pt2); 210 break; 211 case ARROW: 212 if (!isCurved) { 213 renderArrowHead(vertices[0], vertices[1], 0, false, true, dmesh.isBarb); 214 return; 215 } 216 int nHermites = 5; 217 if (controlHermites == null || controlHermites.length < nHermites + 1) { 218 controlHermites = new P3[nHermites + 1]; 219 } 220 GData.getHermiteList(tension, vertices[vertexCount - 3], 221 vertices[vertexCount - 2], vertices[vertexCount - 1], 222 vertices[vertexCount - 1], vertices[vertexCount - 1], 223 controlHermites, 0, nHermites, true); 224 renderArrowHead(controlHermites[nHermites - 2], 225 controlHermites[nHermites - 1], 0, false, false, dmesh.isBarb); 226 break; 227 } 228 // CURVE ARC ARROW only 229 if (diameter == 0) 230 diameter = 3; 231 if (isCurved) { 232 g3d.addRenderer(T.hermitelevel); 233 for (int i = 0, i0 = 0; i < nPoints - 1; i++) { 234 g3d.fillHermite(tension, diameter, diameter, diameter, p3Screens[i0], 235 p3Screens[i], p3Screens[i + 1], p3Screens[i 236 + (i == nPoints - 2 ? 1 : 2)]); 237 i0 = i; 238 } 239 } else { 240 render2b(false); 241 } 242 243 } 244 setArc(T3 v1, T3 v2, T3 ptRef, float nDegreesOffset, float theta, float fractionalOffset, float scale)245 private int setArc(T3 v1, T3 v2, T3 ptRef, float nDegreesOffset, 246 float theta, float fractionalOffset, float scale) { 247 vTemp.sub2(v2, v1); 248 // crossing point 249 pt1f.scaleAdd2(fractionalOffset, vTemp, v1); 250 // define rotational axis 251 M3 mat = new M3().setAA(A4.newVA(vTemp, 252 (float) (nDegreesOffset * Math.PI / 180))); 253 // vector to rotate 254 vTemp2.sub2(ptRef, 255 v1); 256 vTemp2.cross(vTemp, vTemp2); 257 vTemp2.cross(vTemp2, vTemp); 258 vTemp2.normalize(); 259 vTemp2.scale(scale / 2); 260 mat.rotate(vTemp2); 261 //control points 262 float degrees = theta / 5; 263 while (Math.abs(degrees) > 5) 264 degrees /= 2; 265 int nPoints = Math.round(theta / degrees) + 1; 266 while (nPoints < 10) { 267 degrees /= 2; 268 nPoints = Math.round(theta / degrees) + 1; 269 } 270 mat.setAA(A4.newVA(vTemp, (float) (degrees * Math.PI / 180))); 271 screens = vwr.allocTempScreens(nPoints); 272 p3Screens = vwr.allocTempPoints(nPoints); 273 int iBase = nPoints - (dmesh.scale < 2 ? 3 : 3); 274 for (int i = 0; i < nPoints; i++) { 275 if (i == iBase) 276 pt0.setT(pt1); 277 pt1.scaleAdd2(1, vTemp2, pt1f); 278 if (i == 0) 279 pt2.setT(pt1); 280 tm.transformPtScr(pt1, screens[i]); 281 tm.transformPtScrT3(pt1, p3Screens[i]); 282 mat.rotate(vTemp2); 283 } 284 return nPoints; 285 } 286 getConnectionPoints()287 private void getConnectionPoints() { 288 // now we screens and any adjustment to positions 289 // we need to set the actual control points 290 291 292 vertexCount = 3; 293 float dmax = Float.MAX_VALUE; 294 int i0 = 0; 295 int j0 = 0; 296 for (int i = 0; i < 2; i++) 297 for (int j = 2; j < 4; j++) { 298 float d = vertices[i].distance(vertices[j]); 299 if (d < dmax) { 300 dmax = d; 301 i0 = i; 302 j0 = j; 303 } 304 } 305 pt0.ave(vertices[0], vertices[1]); 306 pt2.ave(vertices[2], vertices[3]); 307 pt1.ave(pt0, pt2); 308 vertices[3] = P3.newP(vertices[i0]); 309 vertices[3].add(vertices[j0]); 310 vertices[3].scale(0.5f); 311 vertices[1] = P3.newP(pt1); 312 vertices[0] = P3.newP(pt0); 313 vertices[2] = P3.newP(pt2); 314 315 for (int i = 0; i < 4; i++) 316 tm.transformPtScr(vertices[i], screens[i]); 317 318 float f = 4 * getArrowScale(); // bendiness 319 float endoffset = 0.2f; 320 float offsetside = (width == 0 ? 0.1f : width); 321 322 pt0.set(screens[0].x, screens[0].y, screens[0].z); 323 pt1.set(screens[1].x, screens[1].y, screens[1].z); 324 pt2.set(screens[3].x, screens[3].y, screens[3].z); 325 float dx = (screens[1].x - screens[0].x) * f; 326 float dy = (screens[1].y - screens[0].y) * f; 327 328 if (dmax == 0 || Measure.computeTorsion(pt2, pt0, P3.new3(pt0.x, pt0.y, 10000f), pt1, false) > 0) { 329 dx = -dx; 330 dy = -dy; 331 } 332 pt2.set(dy, -dx, 0); 333 pt1.add(pt2); 334 tm.unTransformPoint(pt1, vertices[1]); 335 pt2.scale(offsetside); 336 vTemp.sub2(vertices[1], vertices[0]); 337 vTemp.scale(endoffset); 338 vertices[0].add(vTemp); 339 vTemp.sub2(vertices[1], vertices[2]); 340 vTemp.scale(endoffset); 341 vertices[2].add(vTemp); 342 for (int i = 0; i < 3; i++) { 343 tm.transformPtScr(vertices[i], screens[i]); 344 if (offsetside != 0) { 345 screens[i].x += Math.round(pt2.x); 346 screens[i].y += Math.round(pt2.y); 347 pt1.set(screens[i].x, screens[i].y, screens[i].z); 348 tm.unTransformPoint(pt1 , vertices[i]); 349 } 350 } 351 } 352 drawLineData(Lst<P3[]> lineData)353 private void drawLineData(Lst<P3[]> lineData) { 354 if (diameter == 0) 355 diameter = 3; 356 for (int i = lineData.size(); --i >= 0;) { 357 P3[] pts = lineData.get(i); 358 tm.transformPtScr(pts[0], pt1i); 359 tm.transformPtScr(pts[1], pt2i); 360 drawEdge(-1, -2, true, pts[0], pts[1], pt1i, pt2i); 361 } 362 } 363 renderXyPoint()364 private void renderXyPoint() { 365 // new in Jmol 14.5 366 int f = (g3d.isAntialiased() ? 2 : 1); 367 pt0.setT(vertices[0]); 368 if (diameter == 0) 369 diameter = (int) width; 370 if (pt0.z == -Float.MAX_VALUE) { 371 pt0.x *= vwr.tm.width / 100f; 372 pt0.y *= vwr.tm.height / 100f; 373 diameter = (int) (diameter * vwr.getScreenDim() / 100f); 374 } 375 diameter *= f; 376 pt1i.set((int) (pt0.x), (int) (vwr.tm.height - pt0.y), (int) vwr.tm.cameraDistance); 377 g3d.fillSphereI(diameter, pt1i); 378 } 379 renderXyArrow(int ptXY)380 private void renderXyArrow(int ptXY) { 381 // only 0 or 1 here; so ptXYZ is 1 or 0 382 int ptXYZ = 1 - ptXY; 383 P3[] arrowPt = new P3[2]; 384 arrowPt[ptXYZ] = pt1; 385 arrowPt[ptXY] = pt0; 386 // set up (0,0,0) to ptXYZ in real and screen coordinates 387 pt0.set(screens[ptXY].x, screens[ptXY].y, screens[ptXY].z); 388 tm.rotatePoint(vertices[ptXYZ], pt1); 389 pt1.z *= -1; 390 float zoomDimension = vwr.getScreenDim(); 391 float scaleFactor = zoomDimension / 20f; 392 pt1.scaleAdd2(dmesh.scale * scaleFactor, pt1, pt0); 393 if (diameter == 0) 394 diameter = 1; 395 if (diameter < 0) 396 g3d.drawDashedLineBits(8, 4, pt0, pt1); 397 else 398 g3d.fillCylinderBits(GData.ENDCAPS_FLAT, diameter, pt0, pt1); 399 renderArrowHead(pt0, pt1, 0, true, false, false); 400 } 401 402 private final P3 pt0f = new P3(); 403 protected P3i pt0i = new P3i(); 404 private P3 s0f; 405 private P3 s1f; 406 private P3 s2f; 407 renderArrowHead(T3 pt1, T3 pt2, float factor2, boolean isTransformed, boolean withShaft, boolean isBarb)408 private void renderArrowHead(T3 pt1, T3 pt2, float factor2, 409 boolean isTransformed, boolean withShaft, 410 boolean isBarb) { 411 if (dmesh.noHead) 412 return; 413 if (s0f == null) { 414 s0f = new P3(); 415 s1f = new P3(); 416 s2f = new P3(); 417 } 418 float fScale = getArrowScale(); 419 if (isTransformed) 420 fScale *= 40; 421 if (factor2 > 0) 422 fScale *= factor2; 423 424 pt0f.setT(pt1); 425 pt2f.setT(pt2); 426 float d = pt0f.distance(pt2f); 427 if (d == 0) 428 return; 429 vTemp.sub2(pt2f, pt0f); 430 vTemp.normalize(); 431 vTemp.scale(fScale / 5); 432 if (!withShaft) 433 pt2f.add(vTemp); 434 vTemp.scale(5); 435 pt1f.sub2(pt2f, vTemp); 436 if (isTransformed) { 437 s1f.setT(pt1f); 438 s2f.setT(pt2f); 439 } else { 440 tm.transformPtScrT3(pt2f, s2f); 441 tm.transformPtScrT3(pt1f, s1f); 442 tm.transformPtScrT3(pt0f, s0f); 443 } 444 if (s2f.z == 1 || s1f.z == 1) //slabbed 445 return; 446 int headDiameter; 447 if (diameter > 0) { 448 headDiameter = diameter * 3; 449 } else { 450 vTemp.set(s2f.x - s1f.x, s2f.y - s1f.y, s2f.z - s1f.z); 451 headDiameter = Math.round(vTemp.length() * .5f); 452 diameter = headDiameter / 5; 453 } 454 if (diameter < 1) 455 diameter = 1; 456 if (headDiameter > 2) 457 g3d.fillConeScreen3f(GData.ENDCAPS_FLAT, headDiameter, s1f, s2f, 458 isBarb); 459 if (withShaft) 460 g3d.fillCylinderScreen3I(GData.ENDCAPS_FLAT, diameter, s0f, s1f, null, null, 0); 461 } 462 getArrowScale()463 private float getArrowScale() { 464 float fScale = (dmesh.isScaleSet ? dmesh.scale : 0); 465 if (fScale == 0) 466 fScale = vwr.getFloat(T.defaultdrawarrowscale) * (dmesh.connectedAtoms == null ? 1f : 0.5f); 467 if (fScale <= 0) 468 fScale = 0.5f; 469 return fScale; 470 } 471 472 private final BS bsHandles = new BS(); 473 renderHandles()474 private void renderHandles() { 475 int diameter = Math.round(10 * imageFontScaling); 476 switch (drawType) { 477 case NONE: 478 return; 479 default: 480 short colixFill = C.getColixTranslucent3(C.GOLD, true, 481 0.5f); 482 bsHandles.clearAll(); 483 g3d.addRenderer(T.circle); 484 for (int i = dmesh.pc; --i >= 0;) { 485 if (!isPolygonDisplayable(i)) 486 continue; 487 int[] vertexIndexes = dmesh.pis[i]; 488 if (vertexIndexes == null) 489 continue; 490 for (int j = (dmesh.isDrawPolygon ? 3 : vertexIndexes.length); --j >= 0;) { 491 int k = vertexIndexes[j]; 492 if (bsHandles.get(k)) 493 continue; 494 bsHandles.set(k); 495 g3d.drawFilledCircle(C.GOLD, colixFill, diameter, 496 screens[k].x, screens[k].y, screens[k].z); 497 } 498 } 499 break; 500 } 501 } 502 renderInfo()503 private void renderInfo() { 504 if (isExport || mesh.title == null || vwr.getDrawHover() 505 || !g3d.setC(vwr.cm.colixBackgroundContrast)) 506 return; 507 for (int i = dmesh.pc; --i >= 0;) 508 if (isPolygonDisplayable(i)) { 509 //just the first line of the title -- nothing fancy here. 510 float size = vwr.getFloat(T.drawfontsize); 511 if (size <= 0) 512 size = 14; 513 vwr.gdata.setFontFid(vwr.gdata.getFontFid(size * imageFontScaling)); 514 String s = mesh.title[i < mesh.title.length ? i : mesh.title.length - 1]; 515 int pt = 0; 516 if (s.length() > 1 && s.charAt(0) == '>') { 517 pt = dmesh.pis[i].length - 1; 518 s = s.substring(1); 519 if (drawType == EnumDrawType.ARC) 520 pt1f.setT(pt2f); 521 } 522 if (drawType != EnumDrawType.ARC) 523 pt1f.setT(vertices[dmesh.pis[i][pt]]); 524 tm.transformPtScr(pt1f, pt1i); 525 int offset = Math.round(5 * imageFontScaling); 526 g3d.drawString(s, null, pt1i.x + offset, pt1i.y - offset, pt1i.z, 527 pt1i.z, (short) 0); 528 break; 529 } 530 } 531 532 } 533