Source code: com/xerox/VTM/glyphs/VShape.java
1 /* FILE: VShape.java
2 * DATE OF CREATION: Aug 01 2001
3 * AUTHOR : Emmanuel Pietriga (emmanuel.pietriga@xrce.xerox.com)
4 * MODIF: Thu Jul 10 17:05:16 2003 by Emmanuel Pietriga (emmanuel@w3.org, emmanuel@claribole.net)
5 * Copyright (c) Xerox Corporation, XRCE/Contextual Computing, 2002. All Rights Reserved
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * For full terms see the file COPYING.
18 */
19
20 package com.xerox.VTM.glyphs;
21
22 import java.awt.Color;
23 import java.awt.Graphics2D;
24 import java.awt.Font;
25 import java.awt.Polygon;
26 import java.awt.Stroke;
27 import java.awt.geom.AffineTransform;
28 import java.awt.geom.Point2D;
29 import java.lang.Math;
30 import java.util.Vector;
31 import com.xerox.VTM.engine.*;
32
33 /**
34 * Custom shape implementing Jean-Yves Vion-Dury's model - defined by its N vertices (every vertex is between 0 (distance from shape's center=0) and 1.0 (distance from shape's center equals bounding circle radius)) - angle between each vertices is 2*Pi/N - can be reoriented
35 * @author Emmanuel Pietriga
36 **/
37
38 public class VShape extends Glyph implements Cloneable {
39
40 /**height=width in virtual space*/
41 long vs;
42
43 /**array of projected coordinates - index of camera in virtual space is equal to index of projected coords in this array*/
44 ProjShape[] pc;
45
46 /**list of vertex distance to the shape's center in the 0-1.0 range (relative to bounding circle) --vertices are layed out counter clockwise, with the first vertex placed at the same Y coord as the shape's center (provided orient=0)*/
47 float[] vertices;
48
49 int[] xcoords;
50 int[] ycoords;
51
52 /**
53 *@param v list of vertex distance to the shape's center in the 0-1.0 range (relative to bounding circle)
54 */
55 public VShape(float[] v){
56 vx=0;
57 vy=0;
58 vz=0;
59 vs=10;
60 vertices=v;
61 xcoords=new int[vertices.length];
62 ycoords=new int[vertices.length];
63 computeSize();
64 orient=0;
65 setColor(Color.white);
66 setBorderColor(Color.black);
67 }
68
69 /**
70 *@param x coordinate in virtual space
71 *@param y coordinate in virtual space
72 *@param z altitude
73 *@param s size (width=height) in virtual space
74 *@param v list of vertex distance to the shape's center in the 0-1.0 range (relative to bounding circle) --vertices are layed out counter clockwise, with the first vertex placed at the same Y coord as the shape's center (provided orient=0)
75 *@param c fill color
76 */
77 public VShape(long x,long y,float z,long s,float[] v,Color c,float or){
78 vx=x;
79 vy=y;
80 vz=z;
81 vs=s;
82 vertices=v;
83 xcoords=new int[vertices.length];
84 ycoords=new int[vertices.length];
85 computeSize();
86 orient=or;
87 setColor(c);
88 setBorderColor(bColor);
89 }
90
91 /**called when glyph is created in order to create the initial set of projected coordinates wrt the number of cameras in the space
92 *@param nbCam current number of cameras in the virtual space
93 */
94 public void initCams(int nbCam){
95 pc=new ProjShape[nbCam];
96 for (int i=0;i<nbCam;i++){
97 pc[i]=new ProjShape();
98 }
99 }
100
101 /**used internally to create new projected coordinates to use with the new camera
102 *@param verifIndex camera index, just to be sure that the number of projected coordinates is consistent with the number of cameras
103 */
104 public void addCamera(int verifIndex){
105 if (pc!=null){
106 if (verifIndex==pc.length){
107 ProjShape[] ta=pc;
108 pc=new ProjShape[ta.length+1];
109 for (int i=0;i<ta.length;i++){
110 pc[i]=ta[i];
111 }
112 pc[pc.length-1]=new ProjShape();
113 }
114 else {System.err.println("VShape:Error while adding camera "+verifIndex);}
115 }
116 else {
117 if (verifIndex==0){
118 pc=new ProjShape[1];
119 pc[0]=new ProjShape();
120 }
121 else {System.err.println("VShape:Error while adding camera "+verifIndex);}
122 }
123 }
124
125 /**if a camera is removed from the virtual space, we should delete the corresponding projected coordinates, but do not modify the array it self because we do not want to change other cameras' index - just point to null*/
126 public void removeCamera(int index){
127 pc[index]=null;
128 }
129
130 /**reset prevMouseIn for projected coordinates nb i*/
131 public void resetMouseIn(int i){
132 if (pc[i]!=null){pc[i].prevMouseIn=false;}
133 }
134
135 /**get orientation*/
136 public float getOrient(){return orient;}
137
138 /**set orientation (absolute)*/
139 public void orientTo(float angle){
140 orient=angle;
141 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
142 // vsm.constMgr.suggestAValue(this.ID,"or",orient);
143 //computeOrientCoords();
144 }
145
146 /**set orientation (absolute)*/
147 // public void orientToNS(float angle){
148 // orient=angle;
149 // //computeOrientCoords();
150 // }
151
152 /**get size (bounding circle radius)*/
153 public float getSize(){return size;}
154
155 /**compute size (bounding circle radius)*/
156 void computeSize(){
157 size=(float)vs;
158 }
159
160 /**set absolute size by setting bounding circle radius*/
161 public void sizeTo(float radius){
162 size=radius;
163 vs=Math.round(size);
164 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
165 // vsm.constMgr.suggestAValue(this.ID,"sz",size);
166 }
167
168 /**set absolute size by setting bounding circle radius*/
169 // public void sizeToNS(float radius){
170 // size=radius;
171 // vs=Math.round(size);
172 // }
173
174 /**multiply bounding circle radius by factor*/
175 public void reSize(float factor){
176 size*=factor;
177 vs=(long)Math.round(size);
178 try{vsm.repaintNow();}catch(NullPointerException e){/*System.err.println("VSM null in Glyph "+e);*/}
179 // vsm.constMgr.suggestAValue(this.ID,"sz",size);
180 }
181
182 /**used to find out if glyph completely fills the view (in which case it is not necessary to repaint objects at a lower altitude)*/
183 public boolean fillsView(long w,long h,int camIndex){
184 if ((pc[camIndex].p.contains(0,0)) && (pc[camIndex].p.contains(w,0)) && (pc[camIndex].p.contains(0,h)) && (pc[camIndex].p.contains(w,h))){return true;}
185 else {return false;}
186 }
187
188 /**detects whether the given point is inside this glyph or not
189 *@param x EXPECTS PROJECTED JPanel COORDINATE
190 *@param y EXPECTS PROJECTED JPanel COORDINATE
191 */
192 public boolean coordInside(int x,int y,int camIndex){
193 if (pc[camIndex].p.contains(x,y)){return true;}
194 else {return false;}
195 }
196
197 /**returns 1 if mouse has entered the glyph, -1 if it has exited the glyph, 0 if nothing has changed (meaning it was already inside or outside it)*/
198 public int mouseInOut(int x,int y,int camIndex){
199 if (coordInside(x,y,camIndex)){//if the mouse is inside the glyph
200 if (!pc[camIndex].prevMouseIn){//if it was not inside it last time, mouse has entered the glyph
201 pc[camIndex].prevMouseIn=true;
202 return 1;
203 }
204 else {return 0;} //if it was inside last time, nothing has changed
205 }
206 else{//if the mouse is not inside the glyph
207 if (pc[camIndex].prevMouseIn){//if it was inside it last time, mouse has exited the glyph
208 pc[camIndex].prevMouseIn=false;
209 return -1;
210 }
211 else {return 0;} //if it was not inside last time, nothing has changed
212 }
213 }
214
215 /**list of vertex distance to the shape's center in the 0-1.0 range (relative to bounding circle) --vertices are layed out counter clockwise, with the first vertex placed at the same Y coord as the shape's center (provided orient=0)*/
216 public float[] getVertices(){
217 return vertices;
218 }
219
220 /**
221 *returns a comma-separated string representation of the vertex distance to the shape's center
222 */
223 public String getVerticesAsText(){
224 StringBuffer res=new StringBuffer();
225 for (int i=0;i<vertices.length-1;i++){
226 res.append(vertices[i]+",");
227 }
228 res.append(vertices[vertices.length-1]);
229 return res.toString();
230 }
231
232 /**project shape in camera coord sys prior to actual painting*/
233 public void project(Camera c,ViewPanel v){
234 int i=c.getIndex();
235 coef=(float)(c.focal/(c.focal+c.altitude));
236 //find coordinates of object's geom center wrt to camera center and project
237 pc[i].cx=Math.round((vx-c.posx)*coef);
238 pc[i].cy=Math.round((vy-c.posy)*coef);
239 //translate in JPanel coords
240 pc[i].cx=(v.getSize().width/2)+pc[i].cx;
241 pc[i].cy=(v.getSize().height/2)-pc[i].cy;
242 //project height and construct polygon
243 pc[i].cs=Math.round(vs*coef);
244 float vertexAngle=orient;
245 for (int j=0;j<vertices.length-1;j++){
246 xcoords[j]=(int)Math.round(pc[i].cx+pc[i].cs*Math.cos(vertexAngle)*vertices[j]);
247 ycoords[j]=(int)Math.round(pc[i].cy-pc[i].cs*Math.sin(vertexAngle)*vertices[j]);
248 vertexAngle+=2*Math.PI/vertices.length;
249 }//last iteration outside to loop to avoid one vertexAngle computation too many
250 xcoords[vertices.length-1]=(int)Math.round(pc[i].cx+pc[i].cs*Math.cos(vertexAngle)*vertices[vertices.length-1]);
251 ycoords[vertices.length-1]=(int)Math.round(pc[i].cy-pc[i].cs*Math.sin(vertexAngle)*vertices[vertices.length-1]);
252 pc[i].p=new Polygon(xcoords,ycoords,vertices.length);
253 }
254
255 /**draw text associated with this glyph
256 *@param i camera index in the virtual space
257 */
258 void textDraw(Graphics2D g,int i){
259 if ((fontSizePolicy>=0) && (text!=null)) {
260 if (!text.equals("")){
261 //g.setColor(this.color);
262 textWidth=(int)g.getFontMetrics().getStringBounds(text,g).getWidth();
263 if ((fontSizePolicy==1) || ((fontSizePolicy==0) && (textWidth<2*pc[i].cs))) {
264 if (textPos==1){
265 //textHeight=(int)g.getFontMetrics().getStringBounds(text,g).getHeight();
266 g.drawString(text,pc[i].cx-textWidth/2,pc[i].cy-pc[i].cs-4);
267 }
268 else if (textPos==-1){
269 textHeight=(int)g.getFontMetrics().getStringBounds(text,g).getHeight();
270 g.drawString(text,pc[i].cx-textWidth/2,pc[i].cy+pc[i].cs+textHeight);
271 }
272 else {g.drawString(text,pc[i].cx-textWidth/2,pc[i].cy);}
273 }
274 else if (fontSizePolicy==2) { //modify font size to make string fit in glyph THIS OPTION IS RATHER TIME CONSUMING
275 Font tf=new Font(g.getFont().getName(),g.getFont().getStyle(),g.getFont().getSize());
276 int s=1;
277 while (textWidth>2*pc[i].cs){
278 s=g.getFont().getSize()-2; if (s<0){s=0;break;}
279 g.setFont(new Font(tf.getName(),tf.getStyle(),s));
280 textWidth=(int)g.getFontMetrics().getStringBounds(text,g).getWidth();
281 }
282 if (s>0) {
283 if (textPos==1){
284 //textHeight=(int)g.getFontMetrics().getStringBounds(text,g).getHeight();
285 g.drawString(text,pc[i].cx-textWidth/2,pc[i].cy-pc[i].cs-4);
286 }
287 else if (textPos==-1){
288 textHeight=(int)g.getFontMetrics().getStringBounds(text,g).getHeight();
289 g.drawString(text,pc[i].cx-textWidth/2,pc[i].cy+pc[i].cs+textHeight);
290 }
291 else {g.drawString(text,pc[i].cx-textWidth/2,pc[i].cy);}
292 }
293 g.setFont(tf);
294 }
295 }
296 }
297 }
298
299 /**draw glyph
300 *@param i camera index in the virtual space
301 */
302 public void draw(Graphics2D g,int vW,int vH,int i,Stroke stdS,AffineTransform stdT){
303 if (pc[i].cs>1){//repaint only if object is visible
304 if (filled) {
305 g.setColor(this.color);
306 g.fillPolygon(pc[i].p);
307 }
308 g.setColor(borderColor);
309 if (paintBorder){
310 if (stroke!=null) {
311 g.setStroke(stroke);
312 g.drawPolygon(pc[i].p);
313 g.setStroke(stdS);
314 }
315 else {
316 g.drawPolygon(pc[i].p);
317 }
318 }
319 this.textDraw(g,i);
320 }
321 else g.fillRect(pc[i].cx,pc[i].cy,1,1);
322 }
323
324 /**
325 * returns a given VShape's area
326 */
327 public double getArea(){
328 long[] xcoordsForArea=new long[vertices.length];
329 long[] ycoordsForArea=new long[vertices.length];
330 float vertexAngle=orient;
331 for (int i=0;i<vertices.length-1;i++){
332 xcoordsForArea[i]=Math.round(vx+vs*Math.cos(vertexAngle)*vertices[i]);
333 ycoordsForArea[i]=Math.round(vy+vs*Math.sin(vertexAngle)*vertices[i]);
334 vertexAngle+=2*Math.PI/vertices.length;
335 }//last iteration outside to loop to avoid one vertexAngle computation too many
336 xcoordsForArea[vertices.length-1]=Math.round(vx+vs*Math.cos(vertexAngle)*vertices[vertices.length-1]);
337 ycoordsForArea[vertices.length-1]=Math.round(vy+vs*Math.sin(vertexAngle)*vertices[vertices.length-1]);
338 int j,k;
339 double res=0;
340 for (j=0;j<vertices.length;j++){
341 k=(j+1) % vertices.length;
342 res+=(xcoordsForArea[j]*ycoordsForArea[k]-ycoordsForArea[j]*xcoordsForArea[k]);
343 }
344 res=res/2.0;
345 return ((res<0) ? -res : res);
346 }
347
348 /**
349 *return the double precision coordinates of this VShape's centroid
350 */
351 public Point2D.Double getPreciseCentroid(){
352 //compute polygon vertices
353 long[] xcoordsForArea=new long[vertices.length];
354 long[] ycoordsForArea=new long[vertices.length];
355 float vertexAngle=orient;
356 for (int i=0;i<vertices.length-1;i++){
357 xcoordsForArea[i]=Math.round(vx+vs*Math.cos(vertexAngle)*vertices[i]);
358 ycoordsForArea[i]=Math.round(vy+vs*Math.sin(vertexAngle)*vertices[i]);
359 vertexAngle+=2*Math.PI/vertices.length;
360 }//last iteration outside to loop to avoid one vertexAngle computation too many
361 xcoordsForArea[vertices.length-1]=Math.round(vx+vs*Math.cos(vertexAngle)*vertices[vertices.length-1]);
362 ycoordsForArea[vertices.length-1]=Math.round(vy+vs*Math.sin(vertexAngle)*vertices[vertices.length-1]);
363 //compute polygon area
364 int j,k;
365 double area=0;
366 for (j=0;j<vertices.length;j++){
367 k=(j+1) % vertices.length;
368 area+=(xcoordsForArea[j]*ycoordsForArea[k]-ycoordsForArea[j]*xcoordsForArea[k]);
369 }
370 area=area/2.0;
371 //area=((area<0) ? -area : area); //do not do that!!! it can change the centroid's coordinates
372 //(-x,-y instead of x,y) depending on the order in which the
373 //sequence of vertex coords
374 //compute centroid
375 double factor=0;
376 double cx=0;
377 double cy=0;
378 for (j=0;j<vertices.length;j++){
379 k=(j+1) % vertices.length;
380 factor=xcoordsForArea[j]*ycoordsForArea[k]-xcoordsForArea[k]*ycoordsForArea[j];
381 cx+=(xcoordsForArea[j]+xcoordsForArea[k])*factor;
382 cy+=(ycoordsForArea[j]+ycoordsForArea[k])*factor;
383 }
384 area*=6.0;
385 factor=1/area;
386 cx*=factor;
387 cy*=factor;
388 Point2D.Double res=new Point2D.Double(cx,cy);
389 return res;
390 }
391
392 /**
393 *return the coordinates of this VShape's centroid in virtual space
394 */
395 public LongPoint getCentroid(){
396 Point2D.Double p2dd=this.getPreciseCentroid();
397 return new LongPoint(Math.round(p2dd.getX()),Math.round(p2dd.getY()));
398 }
399
400 /**returns a clone of this object (only basic information is cloned for now: shape, orientation, position, size)*/
401 public Object clone(){
402 VShape res=new VShape(vx,vy,0,vs,(float[])vertices.clone(),color,orient);
403 res.borderColor=this.borderColor;
404 res.selectedColor=this.selectedColor;
405 res.mouseInsideColor=this.mouseInsideColor;
406 res.bColor=this.bColor;
407 return res;
408 }
409
410 }