Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.shape; 027 028import javafx.beans.property.BooleanProperty; 029import javafx.beans.property.BooleanPropertyBase; 030import javafx.beans.property.DoubleProperty; 031import javafx.beans.property.DoublePropertyBase; 032 033import com.sun.javafx.geom.Arc2D; 034import com.sun.javafx.geom.Path2D; 035import com.sun.javafx.geom.PathIterator; 036import com.sun.javafx.geom.transform.BaseTransform; 037import com.sun.javafx.sg.PGPath; 038import javafx.scene.paint.Paint; 039 040// PENDING_DOC_REVIEW 041/** 042 * A path element that forms an arc from the previous coordinates 043 * to the specified x and y coordinates using the specified radius. 044 * 045 * <p>For more information on path elements see the {@link Path} and 046 * {@link PathElement} classes. 047 * 048 * <p>Example: 049 * 050<PRE> 051import javafx.scene.shape.*; 052 053Path path = new Path(); 054 055MoveTo moveTo = new MoveTo(); 056moveTo.setX(0.0); 057moveTo.setY(0.0); 058 059ArcTo arcTo = new ArcTo(); 060arcTo.setX(50.0); 061arcTo.setY(50.0); 062arcTo.setRadiusX(50.0); 063arcTo.setRadiusY(50.0); 064 065path.getElements().add(moveTo); 066path.getElements().add(arcTo); 067</PRE> 068 * 069 * <p> 070 * Following image demonstrates {@code radiusX}, {@code radiusY} and 071 * {@code xAxisRotation} parameters: 072 * {@code radiusX} is the horizontal radius of the full ellipse of which this arc is 073 * a partial section, {@code radiusY} is its vertical radius. 074 * {@code xAxisRotation} defines the rotation of the ellipse in degrees. 075 * </p> 076 * <p> 077 * <img src="doc-files/arcto.png"/> 078 * </p> 079 * <p> 080 * In most cases, there are four options of how to draw an arc from 081 * starting point to given end coordinates. They can be distinguished by 082 * {@code largeArcFlag} and {@code sweepFlag} parameters. 083 * {@code largeArcFlag == true} means that the arc greater than 180 degrees will 084 * be drawn. {@code sweepFlag == true} means that the arc will be drawn 085 * in the positive angle direction - i.e. the angle in the 086 * ellipse formula will increase from {@code [fromX, fromY]} to {@code [x,y]}. 087 * Following images demonstrate this behavior: 088 * </p> 089 * <p> 090 * <img src="doc-files/arcto-flags.png"/> 091 * </p> 092 */ 093public class ArcTo extends PathElement { 094 095 096 /** 097 * Creates an empty instance of ArcTo. 098 */ 099 public ArcTo() { 100 } 101 102 /** 103 * Creates a new instance of ArcTo. 104 * @param radiusX horizontal radius of the arc 105 * @param radiusY vertical radius of the arc 106 * @param xAxisRotation the x-axis rotation in degrees 107 * @param x horizontal position of the arc end point 108 * @param y vertical position of the arc end point 109 * @param largeArcFlag large arg flag: determines which arc to use (large/small) 110 * @param sweepFlag sweep flag: determines which arc to use (direction) 111 */ 112 public ArcTo(double radiusX, double radiusY, double xAxisRotation, 113 double x, double y, boolean largeArcFlag, boolean sweepFlag) 114 { 115 setRadiusX(radiusX); 116 setRadiusY(radiusY); 117 setXAxisRotation(xAxisRotation); 118 setX(x); 119 setY(y); 120 setLargeArcFlag(largeArcFlag); 121 setSweepFlag(sweepFlag); 122 } 123 124 /** 125 * The horizontal radius to use for the arc. 126 * 127 * @defaultValue 0.0 128 */ 129 private DoubleProperty radiusX = new DoublePropertyBase() { 130 131 @Override 132 public void invalidated() { 133 u(); 134 } 135 136 @Override 137 public Object getBean() { 138 return ArcTo.this; 139 } 140 141 @Override 142 public String getName() { 143 return "radiusX"; 144 } 145 }; 146 147 public final void setRadiusX(double value) { 148 radiusX.set(value); 149 } 150 151 public final double getRadiusX() { 152 return radiusX.get(); 153 } 154 155 public final DoubleProperty radiusXProperty() { 156 return radiusX; 157 } 158 159 /** 160 * The vertical radius to use for the arc. 161 * 162 * @defaultValue 0.0 163 */ 164 private DoubleProperty radiusY = new DoublePropertyBase() { 165 166 @Override 167 public void invalidated() { 168 u(); 169 } 170 171 @Override 172 public Object getBean() { 173 return ArcTo.this; 174 } 175 176 @Override 177 public String getName() { 178 return "radiusY"; 179 } 180 }; 181 182 public final void setRadiusY(double value) { 183 radiusY.set(value); 184 } 185 186 public final double getRadiusY() { 187 return radiusY.get(); 188 } 189 190 public final DoubleProperty radiusYProperty() { 191 return radiusY; 192 } 193 194 /** 195 * The x-axis rotation in degrees. 196 * 197 * @defaultValue 0.0 198 */ 199 private DoubleProperty xAxisRotation; 200 201 /** 202 * Sets the x-axis rotation in degrees. 203 * @param value the x-axis rotation in degrees. 204 */ 205 public final void setXAxisRotation(double value) { 206 if (xAxisRotation != null || value != 0.0) { 207 XAxisRotationProperty().set(value); 208 } 209 } 210 211 /** 212 * Gets the x-axis rotation in degrees. 213 * @return the x-axis rotation in degrees. 214 */ 215 public final double getXAxisRotation() { 216 return xAxisRotation == null ? 0.0 : xAxisRotation.get(); 217 } 218 219 /** 220 * The x-axis rotation in degrees. 221 * @return The XAxisRotation property 222 */ 223 public final DoubleProperty XAxisRotationProperty() { 224 if (xAxisRotation == null) { 225 xAxisRotation = new DoublePropertyBase() { 226 227 @Override 228 public void invalidated() { 229 u(); 230 } 231 232 @Override 233 public Object getBean() { 234 return ArcTo.this; 235 } 236 237 @Override 238 public String getName() { 239 return "XAxisRotation"; 240 } 241 }; 242 } 243 return xAxisRotation; 244 } 245 246 /** 247 * The large arc flag. 248 * 249 * @defaultValue false 250 */ 251 private BooleanProperty largeArcFlag; 252 253 public final void setLargeArcFlag(boolean value) { 254 if (largeArcFlag != null || value != false) { 255 largeArcFlagProperty().set(value); 256 } 257 } 258 259 public final boolean isLargeArcFlag() { 260 return largeArcFlag == null ? false : largeArcFlag.get(); 261 } 262 263 public final BooleanProperty largeArcFlagProperty() { 264 if (largeArcFlag == null) { 265 largeArcFlag = new BooleanPropertyBase() { 266 267 @Override 268 public void invalidated() { 269 u(); 270 } 271 272 @Override 273 public Object getBean() { 274 return ArcTo.this; 275 } 276 277 @Override 278 public String getName() { 279 return "largeArcFlag"; 280 } 281 }; 282 } 283 return largeArcFlag; 284 } 285 286 /** 287 * The sweep flag 288 * 289 * @defaultValue false 290 */ 291 private BooleanProperty sweepFlag; 292 293 public final void setSweepFlag(boolean value) { 294 if (sweepFlag != null || value != false) { 295 sweepFlagProperty().set(value); 296 } 297 } 298 299 public final boolean isSweepFlag() { 300 return sweepFlag == null ? false : sweepFlag.get(); 301 } 302 303 public final BooleanProperty sweepFlagProperty() { 304 if (sweepFlag == null) { 305 sweepFlag = new BooleanPropertyBase() { 306 307 @Override 308 public void invalidated() { 309 u(); 310 } 311 312 @Override 313 public Object getBean() { 314 return ArcTo.this; 315 } 316 317 @Override 318 public String getName() { 319 return "sweepFlag"; 320 } 321 }; 322 } 323 return sweepFlag; 324 } 325 326 /** 327 * The x coordinate to arc to. 328 * 329 * @defaultValue 0.0 330 */ 331 private DoubleProperty x; 332 333 334 335 public final void setX(double value) { 336 if (x != null || value != 0.0) { 337 xProperty().set(value); 338 } 339 } 340 341 public final double getX() { 342 return x == null ? 0.0 : x.get(); 343 } 344 345 public final DoubleProperty xProperty() { 346 if (x == null) { 347 x = new DoublePropertyBase() { 348 349 @Override 350 public void invalidated() { 351 u(); 352 } 353 354 @Override 355 public Object getBean() { 356 return ArcTo.this; 357 } 358 359 @Override 360 public String getName() { 361 return "x"; 362 } 363 }; 364 } 365 return x; 366 } 367 368 /** 369 * The y coordinate to arc to. 370 * 371 * @defaultValue 0.0 372 */ 373 private DoubleProperty y; 374 375 376 377 public final void setY(double value) { 378 if (y != null || value != 0.0) { 379 yProperty().set(value); 380 } 381 } 382 383 public final double getY() { 384 return y == null ? 0.0 : y.get(); 385 } 386 387 public final DoubleProperty yProperty() { 388 if (y == null) { 389 y = new DoublePropertyBase() { 390 391 @Override 392 public void invalidated() { 393 u(); 394 } 395 396 @Override 397 public Object getBean() { 398 return ArcTo.this; 399 } 400 401 @Override 402 public String getName() { 403 return "y"; 404 } 405 }; 406 } 407 return y; 408 } 409 410 @Override 411 void addTo(PGPath pgPath) { 412 addArcTo(pgPath, null, pgPath.getCurrentX(), pgPath.getCurrentY()); 413 } 414 415 /** 416 * @treatAsPrivate implementation detail 417 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 418 */ 419 @Deprecated 420 @Override public void impl_addTo(Path2D path) { 421 addArcTo(null, path, path.getCurrentX(), path.getCurrentY()); 422 } 423 424 // This function is nearly identical to the one written for the 425 // original port of the F3 graphics/UI library: 426 // javafx.ui.canvas.ArcTo#addTo 427 private void addArcTo(PGPath pgPath, Path2D path, 428 final double x0, final double y0) 429 { 430 double localX = getX(); 431 double localY = getY(); 432 boolean localSweepFlag = isSweepFlag(); 433 boolean localLargeArcFlag = isLargeArcFlag(); 434 435 // Determine target "to" position 436 final double xto = (isAbsolute()) ? localX : localX + x0; 437 final double yto = (isAbsolute()) ? localY : localY + y0; 438 // Compute the half distance between the current and the final point 439 final double dx2 = (x0 - xto) / 2.0; 440 final double dy2 = (y0 - yto) / 2.0; 441 // Convert angle from degrees to radians 442 final double xAxisRotationR = Math.toRadians(getXAxisRotation()); 443 final double cosAngle = Math.cos(xAxisRotationR); 444 final double sinAngle = Math.sin(xAxisRotationR); 445 446 // 447 // Step 1 : Compute (x1, y1) 448 // 449 final double x1 = ( cosAngle * dx2 + sinAngle * dy2); 450 final double y1 = (-sinAngle * dx2 + cosAngle * dy2); 451 // Ensure radii are large enough 452 double rx = Math.abs(getRadiusX()); 453 double ry = Math.abs(getRadiusY()); 454 double Prx = rx * rx; 455 double Pry = ry * ry; 456 final double Px1 = x1 * x1; 457 final double Py1 = y1 * y1; 458 // check that radii are large enough 459 final double radiiCheck = Px1/Prx + Py1/Pry; 460 if (radiiCheck > 1.0) { 461 rx = Math.sqrt(radiiCheck) * rx; 462 ry = Math.sqrt(radiiCheck) * ry; 463 if (rx == rx && ry == ry) {/* not NANs */} else { 464 if (pgPath == null) { 465 path.lineTo((float) xto, (float) yto); 466 } else { 467 pgPath.addLineTo((float) xto, (float) yto); 468 } 469 return; 470 } 471 Prx = rx * rx; 472 Pry = ry * ry; 473 } 474 475 // 476 // Step 2 : Compute (cx1, cy1) 477 // 478 double sign = ((localLargeArcFlag == localSweepFlag) ? -1.0 : 1.0); 479 double sq = ((Prx*Pry)-(Prx*Py1)-(Pry*Px1)) / ((Prx*Py1)+(Pry*Px1)); 480 sq = (sq < 0.0) ? 0.0 : sq; 481 final double coef = (sign * Math.sqrt(sq)); 482 final double cx1 = coef * ((rx * y1) / ry); 483 final double cy1 = coef * -((ry * x1) / rx); 484 485 // 486 // Step 3 : Compute (cx, cy) from (cx1, cy1) 487 // 488 final double sx2 = (x0 + xto) / 2.0; 489 final double sy2 = (y0 + yto) / 2.0; 490 final double cx = sx2 + (cosAngle * cx1 - sinAngle * cy1); 491 final double cy = sy2 + (sinAngle * cx1 + cosAngle * cy1); 492 493 // 494 // Step 4 : Compute the angleStart (angle1) and the angleExtent (dangle) 495 // 496 final double ux = (x1 - cx1) / rx; 497 final double uy = (y1 - cy1) / ry; 498 final double vx = (-x1 - cx1) / rx; 499 final double vy = (-y1 - cy1) / ry; 500 // Compute the angle start 501 double n = Math.sqrt((ux * ux) + (uy * uy)); 502 double p = ux; // (1 * ux) + (0 * uy) 503 sign = ((uy < 0.0) ? -1.0 : 1.0); 504 double angleStart = Math.toDegrees(sign * Math.acos(p / n)); 505 506 // Compute the angle extent 507 n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)); 508 p = ux * vx + uy * vy; 509 sign = ((ux * vy - uy * vx < 0.0) ? -1.0 : 1.0); 510 double angleExtent = Math.toDegrees(sign * Math.acos(p / n)); 511 if (!localSweepFlag && (angleExtent > 0)) { 512 angleExtent -= 360.0; 513 } else if (localSweepFlag && (angleExtent < 0)) { 514 angleExtent += 360.0; 515 } 516 angleExtent = angleExtent % 360; 517 angleStart = angleStart % 360; 518 519 // 520 // We can now build the resulting Arc2D 521 // 522 final float arcX = (float) (cx - rx); 523 final float arcY = (float) (cy - ry); 524 final float arcW = (float) (rx * 2.0); 525 final float arcH = (float) (ry * 2.0); 526 final float arcStart = (float) -angleStart; 527 final float arcExtent = (float) -angleExtent; 528 529 if (pgPath == null) { 530 final Arc2D arc = 531 new Arc2D(arcX, arcY, arcW, arcH, 532 arcStart, arcExtent, Arc2D.OPEN); 533 BaseTransform xform = (xAxisRotationR == 0) ? null : 534 BaseTransform.getRotateInstance(xAxisRotationR, cx, cy); 535 PathIterator pi = arc.getPathIterator(xform); 536 // RT-8926, append(true) converts the initial moveTo into a 537 // lineTo which can generate huge miter joins if the segment 538 // is small enough. So, we manually skip it here instead. 539 pi.next(); 540 path.append(pi, true); 541 } else { 542 pgPath.addArcTo(arcX, arcY, arcW, arcH, 543 arcStart, arcExtent, (float) xAxisRotationR); 544 } 545 } 546 547 /** 548 * Returns a string representation of this {@code ArcTo} object. 549 * @return a string representation of this {@code ArcTo} object. 550 */ 551 @Override 552 public String toString() { 553 final StringBuilder sb = new StringBuilder("ArcTo["); 554 555 sb.append("x=").append(getX()); 556 sb.append(", y=").append(getY()); 557 sb.append(", radiusX=").append(getRadiusX()); 558 sb.append(", radiusY=").append(getRadiusY()); 559 sb.append(", xAxisRotation=").append(getXAxisRotation()); 560 561 if (isLargeArcFlag()) { 562 sb.append(", lartArcFlag"); 563 } 564 565 if (isSweepFlag()) { 566 sb.append(", sweepFlag"); 567 } 568 569 return sb.append("]").toString(); 570 } 571}