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

javafx.scene.LightBase Maven / Gradle / Ivy

There is a newer version: 24-ea+15
Show newest version
/*
 * Copyright (c) 2013, 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 com.sun.javafx.collections.TrackableObservableList;
import com.sun.javafx.geom.BaseBounds;
import com.sun.javafx.geom.BoxBounds;
import com.sun.javafx.geom.transform.Affine3D;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.javafx.scene.DirtyBits;
import com.sun.javafx.scene.LightBaseHelper;
import com.sun.javafx.scene.NodeHelper;
import com.sun.javafx.scene.transform.TransformHelper;
import com.sun.javafx.sg.prism.NGLightBase;
import com.sun.javafx.sg.prism.NGNode;
import com.sun.javafx.tk.Toolkit;

import java.util.List;
import java.util.stream.Collectors;

import javafx.application.ConditionalFeature;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.scene.paint.Color;
import javafx.scene.shape.Shape3D;
import com.sun.javafx.logging.PlatformLogger;

/**
 * The {@code LightBase} class is the base class for all objects that represent a form of light source. All light types
 * share the following common properties that are defined in this class:
 * 
    *
  • {@code color} - the color of the light source
  • *
  • {@code scope} - a list of nodes the light source affects
  • *
  • {@code exlusionScope} - a list of nodes the light source does not affect
  • *
* * In addition to these, each light type supports a different set of properties, summarized in the following table: * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary of Light types by attributes
Light typeRotation and DirectionLocation and Attenuation
{@link AmbientLight}
{@link DirectionalLight}
{@link PointLight}
{@link SpotLight}*
* * Supports spotlight attenuation factors as described in its class docs. * *

* An application cannot add its own light types. Extending {@code LightBase} directly may lead to an * {@code UnsupportedOperationException} being thrown. *

* All light types are not affected by scaling and shearing transforms. * *

Color

* A light source produces a light of a single color. The appearance of an object illuminated by the light is a result * of the color of the light and the material the object is made of. For example, for a blue light, a white object will * appear blue, a black object will appear black, and a (255, 0, 255)-colored object will appear blue, as the light * "selects" only the blue component of the material color. The mathematical definition of the material-light * interaction is available in {@link javafx.scene.paint.PhongMaterial PhongMaterial}. *

* See also the implementation notes section. * *

Scopes

* A light has a {@code scope} list and an {@code exclusionScope} list that define which nodes are illuminated by it and * which aren't. A node can exist in only one of the lists: if it is added to one, it is silently removed from the * other. If a node does not exist in any list, it inherits its affected state from its parent, recursively. An * exception to this is that a light with an empty {@code scope} affects all nodes in its scene/subscene implicitly * (except for those in its {@code exlusionScope}) as if the root of the scene is in the {@code scope}. *

* The {@code exlusionScope} is useful only for nodes that would otherwise be in scope of the light. Excluding a node is * a convenient alternative to traversing the scenegraph hierarchy and adding all of the other nodes to the light's * scope. Instead, the scope can remain wide, and specific nodes can be excluded with the exclusion scope. * *

Direction

* The direction of the light is defined by the {@code direction} vector property of the light. The light's * direction can be rotated by setting a rotation transform on the light. For example, if the direction vector is * {@code (1, 1, 1)} and the light is not rotated, it will point in the {@code (1, 1, 1)} direction, and if the light is * rotated 90 degrees on the y axis, it will point in the {@code (1, 1, -1)} direction. *

* Light types that do not have a direction are not affected by rotation transforms. * *

Distance Attenuation

* Any pixel within the range of the light will be illuminated by it (unless it belongs to a {@code Shape3D} outside of * its scope). The distance is measured from the light's position to the pixel's position, and is * checked to be within the maximum range of the light, as defined by its {@code maxRange} property. The light's * position can be changed by setting a translation transform on the light. *

* The light's intensity can be set to change over distance by attenuating it. The attenuation formula *

* {@code attn = 1 / (ca + la * dist + qa * dist^2)} *

* defines 3 coefficients: {@code ca}, {@code la}, and {@code qa}, which control the constant, linear, and quadratic * behaviors of intensity falloff over distance, respectively. The effective color of the light at a given point in * space is {@code color * attn}. It is possible, albeit unrealistic, to specify negative values to attenuation * coefficients. This allows the resulting attenuation factor to be negative, which results in the light's color being * subtracted from the material color instead of added to it, thus creating a "shadow caster". *

* For a realistic effect, {@code maxRange} should be set to a distance at which the attenuation is close to 0, as this * will give a soft cutoff. *

* Light types that are not distance-attenuated are not affected by translation transforms. For these types, a decrease * in intensity can be achieved by using a darker color. * *

* Note: this is a conditional feature. See * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D} for more information. * * @implNote * The following applies to the {@code color} property of the light: *

    *
  1. A black colored light is ignored since its contribution is 0. *
  2. The transparency (alpha) component of a light is ignored. *
* These behaviors are not specified and could change in the future. * * @since JavaFX 8.0 */ public abstract class LightBase extends Node { static { // This is used by classes in different packages to get access to // private and package private methods. LightBaseHelper.setLightBaseAccessor(new LightBaseHelper.LightBaseAccessor() { @Override public void doMarkDirty(Node node, DirtyBits dirtyBit) { ((LightBase) node).doMarkDirty(dirtyBit); } @Override public void doUpdatePeer(Node node) { ((LightBase) node).doUpdatePeer(); } @Override public BaseBounds doComputeGeomBounds(Node node, BaseBounds bounds, BaseTransform tx) { return ((LightBase) node).doComputeGeomBounds(bounds, tx); } @Override public boolean doComputeContains(Node node, double localX, double localY) { return ((LightBase) node).doComputeContains(localX, localY); } }); } private Affine3D localToSceneTx = new Affine3D(); { // To initialize the class helper at the beginning of each constructor of this class LightBaseHelper.initHelper(this); } /** * Creates a new instance of {@code LightBase} class with a default Color.WHITE light source. */ protected LightBase() { this(Color.WHITE); } /** * Creates a new instance of {@code LightBase} class using the specified color. * * @param color the color of the light source */ protected LightBase(Color color) { if (!Platform.isSupported(ConditionalFeature.SCENE3D)) { String logname = LightBase.class.getName(); PlatformLogger.getLogger(logname).warning("System can't support " + "ConditionalFeature.SCENE3D"); } setColor(color); this.localToSceneTransformProperty().addListener(observable -> NodeHelper.markDirty(this, DirtyBits.NODE_LIGHT_TRANSFORM)); } /** * Specifies the color of light source. * * @defaultValue null */ private ObjectProperty color; public final void setColor(Color value) { colorProperty().set(value); } public final Color getColor() { return color == null ? null : color.get(); } public final ObjectProperty colorProperty() { if (color == null) { color = new SimpleObjectProperty<>(LightBase.this, "color") { @Override protected void invalidated() { NodeHelper.markDirty(LightBase.this, DirtyBits.NODE_LIGHT); } }; } return color; } /** * Defines the light on or off. * * @defaultValue true */ private BooleanProperty lightOn; public final void setLightOn(boolean value) { lightOnProperty().set(value); } public final boolean isLightOn() { return lightOn == null ? true : lightOn.get(); } public final BooleanProperty lightOnProperty() { if (lightOn == null) { lightOn = new SimpleBooleanProperty(LightBase.this, "lightOn", true) { @Override protected void invalidated() { NodeHelper.markDirty(LightBase.this, DirtyBits.NODE_LIGHT); } }; } return lightOn; } private ObservableList scope; /** * Gets the list of nodes that specifies the hierarchical scope of this light. Any {@code Shape3D}s in this list or * under a {@code Parent} in this list are affected by this light, unless a closer parent exists in the * {@code exclusionScope} list. If the list is empty, all nodes under the light's scene/subscene are affected by it * (unless they are in the {@code exclusionScope}). * * @return the list of nodes that specifies the hierarchical scope of this light * @see #getExclusionScope */ public ObservableList getScope() { if (scope == null) { scope = new TrackableObservableList<>() { @Override protected void onChanged(Change c) { doOnChanged(c, exclusionScope); } }; } return scope; } private ObservableList exclusionScope; /** * Gets the list of nodes that specifies the hierarchical exclusion scope of this light. Any {@code Shape3D}s in * this list or under a {@code Parent} in this list are not affected by this light, unless a closer parent exists in * the {@code scope} list.
* This is a convenience list for excluding nodes that would otherwise be in scope of the light. * * @return the list of nodes that specifies the hierarchical exclusion scope of this light * @see #getScope * @since 13 */ public ObservableList getExclusionScope() { if (exclusionScope == null) { exclusionScope = new TrackableObservableList<>() { @Override protected void onChanged(Change c) { doOnChanged(c, scope); } }; } return exclusionScope; } private void doOnChanged(Change c, ObservableList otherScope) { NodeHelper.markDirty(this, DirtyBits.NODE_LIGHT_SCOPE); while (c.next()) { c.getRemoved().forEach(this::markChildrenDirty); c.getAddedSubList().forEach(node -> { if (otherScope != null && otherScope.remove(node)) { return; // the other list will take care of the change } markChildrenDirty(node); }); } } @Override void scenesChanged(final Scene newScene, final SubScene newSubScene, final Scene oldScene, final SubScene oldSubScene) { // This light is owned by the Scene/SubScene, and thus must change // accordingly. Note lights can owned by either a Scene or SubScene, // but not both. if (oldSubScene != null) { oldSubScene.removeLight(this); } else if (oldScene != null) { oldScene.removeLight(this); } if (newSubScene != null) { newSubScene.addLight(this); } else if (newScene != null) { newScene.addLight(this); } } /** * For use by implementing subclasses. Treat as protected. * * Creates and returns a SimpleDoubleProperty with an invalidation scheme. */ DoubleProperty getLightDoubleProperty(String name, double initialValue) { return new SimpleDoubleProperty(this, name, initialValue) { @Override protected void invalidated() { NodeHelper.markDirty(LightBase.this, DirtyBits.NODE_LIGHT); } }; } private void markOwnerDirty() { // if the light is part of the scene/subScene, we will need to notify // the owner to mark the entire scene/subScene dirty. SubScene subScene = getSubScene(); if (subScene != null) { subScene.markContentDirty(); } else { Scene scene = getScene(); if (scene != null) { scene.setNeedsRepaint(); } } } /** * Marks dirty all the 3D shapes that had their scoped/excluded state change. The method recursively traverses the * given node's graph top-down to find all the leaves (3D shapes). Nodes that are not contained in one of the scope * lists inherit their parent's scope, and nodes that are contained in one of the lists override their parent's * state. For this reason, when traversing the graph, if a node that is contained in a list is reached, its branch * is skipped. * * @param node the node that was added/removed from a scope */ private void markChildrenDirty(Node node) { if (node instanceof Shape3D) { // Dirty using a lightweight DirtyBits.NODE_DRAWMODE bit NodeHelper.markDirty(node, DirtyBits.NODE_DRAWMODE); } else if (node instanceof Parent) { for (Node child : ((Parent) node).getChildren()) { if ((scope != null && getScope().contains(child)) || (exclusionScope != null && getExclusionScope().contains(child))) { continue; // child overrides parent, no need to propagate the change } markChildrenDirty(child); } } } /* * Note: This method MUST only be called via its accessor method. */ private void doMarkDirty(DirtyBits dirtyBit) { if ((scope == null) || getScope().isEmpty()) { // This light affects the entire scene/subScene markOwnerDirty(); } else if (dirtyBit != DirtyBits.NODE_LIGHT_SCOPE) { // Skip NODE_LIGHT_SCOPE dirty since it is processed on scope change. getScope().forEach(this::markChildrenDirty); } } /* * Note: This method MUST only be called via its accessor method. */ private void doUpdatePeer() { NGLightBase peer = getPeer(); if (isDirty(DirtyBits.NODE_LIGHT)) { peer.setColor((getColor() == null) ? Toolkit.getPaintAccessor().getPlatformPaint(Color.WHITE) : Toolkit.getPaintAccessor().getPlatformPaint(getColor())); peer.setLightOn(isLightOn()); } if (isDirty(DirtyBits.NODE_LIGHT_SCOPE)) { if (scope != null) { if (getScope().isEmpty()) { peer.setScope(List.of()); } else { peer.setScope(getScope().stream().map(n -> n.getPeer()).collect(Collectors.toList())); } } if (exclusionScope != null) { if (getExclusionScope().isEmpty()) { peer.setExclusionScope(List.of()); } else { peer.setExclusionScope(getExclusionScope().stream().map(n -> n.getPeer()).collect(Collectors.toList())); } } } if (isDirty(DirtyBits.NODE_LIGHT_TRANSFORM)) { localToSceneTx.setToIdentity(); TransformHelper.apply(getLocalToSceneTransform(), localToSceneTx); // TODO: 3D - For now, we are treating the scene as world. This may need to change // for the fixed eye position case. peer.setWorldTransform(localToSceneTx); } } /* * Note: This method MUST only be called via its accessor method. */ private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) { // TODO: 3D - Check is this the right default return new BoxBounds(); } /* * Note: This method MUST only be called via its accessor method. */ private boolean doComputeContains(double localX, double localY) { // TODO: 3D - Check is this the right default return false; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy