初めての JavaFX 3D その2
JavaFX の 3D API と戯れてみたのですが、問題発生しました。
この前、試しに作ったアプリのカメラアングルを変更したところ下図のような状態となりました。
これではスライダーが上手く操作できません。(そこか!?
カメラアングル変更のコードは下記のようで間違いなさそうです。
cam.getTransforms().add(new Rotate(90, Rotate.Y_AXIS));
さて、困った。。。
カメラアングル変更の対象となる物を限定したい場合どうすればいいのだろうか?
そこで SubScene が颯爽と姿を現すのであった!
つまり、SubScene に専用カメラを設置して、動いて欲しくないものは今までの Scene 用のカメラで映す。
実に論理的かつ合理的ですね。(^_^)
でも、FXML アプリとしてはどのようにすればいいのか解らなかった。
SceneBuilder で簡単にできるようになればいいのにね。
ってことで久しぶりにコードで UI を構築するはめとなった。
従って手抜きのいつもより汚くておかしなコードになっているかもしれない。(^_^;)
これだけだと面白くないし、現実的に 3D データは専用のソフトで作成された物を使うケースが一般的だろう。
今回は、OBJ ファイルを読み込んでそれをグリグリすることにしました。
と言っても ジムさんがネット上で公開していたサンプルを参考にさせていただきました。
Thank you from the bottom of my heart!
まだ JavaFX 本体にそれらを読み込む機能は搭載されてないので早期お試しライブラリを使用しました。
Java One でもこれについては実装予定であると言及されていましたので良い子のみんなはおとなしく待ちましょうね♡
困ったちゃんの私は玉砕覚悟で突き進みます。(ヲヒ
ということでこんなのができちゃいました。
えっ?
よく解らないって?
そうでしょう。そうでしょう。
そういうあなたのために動画も用意しました。
動画みても感激はなかったって?
たしかにただ絵が動くにすぎないですからね。
でもこのご時世 3D をなめていたらあっというまに時代の波に飲み込まれてしまうかもしれません。
それだは興味のあるかたはこの面白くもなく役にたたないアプリのソースコードをご覧くださいませ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
package jp.yucchi.try3d; import com.interactivemesh.jfx.importer.ImportException; import com.interactivemesh.jfx.importer.obj.ObjModelImporter; import java.net.URL; import javafx.application.Application; import javafx.beans.binding.When; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.event.ActionEvent; import javafx.scene.AmbientLight; import javafx.scene.Group; import javafx.scene.PerspectiveCamera; import javafx.scene.PointLight; import javafx.scene.Scene; import javafx.scene.SubScene; import javafx.scene.control.Accordion; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.Slider; import javafx.scene.control.TitledPane; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Background; import javafx.scene.layout.HBox; import javafx.scene.paint.Color; import javafx.scene.shape.DrawMode; import javafx.scene.shape.MeshView; import javafx.scene.transform.Rotate; import javafx.stage.Stage; public class Try3D extends Application { private final DoubleProperty xPos = new SimpleDoubleProperty(0); private final DoubleProperty yPos = new SimpleDoubleProperty(0); private final DoubleProperty zPos = new SimpleDoubleProperty(0); private final DoubleProperty sxPos = new SimpleDoubleProperty(1.0); private final DoubleProperty syPos = new SimpleDoubleProperty(1.0); private final DoubleProperty szPos = new SimpleDoubleProperty(1.0); private final DoubleProperty rxPos = new SimpleDoubleProperty(0); private final DoubleProperty ryPos = new SimpleDoubleProperty(0); private final DoubleProperty rzPos = new SimpleDoubleProperty(0); private final BooleanProperty showWireframe = new SimpleBooleanProperty(false); private double anchorAngleX = 0; private double anchorAngleY = 0; private final DoubleProperty angleX = new SimpleDoubleProperty(0); private final DoubleProperty angleY = new SimpleDoubleProperty(0); private double anchorX; private double anchorY; private MeshView[] objMesh; @Override public void start(Stage primaryStage) { Group root = new Group(); Label translateLabel = new Label("Translate"); Label xLabel = new Label("X"); Label yLabel = new Label("Y"); Label zLabel = new Label("Z"); Label scaleLabel = new Label("Scale "); Label sxLabel = new Label("X"); Label syLabel = new Label("Y"); Label szLabel = new Label("Z"); Label rotateLabel = new Label("Rotate "); Label rxLabel = new Label("X"); Label ryLabel = new Label("Y"); Label rzLabel = new Label("Z"); Label drawModeLabel = new Label("Wireframe"); Label resetLabel = new Label(" Reset"); Slider xSlider = new Slider(-3000, 3000, 0); Slider ySlider = new Slider(-3000, 3000, 0); Slider zSlider = new Slider(-5000, 5000, 0); Slider sxSlider = new Slider(0.01, 10, 1.0); Slider sySlider = new Slider(0.01, 10, 1.0); Slider szSlider = new Slider(0.01, 10, 1.0); Slider rxSlider = new Slider(-360, 360, 0); Slider rySlider = new Slider(-360, 360, 0); Slider rzSlider = new Slider(-360, 360, 0); xSlider.setPrefSize(533, 0); ySlider.setPrefSize(533, 0); zSlider.setPrefSize(533, 0); sxSlider.setPrefSize(533, 0); sySlider.setPrefSize(533, 0); szSlider.setPrefSize(533, 0); rxSlider.setPrefSize(533, 0); rySlider.setPrefSize(533, 0); rzSlider.setPrefSize(533, 0); xSlider.setShowTickLabels(true); xSlider.setMajorTickUnit(500); ySlider.setShowTickLabels(true); ySlider.setMajorTickUnit(500); zSlider.setShowTickLabels(true); zSlider.setMajorTickUnit(500); sxSlider.setShowTickLabels(true); sxSlider.setMajorTickUnit(2.0); sySlider.setShowTickLabels(true); sySlider.setMajorTickUnit(2.0); szSlider.setShowTickLabels(true); szSlider.setMajorTickUnit(2.0); rxSlider.setShowTickLabels(true); rxSlider.setMajorTickUnit(90); rySlider.setShowTickLabels(true); rySlider.setMajorTickUnit(90); rzSlider.setShowTickLabels(true); rzSlider.setMajorTickUnit(90); CheckBox meshCheckBox = new CheckBox("Wireframe"); Button btn = new Button(); btn.setText("Reset"); final Accordion accordion = new Accordion(); final TitledPane translatePane = new TitledPane("Camera Translate", new HBox(30, translateLabel, xLabel, xSlider, yLabel, ySlider, zLabel, zSlider)); final TitledPane scalePane = new TitledPane("Camera Scale", new HBox(30, scaleLabel, sxLabel, sxSlider, syLabel, sySlider, szLabel, szSlider)); final TitledPane rotatePane = new TitledPane("Camera Rotate", new HBox(30, rotateLabel, rxLabel, rxSlider, ryLabel, rySlider, rzLabel, rzSlider)); final TitledPane drawModePane = new TitledPane("DrawMode & Reset", new HBox(20, drawModeLabel, meshCheckBox, resetLabel, btn)); translatePane.setAnimated(true); scalePane.setAnimated(true); rotatePane.setAnimated(true); drawModePane.setAnimated(true); accordion.getPanes().addAll(translatePane, scalePane, rotatePane, drawModePane); accordion.setExpandedPane(translatePane); root.getChildren().add(accordion); accordion.setLayoutX(17); accordion.setLayoutY(873); Scene scene = new Scene(root, 1910, 1020, true); scene.setFill(Color.PINK); PerspectiveCamera cam = new PerspectiveCamera(false); scene.setCamera(cam); root.getChildren().add(cam); // Create SubScene PerspectiveCamera subCam = new PerspectiveCamera(false); AnchorPane subRoot = new AnchorPane(); subRoot.setBackground(Background.EMPTY); ObjModelImporter objImporter = new ObjModelImporter(); try { URL objUrl = this.getClass().getResource("bunny/b.obj"); objImporter.read(objUrl); } catch (ImportException e) { e.printStackTrace(); return; } MeshView[] objMesh = objImporter.getImport(); objImporter.close(); Group objGroup = new Group(); for (int i = 0; i < objMesh.length; i++) { objGroup.getChildren().addAll(objMesh[i]); objMesh[i].drawModeProperty().bind(new When(showWireframe).then(DrawMode.LINE).otherwise(DrawMode.FILL)); } objGroup.setLayoutX(960); objGroup.setLayoutY(430); AmbientLight ambient = new AmbientLight(); ambient.setColor(Color.rgb(255, 255, 255, 0.6)); PointLight point = new PointLight(); point.setColor(Color.rgb(255, 255, 255, 1.0)); point.setLayoutX(400); point.setLayoutY(100); point.setTranslateZ(-1200); point.getScope().add(objGroup); subRoot.getChildren().addAll(objGroup, subCam, ambient, point); SubScene subScene = new SubScene(subRoot, 1920, 860, true, true); subScene.setFill(Color.BLACK); subScene.setCamera(subCam); root.getChildren().addAll(subScene); Rotate xRotate; Rotate yRotate; objGroup.getTransforms().setAll( xRotate = new Rotate(0, Rotate.X_AXIS), yRotate = new Rotate(0, Rotate.Y_AXIS) ); xRotate.angleProperty().bind(angleX); yRotate.angleProperty().bind(angleY); subScene.setOnMousePressed(event -> { anchorX = event.getSceneX(); anchorY = event.getSceneY(); anchorAngleX = angleX.get(); anchorAngleY = angleY.get(); }); subScene.setOnMouseDragged(event -> { angleX.set(anchorAngleX - (anchorY - event.getSceneY())); angleY.set(anchorAngleY + anchorX - event.getSceneX()); }); xSlider.valueProperty().bindBidirectional(xPos); subCam.translateXProperty().bind(xPos); ySlider.valueProperty().bindBidirectional(yPos); subCam.translateYProperty().bind(yPos); zSlider.valueProperty().bindBidirectional(zPos); subCam.translateZProperty().bind(zPos); sxSlider.valueProperty().bindBidirectional(sxPos); subCam.scaleXProperty().bind(sxPos); sySlider.valueProperty().bindBidirectional(syPos); subCam.scaleYProperty().bind(syPos); szSlider.valueProperty().bindBidirectional(szPos); subCam.scaleZProperty().bind(szPos); Rotate rxRotate; Rotate ryRotate; Rotate rzRotate; subCam.getTransforms().setAll( rxRotate = new Rotate(0, Rotate.X_AXIS), ryRotate = new Rotate(0, Rotate.Y_AXIS), rzRotate = new Rotate(0, Rotate.Z_AXIS) ); rxRotate.angleProperty().bind(rxPos); ryRotate.angleProperty().bind(ryPos); rzRotate.angleProperty().bind(rzPos); rxSlider.valueProperty().bindBidirectional(rxPos); rySlider.valueProperty().bindBidirectional(ryPos); rzSlider.valueProperty().bindBidirectional(rzPos); meshCheckBox.selectedProperty().bindBidirectional(showWireframe); btn.setOnAction((ActionEvent event) -> { objGroup.setLayoutX(960); objGroup.setLayoutY(430); anchorAngleX = 0; anchorAngleY = 0; angleX.set(0); angleY.set(0); xSlider.setValue(0); ySlider.setValue(0); zSlider.setValue(0); sxSlider.setValue(1.0); sySlider.setValue(1.0); szSlider.setValue(1.0); rxSlider.setValue(0); rySlider.setValue(0); rzSlider.setValue(0); }); primaryStage.setTitle("はじめての JavaFX 3D"); primaryStage.setScene(scene); primaryStage.setResizable(false); primaryStage.show(); } public static void main(String[] args) { launch(args); } } |
最後に Java 8 実行環境をお持ちの勇者様には Java Web Start をご用意させていただいております。
Webstart: click to launch this app as webstart (Java 8 実行環境必須)
ちなみにこの素敵な 3D データは SaYaKaProject 様のものを使用させていただきました。
このように創造性豊かな作者様には本当に尊敬します。
私は以前 Shade を購入してわずか3ヶ月で挫折した経験をもってます。(>_<。)
それだはまたね~♪
TAGS: JavaFX | 2013年10月5日8:42 PM | Comment : 0