/* * 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 java.io.File; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; import java.util.ResourceBundle; import javafx.application.Platform; import javafx.beans.binding.DoubleBinding; import javafx.beans.property.DoubleProperty; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.Group; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; import javafx.scene.control.Slider; import javafx.scene.control.ToggleButton; import javafx.scene.control.Tooltip; import javafx.scene.input.DragEvent; import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.stage.FileChooser; import de.tesis.dynaware.javafx.graphics.importers.Importer3D; /** * Controller for FbxViewer.fxml. * * <p> * The buttons and controls of the viewer are configured here. * </p> */ public class FbxViewerController implements Initializable { private static final String NEAR_CLIP_TOOLTIP_TEXT = "Camera near-clip value"; private static final String FAR_CLIP_TOOLTIP_TEXT = "Camera far-clip value"; private static final String SUPPORTED_FILES = "Supported files"; private static final String SELECT_FILE_TO_LOAD = "Select file to load"; private static final double MIN_NEAR_CLIP = 0.01; private static final double MAX_NEAR_CLIP = 10; private static final double MIN_FAR_CLIP = 100; private static final double MAX_FAR_CLIP = 1e7; @FXML private Pane outerPane; @FXML private SubSceneContainer subSceneContainer; @FXML private HBox controlsOverlay; @FXML private Button openButton; @FXML private ToggleButton rotateButton; @FXML private Label nearClipLabel; @FXML private Label farClipLabel; @FXML private Slider nearClipSlider; @FXML private Slider farClipSlider; @FXML private ProgressIndicator progressIndicator; @FXML private Label status; private File loadedPath; private FbxViewerModel model; @Override public void initialize(final URL location, final ResourceBundle resources) { model = new FbxViewerModel(); subSceneContainer.setSubScene(model.getSubScene()); subSceneContainer.prefWidthProperty().bind(outerPane.widthProperty()); subSceneContainer.prefHeightProperty().bind(outerPane.heightProperty()); controlsOverlay.prefWidthProperty().bind(subSceneContainer.widthProperty().subtract(20)); rotateButton.disableProperty().bind(model.contentProperty().isNull()); initializeClipSliders(); initializeProgressIndicator(); addDragDropHandlers(); loadSample(); } /** * Initializes the sliders that set the camera near and far clip values. */ private void initializeClipSliders() { nearClipSlider.setMin(Math.log10(MIN_NEAR_CLIP)); nearClipSlider.setMax(Math.log10(MAX_NEAR_CLIP)); nearClipSlider.setValue(Math.log10(model.getCamera().getNearClip())); farClipSlider.setMin(Math.log10(MIN_FAR_CLIP)); farClipSlider.setMax(Math.log10(MAX_FAR_CLIP)); farClipSlider.setValue(Math.log10(model.getCamera().getFarClip())); nearClipSlider.setTooltip(new Tooltip(NEAR_CLIP_TOOLTIP_TEXT)); farClipSlider.setTooltip(new Tooltip(FAR_CLIP_TOOLTIP_TEXT)); model.getCamera().nearClipProperty().bind(new Power10DoubleBinding(nearClipSlider.valueProperty())); model.getCamera().farClipProperty().bind(new Power10DoubleBinding(farClipSlider.valueProperty())); nearClipLabel.textProperty().bind(model.getCamera().nearClipProperty().asString()); farClipLabel.textProperty().bind(model.getCamera().farClipProperty().asString()); } /** * Initializes the progress indicator. */ private void initializeProgressIndicator() { progressIndicator.setVisible(false); progressIndicator.layoutXProperty().bind(subSceneContainer.widthProperty().divide(2).subtract(15)); progressIndicator.layoutYProperty().bind(subSceneContainer.heightProperty().divide(2).subtract(15)); } /** * Adds handlers for dragging and dropping files into the viewer. */ private void addDragDropHandlers() { String[] supportedFormatRegex = Importer3D.getSupportedFormatExtensionFilters(); for (int i = 0; i < supportedFormatRegex.length; i++) { supportedFormatRegex[i] = "." + supportedFormatRegex[i].replaceAll("\\.", "\\."); } model.getSubScene().setOnDragOver(new EventHandler<DragEvent>() { @Override public void handle(final DragEvent event) { Dragboard db = event.getDragboard(); if (db.hasFiles()) { boolean hasSupportedFile = false; fileLoop: for (File file : db.getFiles()) { for (String format : supportedFormatRegex) { if (file.getName().toLowerCase().matches(format)) { hasSupportedFile = true; break fileLoop; } } } if (hasSupportedFile) { event.acceptTransferModes(TransferMode.COPY_OR_MOVE); } } event.consume(); } }); model.getSubScene().setOnDragDropped(new EventHandler<DragEvent>() { @Override public void handle(final DragEvent event) { Dragboard db = event.getDragboard(); boolean success = false; if (db.hasFiles()) { File supportedFile = null; fileLoop: for (File file : db.getFiles()) { for (String format : supportedFormatRegex) { if (file.getName().toLowerCase().matches(format)) { supportedFile = file; break fileLoop; } } } if (supportedFile != null) { if (supportedFile.getAbsolutePath().indexOf('%') != -1) { try { supportedFile = new File(URLDecoder.decode(supportedFile.getAbsolutePath(), "utf-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } load(supportedFile); } success = true; } event.setDropCompleted(success); event.consume(); } }); } /** * Try to load a sample FBX file. */ private void loadSample() { String userDir = System.getProperty("user.dir"); String projectDir = userDir; if (userDir.endsWith("build\\libs")) { projectDir = userDir.substring(0, userDir.length()-"build\\libs".length()); } File sample = new File(projectDir+"\\samples\\Zombie.fbx"); if (sample.exists()) { load(sample); } } @FXML private void open() { FileChooser chooser = new FileChooser(); chooser.getExtensionFilters().add( new FileChooser.ExtensionFilter(SUPPORTED_FILES, Importer3D.getSupportedFormatExtensionFilters())); if (loadedPath != null && loadedPath.exists()) { chooser.setInitialDirectory(loadedPath.getAbsoluteFile().getParentFile()); } chooser.setTitle(SELECT_FILE_TO_LOAD); File newFile = chooser.showOpenDialog(subSceneContainer.getScene().getWindow()); if (newFile != null) { load(newFile); } } @FXML private void toggleRotation() { model.toggleRotation(); } /** * * Attemps to load a file using {@link Importer3D}. * * <p> * The loading is done in a background thread so the viewer doesn't appear to hang. * </p> * * @param file the file to be loaded */ private void load(final File file) { loadedPath = file; updateStatus(""); disableControls(true); model.setContent(null); progressIndicator.setVisible(true); new Thread(new Runnable() { @Override public void run() { try { Group content = Importer3D.load(file.toURI().toURL().toString()); handleLoadResult(content, "Loaded file " + loadedPath); } catch (OutOfMemoryError e) { handleLoadResult(null, "Not enough memory to load file " + loadedPath); e.printStackTrace(); } catch (UnsatisfiedLinkError e) { handleLoadResult(null, "Dependency jfbxlib could not be loaded"); e.printStackTrace(); } catch (Throwable e) { handleLoadResult(null, "Failed to load file " + loadedPath); e.printStackTrace(); } } }).start(); } /** * Updates the status bar text with the given string. * * @param text the text to appear in the status bar */ private void updateStatus(final String text) { status.setText(text); } /** * Handles the result of the load action. * * @param content the loaded content * @param status the new status text */ private void handleLoadResult(Group content, String status) { Platform.runLater(new Runnable() { @Override public void run() { model.setContent(content); updateStatus(status); disableControls(false); progressIndicator.setVisible(false); } }); } private void disableControls(boolean disabled) { openButton.setDisable(disabled); nearClipSlider.setDisable(disabled); farClipSlider.setDisable(disabled); } private class Power10DoubleBinding extends DoubleBinding { private final DoubleProperty prop; public Power10DoubleBinding(final DoubleProperty prop) { this.prop = prop; bind(prop); } @Override protected double computeValue() { return Math.pow(10, prop.getValue()); } } }