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