/*
* Copyright (c) 2010, 2013 Oracle and/or its affiliates.
* All rights reserved. Use is subject to license terms.
*
* This file is available and licensed under the following license:
*
* 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 Oracle Corporation 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 THE COPYRIGHT
* OWNER OR CONTRIBUTORS 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 de.tesis.dynaware.javafx.graphics.viewer;
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.RotateTransition;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.util.Duration;
/**
* Stores the content of the 3D scene including meshes, lights, and cameras.
*/
public class FbxViewerModel {
private final ObjectProperty<Node> contentProperty = new SimpleObjectProperty<>();
private final Group root = new Group();
private final SubScene subScene;
private final PerspectiveCamera camera = new PerspectiveCamera(true);
private final Rotate cameraXRotate = new Rotate(-20, 0, 0, 0, Rotate.X_AXIS);
private final Rotate cameraYRotate = new Rotate(-20, 0, 0, 0, Rotate.Y_AXIS);
private final Translate cameraPosition = new Translate(0, 0, -20);
private double dragStartX, dragStartY, dragStartRotateX, dragStartRotateY;
private RotateTransition rotateTransition;
private boolean isRotating;
private double scaleFactor = 1;
/**
* Creates a content model for the 3D scene.
*/
public FbxViewerModel() {
subScene = new SubScene(root, 400, 400, true, SceneAntialiasing.BALANCED);
subScene.setFill(Color.ALICEBLUE);
camera.setNearClip(1);
camera.setFarClip(10000);
camera.getTransforms().addAll(cameraXRotate, cameraYRotate, cameraPosition);
subScene.setCamera(camera);
root.getChildren().add(camera);
subScene.addEventHandler(MouseEvent.ANY, new EventHandler<MouseEvent>() {
@Override
public void handle(final MouseEvent event) {
if (event.getEventType() == MouseEvent.MOUSE_PRESSED) {
dragStartX = event.getSceneX();
dragStartY = event.getSceneY();
dragStartRotateX = cameraXRotate.getAngle();
dragStartRotateY = cameraYRotate.getAngle();
} else if (event.getEventType() == MouseEvent.MOUSE_DRAGGED) {
double xDelta = event.getSceneX() - dragStartX;
double yDelta = event.getSceneY() - dragStartY;
cameraXRotate.setAngle(dragStartRotateX - (yDelta * 0.7));
cameraYRotate.setAngle(dragStartRotateY + (xDelta * 0.7));
}
}
});
subScene.addEventHandler(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
@Override
public void handle(final ScrollEvent event) {
double z = cameraPosition.getZ() - (event.getDeltaY() * 0.1 * scaleFactor);
z = Math.min(z, 0);
cameraPosition.setZ(z);
}
});
contentProperty.addListener(new ChangeListener<Node>() {
@Override
public void changed(final ObservableValue<? extends Node> ov, final Node oldContent, final Node newContent) {
root.getChildren().remove(oldContent);
if (newContent!=null) {
root.getChildren().add(newContent);
adjustForSize();
rotateTransition = new RotateTransition();
rotateTransition.setAxis(Rotate.Y_AXIS);
rotateTransition.setDelay(Duration.millis(4));
rotateTransition.setDuration(Duration.millis(5000));
rotateTransition.setCycleCount(Animation.INDEFINITE);
rotateTransition.setAutoReverse(false);
rotateTransition.setInterpolator(Interpolator.LINEAR);
rotateTransition.setByAngle(360);
rotateTransition.setNode(newContent);
if (isRotating) {
rotateTransition.play();
}
}
}
});
}
/**
* Property for the content of the 3D scene.
*
* @return the content property of the 3D scene
*/
public ObjectProperty<Node> contentProperty() {
return contentProperty;
}
/**
* Gets the current content of the 3D scene.
*
* @return the current content of the 3D scene
*/
public Node getContent() {
return contentProperty.get();
}
/**
* Sets the content to be displayed in the 3D scene.
*
* @return the content to be displayed
*/
public void setContent(Node content) {
contentProperty.set(content);
}
/**
* Gets the sub-scene that the 3D model is displayed in.
*
* @return the sub-scene the 3D model is displayed in
*/
public SubScene getSubScene() {
return subScene;
}
/**
* Toggles rotation of the content on and off.
*/
public void toggleRotation() {
if (isRotating) {
rotateTransition.pause();
isRotating = false;
} else {
rotateTransition.play();
isRotating = true;
}
}
/**
* Gets the 3D scene's camera.
*
* @return the perspective camera of the 3D scene
*/
public PerspectiveCamera getCamera() {
return camera;
}
/**
* Sets initial translate values and camera settings based on the size of the model.
*/
private void adjustForSize() {
Node content = contentProperty.get();
double width = content.getLayoutBounds().getWidth();
double height = content.getLayoutBounds().getHeight();
double depth = content.getLayoutBounds().getDepth();
content.setTranslateX(-content.getLayoutBounds().getMinX() - width / 2);
content.setTranslateY(-content.getLayoutBounds().getMinY() - height / 2);
content.setTranslateZ(-content.getLayoutBounds().getMinZ() - depth / 2);
scaleFactor = Math.max(Math.max(width, height), depth)/25;
cameraPosition.setZ(-60*scaleFactor);
}
}