1 /* 2 * Copyright (c) 2008, 2017, Oracle and/or its affiliates. 3 * All rights reserved. Use is subject to license terms. 4 * 5 * This file is available and licensed under the following license: 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * - Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the distribution. 16 * - Neither the name of Oracle Corporation nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package ensemble.samples.graphics3d.xylophone; 33 34 import ensemble.samples.media.audioclip.AudioClipApp; 35 import java.io.File; 36 import java.net.HttpURLConnection; 37 import java.net.URI; 38 import java.net.URL; 39 import javafx.animation.Interpolator; 40 import javafx.animation.KeyFrame; 41 import javafx.animation.KeyValue; 42 import javafx.animation.Timeline; 43 import javafx.application.Application; 44 import javafx.scene.Group; 45 import javafx.scene.SceneAntialiasing; 46 import javafx.scene.Parent; 47 import javafx.scene.PerspectiveCamera; 48 import javafx.scene.Scene; 49 import javafx.scene.SubScene; 50 import javafx.scene.input.MouseEvent; 51 import javafx.scene.media.AudioClip; 52 import javafx.scene.paint.Color; 53 import javafx.scene.paint.PhongMaterial; 54 import javafx.scene.shape.Box; 55 import javafx.stage.Stage; 56 import javafx.util.Duration; 57 58 /** 59 * A sample that demonstrates a xylophone made of 3D cubes. It is animated and 60 * plays sounds when clicked. 61 * 62 * @sampleName Xylophone 63 * @preview preview.png 64 * @docUrl http://docs.oracle.com/javase/8/javafx/graphics-tutorial/javafx-3d-graphics.htm#JFXGR256 JavaFX 3D Graphics 65 * @see javafx.scene.PerspectiveCamera 66 * @see javafx.scene.SceneAntialiasing 67 * @see javafx.scene.SubScene 68 * @see javafx.scene.input.MouseEvent 69 * @see javafx.scene.media.AudioClip 70 * @see javafx.scene.paint.PhongMaterial 71 * @see javafx.scene.shape.Box 72 * @see javafx.scene.transform.Rotate 73 * @see javafx.scene.transform.Scale 74 * @conditionalFeatures SCENE3D 75 * 76 * @related /Graphics 3d/3D Box 77 * @related /Graphics 3d/3D Cubes 78 * @related /Graphics 3d/3D Sphere 79 * @related /Graphics 3d/3D Sphere System 80 */ 81 public class XylophoneApp extends Application { 82 83 private Timeline animation; 84 private Timeline animation2; 85 86 /* 87 * See JDK-8177428 for an explanation of why this is here. 88 */ 89 private static AudioClip getNoteClip(String name) { 90 // First look for the clips in a directory next to our jar file 91 try { 92 // Get a URI to this class file 93 URI baseURI = XylophoneApp.class.getResource("XylophoneApp.class").toURI(); 94 95 // If we have a jar URL, get the embedded http or file URL 96 // and trim off the internal jar path, this will leave us 97 // with a URL to the jar file 98 if (baseURI.getScheme().equals("jar")) { 99 String basePath = baseURI.getSchemeSpecificPart(); 100 if (basePath.contains("!/")) { 101 basePath = basePath.substring(0, basePath.indexOf("!/")); 102 } 103 baseURI = new URI(basePath); 104 } 105 106 URL noteURL = baseURI.resolve("resources/"+name).toURL(); 107 108 // check if the resource exists, then try to load it 109 if (noteURL.getProtocol().equals("http")) { 110 HttpURLConnection urlCon = (HttpURLConnection)noteURL.openConnection(); 111 urlCon.setRequestMethod("HEAD"); 112 urlCon.connect(); 113 if (urlCon.getResponseCode() != HttpURLConnection.HTTP_OK) { 114 noteURL = null; 115 } 116 urlCon.disconnect(); 117 } else if (noteURL.getProtocol().equals("file")) { 118 File f = new File(noteURL.getPath()); 119 if (!f.exists() || !f.isFile()) { 120 noteURL = null; 121 } 122 } else { 123 // unsupported protocol 124 noteURL = null; 125 } 126 if (noteURL != null) { 127 return new AudioClip(noteURL.toExternalForm()); 128 } 129 } catch (Exception e) {} // fail gracefully 130 131 // Fall back on the embedded clips 132 return new AudioClip( 133 AudioClipApp.class.getResource("/ensemble/samples/shared-resources/"+name).toExternalForm()); 134 } 135 136 public Parent createContent() { 137 Xform sceneRoot = new Xform(); 138 sceneRoot.rx.setAngle(45.0); 139 sceneRoot.ry.setAngle(30.0); 140 sceneRoot.setScale(2 * 1.5); 141 142 Group rectangleGroup = new Group(); 143 144 double xStart = -110.0; 145 double xOffset = 30.0; 146 double yPos = 25.0; 147 double barWidth = 22.0; 148 double barDepth = 7.0; 149 150 // Base1 151 Box base1Cube = new Box(barWidth * 11.5, barDepth * 2.0, 10.0); 152 base1Cube.setMaterial(new PhongMaterial(new Color(0.2, 0.12, 0.1, 1.0))); 153 base1Cube.setTranslateX(xStart + 128); 154 base1Cube.setTranslateZ(yPos + 20.0); 155 base1Cube.setTranslateY(11.0); 156 157 // Base2 158 Box base2Cube = new Box(barWidth * 11.5, barDepth * 2.0, 10.0); 159 base2Cube.setMaterial(new PhongMaterial(new Color(0.2, 0.12, 0.1, 1.0))); 160 base2Cube.setTranslateX(xStart + 128); 161 base2Cube.setTranslateZ(yPos - 20.0); 162 base2Cube.setTranslateY(11.0); 163 164 Box[] barCubes = new Box[8]; 165 Color[] colors = { 166 Color.PURPLE, Color.BLUEVIOLET, Color.BLUE, Color.GREEN, 167 Color.GREENYELLOW, Color.YELLOW, Color.ORANGE, Color.RED 168 }; 169 for (int i = 0; i < barCubes.length; i++) { 170 final AudioClip barNote = getNoteClip("Note"+(i+1)+".wav"); 171 172 barCubes[i] = new Box(barWidth, barDepth, 100.0 - (i * 5.0)); 173 barCubes[i].setTranslateX(xStart + ((1 + i) * xOffset)); 174 barCubes[i].setTranslateZ(yPos); 175 barCubes[i].setMaterial(new PhongMaterial(colors[i])); 176 barCubes[i].setOnMousePressed((MouseEvent me) -> { 177 barNote.play(); 178 }); 179 } 180 181 rectangleGroup.getChildren().addAll(base1Cube, base2Cube); 182 rectangleGroup.getChildren().addAll(java.util.Arrays.asList(barCubes)); 183 sceneRoot.getChildren().add(rectangleGroup); 184 185 animation = new Timeline(); 186 animation.getKeyFrames().addAll(new KeyFrame(Duration.ZERO, 187 new KeyValue(sceneRoot.ry.angleProperty(), 390d, 188 Interpolator.TANGENT(Duration.seconds(0.5), 390d, 189 Duration.seconds(0.5), 390d))), 190 new KeyFrame(Duration.seconds(2), 191 new KeyValue(sceneRoot.ry.angleProperty(), 30d, 192 Interpolator.TANGENT(Duration.seconds(0.5), 30d, 193 Duration.seconds(0.5), 30d)))); 194 195 animation2 = new Timeline(); 196 animation2.getKeyFrames().addAll(new KeyFrame(Duration.ZERO, 197 new KeyValue(sceneRoot.rx.angleProperty(), 60d, 198 Interpolator.TANGENT(Duration.seconds(1.0), 60d))), 199 new KeyFrame(Duration.seconds(4), 200 new KeyValue(sceneRoot.rx.angleProperty(), 80d, 201 Interpolator.TANGENT(Duration.seconds(1.0), 80d))), 202 new KeyFrame(Duration.seconds(8), 203 new KeyValue(sceneRoot.rx.angleProperty(), 60d, 204 Interpolator.TANGENT(Duration.seconds(1.0), 60d)))); 205 animation2.setCycleCount(Timeline.INDEFINITE); 206 207 PerspectiveCamera camera = new PerspectiveCamera(); 208 209 SubScene subScene = new SubScene(sceneRoot, 780 * 1.5, 380 * 1.5, 210 true, SceneAntialiasing.BALANCED); 211 subScene.setCamera(camera); 212 213 sceneRoot.translateXProperty().bind(subScene.widthProperty().divide(2.2)); 214 sceneRoot.translateYProperty().bind(subScene.heightProperty().divide(1.6)); 215 216 return new Group(subScene); 217 } 218 219 public void play() { 220 animation.play(); 221 animation2.play(); 222 } 223 224 @Override 225 public void stop() { 226 animation.pause(); 227 animation2.pause(); 228 } 229 230 @Override 231 public void start(Stage primaryStage) throws Exception { 232 primaryStage.setScene(new Scene(createContent())); 233 primaryStage.show(); 234 play(); 235 } 236 237 /** 238 * Java main for when running without JavaFX launcher 239 * @param args command line arguments 240 */ 241 public static void main(String[] args) { 242 launch(args); 243 } 244 }