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.PickRay;
029import com.sun.javafx.geom.Vec3d;
030import com.sun.javafx.geom.transform.Affine3D;
031import com.sun.javafx.geom.transform.GeneralTransform3D;
032import com.sun.javafx.scene.DirtyBits;
033import com.sun.javafx.sg.PGNode;
034import com.sun.javafx.sg.PGPerspectiveCamera;
035import com.sun.javafx.tk.Toolkit;
036import javafx.application.ConditionalFeature;
037import javafx.application.Platform;
038import javafx.beans.property.BooleanProperty;
039import javafx.beans.property.DoubleProperty;
040import javafx.beans.property.SimpleBooleanProperty;
041import javafx.beans.property.SimpleDoubleProperty;
042import sun.util.logging.PlatformLogger;
043
044
045
046/**
047 * Specifies a perspective camera for rendering a scene.
048 *
049 * <p> This camera defines a viewing volume for a perspective projection;
050 * a truncated right pyramid.
051 * The {@code fieldOfView} value can be used to change viewing volume.
052 * This camera is always located at center of the scene and looks along the
053 * positive z-axis. The coordinate system defined by this camera has its
054 * origin in the upper left corner of the panel with the Y-axis pointing
055 * down and the Z axis pointing away from the viewer (into the screen). 
056 * 
057 * <p> In the default camera, where fixedEyeAtCameraZero is false, the Z value
058 * of the eye position is adjusted in Z such that the projection matrix generated
059 * using the specified {@code fieldOfView} will produce units at 
060 * Z = 0 (the projection plane), in device-independent pixels, matches that of
061 * the ParallelCamera.
062 * When the Scene is resized,
063 * the objects in the scene at the projection plane (Z = 0) will stay the same size,
064 * but more or less content of the scene is viewable.
065 * 
066 * <p> If fixedEyeAtCameraZero is true, the eye position is fixed at (0, 0, 0)
067 * in the local coordinates of the camera. The projection matrix is generated
068 * using the specified {@code fieldOfView} and the projection volume is mapped
069 * onto the viewport (window) such that it is stretched over more or fewer
070 * device-independent pixels at the projection plane.
071 * When the Scene is resized,
072 * the objects in the scene will shrink or grow proportionally,
073 * but the visible portion of the content is unchanged.
074 * 
075 * <p> We recommend setting fixedEyeAtCameraZero to true if you are going to
076 * transform (move) the camera. Transforming the camera when fixedEyeAtCameraZero
077 * is set to false may lead to results that are not intuitive.
078 * 
079 * <p> Note that this is a conditional feature. See
080 * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
081 * for more information.
082 * 
083 * @since JavaFX 1.3
084 */
085public  class PerspectiveCamera extends Camera {   
086
087    private boolean fixedEyeAtCameraZero = false;
088
089    // Lookat transform for legacy case
090    private static final Affine3D LOOK_AT_TX = new Affine3D();
091
092    // Lookat transform for fixedEyeAtCameraZero case
093    private static final Affine3D LOOK_AT_TX_FIXED_EYE = new Affine3D();
094
095    static {
096        // Compute the legacy look at matrix such that the zero point ends up at
097        // the z=-1 plane.
098        LOOK_AT_TX.setToTranslation(0, 0, -1);
099        // Y-axis pointing down
100        LOOK_AT_TX.rotate(Math.PI, 1, 0, 0);
101
102        // Compute the fixed eye at (0, 0, 0) look at matrix such that the zero point
103        // ends up at the z=0 plane and Y-axis pointing down
104        LOOK_AT_TX_FIXED_EYE.rotate(Math.PI, 1, 0, 0);
105    }
106
107    /**
108     * Specifies the field of view angle of the camera's projection plane,
109     * measured in degrees.
110     *
111     * @defaultValue 30.0
112     */
113    private DoubleProperty fieldOfView;
114    
115    public final void setFieldOfView(double value){
116        fieldOfViewProperty().set(value);
117    }
118
119    public final double getFieldOfView() {
120        return fieldOfView == null ? 30 : fieldOfView.get();
121    }
122
123    public final DoubleProperty fieldOfViewProperty() {
124        if (fieldOfView == null) {
125            fieldOfView = new SimpleDoubleProperty(PerspectiveCamera.this, "fieldOfView", 30) {
126                @Override
127                protected void invalidated() {
128                    impl_markDirty(DirtyBits.NODE_CAMERA);
129                }
130            };
131        }
132        return fieldOfView;
133    }
134
135    /**
136     * Defines whether the {@code fieldOfView} property is to apply to the vertical 
137     * dimension of the projection plane. If it is false, {@code fieldOfView} is to 
138     * apply to the horizontal dimension of the projection plane.
139     *
140     * @defaultValue true
141     * @since JavaFX 8
142     */
143    private BooleanProperty verticalFieldOfView;
144
145    public final void setVerticalFieldOfView(boolean value) {
146        verticalFieldOfViewProperty().set(value);
147    }
148
149    public final boolean isVerticalFieldOfView() {
150        return verticalFieldOfView == null ? true : verticalFieldOfView.get();
151    }
152
153    public final BooleanProperty verticalFieldOfViewProperty() {
154        if (verticalFieldOfView == null) {
155            verticalFieldOfView = new SimpleBooleanProperty(PerspectiveCamera.this, "verticalFieldOfView", true) {
156                @Override
157                protected void invalidated() {
158                    impl_markDirty(DirtyBits.NODE_CAMERA);
159                }
160            };
161        }
162        return verticalFieldOfView;
163    }
164
165    public PerspectiveCamera() {
166        this(false);
167    }
168
169    /**
170     * Constructs a PerspectiveCamera with the specified fixedEyeAtCameraZero flag.
171     *
172     * <p> In the default camera, where fixedEyeAtCameraZero is false, the Z value of 
173     * the eye position is adjusted in Z such that the projection matrix generated
174     * using the specified {@code fieldOfView} will produce units at
175     * Z = 0 (the projection plane), in device-independent pixels, matches that of
176     * the ParallelCamera.
177     * When the Scene is resized,
178     * the objects in the scene at the projection plane (Z = 0) will stay the same size,
179     * but more or less content of the scene is viewable.
180     *
181     * <p> If fixedEyeAtCameraZero is true, the eye position is fixed at (0, 0, 0)
182     * in the local coordinates of the camera. The projection matrix is generated
183     * using the specified {@code fieldOfView} and the projection volume is mapped
184     * onto the viewport (window) such that it is stretched over more or fewer
185     * device-independent pixels at the projection plane.
186     * When the Scene is resized,
187     * the objects in the scene will shrink or grow proportionally,
188     * but the visible portion of the content is unchanged.
189     *
190     * <p> We recommend setting fixedEyeAtCameraZero to true if you are going to
191     * transform (move) the camera. Transforming the camera when fixedEyeAtCameraZero
192     * is set to false may lead to results that are not intuitive.
193     * 
194     * @since JavaFX 8
195     */ 
196    public PerspectiveCamera(boolean fixedEyeAtCameraZero) {
197        if (!Platform.isSupported(ConditionalFeature.SCENE3D)) {
198            String logname = PerspectiveCamera.class.getName();
199            PlatformLogger.getLogger(logname).warning("System can't support "
200                    + "ConditionalFeature.SCENE3D");
201        }
202        this.fixedEyeAtCameraZero = fixedEyeAtCameraZero;
203    }
204
205    public final boolean isFixedEyeAtCameraZero() {
206        return fixedEyeAtCameraZero;
207    }
208
209    @Override
210    final PickRay computePickRay(double x, double y, PickRay pickRay) {
211
212        return PickRay.computePerspectivePickRay(x, y, fixedEyeAtCameraZero,
213                getViewWidth(), getViewHeight(),
214                getFieldOfView(), isVerticalFieldOfView(),
215                getCameraTransform(),
216                //TODO: use actual clips always after rendering uses them
217                fixedEyeAtCameraZero ? getNearClip() : 0.0,
218                fixedEyeAtCameraZero ? getFarClip() : Double.POSITIVE_INFINITY,
219                pickRay);
220    }
221
222    @Override Camera copy() {
223        PerspectiveCamera c = new PerspectiveCamera(fixedEyeAtCameraZero);
224        c.setNearClip(getNearClip());
225        c.setFarClip(getFarClip());
226        c.setFieldOfView(getFieldOfView());
227        return c;
228    }
229
230    /**
231     * @treatAsPrivate implementation detail
232     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
233     */
234    @Deprecated
235    @Override
236    protected PGNode impl_createPGNode() {
237        PGPerspectiveCamera pgCamera = Toolkit.getToolkit().createPGPerspectiveCamera(fixedEyeAtCameraZero);    
238        pgCamera.setNearClip((float) getNearClip());
239        pgCamera.setFarClip((float) getFarClip());
240        pgCamera.setFieldOfView((float) getFieldOfView());
241        return pgCamera;
242    }
243
244    /**
245     * @treatAsPrivate implementation detail
246     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
247     */
248    @Deprecated
249    @Override
250    public void impl_updatePG() {
251        super.impl_updatePG();
252        PGPerspectiveCamera pgPerspectiveCamera = (PGPerspectiveCamera)impl_getPGNode();
253        if (impl_isDirty(DirtyBits.NODE_CAMERA)) {
254            pgPerspectiveCamera.setVerticalFieldOfView(isVerticalFieldOfView());
255            pgPerspectiveCamera.setFieldOfView((float) getFieldOfView());
256        }
257    }
258
259    @Override
260    void computeProjectionTransform(GeneralTransform3D proj) {
261        proj.perspective(isVerticalFieldOfView(), Math.toRadians(getFieldOfView()),
262                getViewWidth() / getViewHeight(), getNearClip(), getFarClip());
263    }
264
265    @Override
266    protected void computeViewTransform(Affine3D view) {
267
268        // In the case of fixedEyeAtCameraZero the camera position is (0,0,0) in
269        // local coord. of the camera node. In non-fixed eye case, the camera
270        // position is (w/2, h/2, h/2/tan) in local coord. of the camera.
271        if (isFixedEyeAtCameraZero()) {
272            view.setTransform(LOOK_AT_TX_FIXED_EYE);
273        } else {
274            final double viewWidth = getViewWidth();
275            final double viewHeight = getViewHeight();
276            final boolean verticalFOV = isVerticalFieldOfView();
277
278            final double aspect = viewWidth / viewHeight;
279            final double tanOfHalfFOV = Math.tan(Math.toRadians(getFieldOfView()) / 2.0);
280
281            // Translate the zero point to the upper-left corner
282            final double xOffset = -tanOfHalfFOV * (verticalFOV ? aspect : 1.0);
283            final double yOffset = tanOfHalfFOV * (verticalFOV ? 1.0 : 1.0 / aspect);
284
285            // Compute scale factor as 2/viewport.width or height, after adjusting for fov
286            final double scale = 2.0 * tanOfHalfFOV /
287                    (verticalFOV ? viewHeight : viewWidth);
288
289            view.setToTranslation(xOffset, yOffset, 0.0);
290            view.concatenate(LOOK_AT_TX);
291            view.scale(scale, scale, scale);
292        }
293    }
294
295    @Override
296    Vec3d computePosition(Vec3d position) {
297        if (position == null) {
298            position = new Vec3d();
299        }
300
301        if (fixedEyeAtCameraZero) {
302            position.set(0.0, 0.0, 0.0);
303        } else {
304            final double halfViewWidth = getViewWidth() / 2.0;
305            final double halfViewHeight = getViewHeight() / 2.0;
306            final double halfViewDim = isVerticalFieldOfView()
307                    ? halfViewHeight : halfViewWidth;
308            final double distanceZ = halfViewDim
309                    / Math.tan(Math.toRadians(getFieldOfView() / 2.0));
310
311            position.set(halfViewWidth, halfViewHeight, -distanceZ);
312        }
313        return position;
314    }
315}