001/* =========================================================== 002 * Orson Charts : a 3D chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C)opyright 2013-2016, by Object Refinery Limited. All rights reserved. 006 * 007 * http://www.object-refinery.com/orsoncharts/index.html 008 * 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as published by 011 * the Free Software Foundation, either version 3 of the License, or 012 * (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public License 020 * along with this program. If not, see <http://www.gnu.org/licenses/>. 021 * 022 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 023 * Other names may be trademarks of their respective owners.] 024 * 025 * If you do not wish to be bound by the terms of the GPL, an alternative 026 * commercial license can be purchased. For details, please see visit the 027 * Orson Charts home page: 028 * 029 * http://www.object-refinery.com/orsoncharts/index.html 030 * 031 */ 032 033package com.orsoncharts.graphics3d; 034 035import java.awt.Color; 036import java.awt.Font; 037import java.awt.geom.Point2D; 038import java.util.ArrayList; 039import java.util.HashMap; 040import java.util.List; 041import java.util.Map; 042import com.orsoncharts.util.ArgChecks; 043 044/** 045 * An object defined in 3D space by (a) a list of coordinates, and (b) a list 046 * of faces. This class has methods to calculate projected points in 2D when 047 * a {@link ViewPoint3D} is provided. 048 * <br><br> 049 * This class also contains a collection of static methods for constructing 050 * common 3D objects. 051 */ 052public class Object3D { 053 054 /** 055 * The key for storing the object class as an optional property for this 056 * object. 057 * 058 * @since 1.4 059 */ 060 public static final String CLASS_KEY = "class"; 061 062 /** 063 * The key for storing item keys as property values. 064 * 065 * @since 1.3 066 */ 067 public static final String ITEM_KEY = "key"; 068 069 /** 070 * A prefix used for setting color properties for an object. 071 * 072 * @since 1.3 073 */ 074 public static final String COLOR_PREFIX = "color/"; 075 076 /** World coordinates. */ 077 private List<Point3D> vertices; 078 079 /** Faces for the object, specified by indices to the world coords. */ 080 private List<Face> faces; 081 082 /** The primary color for the object. */ 083 private Color color; 084 085 /** 086 * A flag that indicates whether or not faces for this object have their 087 * outlines drawn (that is, the shape is filled then drawn versus just 088 * filled only). 089 */ 090 private boolean outline; 091 092 /** 093 * A map containing properties for the object. If there are no properties 094 * defined, then we leave this as {@code null} as an empty map would 095 * consume memory unnecessarily. 096 */ 097 private Map<String, Object> properties; 098 099 /** 100 * Creates a new object, initially with no vertices or faces. 101 * 102 * @param color the default face color ({@code null} not permitted). 103 * 104 * @since 1.3 105 */ 106 public Object3D(Color color) { 107 this(color, false); 108 } 109 110 /** 111 * Creates a new object, initially with no vertices or faces. 112 * 113 * @param color the default face color ({@code null} not permitted). 114 * @param outline the default flag that determines whether face outlines 115 * are drawn. 116 * 117 * @since 1.3 118 */ 119 public Object3D(Color color, boolean outline) { 120 ArgChecks.nullNotPermitted(color, "color"); 121 this.color = color; 122 this.outline = outline; 123 this.vertices = new java.util.ArrayList<Point3D>(); 124 this.faces = new java.util.ArrayList<Face>(); 125 } 126 127 /** 128 * Returns the default face color as specified in the constructor. 129 * 130 * @return The color (never {@code null}). 131 * 132 * @since 1.3 133 */ 134 public Color getColor() { 135 return this.color; 136 } 137 138 /** 139 * Returns the outline flag. 140 * 141 * @return The outline flag. 142 * 143 * @since 1.3 144 */ 145 public boolean getOutline() { 146 return this.outline; 147 } 148 149 /** 150 * Sets the outline flag. This determines the default setting for whether 151 * or not the faces of this object have their outlines drawn when rendered. 152 * 153 * @param outline the new flag value. 154 * 155 * @since 1.3 156 */ 157 public void setOutline(boolean outline) { 158 this.outline = outline; 159 } 160 161 /** 162 * Returns the value of the property with the specified key, or 163 * {@code null} if there is no property defined for that key. 164 * 165 * @param key the property key ({@code null} not permitted). 166 * 167 * @return The value (possibly {@code null}). 168 * 169 * @since 1.3 170 */ 171 public Object getProperty(String key) { 172 ArgChecks.nullNotPermitted(key, "key"); 173 if (this.properties == null) { 174 return null; 175 } else { 176 return this.properties.get(key); 177 } 178 } 179 180 /** 181 * Sets the value of a property, overwriting any existing value. One 182 * application for this is storing item key references to link a 3D object 183 * back to the data item that it represents (the key for this is 184 * {@link Object3D#ITEM_KEY}). 185 * 186 * @param key the key ({@code null} not permitted). 187 * @param value the value ({@code null} permitted). 188 * 189 * @since 1.3 190 */ 191 public void setProperty(String key, Object value) { 192 ArgChecks.nullNotPermitted(key, "key"); 193 if (this.properties == null) { 194 this.properties = new HashMap<String, Object>(); 195 } 196 this.properties.put(key, value); 197 } 198 199 /** 200 * Returns the color for a specific face. If the face has a tag, then 201 * this method will look for a property with the key COLOR_PREFIX + tag 202 * and return that color, otherwise it returns the default color for the 203 * object. 204 * 205 * @param face the face ({@code null} not permitted). 206 * 207 * @return The color for the specified face (never {@code null}). 208 * 209 * @since 1.3 210 */ 211 public Color getColor(Face face) { 212 if (face.getTag() != null) { 213 // see if there is a custom color defined for the tag 214 Object obj = getProperty(COLOR_PREFIX + face.getTag()); 215 if (obj != null) { 216 return (Color) obj; 217 } 218 } 219 return this.color; 220 } 221 222 /** 223 * Returns {@code true} if an outline should be drawn for the 224 * specified face, and {@code false} otherwise. 225 * 226 * @param face the face ({@code null} not permitted). 227 * 228 * @return A boolean. 229 * 230 * @since 1.3 231 */ 232 public boolean getOutline(Face face) { 233 return this.outline; 234 } 235 236 /** 237 * Returns the number of vertices for this object. 238 * 239 * @return The number of vertices. 240 */ 241 public int getVertexCount() { 242 return this.vertices.size(); 243 } 244 245 /** 246 * Adds a new object vertex with the specified coordinates. 247 * 248 * @param x the x-coordinate. 249 * @param y the y-coordinate. 250 * @param z the z-coordinate. 251 */ 252 public void addVertex(double x, double y, double z) { 253 addVertex(new Point3D(x, y, z)); 254 } 255 256 /** 257 * Adds a new object vertex. 258 * 259 * @param vertex the vertex ({@code null} not permitted). 260 */ 261 public void addVertex(Point3D vertex) { 262 ArgChecks.nullNotPermitted(vertex, "vertex"); 263 this.vertices.add(vertex); 264 } 265 266 /** 267 * Returns the number of faces. 268 * 269 * @return The number of faces. 270 */ 271 public int getFaceCount() { 272 return this.faces.size(); 273 } 274 275 /** 276 * Adds a face for the given vertices (specified by index value). 277 * 278 * @param vertices the vertices (all should lie in a plane). 279 * 280 * @since 1.3 281 */ 282 public void addFace(int[] vertices) { 283 // defer the arg checks... 284 addFace(new Face(this, vertices)); 285 } 286 287 /** 288 * Adds a tagged face for the given vertices (specified by index value). 289 * 290 * @param vertices the vertices (all should lie in a plane). 291 * @param tag the tag ({@code null} not permitted). 292 * 293 * @since 1.3 294 */ 295 public void addFace(int[] vertices, String tag) { 296 addFace(new TaggedFace(this, vertices, tag)); 297 } 298 299 /** 300 * Adds a double-sided face for the given vertices (specified by index 301 * value) and color. 302 * 303 * @param vertices the vertices (all should lie in a plane). 304 * 305 * @since 1.3 306 */ 307 public void addDoubleSidedFace(int[] vertices) { 308 addFace(new DoubleSidedFace(this, vertices)); 309 } 310 311 /** 312 * Adds a face for this object. 313 * 314 * @param face the face ({@code null} not permitted). 315 */ 316 public void addFace(Face face) { 317 ArgChecks.nullNotPermitted(face, "face"); 318 this.faces.add(face); 319 } 320 321 /** 322 * Returns the faces for this object. Note that the list returned is a 323 * direct reference to the internal storage for this {@code Object3D} 324 * instance, so callers should take care not to modify this list 325 * unintentionally. 326 * 327 * @return The faces. 328 */ 329 public List<Face> getFaces() { 330 return this.faces; 331 } 332 333 /** 334 * Calculates the projected points for the object's vertices, for the 335 * given viewpoint. 336 * 337 * @param viewPoint the view point ({@code null} not permitted). 338 * @param d the projection distance. 339 * 340 * @return The projected points. 341 */ 342 public Point2D[] calculateProjectedPoints(ViewPoint3D viewPoint, double d) { 343 ArgChecks.nullNotPermitted(viewPoint, "viewPoint"); 344 Point2D[] result = new Point2D[this.vertices.size()]; 345 int vertexCount = this.vertices.size(); 346 for (int i = 0; i < vertexCount; i++) { 347 Point3D p = this.vertices.get(i); 348 result[i] = viewPoint.worldToScreen(p, d); 349 } 350 return result; 351 } 352 353 /** 354 * Returns the eye coordinates of the object's vertices. 355 * 356 * @param viewPoint the view point ({@code null} not permitted). 357 * 358 * @return The eye coordinates. 359 */ 360 public Point3D[] calculateEyeCoordinates(ViewPoint3D viewPoint) { 361 ArgChecks.nullNotPermitted(viewPoint, "viewPoint"); 362 Point3D[] result = new Point3D[this.vertices.size()]; 363 int i = 0; 364 for (Point3D vertex : this.vertices) { 365 result[i] = viewPoint.worldToEye(vertex); 366 i++; 367 } 368 return result; 369 } 370 371 /** 372 * Creates a square flat surface in the x-z plane (constant y) with a 373 * single face. 374 * 375 * @param size the sheet size. 376 * @param x the x-coordinate for the center of the square. 377 * @param y the y-coordinate. 378 * @param z the z-coordinate for the center of the square. 379 * @param color the color ({@code null} not permitted). 380 * @param invert invert the order of the face 381 * 382 * @return The sheet. 383 */ 384 public static Object3D createYSheet(double size, double x, double y, 385 double z, Color color, boolean invert) { 386 ArgChecks.nullNotPermitted(color, "color"); 387 Object3D sheet = new Object3D(color); 388 double delta = size / 2.0; 389 sheet.addVertex(new Point3D(x + delta, y, z - delta)); 390 sheet.addVertex(new Point3D(x + delta, y, z + delta)); 391 sheet.addVertex(new Point3D(x - delta, y, z + delta)); 392 sheet.addVertex(new Point3D(x - delta, y, z - delta)); 393 if (invert) { 394 sheet.addFace(new Face(sheet, new int[] {3, 2, 1, 0})); 395 } else { 396 sheet.addFace(new Face(sheet, new int[] {0, 1, 2, 3})); 397 } 398 return sheet; 399 } 400 401 /** 402 * Creates a square flat surface in the x-y plane (constant z). 403 * 404 * @param size the sheet size. 405 * @param x the x-coordinate of a point on the surface. 406 * @param y the y-coordinate of a point on the surface. 407 * @param z the z-coordinate of a point on the surface. 408 * @param color the color. 409 * 410 * @return The sheet. 411 */ 412 public static Object3D createZSheet(double size, double x, double y, 413 double z, Color color) { 414 Object3D sheet = new Object3D(color); 415 double delta = size / 2.0; 416 sheet.addVertex(new Point3D(x + delta, y - delta, z)); 417 sheet.addVertex(new Point3D(x + delta, y + delta, z)); 418 sheet.addVertex(new Point3D(x - delta, y + delta, z)); 419 sheet.addVertex(new Point3D(x - delta, y - delta, z)); 420 sheet.addFace(new Face(sheet, new int[] {0, 1, 2, 3})); 421 return sheet; 422 } 423 424 /** 425 * Creates a cube centered on {@code (x, y, z)} with the specified 426 * {@code size}. 427 * 428 * @param size the size. 429 * @param x the x-offset. 430 * @param y the y-offset. 431 * @param z the z-offset. 432 * @param color the color ({@code null} not permitted). 433 * 434 * @return The cube (never {@code null}). 435 */ 436 public static Object3D createCube(double size, double x, 437 double y, double z, Color color) { 438 return createBox(x, size, y, size, z, size, color); 439 } 440 441 /** 442 * Creates a box centered on {@code (x, y, z)} with the specified 443 * dimensions. 444 * 445 * @param x the x-coordinate. 446 * @param xdim the length of the box in the x-dimension. 447 * @param y the y-coordinate. 448 * @param ydim the length of the box in the y-dimension. 449 * @param z the z-coordinate. 450 * @param zdim the length of the box in the y-dimension. 451 * @param color the color ({@code null} not permitted). 452 * 453 * @return The box (never {@code null}). 454 * 455 * @see #createCube(double, double, double, double, java.awt.Color) 456 */ 457 public static Object3D createBox(double x, double xdim, 458 double y, double ydim, double z, double zdim, 459 Color color) { 460 ArgChecks.nullNotPermitted(color, "color"); 461 Object3D box = new Object3D(color); 462 double xdelta = xdim / 2.0; 463 double ydelta = ydim / 2.0; 464 double zdelta = zdim / 2.0; 465 box.addVertex(new Point3D(x - xdelta, y - ydelta, z - zdelta)); 466 box.addVertex(new Point3D(x + xdelta, y - ydelta, z - zdelta)); 467 box.addVertex(new Point3D(x + xdelta, y - ydelta, z + zdelta)); 468 box.addVertex(new Point3D(x - xdelta, y - ydelta, z + zdelta)); 469 box.addVertex(new Point3D(x - xdelta, y + ydelta, z - zdelta)); 470 box.addVertex(new Point3D(x + xdelta, y + ydelta, z - zdelta)); 471 box.addVertex(new Point3D(x + xdelta, y + ydelta, z + zdelta)); 472 box.addVertex(new Point3D(x - xdelta, y + ydelta, z + zdelta)); 473 box.addFace(new Face(box, new int[] {4, 5, 1, 0})); 474 box.addFace(new Face(box, new int[] {5, 6, 2, 1})); 475 box.addFace(new Face(box, new int[] {6, 7, 3, 2})); 476 box.addFace(new Face(box, new int[] {3, 7, 4, 0})); 477 box.addFace(new Face(box, new int[] {7, 6, 5, 4})); 478 box.addFace(new Face(box, new int[] {0, 1, 2, 3})); 479 return box; 480 } 481 482 /** 483 * Creates a tetrahedron. 484 * 485 * @param size the size. 486 * @param xOffset the x-offset. 487 * @param yOffset the y-offset. 488 * @param zOffset the z-offset. 489 * @param color the color ({@code null} not permitted). 490 * 491 * @return A tetrahedron. 492 */ 493 public static Object3D createTetrahedron(double size, double xOffset, 494 double yOffset, double zOffset, Color color) { 495 ArgChecks.nullNotPermitted(color, "color"); 496 Object3D tetra = new Object3D(color); 497 tetra.addVertex(new Point3D(size + xOffset, -size + yOffset, 498 -size + zOffset)); 499 tetra.addVertex(new Point3D(-size + xOffset, size + yOffset, 500 -size + zOffset)); 501 tetra.addVertex(new Point3D(size + xOffset, size + yOffset, 502 size + zOffset)); 503 tetra.addVertex(new Point3D(-size + xOffset, -size + yOffset, 504 size + zOffset)); 505 tetra.addFace(new Face(tetra, new int[] {0, 1, 2})); 506 tetra.addFace(new Face(tetra, new int[] {1, 3, 2})); 507 tetra.addFace(new Face(tetra, new int[] {0, 3, 1})); 508 tetra.addFace(new Face(tetra, new int[] {0, 2, 3})); 509 return tetra; 510 } 511 512 /** 513 * Creates an octahedron. 514 * 515 * @param size the size. 516 * @param xOffset the x-offset. 517 * @param yOffset the y-offset. 518 * @param zOffset the z-offset. 519 * @param color the color ({@code null} not permitted). 520 * 521 * @return An octahedron. 522 */ 523 public static Object3D createOctahedron(double size, double xOffset, 524 double yOffset, double zOffset, Color color) { 525 ArgChecks.nullNotPermitted(color, "color"); 526 Object3D octa = new Object3D(color); 527 octa.addVertex(new Point3D(size + xOffset, 0 + yOffset, 0 + zOffset)); 528 octa.addVertex(new Point3D(0 + xOffset, size + yOffset, 0 + zOffset)); 529 octa.addVertex(new Point3D(-size + xOffset, 0 + yOffset, 0 + zOffset)); 530 octa.addVertex(new Point3D(0 + xOffset, -size + yOffset, 0 + zOffset)); 531 octa.addVertex(new Point3D(0 + xOffset, 0 + yOffset, -size + zOffset)); 532 octa.addVertex(new Point3D(0 + xOffset, 0 + yOffset, size + zOffset)); 533 534 octa.addFace(new Face(octa, new int[] {0, 1, 5})); 535 octa.addFace(new Face(octa, new int[] {1, 2, 5})); 536 octa.addFace(new Face(octa, new int[] {2, 3, 5})); 537 octa.addFace(new Face(octa, new int[] {3, 0, 5})); 538 octa.addFace(new Face(octa, new int[] {1, 0, 4})); 539 octa.addFace(new Face(octa, new int[] {2, 1, 4})); 540 octa.addFace(new Face(octa, new int[] {3, 2, 4})); 541 octa.addFace(new Face(octa, new int[] {0, 3, 4})); 542 return octa; 543 } 544 545 /** 546 * Creates an approximation of a sphere. 547 * 548 * @param radius the radius of the sphere (in world units). 549 * @param n the number of layers. 550 * @param x the x-coordinate of the center of the sphere. 551 * @param y the y-coordinate of the center of the sphere. 552 * @param z the z-coordinate of the center of the sphere. 553 * @param extColor the exterior color ({@code null} not permitted). 554 * @param intColor the interior color ({@code null} not permitted). 555 * 556 * @return A sphere. 557 */ 558 public static Object3D createSphere(double radius, int n, 559 double x, double y, double z, Color extColor, Color intColor) { 560 Object3D sphere = new Object3D(extColor); 561 sphere.setProperty(COLOR_PREFIX + "interior", intColor); 562 double theta = Math.PI / n; 563 Point3D[] prevLayer = new Point3D[n * 2 + 1]; 564 for (int i = 0; i <= n * 2; i++) { 565 prevLayer[i] = new Point3D(x, y + radius, z); 566 if (i != n * 2) { 567 sphere.addVertex(prevLayer[i]); 568 } 569 } 570 571 for (int layer = 1; layer < n; layer++) { 572 Point3D[] currLayer = new Point3D[n * 2 + 1]; 573 for (int i = 0; i <= n * 2; i++) { 574 double xx = radius * Math.cos(i * theta) 575 * Math.sin(layer * theta); 576 double yy = radius * Math.cos(layer * theta); 577 double zz = radius * Math.sin(i * theta) 578 * Math.sin(layer * theta); 579 currLayer[i] = new Point3D(x + xx, y + yy, z + zz); 580 if (i != n * 2) { 581 sphere.addVertex(currLayer[i]); 582 } 583 if (i > 0 && layer > 1) { 584 if (i != n * 2) { 585 Face f = new Face(sphere, new int[] { 586 (layer - 1) * n * 2 + i - 1, 587 (layer - 1) * n * 2 + i, layer * n * 2 + i, 588 layer * n * 2 + i - 1}); 589 sphere.addFace(f); 590 f = new TaggedFace(sphere, new int[] { 591 layer * n * 2 + i - 1, layer * n * 2 + i, 592 (layer - 1) * n * 2 + i, 593 (layer - 1) * n * 2 + i - 1}, "interior"); 594 sphere.addFace(f); 595 } else { 596 sphere.addFace(new Face(sphere, new int[] { 597 (layer - 1) * n * 2 + i - 1, (layer - 1) * n * 2, 598 layer * n * 2, layer * n * 2 + i - 1})); 599 sphere.addFace(new TaggedFace(sphere, new int[] { 600 layer * n * 2 + i - 1, layer * n * 2, 601 (layer - 1) * n * 2, (layer - 1) * n * 2 + i - 1}, 602 "interior")); 603 } 604 } 605 } 606 } 607 return sphere; 608 } 609 610 /** 611 * Creates a pie segment with the specified attributes. 612 * 613 * @param radius the radius. 614 * @param explodeRadius the explode radius (0.0 if not exploded). 615 * @param base the base. 616 * @param height the height. 617 * @param angle1 the start angle (radians). 618 * @param angle2 the end angle (radians). 619 * @param inc the increment. 620 * @param color the color ({@code null} not permitted). 621 * 622 * @return A pie segment object. 623 */ 624 public static Object3D createPieSegment(double radius, double explodeRadius, 625 double base, double height, double angle1, double angle2, 626 double inc, Color color) { 627 ArgChecks.nullNotPermitted(color, "color"); 628 Object3D segment = new Object3D(color, true); 629 double angleCentre = (angle1 + angle2) / 2.0; 630 Point3D centre = new Point3D(explodeRadius * Math.cos(angleCentre), 631 base, explodeRadius * Math.sin(angleCentre)); 632 float cx = (float) centre.x; 633 float cz = (float) centre.z; 634 segment.addVertex(new Point3D(cx + 0.0, base, cz + 0.0)); 635 segment.addVertex(new Point3D(cx + 0.0, base + height, cz + 0.0)); 636 Point3D v0 = new Point3D(cx + radius * Math.cos(angle1), base, 637 cz + radius * Math.sin(angle1)); 638 Point3D v1 = new Point3D(cx + radius * Math.cos(angle1), base + height, 639 cz + radius * Math.sin(angle1)); 640 segment.addVertex(v0); 641 segment.addVertex(v1); 642 segment.addFace(new Face(segment, new int[] {1, 3, 2, 0})); 643 int vc = 4; // vertex count 644 double theta = angle1 + inc; 645 while (theta < angle2) { 646 Point3D v2 = new Point3D(cx + radius * Math.cos(theta), base, 647 cz + radius * Math.sin(theta)); 648 Point3D v3 = new Point3D(cx + radius * Math.cos(theta), 649 base + height, cz + radius * Math.sin(theta)); 650 segment.addVertex(v2); 651 segment.addVertex(v3); 652 vc = vc + 2; 653 654 // outside edge 655 segment.addFace(new Face(segment, 656 new int[] {vc - 2, vc - 4, vc - 3, vc - 1})); 657 658 // top and bottom 659 segment.addFace(new Face(segment, 660 new int[] {0, vc - 4, vc - 2, 0})); 661 segment.addFace(new Face(segment, 662 new int[] {1, vc - 1, vc - 3, 1})); 663 theta = theta + inc; 664 } 665 v0 = new Point3D(cx + radius * Math.cos(angle2), base, 666 cz + radius * Math.sin(angle2)); 667 v1 = new Point3D(cx + radius * Math.cos(angle2), base + height, 668 cz + radius * Math.sin(angle2)); 669 segment.addVertex(v0); 670 segment.addVertex(v1); 671 vc = vc + 2; 672 segment.addFace(new Face(segment, 673 new int[] {vc - 2, vc - 4, vc - 3, vc - 1})); 674 675 // top and bottom 676 segment.addFace(new Face(segment, new int[] {0, vc - 4, vc - 2, 0})); 677 segment.addFace(new Face(segment, new int[] {1, vc - 1, vc - 3, 1})); 678 679 // closing side 680 segment.addFace(new Face(segment, new int[] {1, 0, vc-2, vc-1})); 681 return segment; 682 } 683 684 /** 685 * Returns two 3D objects (sheets in the y-plane) that can be used as 686 * alignment anchors for the labels of a pie segment. One sheet is on the 687 * front face of the segment, and the other is on the back face. Depending 688 * on the viewing point, only one of the sheets will be showing, and this 689 * is the one that the pie segment label will be attached to. 690 * 691 * @param radius the pie segment radius (in world units). 692 * @param explodeRadius the pie segment explode radius (in world units). 693 * @param base the base of the pie segment. 694 * @param height the height of the pie segment. 695 * @param angle1 the start angle of the segment (in radians). 696 * @param angle2 the end angle of the segment (in radians). 697 * 698 * @return A list containing the two 3D objects to be used as pie label 699 * markers. 700 */ 701 public static List<Object3D> createPieLabelMarkers(double radius, 702 double explodeRadius, double base, double height, 703 double angle1, double angle2) { 704 List<Object3D> result = new ArrayList<Object3D>(); 705 double angle = (angle1 + angle2) / 2.0; 706 Point3D centre = new Point3D(explodeRadius * Math.cos(angle), 707 base, explodeRadius * Math.sin(angle)); 708 float cx = (float) centre.x; 709 float cz = (float) centre.z; 710 double r = radius * 0.9; 711 Point3D v0 = new Point3D(cx + r * Math.cos(angle), base, 712 cz + r * Math.sin(angle)); 713 Point3D v1 = new Point3D(cx + r * Math.cos(angle), base + height, 714 cz + r * Math.sin(angle)); 715 result.add(Object3D.createYSheet(2.0, v0.x, v0.y, v0.z, Color.RED, 716 false)); 717 result.add(Object3D.createYSheet(2.0, v1.x, v1.y, v1.z, Color.BLUE, 718 true)); 719 return result; 720 } 721 722 /** 723 * Creates a bar with the specified dimensions and color. 724 * 725 * @param xWidth the x-width of the bar. 726 * @param zWidth the z-width (or depth) of the bar. 727 * @param x the x-coordinate for the center of the bar. 728 * @param y the y-coordinate for the top of the bar. 729 * @param z the z-coordinate for the center of the bar. 730 * @param zero the y-coordinate for the bottom of the bar. 731 * @param barColor the color for the bar ({@code null} not permitted). 732 * @param baseColor the color for the base of the bar (if {@code null}, 733 * the {@code color} is used instead). 734 * @param topColor the color for the top of the bar (if 735 * {@code null}, the {@code color} is used instead). 736 * @param inverted a flag that determines whether the baseColor and 737 * topColor should be swapped in their usage. 738 * 739 * @return A 3D object that can represent a bar in a bar chart. 740 */ 741 public static Object3D createBar(double xWidth, double zWidth, double x, 742 double y, double z, double zero, Color barColor, Color baseColor, 743 Color topColor, boolean inverted) { 744 ArgChecks.nullNotPermitted(barColor, "barColor"); 745 Color c0 = baseColor; 746 Color c1 = topColor; 747 if (inverted) { 748 Color cc = c1; 749 c1 = c0; 750 c0 = cc; 751 } 752 Object3D bar = new Object3D(barColor); 753 if (c0 != null) { 754 bar.setProperty(COLOR_PREFIX + "c0", c0); 755 } 756 if (c1 != null) { 757 bar.setProperty(COLOR_PREFIX + "c1", c1); 758 } 759 double xdelta = xWidth / 2.0; 760 double zdelta = zWidth / 2.0; 761 bar.addVertex(new Point3D(x - xdelta, zero, z - zdelta)); 762 bar.addVertex(new Point3D(x + xdelta, zero, z - zdelta)); 763 bar.addVertex(new Point3D(x + xdelta, zero, z + zdelta)); 764 bar.addVertex(new Point3D(x - xdelta, zero, z + zdelta)); 765 bar.addVertex(new Point3D(x - xdelta, y, z - zdelta)); 766 bar.addVertex(new Point3D(x + xdelta, y, z - zdelta)); 767 bar.addVertex(new Point3D(x + xdelta, y, z + zdelta)); 768 bar.addVertex(new Point3D(x - xdelta, y, z + zdelta)); 769 770 bar.addFace(new Face(bar, new int[] {0, 1, 5, 4})); 771 bar.addFace(new Face(bar, new int[] {4, 5, 1, 0})); 772 bar.addFace(new Face(bar, new int[] {1, 2, 6, 5})); 773 bar.addFace(new Face(bar, new int[] {5, 6, 2, 1})); 774 bar.addFace(new Face(bar, new int[] {2, 3, 7, 6})); 775 bar.addFace(new Face(bar, new int[] {6, 7, 3, 2})); 776 bar.addFace(new Face(bar, new int[] {0, 4, 7, 3})); 777 bar.addFace(new Face(bar, new int[] {3, 7, 4, 0})); 778 bar.addFace(new Face(bar, new int[] {4, 5, 6, 7})); 779 bar.addFace(new Face(bar, new int[] {3, 2, 1, 0})); 780 if (c1 != null) { 781 bar.addFace(new TaggedFace(bar, new int[] {7, 6, 5, 4}, "c1")); 782 } else { 783 bar.addFace(new Face(bar, new int[] {7, 6, 5, 4})); 784 } 785 if (c0 != null) { 786 bar.addFace(new TaggedFace(bar, new int[] {0, 1, 2, 3}, "c0")); 787 } else { 788 bar.addFace(new Face(bar, new int[] {0, 1, 2, 3})); 789 } 790 791 return bar; 792 } 793 794 /** 795 * Creates a label object, which has a single transparent face in the 796 * Z-plane plus associated label attributes. These faces are used to 797 * track the location and visibility of labels in a 3D scene. 798 * 799 * @param label the label ({@code null} not permitted). 800 * @param font the font ({@code null} not permitted). 801 * @param fgColor the label foreground color ({@code null} not permitted). 802 * @param bgColor the label background color ({@code null} not permitted). 803 * @param x the x-coordinate in 3D space. 804 * @param y the y-coordinate in 3D space. 805 * @param z the z-coordinate in 3D space. 806 * @param reversed reverse the order of the vertices? 807 * @param doubleSided is the face double-sided (visible from either side)? 808 * 809 * @return A new label object (never {@code null}). 810 * 811 * @since 1.3 812 */ 813 public static Object3D createLabelObject(String label, Font font, 814 Color fgColor, Color bgColor, double x, double y, double z, 815 boolean reversed, boolean doubleSided) { 816 Object3D labelObj = new Object3D(bgColor); 817 labelObj.setProperty(Object3D.CLASS_KEY, "ItemLabel"); 818 labelObj.addVertex(x - 0.1, y, z); 819 labelObj.addVertex(x + 0.1, y, z); 820 labelObj.addVertex(x + 0.1, y + 0.1, z); 821 labelObj.addVertex(x - 0.1, y + 0.1, z); 822 823 if (!reversed || doubleSided) { 824 labelObj.addFace(new LabelFace(labelObj, new int[] {0, 1, 2, 3}, 825 label, font, fgColor, bgColor)); 826 } 827 if (reversed || doubleSided) { 828 labelObj.addFace(new LabelFace(labelObj, new int[] {3, 2, 1, 0}, 829 label, font, fgColor, bgColor)); 830 } 831 return labelObj; 832 } 833 834}