All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javafx.scene.Camera Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javafx.scene;

import javafx.beans.InvalidationListener;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Point2D;
import javafx.geometry.Point3D;
import javafx.scene.transform.Transform;
import com.sun.javafx.geom.BaseBounds;
import com.sun.javafx.geom.BoxBounds;
import com.sun.javafx.geom.PickRay;
import com.sun.javafx.geom.Vec3d;
import com.sun.javafx.geom.transform.Affine3D;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.javafx.geom.transform.GeneralTransform3D;
import com.sun.javafx.geom.transform.NoninvertibleTransformException;
import com.sun.javafx.scene.CameraHelper;
import com.sun.javafx.scene.DirtyBits;
import com.sun.javafx.scene.NodeHelper;
import com.sun.javafx.scene.transform.TransformHelper;
import com.sun.javafx.sg.prism.NGCamera;
import com.sun.javafx.logging.PlatformLogger;


/**
 * Base class for a camera used to render a scene.
 * The camera defines the mapping of the scene coordinate space onto the window.
 * Camera is an abstract class with two concrete subclasses:
 * {@link ParallelCamera} and {@link PerspectiveCamera}.
 *
 * 

* The default camera is positioned in the scene such that its projection plane * in the scene coordinate space is at Z = 0, and it is looking into the screen in * the positive Z direction. The distance in Z from the camera to the projection * plane is determined by the {@code width} and {@code height} of the Scene to * which it is attached and its {@code fieldOfView}. *

* *

* The {@code nearClip} and {@code farClip} of this camera are specified in the * eye coordinate space. This space is defined such that the eye is at its * origin and the projection plane is one unit in front of the eye in the * positive Z direction. *

* *

* The following pseudo code is the math used to compute the near and far clip * distances in the scene coordinate space: *

* *
 * final double tanOfHalfFOV = Math.tan(Math.toRadians(FOV) / 2.0);
 * final double halfHeight = HEIGHT / 2;
 * final double focalLenght = halfHeight / tanOfHalfFOV;
 * final double eyePositionZ = -1.0 * focalLenght;
 * final double nearClipDistance = focalLenght * NEAR + eyePositionZ;
 * final double farClipDistance = focalLenght * FAR + eyePositionZ;
 * 
* *

* where {@code FOV} is {@code fieldOfView} in degrees, * {@code NEAR} is {@code nearClip} specified in eye space, * and {@code FAR} is {@code farClip} specified in eye space. *

* *

* Note: Since the ParallelCamera class has no {@code fieldOfView} property, a * 30 degrees vertical field of view is used. *

* *

* Note: For the case of a PerspectiveCamera where the fixedEyeAtCameraZero * attribute is true, the scene coordinate space is normalized in order to fit * into the view frustum (see {@link PerspectiveCamera} for more details). In * this mode, the eye coordinate space is the same as this Camera node's local * coordinate space. Hence the conversion formula mentioned above is not used. *

* *

* An application should not extend the Camera class directly. Doing so may lead to * an UnsupportedOperationException being thrown. *

* * @since JavaFX 2.0 */ public abstract class Camera extends Node { static { // This is used by classes in different packages to get access to // private and package private methods. CameraHelper.setCameraAccessor(new CameraHelper.CameraAccessor() { @Override public void doMarkDirty(Node node, DirtyBits dirtyBit) { ((Camera) node).doMarkDirty(dirtyBit); } @Override public void doUpdatePeer(Node node) { ((Camera) node).doUpdatePeer(); } @Override public BaseBounds doComputeGeomBounds(Node node, BaseBounds bounds, BaseTransform tx) { return ((Camera) node).doComputeGeomBounds(bounds, tx); } @Override public boolean doComputeContains(Node node, double localX, double localY) { return ((Camera) node).doComputeContains(localX, localY); } @Override public Point2D project(Camera camera, Point3D p) { return camera.project(p); } @Override public Point2D pickNodeXYPlane(Camera camera, Node node, double x, double y) { return camera.pickNodeXYPlane(node, x, y); } @Override public Point3D pickProjectPlane(Camera camera, double x, double y) { return camera.pickProjectPlane(x, y); } }); } private Affine3D localToSceneTx = new Affine3D(); { // To initialize the class helper at the begining each constructor of this class CameraHelper.initHelper(this); } /** * Constructor for subclasses to call. */ protected Camera() { InvalidationListener dirtyTransformListener = observable -> NodeHelper.markDirty(this, DirtyBits.NODE_CAMERA_TRANSFORM); this.localToSceneTransformProperty().addListener(dirtyTransformListener); // if camera is removed from scene it needs to stop using its transforms this.sceneProperty().addListener(dirtyTransformListener); } // NOTE: farClipInScene and nearClipInScene are valid only if there is no rotation private double farClipInScene; private double nearClipInScene; // only one of them can be non-null at a time private Scene ownerScene = null; private SubScene ownerSubScene = null; private GeneralTransform3D projViewTx = new GeneralTransform3D(); private GeneralTransform3D projTx = new GeneralTransform3D(); private Affine3D viewTx = new Affine3D(); private double viewWidth = 1.0; private double viewHeight = 1.0; private Vec3d position = new Vec3d(); private boolean clipInSceneValid = false; private boolean projViewTxValid = false; private boolean localToSceneValid = false; private boolean sceneToLocalValid = false; double getFarClipInScene() { updateClipPlane(); return farClipInScene; } double getNearClipInScene() { updateClipPlane(); return nearClipInScene; } private void updateClipPlane() { if (!clipInSceneValid) { final Transform localToSceneTransform = getLocalToSceneTransform(); nearClipInScene = localToSceneTransform.transform(0, 0, getNearClip()).getZ(); farClipInScene = localToSceneTransform.transform(0, 0, getFarClip()).getZ(); clipInSceneValid = true; } } /** * An affine transform that holds the computed scene-to-local transform. * It is used to convert node to camera coordinate when rotation is involved. */ private Affine3D sceneToLocalTx = new Affine3D(); Affine3D getSceneToLocalTransform() { if (!sceneToLocalValid) { sceneToLocalTx.setTransform(getCameraTransform()); try { sceneToLocalTx.invert(); } catch (NoninvertibleTransformException ex) { String logname = Camera.class.getName(); PlatformLogger.getLogger(logname).severe("getSceneToLocalTransform", ex); sceneToLocalTx.setToIdentity(); } sceneToLocalValid = true; } return sceneToLocalTx; } /** * Specifies the distance from the eye of the near clipping plane of * this {@code Camera} in the eye coordinate space. * Objects closer to the eye than {@code nearClip} are not drawn. * {@code nearClip} is specified as a value greater than zero. A value less * than or equal to zero is treated as a very small positive number. * * @defaultValue 0.1 * @since JavaFX 8.0 */ private DoubleProperty nearClip; public final void setNearClip(double value){ nearClipProperty().set(value); } public final double getNearClip() { return nearClip == null ? 0.1 : nearClip.get(); } public final DoubleProperty nearClipProperty() { if (nearClip == null) { nearClip = new SimpleDoubleProperty(Camera.this, "nearClip", 0.1) { @Override protected void invalidated() { clipInSceneValid = false; NodeHelper.markDirty(Camera.this, DirtyBits.NODE_CAMERA); } }; } return nearClip; } /** * Specifies the distance from the eye of the far clipping plane of * this {@code Camera} in the eye coordinate space. * Objects farther away from the eye than {@code farClip} are not * drawn. * {@code farClip} is specified as a value greater than {@code nearClip}. * A value less than or equal to {@code nearClip} is treated as * {@code nearClip} plus a very small positive number. * * @defaultValue 100.0 * @since JavaFX 8.0 */ private DoubleProperty farClip; public final void setFarClip(double value){ farClipProperty().set(value); } public final double getFarClip() { return farClip == null ? 100.0 : farClip.get(); } public final DoubleProperty farClipProperty() { if (farClip == null) { farClip = new SimpleDoubleProperty(Camera.this, "farClip", 100.0) { @Override protected void invalidated() { clipInSceneValid = false; NodeHelper.markDirty(Camera.this, DirtyBits.NODE_CAMERA); } }; } return farClip; } Camera copy() { return this; } /* * Note: This method MUST only be called via its accessor method. */ private void doUpdatePeer() { NGCamera peer = getPeer(); if (!NodeHelper.isDirtyEmpty(this)) { if (isDirty(DirtyBits.NODE_CAMERA)) { peer.setNearClip((float) getNearClip()); peer.setFarClip((float) getFarClip()); peer.setViewWidth(getViewWidth()); peer.setViewHeight(getViewHeight()); } if (isDirty(DirtyBits.NODE_CAMERA_TRANSFORM)) { // TODO: 3D - For now, we are treating the scene as world. // This may need to change for the fixed eye position case. peer.setWorldTransform(getCameraTransform()); } peer.setProjViewTransform(getProjViewTransform()); position = computePosition(position); getCameraTransform().transform(position, position); peer.setPosition(position); } } void setViewWidth(double width) { this.viewWidth = width; NodeHelper.markDirty(this, DirtyBits.NODE_CAMERA); } double getViewWidth() { return viewWidth; } void setViewHeight(double height) { this.viewHeight = height; NodeHelper.markDirty(this, DirtyBits.NODE_CAMERA); } double getViewHeight() { return viewHeight; } void setOwnerScene(Scene s) { if (s == null) { ownerScene = null; } else if (s != ownerScene) { if (ownerScene != null || ownerSubScene != null) { throw new IllegalArgumentException(this + "is already set as camera in other scene or subscene"); } ownerScene = s; markOwnerDirty(); } } void setOwnerSubScene(SubScene s) { if (s == null) { ownerSubScene = null; } else if (s != ownerSubScene) { if (ownerScene != null || ownerSubScene != null) { throw new IllegalArgumentException(this + "is already set as camera in other scene or subscene"); } ownerSubScene = s; markOwnerDirty(); } } /* * Note: This method MUST only be called via its accessor method. */ private void doMarkDirty(DirtyBits dirtyBit) { if (dirtyBit == DirtyBits.NODE_CAMERA_TRANSFORM) { localToSceneValid = false; sceneToLocalValid = false; clipInSceneValid = false; projViewTxValid = false; } else if (dirtyBit == DirtyBits.NODE_CAMERA) { projViewTxValid = false; } markOwnerDirty(); } private void markOwnerDirty() { // if the camera is part of the scene/subScene, we will need to notify // the owner to mark the entire scene/subScene dirty. if (ownerScene != null) { ownerScene.markCameraDirty(); } if (ownerSubScene != null) { ownerSubScene.markContentDirty(); } } /** * Returns the local-to-scene transform of this camera. * Package private, for use in our internal subclasses. * Returns directly the internal instance, it must not be altered. */ Affine3D getCameraTransform() { if (!localToSceneValid) { localToSceneTx.setToIdentity(); TransformHelper.apply(getLocalToSceneTransform(), localToSceneTx); localToSceneValid = true; } return localToSceneTx; } abstract void computeProjectionTransform(GeneralTransform3D proj); abstract void computeViewTransform(Affine3D view); /** * Returns the projView transform of this camera. * Package private, for internal use. * Returns directly the internal instance, it must not be altered. */ GeneralTransform3D getProjViewTransform() { if (!projViewTxValid) { computeProjectionTransform(projTx); computeViewTransform(viewTx); projViewTx.set(projTx); projViewTx.mul(viewTx); projViewTx.mul(getSceneToLocalTransform()); projViewTxValid = true; } return projViewTx; } /** * Transforms the given 3D point to the flat projected coordinates. */ private Point2D project(Point3D p) { final Vec3d vec = getProjViewTransform().transform(new Vec3d( p.getX(), p.getY(), p.getZ())); final double halfViewWidth = getViewWidth() / 2.0; final double halfViewHeight = getViewHeight() / 2.0; return new Point2D( halfViewWidth * (1 + vec.x), halfViewHeight * (1 - vec.y)); } /** * Computes intersection point of the pick ray cast by the given coordinates * and the node's local XY plane. */ private Point2D pickNodeXYPlane(Node node, double x, double y) { final PickRay ray = computePickRay(x, y, null); final Affine3D localToScene = new Affine3D(); TransformHelper.apply(node.getLocalToSceneTransform(), localToScene); final Vec3d o = ray.getOriginNoClone(); final Vec3d d = ray.getDirectionNoClone(); try { localToScene.inverseTransform(o, o); localToScene.inverseDeltaTransform(d, d); } catch (NoninvertibleTransformException e) { return null; } if (almostZero(d.z)) { return null; } final double t = -o.z / d.z; return new Point2D(o.x + (d.x * t), o.y + (d.y * t)); } /** * Computes intersection point of the pick ray cast by the given coordinates * and the projection plane. */ Point3D pickProjectPlane(double x, double y) { final PickRay ray = computePickRay(x, y, null); final Vec3d p = new Vec3d(); p.add(ray.getOriginNoClone(), ray.getDirectionNoClone()); return new Point3D(p.x, p.y, p.z); } /** * Computes pick ray for the content rendered by this camera. * @param x horizontal coordinate of the pick ray in the projected * view, usually mouse cursor position * @param y vertical coordinate of the pick ray in the projected * view, usually mouse cursor position * @param pickRay pick ray to be reused. New instance is created in case * of null. * @return The PickRay instance computed based on this camera and the given * arguments. */ abstract PickRay computePickRay(double x, double y, PickRay pickRay); /** * Computes local position of the camera in the scene. * @param position Position to be reused. New instance is created in case * of null. * @return The position of the camera in the scene in camera local coords */ abstract Vec3d computePosition(Vec3d position); /* * Note: This method MUST only be called via its accessor method. */ private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) { return new BoxBounds(0, 0, 0, 0, 0, 0); } /* * Note: This method MUST only be called via its accessor method. */ private boolean doComputeContains(double localX, double localY) { return false; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy