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;
027
028import com.sun.javafx.geom.BaseBounds;
029import com.sun.javafx.geom.BoxBounds;
030import com.sun.javafx.geom.PickRay;
031import com.sun.javafx.geom.Vec3d;
032import com.sun.javafx.geom.transform.Affine3D;
033import com.sun.javafx.geom.transform.BaseTransform;
034import com.sun.javafx.geom.transform.GeneralTransform3D;
035import com.sun.javafx.geom.transform.NoninvertibleTransformException;
036import com.sun.javafx.jmx.MXNodeAlgorithm;
037import com.sun.javafx.jmx.MXNodeAlgorithmContext;
038import com.sun.javafx.scene.CameraHelper;
039import com.sun.javafx.scene.DirtyBits;
040import com.sun.javafx.sg.PGCamera;
041import javafx.beans.InvalidationListener;
042import javafx.beans.Observable;
043import javafx.beans.property.DoubleProperty;
044import javafx.beans.property.SimpleDoubleProperty;
045import javafx.geometry.Point2D;
046import javafx.geometry.Point3D;
047import javafx.scene.transform.Transform;
048import sun.util.logging.PlatformLogger;
049
050
051/**
052 * Base class for a camera used to render a scene.
053 *
054 * @since JavaFX 1.3
055 */
056public abstract class Camera extends Node {
057   
058    private Affine3D localToSceneTx = new Affine3D();
059
060    protected Camera() {
061        InvalidationListener dirtyTransformListener = new InvalidationListener() {
062            @Override
063            public void invalidated(Observable observable) {
064                impl_markDirty(DirtyBits.NODE_CAMERA_TRANSFORM);
065            }
066        };
067
068        this.localToSceneTransformProperty().addListener(dirtyTransformListener);
069        // if camera is removed from scene it needs to stop using its transforms
070        this.sceneProperty().addListener(dirtyTransformListener);
071    }
072
073    // NOTE: farClipInScene and nearClipInScene are valid only if there is no rotation
074    private double farClipInScene;
075    private double nearClipInScene;
076
077    // only one of them can be non-null at a time
078    private Scene ownerScene = null;
079    private SubScene ownerSubScene = null;
080
081    private GeneralTransform3D projViewTx = new GeneralTransform3D();
082    private GeneralTransform3D projTx = new GeneralTransform3D();
083    private Affine3D viewTx = new Affine3D();
084    private double viewWidth = 1.0;
085    private double viewHeight = 1.0;
086    private Vec3d position = new Vec3d();
087
088    private boolean clipInSceneValid = false;
089    private boolean projViewTxValid = false;
090    private boolean localToSceneValid = false;
091    private boolean sceneToLocalValid = false;
092
093    double getFarClipInScene() {
094        updateClipPlane();
095        return farClipInScene;
096    }
097
098    double getNearClipInScene() {
099        updateClipPlane();
100        return nearClipInScene;
101    }
102
103    private void updateClipPlane() {
104        if (!clipInSceneValid) {
105            final Transform localToSceneTransform = getLocalToSceneTransform();
106            nearClipInScene = localToSceneTransform.transform(0, 0, getNearClip()).getZ();
107            farClipInScene = localToSceneTransform.transform(0, 0, getFarClip()).getZ();
108            clipInSceneValid = true;
109        }
110    }
111
112    /**
113     * An affine transform that holds the computed scene-to-local transform.
114     * It is used to convert node to camera coordinate when rotation is involved.
115     */
116    private Affine3D sceneToLocalTx = new Affine3D();
117
118    Affine3D getSceneToLocalTransform() {
119        if (!sceneToLocalValid) {
120            sceneToLocalTx.setTransform(getCameraTransform());
121            try {
122                sceneToLocalTx.invert();
123            } catch (NoninvertibleTransformException ex) {
124                String logname = Camera.class.getName();
125                PlatformLogger.getLogger(logname).severe("getSceneToLocalTransform", ex);
126                sceneToLocalTx.setToIdentity();
127            }
128            sceneToLocalValid = true;
129        }
130
131        return sceneToLocalTx;
132    }
133
134    /**
135     * Specifies the near clipping plane of this {@code Camera} in the eye
136     * coordinate system of this node. Objects closer to the eye than the 
137     * {@code nearClip} plane are not drawn.
138     *
139     * @defaultValue 0.1
140     * @since JavaFX 8
141     */
142    private DoubleProperty nearClip;
143
144    public final void setNearClip(double value){
145        nearClipProperty().set(value);
146    }
147
148    public final double getNearClip() {
149        return nearClip == null ? 0.1 : nearClip.get();
150    }
151
152    public final DoubleProperty nearClipProperty() {
153        if (nearClip == null) {
154            nearClip = new SimpleDoubleProperty(Camera.this, "nearClip", 0.1) {
155                @Override
156                protected void invalidated() {
157                    clipInSceneValid = false;
158                    impl_markDirty(DirtyBits.NODE_CAMERA);
159                }
160            };
161        }
162        return nearClip;
163    }
164
165    /**
166     * Specifies the far clipping plane of this {@code Camera} in the eye
167     * coordinate system of this node. Objects farther away from the eye than
168     * the {@code farClip} plane are not drawn.
169     * <p>
170     *
171     * @defaultValue 100.0
172     * @since JavaFX 8
173     */
174    private DoubleProperty farClip;
175
176    public final void setFarClip(double value){
177        farClipProperty().set(value);
178    }
179
180    public final double getFarClip() {
181        return farClip == null ? 100.0 : farClip.get();
182    }
183
184    public final DoubleProperty farClipProperty() {
185        if (farClip == null) {
186            farClip = new SimpleDoubleProperty(Camera.this, "farClip", 100.0) {
187                @Override
188                protected void invalidated() {
189                    clipInSceneValid = false;
190                    impl_markDirty(DirtyBits.NODE_CAMERA);
191                }
192            };
193        }
194        return farClip;
195    }
196    
197    PGCamera getPlatformCamera() {
198        return (PGCamera) impl_getPGNode();
199    }
200
201    Camera copy() {
202        return this;
203    }
204
205    /**
206     * @treatAsPrivate implementation detail
207     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
208     */
209    @Deprecated
210    @Override
211    public void impl_updatePG() {
212        super.impl_updatePG();
213        PGCamera pgCamera = (PGCamera)impl_getPGNode();
214        if (!impl_isDirtyEmpty()) {
215            if (impl_isDirty(DirtyBits.NODE_CAMERA)) {
216                pgCamera.setNearClip((float) getNearClip());
217                pgCamera.setFarClip((float) getFarClip());
218                pgCamera.setViewWidth(getViewWidth());
219                pgCamera.setViewHeight(getViewHeight());
220            }
221            if (impl_isDirty(DirtyBits.NODE_CAMERA_TRANSFORM)) {
222                // TODO: 3D - For now, we are treating the scene as world.
223                // This may need to change for the fixed eye position case.
224                pgCamera.setWorldTransform(getCameraTransform());
225            }
226
227            pgCamera.setProjViewTransform(getProjViewTransform());
228
229            position = computePosition(position);
230            getCameraTransform().transform(position, position);
231            pgCamera.setPosition(position);
232        }
233    }
234
235    void setViewWidth(double width) {
236        this.viewWidth = width;
237        impl_markDirty(DirtyBits.NODE_CAMERA);
238    }
239
240    double getViewWidth() {
241        return viewWidth;
242    }
243
244    void setViewHeight(double height) {
245        this.viewHeight = height;
246        impl_markDirty(DirtyBits.NODE_CAMERA);
247    }
248
249    double getViewHeight() {
250        return viewHeight;
251    }
252
253    void setOwnerScene(Scene s) {
254        if (s == null) {
255            ownerScene = null;
256        } else if (s != ownerScene) {
257            if (ownerScene != null || ownerSubScene != null) {
258                throw new IllegalArgumentException(this
259                        + "is already set as camera in other scene or subscene");
260            }
261            ownerScene = s;
262            markOwnerDirty();
263        }
264    }
265
266    void setOwnerSubScene(SubScene s) {
267        if (s == null) {
268            ownerSubScene = null;
269        } else if (s != ownerSubScene) {
270            if (ownerScene != null || ownerSubScene != null) {
271                throw new IllegalArgumentException(this
272                        + "is already set as camera in other scene or subscene");
273            }
274            ownerSubScene = s;
275            markOwnerDirty();
276        }
277    }
278
279    @Override
280    protected void impl_markDirty(DirtyBits dirtyBit) {
281        super.impl_markDirty(dirtyBit);
282        if (dirtyBit == DirtyBits.NODE_CAMERA_TRANSFORM) {
283            localToSceneValid = false;
284            sceneToLocalValid = false;
285            clipInSceneValid = false;
286            projViewTxValid = false;
287        } else if (dirtyBit == DirtyBits.NODE_CAMERA) {
288            projViewTxValid = false;
289        }
290        markOwnerDirty();
291    }
292
293    private void markOwnerDirty() {
294        // if the camera is part of the scene/subScene, we don't need to notify
295        // the owner as the camera will be added to its dirty list as usual
296
297        if (ownerScene != null && ownerScene != getScene()) {
298            ownerScene.markCameraDirty();
299        }
300        if (ownerSubScene != null && ownerSubScene != getSubScene()) {
301            ownerSubScene.markContentDirty();
302        }
303    }
304
305    /**
306     * Returns the local-to-scene transform of this camera.
307     * Package private, for use in our internal subclasses.
308     * Returns directly the internal instance, it must not be altered.
309     */
310    Affine3D getCameraTransform() {
311        if (!localToSceneValid) {
312            localToSceneTx.setToIdentity();
313            getLocalToSceneTransform().impl_apply(localToSceneTx);
314            localToSceneValid = true;
315        }
316        return localToSceneTx;
317    }
318
319    abstract void computeProjectionTransform(GeneralTransform3D proj);
320    abstract void computeViewTransform(Affine3D view);
321
322    /**
323     * Returns the projView transform of this camera.
324     * Package private, for internal use.
325     * Returns directly the internal instance, it must not be altered.
326     */
327    GeneralTransform3D getProjViewTransform() {
328        if (!projViewTxValid) {
329            computeProjectionTransform(projTx);
330            computeViewTransform(viewTx);
331
332            projViewTx.set(projTx);
333            projViewTx.mul(viewTx);
334            projViewTx.mul(getSceneToLocalTransform());
335
336            projViewTxValid = true;
337        }
338
339        return projViewTx;
340    }
341
342    /**
343     * Transforms the given 3D point to the flat projected coordinates.
344     */
345    private Point2D project(Point3D p) {
346
347        final Vec3d vec = getProjViewTransform().transform(new Vec3d(
348                p.getX(), p.getY(), p.getZ()));
349
350        final double halfViewWidth = getViewWidth() / 2.0;
351        final double halfViewHeight = getViewHeight() / 2.0;
352
353        return new Point2D(
354                halfViewWidth * (1 + vec.x),
355                halfViewHeight * (1 - vec.y));
356    }
357
358    /**
359     * Computes intersection point of the pick ray cast by the given coordinates
360     * and the node's local XY plane.
361     */
362    private Point2D pickNodeXYPlane(Node node, double x, double y) {
363        final PickRay ray = computePickRay(x, y, null);
364
365        final Affine3D localToScene = new Affine3D();
366        node.getLocalToSceneTransform().impl_apply(localToScene);
367
368        final Vec3d o = ray.getOriginNoClone();
369        final Vec3d d = ray.getDirectionNoClone();
370
371        try {
372            localToScene.inverseTransform(o, o);
373            localToScene.inverseDeltaTransform(d, d);
374        } catch (NoninvertibleTransformException e) {
375            return null;
376        }
377
378        if (almostZero(d.z)) {
379            return null;
380        }
381
382        final double t = -o.z / d.z;
383        return new Point2D(o.x + (d.x * t), o.y + (d.y * t));
384    }
385
386    /**
387     * Computes intersection point of the pick ray cast by the given coordinates
388     * and the projection plane.
389     */
390    Point3D pickProjectPlane(double x, double y) {
391        final PickRay ray = computePickRay(x, y, null);
392        final Vec3d p = new Vec3d();
393        p.add(ray.getOriginNoClone(), ray.getDirectionNoClone());
394
395        return new Point3D(p.x, p.y, p.z);
396    }
397
398
399    /**
400     * Computes pick ray for the content rendered by this camera.
401     * @param x horizontal coordinate of the pick ray in the projected
402     *               view, usually mouse cursor position
403     * @param y vertical coordinate of the pick ray in the projected
404     *               view, usually mouse cursor position
405     * @param pickRay pick ray to be reused. New instance is created in case
406     *                of null.
407     * @return The PickRay instance computed based on this camera and the given
408     *         arguments.
409     */
410    abstract PickRay computePickRay(double x, double y, PickRay pickRay);
411
412    /**
413     * Computes local position of the camera in the scene.
414     * @param position Position to be reused. New instance is created in case 
415     *                 of null.
416     * @return The position of the camera in the scene in camera local coords
417     */
418    abstract Vec3d computePosition(Vec3d position);
419
420    /**
421     * @treatAsPrivate implementation detail
422     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
423     */
424    @Deprecated
425    @Override
426    public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
427        return new BoxBounds(0, 0, 0, 0, 0, 0);
428    }
429
430    /**
431     * @treatAsPrivate implementation detail
432     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
433     */
434    @Deprecated
435    @Override
436    protected boolean impl_computeContains(double localX, double localY) {
437        return false;
438    }
439
440    /**
441     * @treatAsPrivate implementation detail
442     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
443     */
444    @Deprecated
445    @Override
446    public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) {
447        throw new UnsupportedOperationException("Not supported yet.");
448    }
449
450
451    static {
452         // This is used by classes in different packages to get access to
453         // private and package private methods.
454        CameraHelper.setCameraAccessor(new CameraHelper.CameraAccessor() {
455
456            @Override
457            public Point2D project(Camera camera, Point3D p) {
458                return camera.project(p);
459            }
460
461            @Override
462            public Point2D pickNodeXYPlane(Camera camera, Node node, double x, double y) {
463                return camera.pickNodeXYPlane(node, x, y);
464            }
465
466            @Override
467            public Point3D pickProjectPlane(Camera camera, double x, double y) {
468                return camera.pickProjectPlane(x, y);
469            }
470        });
471    }
472}