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