/*
* Copyright (C) 2013-2015 F(X)yz,
* Sean Phillips, Jason Pollastrini and Jose Pereda
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.fxyz.extras;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Bounds;
import javafx.geometry.Point3D;
import javafx.scene.Node;
import javafx.scene.shape.Shape3D;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Transform;
import org.fxyz.geometry.Vector3D;
/**
* Basic interface for Billboard Nodes.
* ie: Keeps this Node oriented towards specified Node.
*
* @author jdub1581
* @param <T> Type of node to be used for (this) "Billboard".
*/
public interface BillboardBehavior<T extends Node>{
/**
* Spherical means object will look at other on all axes
* Cylindrical means object will rotate on Y axis only
*/
public enum BillboardMode{
SPHERICAL,
CYLINDRICAL;
}
static BillboardTimer timer = new BillboardTimer();
/**
*
* @return The node to be used for this behavior.
*/
public T getBillboardNode();
/**
*
* @return The node to look at.
*/
public Node getOther();
public Affine affine = new Affine();
/**
* Adds the Affine transform to Node and starts timer.
*/
default void startBillboardBehavior(){
if(timer.getUpdateList().isEmpty()){
timer.addUpdate(() -> {
updateMatrix();
return null;
});
}
getBillboardNode().getTransforms().addAll(affine);
timer.start();
}
/**
* Stops timer and removes transform
*/
default void stopBillboardBehavior(){
timer.stop();
getBillboardNode().getTransforms().clear();
}
/**
* Updates the transformation matrix.
* can change the Translate for fixed distance
*/
default void updateMatrix(){
Transform cam = getOther().getLocalToSceneTransform(),
self = getBillboardNode().getLocalToSceneTransform();
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);
Vector3D up = Vector3D.UP,
forward = new Vector3D(
(selfPos.getX()) - camPos.getX(),
(selfPos.getY()) - camPos.getY(),
(selfPos.getZ()) - camPos.getZ()
).toNormal(),
right = up.crossProduct(forward).toNormal();
up = forward.crossProduct(right).toNormal();
switch(getBillboardMode()){
case SPHERICAL:
affine.setMxx(right.x); affine.setMxy(up.x); affine.setMzx(forward.x);
affine.setMyx(right.y); affine.setMyy(up.y); affine.setMzy(forward.y);
affine.setMzx(right.z); affine.setMzy(up.z); affine.setMzz(forward.z);
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.x); affine.setMxy(0); affine.setMzx(forward.x);
affine.setMyx(0); affine.setMyy(1); affine.setMzy(0);
affine.setMzx(right.z); affine.setMzy(0); affine.setMzz(forward.z);
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;
}
}
ObjectProperty<BillboardMode> mode = new SimpleObjectProperty<>(BillboardMode.SPHERICAL);
default BillboardMode getBillboardMode(){
return mode.get();
}
default void setBillboardMode(BillboardMode m){
mode.set(m);
}
default ObjectProperty<BillboardMode> getBillboardModeProperty(){
return mode;
}
}