/** * BillboardNode.java * * Copyright (c) 2013-2016, F(X)yz * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of F(X)yz, any associated website, nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL F(X)yz BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.fxyz3d.scene; import java.util.Collection; import javafx.animation.AnimationTimer; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.geometry.Bounds; import javafx.geometry.Point3D; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.shape.Shape3D; import javafx.scene.transform.Affine; import javafx.scene.transform.Transform; /** * * @author Jason Pollastrini aka jdub1581 * @param <T> */ public abstract class BillboardNode<T extends Node> extends Group { public enum BillboardMode { SPHERICAL, CYLINDRICAL; } protected abstract T getBillboardNode(); protected abstract Node getTarget(); private final Affine affine; private final AnimationTimer timer; public BillboardNode() { affine = new Affine(); getTransforms().add(affine); timer = new AnimationTimer() { @Override public void handle(long now) { updateMatrix(); //System.out.println("Updated Matrix " + now / 1000000000); } }; } private BillboardNode(Node... children) { throw new UnsupportedOperationException(" not supported yet."); } private BillboardNode(Collection<Node> children) { throw new UnsupportedOperationException(" not supported yet."); } public final void startBillboarding() { timer.start(); } public final void stopBillboarding() { timer.stop(); } private void updateMatrix() { Transform cam = getTarget().getLocalToSceneTransform(), self = getBillboardNode().getLocalToSceneTransform(); if (cam != null && self != null) { Bounds b; double cX, cY, cZ; if (!(getBillboardNode() instanceof Shape3D)) { b = getBillboardNode().getBoundsInLocal(); cX = b.getWidth() / 2; cY = b.getHeight() / 2; cZ = b.getDepth() / 2; } else { cX = self.getTx(); cY = self.getTy(); cZ = self.getTz(); } Point3D camPos = new Point3D(cam.getTx(), cam.getTy(), cam.getTz()); Point3D selfPos = new Point3D(cX, cY, cZ); Point3D up = Point3D.ZERO.add(0, -1, 0), forward = new Point3D( (selfPos.getX()) - camPos.getX(), (selfPos.getY()) - camPos.getY(), (selfPos.getZ()) - camPos.getZ() ).normalize(), right = up.crossProduct(forward).normalize(); up = forward.crossProduct(right).normalize(); switch (getBillboardMode()) { case SPHERICAL: affine.setMxx(right.getX()); affine.setMxy(up.getX()); affine.setMzx(forward.getX()); affine.setMyx(right.getY()); affine.setMyy(up.getY()); affine.setMzy(forward.getY()); affine.setMzx(right.getZ()); affine.setMzy(up.getZ()); affine.setMzz(forward.getZ()); affine.setTx(cX * (1 - affine.getMxx()) - cY * affine.getMxy() - cZ * affine.getMxz()); affine.setTy(cY * (1 - affine.getMyy()) - cX * affine.getMyx() - cZ * affine.getMyz()); affine.setTz(cZ * (1 - affine.getMzz()) - cX * affine.getMzx() - cY * affine.getMzy()); break; case CYLINDRICAL: affine.setMxx(right.getX()); affine.setMxy(0); affine.setMzx(forward.getY()); affine.setMyx(0); affine.setMyy(1); affine.setMzy(0); affine.setMzx(right.getZ()); affine.setMzy(0); affine.setMzz(forward.getZ()); affine.setTx(cX * (1 - affine.getMxx()) - cY * affine.getMxy() - cZ * affine.getMxz()); affine.setTy(cY * (1 - affine.getMyy()) - cX * affine.getMyx() - cZ * affine.getMyz()); affine.setTz(cZ * (1 - affine.getMzz()) - cX * affine.getMzx() - cY * affine.getMzy()); break; } } } private final ObjectProperty<BillboardMode> mode = new SimpleObjectProperty<>(this, "mode"); public final BillboardMode getBillboardMode() { return mode.getValue(); } public final void setBillboardMode(BillboardMode m) { mode.set(m); } public final ObjectProperty<BillboardMode> billboardModeProperty() { return mode; } private final BooleanProperty active = new SimpleBooleanProperty(this, "Billboarding Active") { @Override protected void invalidated() { if (getValue()) { startBillboarding(); System.out.println("timer started"); } else if (!getValue()) { stopBillboarding(); System.out.println("timer stopped"); } } }; public final boolean isActive() { return active.getValue(); } public final void setActive(boolean b) { active.set(b); } public final BooleanProperty activeProperty() { return active; } }