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.PGBox;
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;
042
043/**
044 * The {@code Box} class defines a 3 dimensional box with the specified size.
045 * A {@code Box} is a 3D geometry primitive created with a given depth, width,
046 * and height. It is centered at the origin.
047 *
048 * @since JavaFX 8    
049 */
050public class Box extends Shape3D {
051
052    private TriangleMesh mesh;
053
054    /**
055     * Creates a new instance of {@code Box} of dimension 2 by 2 by 2.
056     */
057    
058    public static final double DEFAULT_SIZE = 2;
059    
060    public Box() {
061        this(DEFAULT_SIZE, DEFAULT_SIZE, DEFAULT_SIZE);
062    }
063
064    /**
065     * Creates a new instance of {@code Box} of dimension width by height 
066     * by depth.
067     */
068    public Box(double width, double height, double depth) {
069        setWidth(width);
070        setHeight(height);
071        setDepth(depth);
072    }
073    
074    /**
075     * Defines the depth or the Z dimension of the Box.
076     *
077     * @defaultValue 2.0
078     */
079    private DoubleProperty depth;
080
081    public final void setDepth(double value) {
082        depthProperty().set(value);
083    }
084
085    public final double getDepth() {
086        return depth == null ? 2 : depth.get();
087    }
088
089    public final DoubleProperty depthProperty() {
090        if (depth == null) {
091            depth = new SimpleDoubleProperty(Box.this, "depth", DEFAULT_SIZE) {
092                @Override
093                public void invalidated() {
094                    impl_markDirty(DirtyBits.MESH_GEOM);
095                    manager.invalidateBoxMesh(key);
096                    key = 0;
097                }
098            };
099        }
100        return depth;
101    }
102
103    /**
104     * Defines the height or the Y dimension of the Box.
105     *
106     * @defaultValue 2.0
107     */
108    private DoubleProperty height;
109
110    public final void setHeight(double value) {
111        heightProperty().set(value);
112    }
113
114    public final double getHeight() {
115        return height == null ? 2 : height.get();
116    }
117
118    public final DoubleProperty heightProperty() {
119        if (height == null) {
120            height = new SimpleDoubleProperty(Box.this, "height", DEFAULT_SIZE) {
121                @Override
122                public void invalidated() {
123                    impl_markDirty(DirtyBits.MESH_GEOM);
124                    manager.invalidateBoxMesh(key);
125                    key = 0;
126                }
127            };
128        }
129        return height;
130    }
131
132    /**
133     * Defines the width or the X dimension of the Box.
134     *
135     * @defaultValue 2.0
136     */
137    private DoubleProperty width;
138
139    public final void setWidth(double value) {
140        widthProperty().set(value);
141    }
142
143    public final double getWidth() {
144        return width == null ? 2 : width.get();
145    }
146
147    public final DoubleProperty widthProperty() {
148        if (width == null) {
149            width = new SimpleDoubleProperty(Box.this, "width", DEFAULT_SIZE) {
150                @Override
151                public void invalidated() {
152                    impl_markDirty(DirtyBits.MESH_GEOM);
153                    manager.invalidateBoxMesh(key);
154                    key = 0;
155                }
156            };
157        }
158        return width;
159    }
160    /**
161     * @treatAsPrivate implementation detail
162     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
163     */
164    @Deprecated
165    @Override
166    protected PGNode impl_createPGNode() {
167        return Toolkit.getToolkit().createPGBox();
168    }
169
170    /**
171     * @treatAsPrivate implementation detail
172     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
173     */
174    @Deprecated
175    public void impl_updatePG() {
176        super.impl_updatePG();
177        if (impl_isDirty(DirtyBits.MESH_GEOM)) {
178            PGBox pgBox = (PGBox) impl_getPGNode();
179            final float w = (float) getWidth();
180            final float h = (float) getHeight();
181            final float d = (float) getDepth();
182            if (w < 0 || h < 0 || d < 0) {
183                pgBox.updateMesh(null);
184            } else {
185                if (key == 0) {
186                    key = generateKey(w, h, d);
187                }
188                mesh = manager.getBoxMesh(w, h, d, key);
189                mesh.impl_updatePG();
190                pgBox.updateMesh(mesh.impl_getPGTriangleMesh());
191            }
192        }
193    }
194    
195    /**
196     * @treatAsPrivate implementation detail
197     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
198     */
199    @Deprecated
200    @Override
201    public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
202        final float w = (float) getWidth();
203        final float h = (float) getHeight();
204        final float d = (float) getDepth();
205
206        if (w < 0 || h < 0 || d < 0) {
207            return bounds.makeEmpty();
208        }
209
210        final float hw = w * 0.5f;
211        final float hh = h * 0.5f;
212        final float hd = d * 0.5f;
213        
214        bounds = bounds.deriveWithNewBounds(-hw, -hh, -hd, hw, hh, hd);
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 = getWidth();
227        double h = getHeight();
228        return -w <= localX && localX <= w && 
229                -h <= localY && localY <= h;
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 double w = getWidth();
241        final double h = getHeight();
242        final double d = getDepth();
243        final double hWidth = w / 2.0;
244        final double hHeight = h / 2.0;
245        final double hDepth = d / 2.0;
246        final Vec3d dir = pickRay.getDirectionNoClone();
247        final double invDirX = dir.x == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.x);
248        final double invDirY = dir.y == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.y);
249        final double invDirZ = dir.z == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.z);
250        final Vec3d origin = pickRay.getOriginNoClone();
251        final double originX = origin.x;
252        final double originY = origin.y;
253        final double originZ = origin.z;
254        final boolean signX = invDirX < 0.0;
255        final boolean signY = invDirY < 0.0;
256        final boolean signZ = invDirZ < 0.0;
257
258        double t0 = Double.NEGATIVE_INFINITY;
259        double t1 = Double.POSITIVE_INFINITY;
260        char side0 = '0';
261        char side1 = '0';
262
263        if (Double.isInfinite(invDirX)) {
264            if (-hWidth <= originX && hWidth >= originX) {
265                // move on, we are inside for the whole length
266            } else {
267                return false;
268            }
269        } else {
270            t0 = ((signX ? hWidth : -hWidth) - originX) * invDirX;
271            t1 = ((signX ? -hWidth : hWidth) - originX) * invDirX;
272            side0 = signX ? 'X' : 'x';
273            side1 = signX ? 'x' : 'X';
274        }
275
276        if (Double.isInfinite(invDirY)) {
277            if (-hHeight <= originY && hHeight >= originY) {
278                // move on, we are inside for the whole length
279            } else {
280                return false;
281            }
282        } else {
283            final double ty0 = ((signY ? hHeight : -hHeight) - originY) * invDirY;
284            final double ty1 = ((signY ? -hHeight : hHeight) - originY) * invDirY;
285
286            if ((t0 > ty1) || (ty0 > t1)) {
287                return false;
288            }
289            if (ty0 > t0) {
290                side0 = signY ? 'Y' : 'y';
291                t0 = ty0;
292            }
293            if (ty1 < t1) {
294                side1 = signY ? 'y' : 'Y';
295                t1 = ty1;
296            }
297        }
298
299        if (Double.isInfinite(invDirZ)) {
300            if (-hDepth <= originZ && hDepth >= originZ) {
301                // move on, we are inside for the whole length
302            } else {
303                return false;
304            }
305        } else {
306            double tz0 = ((signZ ? hDepth : -hDepth) - originZ) * invDirZ;
307            double tz1 = ((signZ ? -hDepth : hDepth) - originZ) * invDirZ;
308
309            if ((t0 > tz1) || (tz0 > t1)) {
310                return false;
311            }
312            if (tz0 > t0) {
313                side0 = signZ ? 'Z' : 'z';
314                t0 = tz0;
315            }
316            if (tz1 < t1) {
317                side1 = signZ ? 'z' : 'Z';
318                t1 = tz1;
319            }
320        }
321
322        char side = side0;
323        double t = t0;
324        final CullFace cullFace = getCullFace();
325        final double minDistance = pickRay.getNearClip();
326        final double maxDistance = pickRay.getFarClip();
327
328        if (t0 > maxDistance) {
329            return false;
330        }
331        if (t0 < minDistance || cullFace == CullFace.FRONT) {
332            if (t1 >= minDistance && t1 <= maxDistance && cullFace != CullFace.BACK) {
333                side = side1;
334                t = t1;
335            } else {
336                return false;
337            }
338        }
339
340        if (Double.isInfinite(t) || Double.isNaN(t)) {
341            // We've got a nonsense pick ray or box size.
342            return false;
343        }
344
345        if (pickResult != null && pickResult.isCloser(t)) {
346            Point3D point = PickResultChooser.computePoint(pickRay, t);
347
348            Point2D txtCoords = null;
349            
350            switch (side) {
351                case 'x': // left
352                    txtCoords = new Point2D(
353                            0.5 - point.getZ() / d,
354                            0.5 + point.getY() / h);
355                    break;
356                case 'X': // right
357                    txtCoords = new Point2D(
358                            0.5 + point.getZ() / d,
359                            0.5 + point.getY() / h);
360                    break;
361                case 'y': // top
362                    txtCoords = new Point2D(
363                            0.5 + point.getX() / w,
364                            0.5 - point.getZ() / d);
365                    break;
366                case 'Y': // bottom
367                    txtCoords = new Point2D(
368                            0.5 + point.getX() / w,
369                            0.5 + point.getZ() / d);
370                    break;
371                case 'z': // front
372                    txtCoords = new Point2D(
373                            0.5 + point.getX() / w,
374                            0.5 + point.getY() / h);
375                    break;
376                case 'Z': // back
377                    txtCoords = new Point2D(
378                            0.5 - point.getX() / w,
379                            0.5 + point.getY() / h);
380                    break;
381                default:
382                    // No hit with any of the planes. We must have had a zero
383                    // pick ray direction vector. Should never happen.
384                    return false;
385            }
386
387            pickResult.offer(this, t, PickResult.FACE_UNDEFINED, point, txtCoords);
388        }
389        
390        return true;
391    }
392
393    static TriangleMesh createMesh(float w, float h, float d) {
394
395        // NOTE: still create mesh for degenerated box       
396        float hw = w / 2f;
397        float hh = h / 2f;
398        float hd = d / 2f;
399
400        float points[] = {
401            -hw, -hh, -hd,
402             hw, -hh, -hd,
403             hw,  hh, -hd,
404            -hw,  hh, -hd,
405            -hw, -hh,  hd,
406             hw, -hh,  hd,
407             hw,  hh,  hd,
408            -hw,  hh,  hd};
409
410        float texCoords[] = {0, 0, 1, 0, 1, 1, 0, 1};
411
412        int faceSmoothingGroups[] = {
413            1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4
414        };
415
416        int faces[] = {
417            0, 0, 2, 2, 1, 1,
418            2, 2, 0, 0, 3, 3,            
419            1, 0, 6, 2, 5, 1,
420            6, 2, 1, 0, 2, 3,            
421            5, 0, 7, 2, 4, 1,
422            7, 2, 5, 0, 6, 3,
423            4, 0, 3, 2, 0, 1,
424            3, 2, 4, 0, 7, 3,            
425            3, 0, 6, 2, 2, 1,
426            6, 2, 3, 0, 7, 3,
427            4, 0, 1, 2, 5, 1,
428            1, 2, 4, 0, 0, 3,
429        };
430
431        TriangleMesh mesh = new TriangleMesh();
432        mesh.getPoints().setAll(points);
433        mesh.getTexCoords().setAll(texCoords);
434        mesh.getFaces().setAll(faces);
435        mesh.getFaceSmoothingGroups().setAll(faceSmoothingGroups);
436
437        return mesh;
438    }
439
440    private static int generateKey(float w, float h, float d) {
441        int hash = 3;
442        hash = 97 * hash + Float.floatToIntBits(w);
443        hash = 97 * hash + Float.floatToIntBits(h);
444        hash = 97 * hash + Float.floatToIntBits(d);
445        return hash;
446    }
447}