Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 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 com.sun.javafx.geom.BaseBounds;
029import com.sun.javafx.geom.PickRay;
030import com.sun.javafx.geom.Vec3d;
031import com.sun.javafx.geom.transform.BaseTransform;
032import com.sun.javafx.scene.DirtyBits;
033import com.sun.javafx.scene.input.PickResultChooser;
034import com.sun.javafx.sg.PGCylinder;
035import com.sun.javafx.sg.PGNode;
036import com.sun.javafx.tk.Toolkit;
037import javafx.beans.property.DoubleProperty;
038import javafx.beans.property.SimpleDoubleProperty;
039import javafx.geometry.Point2D;
040import javafx.geometry.Point3D;
041import javafx.scene.input.PickResult;
042import javafx.scene.transform.Rotate;
043
044/**
045 * The {@code Cylinder} class defines a 3 dimensional cylinder with the specified size.
046 * A {@code Cylinder} is a 3D geometry primitive created with a given radius and height.
047 * It is centered at the origin.
048 *
049 * @since JavaFX 8    
050 */
051public class Cylinder extends Shape3D {
052
053    static final int DEFAULT_DIVISIONS = 64;
054    static final double DEFAULT_RADIUS = 1;
055    static final double DEFAULT_HEIGHT = 2;
056    
057    private int divisions = DEFAULT_DIVISIONS;
058    private TriangleMesh mesh;
059    
060    /**  
061     * Creates a new instance of {@code Cylinder} of radius of 1.0 and height of 2.0.
062     * Resolution defaults to 15 divisions along X and Z axis.
063     */
064    public Cylinder() {
065        this(DEFAULT_RADIUS, DEFAULT_HEIGHT, DEFAULT_DIVISIONS);
066    }
067
068    /**
069     * Creates a new instance of {@code Cylinder} of a given radius and height.
070     * Resolution defaults to 15 divisions along X and Z axis.
071     * 
072     * @param radius Radius
073     * @param height Height
074     */
075    public Cylinder (double radius, double height) {
076        this(radius, height, DEFAULT_DIVISIONS);
077    }
078
079    /**
080     * Creates a new instance of {@code Cylinder} of a given radius, height, and
081     * divisions. Resolution defaults to 15 divisions along X and Z axis.
082     *
083     * Note that divisions should be at least 3. Any value less than that will be
084     * clamped to 3.
085     * 
086     * @param radius Radius
087     * @param height Height
088     * @param divisions Divisions 
089     */
090    public Cylinder (double radius, double height, int divisions) {
091        this.divisions = divisions < 3 ? 3 : divisions;
092        setRadius(radius);
093        setHeight(height);
094    }
095    
096    /**
097     * Defines the height or the Y dimension of the Cylinder.
098     *
099     * @defaultValue 2.0
100     */
101    private DoubleProperty height;
102
103    public final void setHeight(double value) {
104        heightProperty().set(value);
105    }
106
107    public final double getHeight() {
108        return height == null ? 2 : height.get();
109    }
110
111    public final DoubleProperty heightProperty() {
112        if (height == null) {
113            height = new SimpleDoubleProperty(Cylinder.this, "height", DEFAULT_HEIGHT) {
114                @Override
115                public void invalidated() {
116                    impl_markDirty(DirtyBits.MESH_GEOM);
117                    manager.invalidateCylinderMesh(key);
118                    key = 0;
119                }
120            };
121        }
122        return height;
123    }
124
125    /**
126     * Defines the radius in the Z plane of the Cylinder.
127     *
128     * @defaultValue 1.0
129     */
130    private DoubleProperty radius;
131
132    public final void setRadius(double value) {
133        radiusProperty().set(value);
134    }
135
136    public final double getRadius() {
137        return radius == null ? 1 : radius.get();
138    }
139
140    public final DoubleProperty radiusProperty() {
141        if (radius == null) {
142            radius = new SimpleDoubleProperty(Cylinder.this, "radius", DEFAULT_RADIUS) {
143                @Override
144                public void invalidated() {
145                    impl_markDirty(DirtyBits.MESH_GEOM);
146                    manager.invalidateCylinderMesh(key);
147                    key = 0;
148                }
149            };
150        }
151        return radius;
152    }
153
154    /**
155     * Retrieves the divisions attribute use to generate this cylinder.
156     *
157     * @return the divisions attribute.
158     */
159    public int getDivisions() {
160        return divisions;
161    }
162
163    /**
164     * @treatAsPrivate implementation detail
165     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
166     */
167    @Deprecated   
168    @Override
169    public void impl_updatePG() {
170        super.impl_updatePG();
171        if (impl_isDirty(DirtyBits.MESH_GEOM)) {
172            PGCylinder pgCylinder = (PGCylinder) impl_getPGNode();
173            final float h = (float) getHeight();
174            final float r = (float) getRadius();
175            if (h < 0 || r < 0) {
176                pgCylinder.updateMesh(null);
177            } else {
178                if (key == 0) {
179                    key = generateKey(h, r, divisions);
180                }
181                mesh = manager.getCylinderMesh(h, r, divisions, key);
182                mesh.impl_updatePG();
183                pgCylinder.updateMesh(mesh.impl_getPGTriangleMesh());
184            }
185        }
186    }
187    
188    /**
189     * @treatAsPrivate implementation detail
190     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
191     */
192    @Deprecated
193    @Override
194    protected PGNode impl_createPGNode() {
195        return Toolkit.getToolkit().createPGCylinder();
196    }
197
198    /**
199     * @treatAsPrivate implementation detail
200     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
201     */
202    @Deprecated
203    @Override
204    public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
205        final float h = (float) getHeight();
206        final float r = (float) getRadius();
207
208        if (r < 0 || h < 0) {
209            return bounds.makeEmpty();
210        }
211        
212        final float hh = h * 0.5f;
213        
214        bounds = bounds.deriveWithNewBounds(-r, -hh, -r, r, hh, r);
215        bounds = tx.transform(bounds, bounds);
216        return bounds;
217    }
218
219    /**
220     * @treatAsPrivate implementation detail
221     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
222     */
223    @Deprecated
224    @Override
225    protected boolean impl_computeContains(double localX, double localY) {
226        double w = getRadius();
227        double hh = getHeight()*.5f;
228        return -w <= localX && localX <= w && 
229                -hh <= localY && localY <= hh;
230    }
231
232    /**
233     * @treatAsPrivate implementation detail
234     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
235     */
236    @Deprecated
237    @Override
238    protected boolean impl_computeIntersects(PickRay pickRay, PickResultChooser pickResult) {
239
240        final boolean exactPicking = divisions < DEFAULT_DIVISIONS && mesh != null;
241
242        final double r = getRadius();
243        final Vec3d dir = pickRay.getDirectionNoClone();
244        final double dirX = dir.x;
245        final double dirY = dir.y;
246        final double dirZ = dir.z;
247        final Vec3d origin = pickRay.getOriginNoClone();
248        final double originX = origin.x;
249        final double originY = origin.y;
250        final double originZ = origin.z;
251        final double h = getHeight();
252        final double halfHeight = h / 2.0;
253        final CullFace cullFace = getCullFace();
254
255        // Check the open cylinder first
256
257        // Coeficients of a quadratic equation desribing intersection with an infinite cylinder
258        final double a = dirX * dirX + dirZ * dirZ;
259        final double b = 2 * (dirX * originX + dirZ * originZ);
260        final double c = originX * originX + originZ * originZ - r * r;
261
262        final double discriminant = b * b - 4 * a * c;
263
264        double t0, t1, t = Double.POSITIVE_INFINITY;
265        final double minDistance = pickRay.getNearClip();
266        final double maxDistance = pickRay.getFarClip();
267
268        if (discriminant >= 0 && (dirX != 0.0 || dirZ != 0.0)) {
269            // the line hits the infinite cylinder
270
271            final double distSqrt = Math.sqrt(discriminant);
272            final double q = (b < 0) ? (-b - distSqrt) / 2.0 : (-b + distSqrt) / 2.0;
273
274            t0 = q / a;
275            t1 = c / q;
276
277            if (t0 > t1) {
278                double temp = t0;
279                t0 = t1;
280                t1 = temp;
281            }
282
283            // let's see if the hit is between clipping planes and within the cylinder's height
284            final double y0 = originY + t0 * dirY;
285            if (t0 < minDistance || y0 < -halfHeight || y0 > halfHeight || cullFace == CullFace.FRONT) {
286                final double y1 = originY + t1 * dirY;
287                if (t1 >= minDistance && t1 <= maxDistance && y1 >= -halfHeight && y1 <= halfHeight) {
288                    if (cullFace != CullFace.BACK || exactPicking) {
289                        // t0 is outside or behind but t1 hits.
290
291                        // We need to do the exact picking even if the back wall
292                        // is culled because the front facing triangles may
293                        // still be in front of us
294                        t = t1;
295                    }
296                } // else no hit (but we need to check the caps)
297            } else if (t0 <= maxDistance) {
298                // t0 hits the height between clipping planes
299                t = t0;
300            } // else no hit (but we need to check the caps)
301        }
302
303        // Now check the caps
304        
305        // if we already know we are going to do the exact picking,
306        // there is no need to check the caps
307        
308        boolean topCap = false, bottomCap = false;
309        if (t == Double.POSITIVE_INFINITY || !exactPicking) {
310            final double tBottom = (-halfHeight - originY) / dirY;
311            final double tTop = (halfHeight - originY) / dirY;
312            boolean isT0Bottom = false;
313
314            if (tBottom < tTop) {
315                t0 = tBottom;
316                t1 = tTop;
317                isT0Bottom = true;
318            } else {
319                t0 = tTop;
320                t1 = tBottom;
321            }
322
323            if (t0 >= minDistance && t0 <= maxDistance && t0 < t && cullFace != CullFace.FRONT) {
324                final double tX = originX + dirX * t0;
325                final double tZ = originZ + dirZ * t0;
326                if (tX * tX + tZ * tZ <= r * r) {
327                    bottomCap = isT0Bottom; topCap = !isT0Bottom;
328                    t = t0;
329                }
330            }
331
332            if (t1 >= minDistance && t1 <= maxDistance && t1 < t && (cullFace != CullFace.BACK || exactPicking)) {
333                final double tX = originX + dirX * t1;
334                final double tZ = originZ + dirZ * t1;
335                if (tX * tX + tZ * tZ <= r * r) {
336                    topCap = isT0Bottom; bottomCap = !isT0Bottom;
337                    t = t1;
338                }
339            }
340        }
341
342        if (Double.isInfinite(t) || Double.isNaN(t)) {
343            // no hit
344            return false;
345        }
346
347        if (exactPicking) {
348            return mesh.impl_computeIntersects(pickRay, pickResult, this, cullFace, false);
349        }
350
351        if (pickResult != null && pickResult.isCloser(t)) {
352            final Point3D point = PickResultChooser.computePoint(pickRay, t);
353
354            Point2D txCoords;
355            if (topCap) {
356                txCoords = new Point2D(
357                        0.5 + point.getX() / (2 * r),
358                        0.5 + point.getZ() / (2 * r));
359            } else if (bottomCap) {
360                txCoords = new Point2D(
361                        0.5 + point.getX() / (2 * r),
362                        0.5 - point.getZ() / (2 * r));
363            } else {
364                final Point3D proj = new Point3D(point.getX(), 0, point.getZ());
365                final Point3D cross = proj.crossProduct(Rotate.Z_AXIS);
366                double angle = proj.angle(Rotate.Z_AXIS);
367                if (cross.getY() > 0) {
368                    angle = 360 - angle;
369                }
370                txCoords = new Point2D(1 - angle / 360, 0.5 + point.getY() / h);
371            }
372
373            pickResult.offer(this, t, PickResult.FACE_UNDEFINED, point, txCoords);
374        }
375        return true;
376    }
377
378    static TriangleMesh createMesh(int div, float h, float r) {
379
380        // NOTE: still create mesh for degenerated cylinder
381        final int nPonits = (div + 1) * 2 + 2;
382        final int tcCount = (div + 1) * 4 + 1; // 2 cap tex
383        final int faceCount = div * 4;
384
385        float textureDelta = 1.f / 256;
386
387        float dA = 1.f / div;
388        h *= .5f;
389
390        float points[] = new float[nPonits * 3];
391        float tPoints[] = new float[tcCount * 2];
392        int faces[] = new int[faceCount * 6];
393        int smoothing[] = new int[faceCount];
394
395        int pPos = 0, tPos = 0;
396
397        for (int i = 0; i <= div; ++i) {
398            double a = (i < div) ? dA * i * 2 * Math.PI : 0;
399
400            points[pPos + 0] = (float) (Math.sin(a) * r);
401            points[pPos + 2] = (float) (Math.cos(a) * r);
402            points[pPos + 1] = h;
403            tPoints[tPos + 0] = 1 - dA * i;
404            tPoints[tPos + 1] = 1 - textureDelta;
405            pPos += 3; tPos += 2;
406        }
407
408        for (int i = 0; i <= div; ++i) {
409            double a = (i < div) ? dA * i * 2 * Math.PI : 0;
410            points[pPos + 0] = (float) (Math.sin(a) * r);
411            points[pPos + 2] = (float) (Math.cos(a) * r);
412            points[pPos + 1] = -h;
413            tPoints[tPos + 0] = 1 - dA * i;
414            tPoints[tPos + 1] = textureDelta;
415            pPos += 3; tPos += 2;
416        }
417
418        // add cap central points
419        points[pPos + 0] = 0;
420        points[pPos + 1] = h;
421        points[pPos + 2] = 0;
422        points[pPos + 3] = 0;
423        points[pPos + 4] = -h;
424        points[pPos + 5] = 0;
425        pPos += 6;
426
427        // add cap central points
428        // bottom cap
429        for (int i = 0; i <= div; ++i) {
430            double a = (i < div) ? (dA * i * 2) * Math.PI: 0;
431            tPoints[tPos + 0] = (float) (Math.sin(a) * 0.5f) + 0.5f;
432            tPoints[tPos + 1] = (float) (Math.cos(a) * 0.5f) + 0.5f;
433            tPos += 2;
434        }
435
436        // top cap
437        for (int i = 0; i <= div; ++i) {
438            double a = (i < div) ? (dA * i * 2) * Math.PI: 0;
439            tPoints[tPos + 0] = 0.5f + (float) (Math.sin(a) * 0.5f);
440            tPoints[tPos + 1] = 0.5f - (float) (Math.cos(a) * 0.5f);
441            tPos += 2;
442        }
443
444        tPoints[tPos + 0] = .5f;
445        tPoints[tPos + 1] = .5f;
446        tPos += 2;
447
448        int fIndex = 0;
449
450        // build body faces
451        for (int p0 = 0; p0 != div; ++p0) {
452            int p1 = p0 + 1;
453            int p2 = p0 + div + 1;
454            int p3 = p1 + div + 1;
455
456            // add p0, p1, p2
457            faces[fIndex+0] = p0;
458            faces[fIndex+1] = p0;
459            faces[fIndex+2] = p2;
460            faces[fIndex+3] = p2;
461            faces[fIndex+4] = p1;
462            faces[fIndex+5] = p1;
463            fIndex += 6;
464
465            // add p3, p2, p1
466            // *faces++ = SmFace(p3,p1,p2, p3,p1,p2, 1);
467            faces[fIndex+0] = p3;
468            faces[fIndex+1] = p3;
469            faces[fIndex+2] = p1;
470            faces[fIndex+3] = p1;
471            faces[fIndex+4] = p2;
472            faces[fIndex+5] = p2;
473            fIndex += 6;
474
475        }
476        // build cap faces
477        int tStart = (div + 1) * 2, t1 = (div + 1) * 4, p1 = (div + 1) * 2;
478
479        // bottom cap
480        for (int p0 = 0; p0 != div; ++p0) {
481            int p2 = p0 + 1, t0 = tStart + p0, t2 = t0 + 1;
482            // add p0, p1, p2
483
484            faces[fIndex+0] = p0;
485            faces[fIndex+1] = t0;
486            faces[fIndex+2] = p2;
487            faces[fIndex+3] = t2;
488            faces[fIndex+4] = p1;
489            faces[fIndex+5] = t1;
490            fIndex += 6;
491        }
492
493        p1 = (div + 1) * 2 + 1;
494        tStart = (div + 1) * 3;
495
496        // top cap
497        for (int p0 = 0; p0 != div; ++p0) {
498            int p2 = p0 + 1 + div + 1, t0 = tStart + p0, t2 = t0 + 1;
499            //*faces++ = SmFace(p0+div+1,p1,p2, t0,t1,t2, 2);
500
501            faces[fIndex+0] = p0 + div + 1;
502            faces[fIndex+1] = t0;
503            faces[fIndex+2] = p1;
504            faces[fIndex+3] = t1;
505            faces[fIndex+4] = p2;
506            faces[fIndex+5] = t2;
507            fIndex += 6;
508        }
509
510        for (int i = 0; i < div * 2; ++i) {
511            smoothing[i] = 1;
512        }
513        for (int i = div * 2; i < div * 4; ++i) {
514            smoothing[i] = 2;
515        }
516
517        TriangleMesh m = new TriangleMesh();
518        m.getPoints().setAll(points);
519        m.getTexCoords().setAll(tPoints);
520        m.getFaces().setAll(faces);
521        m.getFaceSmoothingGroups().setAll(smoothing);
522
523        return m;
524    }
525
526    private static int generateKey(float h, float r, int div) {
527        int hash = 7;
528        hash = 47 * hash + Float.floatToIntBits(h);
529        hash = 47 * hash + Float.floatToIntBits(r);
530        hash = 47 * hash + div;
531        return hash;
532    }
533}