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.PGNode;
035import com.sun.javafx.sg.PGSphere;
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 Sphere} class defines a 3 dimensional sphere with the specified size.
046 * A {@code Sphere} is a 3D geometry primitive created with a given radius.
047 * It is centered at the origin.
048 *
049 * @since JavaFX 8    
050 */
051public class Sphere extends Shape3D {
052
053    static final int DEFAULT_DIVISIONS = 64;
054    static final double DEFAULT_RADIUS = 1;
055    private int divisions = DEFAULT_DIVISIONS;
056    private TriangleMesh mesh;
057
058    /**  
059     * Creates a new instance of {@code Sphere} of radius of 1.0.
060     * The resolution defaults to MID_RESOLUTION divisions along sphere's axes.
061     */
062    public Sphere() {
063        this(DEFAULT_RADIUS, DEFAULT_DIVISIONS);
064    }
065
066    /**
067     * Creates a new instance of {@code Sphere} of a given radius.
068     * The resolution defaults to MID_RESOLUTION divisions along sphere's axes.
069     *
070     * @param radius Radius
071     */
072    public Sphere(double radius) {
073        this(radius, DEFAULT_DIVISIONS);
074    }
075
076    /**  
077     * Creates a new instance of {@code Sphere} of a given radius and number
078     * of divisions.
079     * The resolution is defined in terms of number of subdivisions along the
080     * sphere's axes. More divisions lead to more finely tesselated objects.
081     *
082     * Note that divisions should be at least 1. Any value less than that will be
083     * clamped to 1.
084     * 
085     * @param radius Radius
086     * @param divisions Divisions     
087     */
088    public Sphere(double radius, int divisions) {
089        this.divisions = divisions < 1 ? 1: divisions;
090        setRadius(radius);
091    }
092
093    /**
094     * Defines the radius of the Sphere.
095     *
096     * @defaultValue 1.0
097     */
098    private DoubleProperty radius;
099
100    public final void setRadius(double value) {
101        radiusProperty().set(value);
102    }
103
104    public final double getRadius() {
105        return radius == null ? 1 : radius.get();
106    }
107
108    public final DoubleProperty radiusProperty() {
109        if (radius == null) {
110            radius = new SimpleDoubleProperty(Sphere.this, "radius", DEFAULT_RADIUS) {
111                @Override
112                public void invalidated() {
113                    impl_markDirty(DirtyBits.MESH_GEOM);
114                    manager.invalidateSphereMesh(key);
115                    key = 0;
116                }
117            };
118        }
119        return radius;
120    }
121
122    /**
123     * Retrieves the divisions attribute use to generate this sphere.
124     *
125     * @return the divisions attribute.
126     */
127    public int getDivisions() {
128        return divisions;
129    }
130
131    /**
132     * @treatAsPrivate implementation detail
133     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
134     */
135    @Deprecated
136    @Override
137    protected PGNode impl_createPGNode() {
138        return Toolkit.getToolkit().createPGSphere();
139    }
140
141    /**
142     * @treatAsPrivate implementation detail
143     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
144     */
145    @Deprecated
146    public void impl_updatePG() {
147        super.impl_updatePG();
148        if (impl_isDirty(DirtyBits.MESH_GEOM)) {
149            PGSphere pgSphere = (PGSphere) impl_getPGNode();
150            final float r = (float) getRadius();
151            if (r < 0) {
152                pgSphere.updateMesh(null);
153            } else {
154                if (key == 0) {
155                    key = generateKey(r, divisions);
156                }
157                mesh = manager.getSphereMesh(r, divisions, key);
158                mesh.impl_updatePG();
159                pgSphere.updateMesh(mesh.impl_getPGTriangleMesh());
160            }
161        }
162    }
163
164    /**
165     * @treatAsPrivate implementation detail
166     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
167     */
168    @Deprecated
169    @Override
170    public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
171        final float r = (float) getRadius();
172        
173        if (r < 0) {
174            return bounds.makeEmpty();
175        }        
176        
177        bounds = bounds.deriveWithNewBounds(-r, -r, -r, r, r ,r);
178        bounds = tx.transform(bounds, bounds);
179        return bounds;
180    }
181
182    /**
183     * @treatAsPrivate implementation detail
184     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
185     */
186    @Deprecated
187    @Override
188    protected boolean impl_computeContains(double localX, double localY) {
189        double r = getRadius();
190        double n2 = localX * localX + localY * localY;
191        return n2 <= r * r;
192    }
193
194    /**
195     * @treatAsPrivate implementation detail
196     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
197     */
198    @Deprecated
199    @Override
200    protected boolean impl_computeIntersects(PickRay pickRay, PickResultChooser pickResult) {
201
202        final boolean exactPicking = divisions < DEFAULT_DIVISIONS && mesh != null;
203
204        final double r = getRadius();
205        final Vec3d dir = pickRay.getDirectionNoClone();
206        final double dirX = dir.x;
207        final double dirY = dir.y;
208        final double dirZ = dir.z;
209        final Vec3d origin = pickRay.getOriginNoClone();
210        final double originX = origin.x;
211        final double originY = origin.y;
212        final double originZ = origin.z;
213
214        // Coeficients of a quadratic equation desribing intersection with sphere
215        final double a = dirX * dirX + dirY * dirY + dirZ * dirZ;
216        final double b = 2 * (dirX * originX + dirY * originY + dirZ * originZ);
217        final double c = originX * originX + originY * originY + originZ * originZ - r * r;
218
219        final double discriminant = b * b - 4 * a * c;
220        if (discriminant < 0) {
221            // No real roots of the equation, missed the shape
222            return false;
223        }
224
225        final double distSqrt = Math.sqrt(discriminant);
226        final double q = (b < 0) ? (-b - distSqrt) / 2.0 : (-b + distSqrt) / 2.0;
227
228        double t0 = q / a;
229        double t1 = c / q;
230
231        if (t0 > t1) {
232            final double temp = t0;
233            t0 = t1;
234            t1 = temp;
235        }
236
237        final double minDistance = pickRay.getNearClip();
238        final double maxDistance = pickRay.getFarClip();
239
240        if (t1 < minDistance || t0 > maxDistance) {
241            // the sphere is out of clipping planes
242            return false;
243        }
244
245        double t = t0;
246        final CullFace cullFace = getCullFace();
247        if (t0 < minDistance || cullFace == CullFace.FRONT) {
248            if (t1 <= maxDistance && getCullFace() != CullFace.BACK) {
249                // picking the back wall
250                t = t1;
251            } else {
252                // we are inside the sphere with the back wall culled, but the
253                // exact picking still needs to be done because the front faced
254                // triangles may still be in front of us
255                if (!exactPicking) {
256                    return false;
257                }
258            }
259        }
260
261        if (Double.isInfinite(t) || Double.isNaN(t)) {
262            // We've got a nonsense pick ray or sphere size.
263            return false;
264        }
265
266        if (exactPicking) {
267            return mesh.impl_computeIntersects(pickRay, pickResult, this, cullFace, false);
268        }
269
270        if (pickResult != null && pickResult.isCloser(t)) {
271            final Point3D point = PickResultChooser.computePoint(pickRay, t);
272
273            // computing texture coords
274            final Point3D proj = new Point3D(point.getX(), 0, point.getZ());
275            final Point3D cross = proj.crossProduct(Rotate.Z_AXIS);
276            double angle = proj.angle(Rotate.Z_AXIS);
277            if (cross.getY() > 0) {
278                angle = 360 - angle;
279            }
280            Point2D txtCoords = new Point2D(1 - angle / 360, 0.5 + point.getY() / (2 * r));
281
282            pickResult.offer(this, t, PickResult.FACE_UNDEFINED, point, txtCoords);
283        }
284        return true;
285    }
286
287    private static int correctDivisions(int div) {
288        return ((div + 3) / 4) * 4;
289    }
290
291    static TriangleMesh createMesh(int div, float r) {
292        div = correctDivisions(div);
293
294        // NOTE: still create mesh for degenerated sphere
295        final int div2 = div / 2;
296
297        final int nPoints = (div + 1) * (div2 - 1) + 2;
298        final int nTPoints = (div + 1) * (div2 - 1) + div * 2;
299        final int nFaces = div * (div2 - 2) * 2 + div * 2;
300
301        final float rDiv = 1.f / div;
302
303        float points[] = new float[nPoints * 3];
304        float tPoints[] = new float[nTPoints * 2];
305        int faces[] = new int[nFaces * 6];
306        int smoothing[] = new int[nFaces];
307
308        int pPos = 0, tPos = 0;
309
310        for (int y = 0; y < div2 - 1; ++y) {
311            float va = rDiv * (y + 1 - div2 / 2) * 2 * (float) Math.PI;
312            float sin_va = (float) Math.sin(va);
313            float cos_va = (float) Math.cos(va);
314
315            float ty = 0.5f + sin_va * 0.5f;
316            for (int i = 0; i <= div; ++i) {
317                double a = (i < div) ? rDiv * i * 2 * (float) Math.PI : 0;
318                float hSin = (float) Math.sin(a);
319                float hCos = (float) Math.cos(a);
320                points[pPos + 0] = hSin * cos_va * r;
321                points[pPos + 2] = hCos * cos_va * r;
322                points[pPos + 1] = sin_va * r;
323                tPoints[tPos + 0] = 1- rDiv * i;
324                tPoints[tPos + 1] = ty;
325                pPos += 3; tPos += 2;
326            }
327        }
328
329        points[pPos + 0] = 0;
330        points[pPos + 1] = -r;
331        points[pPos + 2] = 0;
332        points[pPos + 3] = 0;
333        points[pPos + 4] = r;
334        points[pPos + 5] = 0;
335        pPos += 6;
336
337        int pS = (div2 - 1) * (div + 1);
338
339        float textureDelta = 1.f / 256;
340        for (int i = 0; i != div; ++i) {
341            tPoints[tPos + 0] = rDiv * (0.5f + i);
342            tPoints[tPos + 1] = textureDelta;
343            tPos += 2;
344        }
345
346        for (int i = 0; i != div; ++i) {
347            tPoints[tPos + 0] = rDiv * (0.5f + i);
348            tPoints[tPos + 1] = 1 - textureDelta;
349            tPos += 2;
350        }
351
352        int fIndex = 0;
353        for (int y = 0; y < div2 - 2; ++y) {
354            for (int x = 0; x < div; ++x) {
355                int p0 = y * (div + 1) + x;
356                int p1 = p0 + 1;
357                int p2 = p0 + (div + 1);
358                int p3 = p1 + (div + 1);
359
360                // add p0, p1, p2
361                faces[fIndex+0] = p0;
362                faces[fIndex+1] = p0;
363                faces[fIndex+2] = p1;
364                faces[fIndex+3] = p1;
365                faces[fIndex+4] = p2;
366                faces[fIndex+5] = p2;
367                fIndex += 6;
368
369                // add p3, p2, p1
370                faces[fIndex+0] = p3;
371                faces[fIndex+1] = p3;
372                faces[fIndex+2] = p2;
373                faces[fIndex+3] = p2;
374                faces[fIndex+4] = p1;
375                faces[fIndex+5] = p1;
376                fIndex += 6;
377            }
378        }
379
380        int p0 = pS;
381        int tB = pS;
382        for (int x = 0; x != div; ++x) {
383            int p2 = x, p1 = x + 1, t0 = tB + x;
384            faces[fIndex+0] = p0;
385            faces[fIndex+1] = t0;
386            faces[fIndex+2] = p1;
387            faces[fIndex+3] = p1;
388            faces[fIndex+4] = p2;
389            faces[fIndex+5] = p2;
390            fIndex += 6;
391        }
392
393        p0 = p0 + 1;
394        tB = tB + div;
395        int pB = (div2 - 2) * (div + 1);
396
397        for (int x = 0; x != div; ++x) {
398            int p1 = pB + x, p2 = pB + x + 1, t0 = tB + x;
399            faces[fIndex+0] = p0;
400            faces[fIndex+1] = t0;
401            faces[fIndex+2] = p1;
402            faces[fIndex+3] = p1;
403            faces[fIndex+4] = p2;
404            faces[fIndex+5] = p2;
405            fIndex += 6;
406        }
407
408        for (int i = 0; i < nFaces; ++i) {
409            smoothing[i] = 1;
410        }
411
412        TriangleMesh m = new TriangleMesh();
413        m.getPoints().setAll(points);
414        m.getTexCoords().setAll(tPoints);
415        m.getFaces().setAll(faces);
416        m.getFaceSmoothingGroups().setAll(smoothing);
417        return m;
418    }
419
420    private static int generateKey(float r, int div) {
421        int hash = 5;
422        hash = 23 * hash + Float.floatToIntBits(r);
423        hash = 23 * hash + div;
424        return hash;
425    }
426}