diff --git a/.idea/artifacts/Old_School_RuneScape_Cache_Tools_jar.xml b/.idea/artifacts/Old_School_RuneScape_Cache_Tools_jar.xml index bc18058..7398137 100644 --- a/.idea/artifacts/Old_School_RuneScape_Cache_Tools_jar.xml +++ b/.idea/artifacts/Old_School_RuneScape_Cache_Tools_jar.xml @@ -3,19 +3,59 @@ $PROJECT_DIR$/out/artifacts/Old_School_RuneScape_Cache_Tools_jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index edbf2f6..537af65 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,12 +1,11 @@ - \ No newline at end of file diff --git a/.idea/libraries/openjfx_javafx_fxml.xml b/.idea/libraries/openjfx_javafx_fxml.xml new file mode 100644 index 0000000..006ca87 --- /dev/null +++ b/.idea/libraries/openjfx_javafx_fxml.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/openjfx_javafx_graphics.xml b/.idea/libraries/openjfx_javafx_graphics.xml new file mode 100644 index 0000000..88109fe --- /dev/null +++ b/.idea/libraries/openjfx_javafx_graphics.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/openjfx_javafx_swing.xml b/.idea/libraries/openjfx_javafx_swing.xml new file mode 100644 index 0000000..de8a631 --- /dev/null +++ b/.idea/libraries/openjfx_javafx_swing.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index b6a274c..e634d84 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/Old-School-RuneScape-Cache-Tools.iml b/Old-School-RuneScape-Cache-Tools.iml index 9111978..1afd0c8 100644 --- a/Old-School-RuneScape-Cache-Tools.iml +++ b/Old-School-RuneScape-Cache-Tools.iml @@ -15,5 +15,8 @@ + + + \ No newline at end of file diff --git a/README.md b/README.md index f1c55b7..a92b5e1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ -# Old School RuneScape Cache Tools +# Old School RuneScape Cache Tools (v0.5-beta) ### The ultimate suite of RuneScape Cache modification tools! -Made with IntelliJ IDEA using Java 1.8 SDK. +Made with IntelliJ IDEA using Java 1.8 SDK with various libraries including JavaFX. ## This suite features: +### A simple 3D Model Viewer, built into the main window! ### Cache Functions * Select Cache Data - A quick way to select a specific archive file in the cache. * Search Cache (Work in progress) @@ -67,11 +68,14 @@ Made with IntelliJ IDEA using Java 1.8 SDK. * Gagravarr (https://github.com/Gagravarr): vorbis-java libraries. * FormDev Software: for the FlatLaf interface look and feel. * RuneLite Contributors (https://github.com/runelite/runelite): for the open source Old School RuneScape Client. +* Suic (https://github.com/Suicolen): For the simple JFX model viewer/editor tool. * Jagex: for being an inspiration in making this happen. ### Known Bugs/Issues * Cache Explorer: Not able to dump/export all files of single archive - only the first file is exported? * Cache Explorer: Some caches may not load correctly * Data Decoders: SoundFont conversion - may not be perfect +* Model Viewer: Does not work on RS2 yet +* Model Viewer: May not work on Mac computers, a workaround would be running the project directly through IntelliJ IDEA. ## That's all for now. Enjoy! \ No newline at end of file diff --git a/lib/javafx-base-18-win.jar b/lib/javafx-base-18-win.jar new file mode 100644 index 0000000..74454f5 Binary files /dev/null and b/lib/javafx-base-18-win.jar differ diff --git a/lib/javafx-base-18.jar b/lib/javafx-base-18.jar new file mode 100644 index 0000000..944b7c2 Binary files /dev/null and b/lib/javafx-base-18.jar differ diff --git a/lib/javafx-base-19-ea+8-win.jar b/lib/javafx-base-19-ea+8-win.jar new file mode 100644 index 0000000..2264277 Binary files /dev/null and b/lib/javafx-base-19-ea+8-win.jar differ diff --git a/lib/javafx-base-19-ea+8.jar b/lib/javafx-base-19-ea+8.jar new file mode 100644 index 0000000..51a701c Binary files /dev/null and b/lib/javafx-base-19-ea+8.jar differ diff --git a/lib/javafx-base-20-ea+2-win.jar b/lib/javafx-base-20-ea+2-win.jar new file mode 100644 index 0000000..cc3c404 Binary files /dev/null and b/lib/javafx-base-20-ea+2-win.jar differ diff --git a/lib/javafx-base-20-ea+2.jar b/lib/javafx-base-20-ea+2.jar new file mode 100644 index 0000000..75dbbfa Binary files /dev/null and b/lib/javafx-base-20-ea+2.jar differ diff --git a/lib/javafx-base-20-ea+9-win.jar b/lib/javafx-base-20-ea+9-win.jar new file mode 100644 index 0000000..4869dff Binary files /dev/null and b/lib/javafx-base-20-ea+9-win.jar differ diff --git a/lib/javafx-base-20-ea+9.jar b/lib/javafx-base-20-ea+9.jar new file mode 100644 index 0000000..9a8f51f Binary files /dev/null and b/lib/javafx-base-20-ea+9.jar differ diff --git a/lib/javafx-controls-19-ea+8-win.jar b/lib/javafx-controls-19-ea+8-win.jar new file mode 100644 index 0000000..f7c861e Binary files /dev/null and b/lib/javafx-controls-19-ea+8-win.jar differ diff --git a/lib/javafx-controls-19-ea+8.jar b/lib/javafx-controls-19-ea+8.jar new file mode 100644 index 0000000..ace25a7 Binary files /dev/null and b/lib/javafx-controls-19-ea+8.jar differ diff --git a/lib/javafx-fxml-19-ea+8-win.jar b/lib/javafx-fxml-19-ea+8-win.jar new file mode 100644 index 0000000..e536a43 Binary files /dev/null and b/lib/javafx-fxml-19-ea+8-win.jar differ diff --git a/lib/javafx-fxml-19-ea+8.jar b/lib/javafx-fxml-19-ea+8.jar new file mode 100644 index 0000000..eeea8b0 Binary files /dev/null and b/lib/javafx-fxml-19-ea+8.jar differ diff --git a/lib/javafx-graphics-18-win.jar b/lib/javafx-graphics-18-win.jar new file mode 100644 index 0000000..53e3dd0 Binary files /dev/null and b/lib/javafx-graphics-18-win.jar differ diff --git a/lib/javafx-graphics-18.jar b/lib/javafx-graphics-18.jar new file mode 100644 index 0000000..54beda6 Binary files /dev/null and b/lib/javafx-graphics-18.jar differ diff --git a/lib/javafx-graphics-19-ea+8-win.jar b/lib/javafx-graphics-19-ea+8-win.jar new file mode 100644 index 0000000..c413ebc Binary files /dev/null and b/lib/javafx-graphics-19-ea+8-win.jar differ diff --git a/lib/javafx-graphics-19-ea+8.jar b/lib/javafx-graphics-19-ea+8.jar new file mode 100644 index 0000000..5319c9c Binary files /dev/null and b/lib/javafx-graphics-19-ea+8.jar differ diff --git a/lib/javafx-graphics-20-ea+2-win.jar b/lib/javafx-graphics-20-ea+2-win.jar new file mode 100644 index 0000000..fa5ca31 Binary files /dev/null and b/lib/javafx-graphics-20-ea+2-win.jar differ diff --git a/lib/javafx-graphics-20-ea+2.jar b/lib/javafx-graphics-20-ea+2.jar new file mode 100644 index 0000000..3443d5b Binary files /dev/null and b/lib/javafx-graphics-20-ea+2.jar differ diff --git a/lib/javafx-graphics-20-ea+9-win.jar b/lib/javafx-graphics-20-ea+9-win.jar new file mode 100644 index 0000000..6d78b38 Binary files /dev/null and b/lib/javafx-graphics-20-ea+9-win.jar differ diff --git a/lib/javafx-graphics-20-ea+9.jar b/lib/javafx-graphics-20-ea+9.jar new file mode 100644 index 0000000..9aa4547 Binary files /dev/null and b/lib/javafx-graphics-20-ea+9.jar differ diff --git a/lib/javafx-swing-18-win.jar b/lib/javafx-swing-18-win.jar new file mode 100644 index 0000000..a5df726 Binary files /dev/null and b/lib/javafx-swing-18-win.jar differ diff --git a/lib/javafx-swing-18.jar b/lib/javafx-swing-18.jar new file mode 100644 index 0000000..1b7d43d Binary files /dev/null and b/lib/javafx-swing-18.jar differ diff --git a/lib/javafx-swing-20-ea+9-win.jar b/lib/javafx-swing-20-ea+9-win.jar new file mode 100644 index 0000000..2ad1df7 Binary files /dev/null and b/lib/javafx-swing-20-ea+9-win.jar differ diff --git a/lib/javafx-swing-20-ea+9.jar b/lib/javafx-swing-20-ea+9.jar new file mode 100644 index 0000000..157411e Binary files /dev/null and b/lib/javafx-swing-20-ea+9.jar differ diff --git a/lib/netty-buffer-4.1.77.Final.jar b/lib/netty-buffer-4.1.77.Final.jar new file mode 100644 index 0000000..815c4ba Binary files /dev/null and b/lib/netty-buffer-4.1.77.Final.jar differ diff --git a/lib/netty-common-4.1.77.Final.jar b/lib/netty-common-4.1.77.Final.jar new file mode 100644 index 0000000..d4761d8 Binary files /dev/null and b/lib/netty-common-4.1.77.Final.jar differ diff --git a/lib/streamex-0.8.1.jar b/lib/streamex-0.8.1.jar new file mode 100644 index 0000000..e063f64 Binary files /dev/null and b/lib/streamex-0.8.1.jar differ diff --git a/out/production/Old-School-RuneScape-Cache-Tools/net/runelite/cache/definitions/TextureDefinition.class b/out/production/Old-School-RuneScape-Cache-Tools/net/runelite/cache/definitions/TextureDefinition.class index e6e0e43..74d3979 100644 Binary files a/out/production/Old-School-RuneScape-Cache-Tools/net/runelite/cache/definitions/TextureDefinition.class and b/out/production/Old-School-RuneScape-Cache-Tools/net/runelite/cache/definitions/TextureDefinition.class differ diff --git a/src/java/com/application/GUI.java b/src/java/com/application/GUI.java index b77ef19..45412b3 100644 --- a/src/java/com/application/GUI.java +++ b/src/java/com/application/GUI.java @@ -7,6 +7,11 @@ import com.sun.media.sound.SF2Soundbank; import decoders.*; import encoders.*; +import javafx.application.Platform; +import javafx.embed.swing.JFXPanel; +import javafx.scene.Scene; +import javafx.stage.Stage; +import modelviewer.ModelViewer; import osrs.*; import javax.sound.midi.MidiDevice; @@ -60,11 +65,11 @@ public class GUI extends JFrame { DefaultMutableTreeNode fileNode; - public int selectedIndex; + public static int selectedIndex; public int[] selectedIndices; - public int selectedArchive; + public static int selectedArchive; public int[] selectedArchives; - public int selectedFile; + public static int selectedFile; public int[] selectedFiles; private static final File defaultCachePath; @@ -73,9 +78,12 @@ public class GUI extends JFrame { defaultCachePath = new File(System.getProperty("user.home") + File.separator + "jagexcache" + File.separator + "oldschool" + File.separator + "LIVE"); } + JFXPanel modelViewPanel; + Scene modelScene; + ModelViewer modelViewer; public GUI() { - super("Old School RuneScape Cache Tools v0.4-beta"); + super("Old School RuneScape Cache Tools v0.5-beta"); setSize(640, 480); setMinimumSize(new Dimension(640, 480)); setExtendedState(JFrame.MAXIMIZED_BOTH); @@ -184,7 +192,7 @@ public GUI() { JMenuItem test = new JMenuItem("Test tool"); test.addActionListener(e -> testTool()); - //toolsMenu.add(test); + toolsMenu.add(test); JMenuItem convertToOldModel = new JMenuItem("Model - Convert to Old Format"); convertToOldModel.addActionListener(e -> new ModelOldConverter(this)); @@ -283,6 +291,7 @@ private void searchCacheData() { } private void testTool() { + /* NPCComposition npcComposition = new NPCComposition(); npcComposition.decode(new Buffer(cacheLibrary.data(selectedIndex, selectedArchive, selectedFile))); System.out.println("Name: " + npcComposition.name); @@ -294,6 +303,7 @@ private void testTool() { System.out.println("Walk Left Sequence: " + npcComposition.walkLeftSequence); System.out.println("Walk Right Sequence: " + npcComposition.walkRightSequence); System.out.println("Actions: " + Arrays.toString(npcComposition.actions)); + */ } private void editSynthPatch() { @@ -801,6 +811,7 @@ private void chooseCacheFolder() { } private void loadCache(File cache) { + if (cache.isDirectory() && cache.exists()) { try { cacheLibrary = new CacheLibrary(cache.getPath(), false, null); @@ -818,11 +829,26 @@ private void loadCache(File cache) { AppConstants.cacheType = "RuneScape 3"; } initModelToolModes(); + + modelViewer = new ModelViewer(); + modelViewPanel = new JFXPanel(); + Platform.runLater(() -> { + try { + modelScene = modelViewer.start(new Stage()).getScene(); + modelViewPanel.setScene(modelScene); + } catch (Exception e) { + e.printStackTrace(); + } + }); + initFileViewer(); + contentPreviewPane.revalidate(); } catch (Exception e) { e.printStackTrace(); } } + + this.revalidate(); } @@ -882,35 +908,51 @@ private void initFileViewer() { cacheScrollPane = new JScrollPane(cacheTree); cacheScrollPane.setViewportView(cacheTree); + cacheScrollPane.getVerticalScrollBar().setPreferredSize(new Dimension(20, 0)); JSplitPane cacheInfoSplitPanel = new JSplitPane(); JPanel cacheInfoPanel = new JPanel(); cacheInfoPanel.setLayout(new GridLayout()); + SpringLayout springLayout = new SpringLayout(); JPanel cacheOperationsButtonPanel = new JPanel(); - cacheOperationsButtonPanel.setLayout(new FlowLayout()); + cacheOperationsButtonPanel.setLayout(springLayout); JButton addFilesButton = new JButton("Add Files"); addFilesButton.addActionListener(e -> addCacheFiles()); + springLayout.putConstraint(SpringLayout.NORTH, addFilesButton, 0, SpringLayout.NORTH, cacheOperationsButtonPanel); + springLayout.putConstraint(SpringLayout.WEST, addFilesButton, 0, SpringLayout.WEST, cacheOperationsButtonPanel); JButton exportFilesButton = new JButton("Export Files"); exportFilesButton.addActionListener(e -> exportFilesData()); + springLayout.putConstraint(SpringLayout.NORTH, exportFilesButton, 0, SpringLayout.NORTH, cacheOperationsButtonPanel); + springLayout.putConstraint(SpringLayout.WEST, exportFilesButton, 100, SpringLayout.WEST, cacheOperationsButtonPanel); JButton removeArchiveButton = new JButton("Remove Archive"); removeArchiveButton.addActionListener(e -> removeArchiveFile()); + springLayout.putConstraint(SpringLayout.NORTH, removeArchiveButton, 0, SpringLayout.NORTH, cacheOperationsButtonPanel); + springLayout.putConstraint(SpringLayout.WEST, removeArchiveButton, 200, SpringLayout.WEST, cacheOperationsButtonPanel); JButton removeFileButton = new JButton("Remove File"); removeFileButton.addActionListener(e -> removeCacheFile()); + springLayout.putConstraint(SpringLayout.NORTH, removeFileButton, 30, SpringLayout.NORTH, cacheOperationsButtonPanel); + springLayout.putConstraint(SpringLayout.WEST, removeFileButton, 0, SpringLayout.WEST, cacheOperationsButtonPanel); JButton setArchiveNameHashButton = new JButton("Set Archive name hash"); setArchiveNameHashButton.addActionListener(e -> setCacheArchiveNameHash()); + springLayout.putConstraint(SpringLayout.NORTH, setArchiveNameHashButton, 30, SpringLayout.NORTH, cacheOperationsButtonPanel); + springLayout.putConstraint(SpringLayout.WEST, setArchiveNameHashButton, 150, SpringLayout.WEST, cacheOperationsButtonPanel); JButton setArchiveNameButton = new JButton("Set Archive name"); setArchiveNameButton.addActionListener(e -> setCacheArchiveName()); + springLayout.putConstraint(SpringLayout.NORTH, setArchiveNameButton, 60, SpringLayout.NORTH, cacheOperationsButtonPanel); + springLayout.putConstraint(SpringLayout.WEST, setArchiveNameButton, 0, SpringLayout.WEST, cacheOperationsButtonPanel); JButton exportAllDataButton = new JButton("Export all Index data"); exportAllDataButton.addActionListener(e -> dumpAllDataFolders()); + springLayout.putConstraint(SpringLayout.NORTH, exportAllDataButton, 60, SpringLayout.NORTH, cacheOperationsButtonPanel); + springLayout.putConstraint(SpringLayout.WEST, exportAllDataButton, 150, SpringLayout.WEST, cacheOperationsButtonPanel); cacheOperationsButtonPanel.add(addFilesButton); cacheOperationsButtonPanel.add(exportFilesButton); @@ -1120,6 +1162,19 @@ private void initFileViewer() { infoTable.revalidate(); } } + + if (modelViewer != null) { + modelViewer = null; + Platform.runLater(() -> { + try { + modelViewer = new ModelViewer(); + modelScene = modelViewer.start(new Stage()).getScene(); + modelViewPanel.setScene(modelScene); + } catch (Exception exception) { + exception.printStackTrace(); + } + }); + } }); cacheInfoPanel.add(infoTable); @@ -1127,24 +1182,21 @@ private void initFileViewer() { cacheInfoSplitPanel.setBottomComponent(cacheOperationsButtonPanel); cacheInfoSplitPanel.setTopComponent(cacheInfoPanel); - cacheInfoSplitPanel.setEnabled(false); cacheInfoSplitPanel.setResizeWeight(0.5); cacheInfoSplitPanel.revalidate(); splitCacheViewPane.setLeftComponent(cacheScrollPane); splitCacheViewPane.setRightComponent(cacheInfoSplitPanel); - splitCacheViewPane.setEnabled(false); splitCacheViewPane.setResizeWeight(0.5); splitCacheViewPane.revalidate(); - //contentPreviewPane.add(new ModelViewer(new JFXPanel())); + contentPreviewPane.add(modelViewPanel); contentPreviewPane.revalidate(); JSplitPane splitCacheDetailedViewPane = new JSplitPane(); splitCacheDetailedViewPane.setLeftComponent(splitCacheViewPane); splitCacheDetailedViewPane.setRightComponent(contentPreviewPane); - splitCacheDetailedViewPane.setEnabled(false); - splitCacheDetailedViewPane.setResizeWeight(0.5); + splitCacheDetailedViewPane.setResizeWeight(0.3); splitCacheDetailedViewPane.revalidate(); contentPanel.add(splitCacheDetailedViewPane, BorderLayout.CENTER); diff --git a/src/java/com/application/Main.java b/src/java/com/application/Main.java index ee36e82..f53bb22 100644 --- a/src/java/com/application/Main.java +++ b/src/java/com/application/Main.java @@ -1,11 +1,11 @@ package com.application; -import com.formdev.flatlaf.FlatIntelliJLaf; +import com.formdev.flatlaf.FlatDarculaLaf; public class Main { public static void main(String[] args) { - FlatIntelliJLaf.setup(); + FlatDarculaLaf.setup(); GUI gui = new GUI(); gui.setVisible(true); } diff --git a/src/java/modelviewer/ModelViewer.java b/src/java/modelviewer/ModelViewer.java new file mode 100644 index 0000000..0159341 --- /dev/null +++ b/src/java/modelviewer/ModelViewer.java @@ -0,0 +1,18 @@ +package modelviewer; + +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.stage.Stage; + +import java.util.Objects; + +public class ModelViewer { + + public Stage start(Stage primaryStage) throws Exception { + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/ui.fxml")); + Scene scene = new Scene(fxmlLoader.load()); + scene.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/css/darktheme.css")).toExternalForm()); + primaryStage.setScene(scene); + return primaryStage; + } +} diff --git a/src/java/modelviewer/controllers/ModelController.java b/src/java/modelviewer/controllers/ModelController.java new file mode 100644 index 0000000..74e2e5e --- /dev/null +++ b/src/java/modelviewer/controllers/ModelController.java @@ -0,0 +1,135 @@ +package modelviewer.controllers; + +import com.application.AppConstants; +import com.application.GUI; +import com.displee.cache.CacheLibrary; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.AmbientLight; +import javafx.scene.Group; +import javafx.scene.SceneAntialiasing; +import javafx.scene.SubScene; +import javafx.scene.layout.AnchorPane; +import javafx.scene.paint.Color; +import modelviewer.model.Grid3D; +import modelviewer.scene.RSMeshGroup; +import modelviewer.scene.camera.OrbitCamera; +import net.runelite.cache.definitions.ModelDefinition; +import net.runelite.cache.definitions.loaders.ModelLoader; +import rs3.RS3ModelData; +import rshd.ModelData; + +import java.io.IOException; +import java.net.URL; +import java.util.Objects; +import java.util.ResourceBundle; + +public class ModelController implements Initializable { + + @FXML + private AnchorPane modelPane; + + private RSMeshGroup meshGroup; + private Group scene; + + @Override + public void initialize(URL location, ResourceBundle resources) { + updateModel(GUI.cacheLibrary, GUI.selectedIndex, GUI.selectedArchive, GUI.selectedFile); + } + + public void initScene(ModelDefinition model) throws IOException { + meshGroup = new RSMeshGroup(model); + meshGroup.buildMeshes(); + scene = buildScene(); + Group grid = new Grid3D().create(48f, 1.25f); + scene.getChildren().add(grid); + SubScene subScene = create3DScene(); + scene.getChildren().add(new AmbientLight(Color.WHITE)); + modelPane.getChildren().addAll(subScene); + } + + private Group buildScene() { + Group group = new Group(); + group.getChildren().addAll(meshGroup.getMeshes()); + return group; + } + + private SubScene create3DScene() { + SubScene scene3D = new SubScene(scene, modelPane.getPrefWidth(), modelPane.getPrefHeight(), true, SceneAntialiasing.BALANCED); + scene3D.setFill(Color.rgb(30, 30, 30)); + new OrbitCamera(scene3D, scene); + return scene3D; + } + + public void updateModel(CacheLibrary cacheLibrary, int selectedIndex, int selectedArchive, int selectedFile) { + + if (AppConstants.cacheType.equals("RuneScape 2")) { + ModelLoader modelLoader = new ModelLoader(); + ModelDefinition model; + if (selectedIndex == 7) { + model = modelLoader.load(0, Objects.requireNonNull(cacheLibrary.data(selectedIndex, selectedArchive, selectedFile))); + } else { + model = modelLoader.load(0, Objects.requireNonNull(cacheLibrary.data(7, 1, 0))); + } + if (model != null) { + try { + initScene(model); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + if (AppConstants.cacheType.equals("Old School RuneScape")) { + ModelLoader modelLoader = new ModelLoader(); + ModelDefinition model; + if (selectedIndex == 7) { + model = modelLoader.load(0, Objects.requireNonNull(cacheLibrary.data(selectedIndex, selectedArchive, selectedFile))); + } else { + model = modelLoader.load(0, Objects.requireNonNull(cacheLibrary.data(7, 1, 0))); + } + if (model != null) { + try { + initScene(model); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + if (AppConstants.cacheType.equals("RuneScape High Definition")) { + ModelData modelData = new ModelData(); + ModelDefinition model; + if (selectedIndex == 7) { + model = modelData.load(0, Objects.requireNonNull(cacheLibrary.data(selectedIndex, selectedArchive, selectedFile))); + } else { + model = modelData.load(0, Objects.requireNonNull(cacheLibrary.data(7, 1, 0))); + } + if (model != null) { + try { + initScene(model); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + if (AppConstants.cacheType.equals("RuneScape 3")) { + RS3ModelData modelData = new RS3ModelData(0); + ModelDefinition model; + if (selectedIndex == 7) { + model = modelData.load(0, Objects.requireNonNull(cacheLibrary.data(selectedIndex, selectedArchive, selectedFile)), cacheLibrary); + } else { + model = modelData.load(0, Objects.requireNonNull(cacheLibrary.data(7, 1, 0)), cacheLibrary); + } + if (model != null) { + try { + initScene(model); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + } + diff --git a/src/java/modelviewer/model/Grid3D.java b/src/java/modelviewer/model/Grid3D.java new file mode 100644 index 0000000..d6e9bc2 --- /dev/null +++ b/src/java/modelviewer/model/Grid3D.java @@ -0,0 +1,101 @@ +package modelviewer.model; + +import javafx.scene.Group; +import javafx.scene.paint.Color; +import javafx.scene.paint.PhongMaterial; +import javafx.scene.shape.CullFace; +import javafx.scene.shape.DrawMode; +import javafx.scene.transform.Rotate; +import javafx.scene.transform.Translate; +import modelviewer.polygonalmesh.PolygonMesh; +import modelviewer.polygonalmesh.PolygonMeshView; + +public class Grid3D { + + public Group create(float size, float delta) { + if (delta < 0.01f) { + delta = 0.01f; + } + + final PolygonMesh plane2 = createQuadrilateralMesh(size, size, (int) (size / delta), (int) (size / delta)); + + PolygonMeshView meshViewXZ2 = new PolygonMeshView(plane2); + meshViewXZ2.setDrawMode(DrawMode.LINE); + meshViewXZ2.setCullFace(CullFace.NONE); + PhongMaterial mat = new PhongMaterial(); + mat.setDiffuseColor(Color.BLACK); + meshViewXZ2.setMaterial(mat); + meshViewXZ2.getTransforms().add(new Translate(size / 1000f, size / 1000f, 0)); + meshViewXZ2.getTransforms().add(new Rotate(90, Rotate.X_AXIS)); + + return new Group(meshViewXZ2); + } + + private PolygonMesh createQuadrilateralMesh(float width, float height, int subDivX, int subDivY) { + final float minX = -width / 2f; + final float minY = -height / 2f; + final float maxX = width / 2f; + final float maxY = height / 2f; + + final int pointSize = 3; + final int texCoordSize = 2; + // 4 point indices and 4 texCoord indices per face + final int faceSize = 8; + int numDivX = subDivX + 1; + int numVerts = (subDivY + 1) * numDivX; + float[] points = new float[numVerts * pointSize]; + float[] texCoords = new float[numVerts * texCoordSize]; + int faceCount = subDivX * subDivY; + int[][] faces = new int[faceCount][faceSize]; + + // Create points and texCoords + for (int y = 0; y <= subDivY; y++) { + float dy = (float) y / subDivY; + double fy = (1 - dy) * minY + dy * maxY; + + for (int x = 0; x <= subDivX; x++) { + float dx = (float) x / subDivX; + double fx = (1 - dx) * minX + dx * maxX; + + int index = y * numDivX * pointSize + (x * pointSize); + points[index] = (float) fx; + points[index + 1] = (float) fy; + points[index + 2] = 0.0f; + + index = y * numDivX * texCoordSize + (x * texCoordSize); + texCoords[index] = dx; + texCoords[index + 1] = dy; + } + } + + // Create faces + int index = 0; + for (int y = 0; y < subDivY; y++) { + for (int x = 0; x < subDivX; x++) { + int p00 = y * numDivX + x; + int p01 = p00 + 1; + int p10 = p00 + numDivX; + int p11 = p10 + 1; + int tc00 = y * numDivX + x; + int tc01 = tc00 + 1; + int tc10 = tc00 + numDivX; + int tc11 = tc10 + 1; + + faces[index][0] = p00; + faces[index][1] = tc00; + faces[index][2] = p10; + faces[index][3] = tc10; + faces[index][4] = p11; + faces[index][5] = tc11; + faces[index][6] = p01; + faces[index++][7] = tc01; + } + } + + int[] smooth = new int[faceCount]; + + PolygonMesh mesh = new PolygonMesh(points, texCoords, faces); + mesh.getFaceSmoothingGroups().addAll(smooth); + return mesh; + } +} diff --git a/src/java/modelviewer/model/Vector3i.java b/src/java/modelviewer/model/Vector3i.java new file mode 100644 index 0000000..8c6c0b7 --- /dev/null +++ b/src/java/modelviewer/model/Vector3i.java @@ -0,0 +1,51 @@ +package modelviewer.model; + +import java.util.Objects; + +public final class Vector3i { + + private final int x; + private final int y; + private final int z; + + public Vector3i(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public int x() { + return x; + } + + public int y() { + return y; + } + + public int z() { + return z; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + Vector3i that = (Vector3i) obj; + return this.x == that.x && + this.y == that.y && + this.z == that.z; + } + + @Override + public int hashCode() { + return Objects.hash(x, y, z); + } + + @Override + public String toString() { + return "Vector3i[" + + "x=" + x + ", " + + "y=" + y + ", " + + "z=" + z + ']'; + } +} \ No newline at end of file diff --git a/src/java/modelviewer/model/Xform.java b/src/java/modelviewer/model/Xform.java new file mode 100644 index 0000000..55cc645 --- /dev/null +++ b/src/java/modelviewer/model/Xform.java @@ -0,0 +1,26 @@ +package modelviewer.model; + +import javafx.scene.Group; +import javafx.scene.transform.Rotate; +import javafx.scene.transform.Scale; +import javafx.scene.transform.Translate; +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; + +@FieldDefaults(level = AccessLevel.PUBLIC) +public class Xform extends Group { + + Translate t = new Translate(); + Translate p = new Translate(); + Rotate rx = new Rotate(); + Rotate ry = new Rotate(); + Rotate rz = new Rotate(); + Scale s = new Scale(); + + public Xform() { + rx.setAxis(Rotate.X_AXIS); + ry.setAxis(Rotate.Y_AXIS); + rz.setAxis(Rotate.Z_AXIS); + getTransforms().addAll(t, rz, ry, rx, s); + } +} diff --git a/src/java/modelviewer/polygonalmesh/OriginalPointArray.java b/src/java/modelviewer/polygonalmesh/OriginalPointArray.java new file mode 100644 index 0000000..d7c33d4 --- /dev/null +++ b/src/java/modelviewer/polygonalmesh/OriginalPointArray.java @@ -0,0 +1,47 @@ +package modelviewer.polygonalmesh; + +/* + * Copyright (c) 2010, 2014, 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. + */ + +public class OriginalPointArray extends SymbolicPointArray { + PolygonMesh mesh; + + public OriginalPointArray(PolygonMesh mesh) { + super(new float[mesh.getPoints().size()]); + this.mesh = mesh; + } + + @Override + public void update() { + mesh.getPoints().copyTo(0, data, 0, data.length); + } +} \ No newline at end of file diff --git a/src/java/modelviewer/polygonalmesh/PolygonMesh.java b/src/java/modelviewer/polygonalmesh/PolygonMesh.java new file mode 100644 index 0000000..f3a31ba --- /dev/null +++ b/src/java/modelviewer/polygonalmesh/PolygonMesh.java @@ -0,0 +1,99 @@ +package modelviewer.polygonalmesh; + +/* + * Copyright (c) 2010, 2014, 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. + */ + +import javafx.collections.FXCollections; +import javafx.collections.ObservableFloatArray; +import javafx.collections.ObservableIntegerArray; + +/** + * A Mesh where each face can be a Polygon + *

+ * can convert to using ObservableIntegerArray + */ +public class PolygonMesh { + private final ObservableFloatArray points = FXCollections.observableFloatArray(); + private final ObservableFloatArray texCoords = FXCollections.observableFloatArray(); + public int[][] faces = new int[0][0]; + private final ObservableIntegerArray faceSmoothingGroups = FXCollections.observableIntegerArray(); + protected int numEdgesInFaces = -1; // TODO invalidate automatically by listening to faces (whenever it is an observable) + + public PolygonMesh() { + } + + public PolygonMesh(float[] points, float[] texCoords, int[][] faces) { + this.points.addAll(points); + this.texCoords.addAll(texCoords); + this.faces = faces; + } + + public ObservableFloatArray getPoints() { + return points; + } + + public ObservableFloatArray getTexCoords() { + return texCoords; + } + + public ObservableIntegerArray getFaceSmoothingGroups() { + return faceSmoothingGroups; + } + + public int getNumEdgesInFaces() { + if (numEdgesInFaces == -1) { + numEdgesInFaces = 0; + for (int[] face : faces) { + numEdgesInFaces += face.length; + } + numEdgesInFaces /= 2; + } + return numEdgesInFaces; + } + + // TODO: Hardcode to constants for FX 8 (only one vertex format) + private static final int NUM_COMPONENTS_PER_POINT = 3; + private static final int NUM_COMPONENTS_PER_TEXCOORD = 2; + private static final int NUM_COMPONENTS_PER_FACE = 6; + + public int getPointElementSize() { + return NUM_COMPONENTS_PER_POINT; + } + + public int getTexCoordElementSize() { + return NUM_COMPONENTS_PER_TEXCOORD; + } + + public int getFaceElementSize() { + return NUM_COMPONENTS_PER_FACE; + } +} \ No newline at end of file diff --git a/src/java/modelviewer/polygonalmesh/PolygonMeshView.java b/src/java/modelviewer/polygonalmesh/PolygonMeshView.java new file mode 100644 index 0000000..3cad705 --- /dev/null +++ b/src/java/modelviewer/polygonalmesh/PolygonMeshView.java @@ -0,0 +1,478 @@ +package modelviewer.polygonalmesh;/* + * Copyright (c) 2010, 2014, 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. + */ + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ArrayChangeListener; +import javafx.collections.ObservableFloatArray; +import javafx.scene.Parent; +import javafx.scene.paint.Material; +import javafx.scene.shape.CullFace; +import javafx.scene.shape.DrawMode; +import javafx.scene.shape.MeshView; +import javafx.scene.shape.TriangleMesh; + +import java.util.Arrays; + +/** + * A MeshView node for Polygon Meshes + */ +public class PolygonMeshView extends Parent { + private static final boolean DEBUG = false; + private final MeshView meshView = new MeshView(); + + private TriangleMesh triangleMesh = new TriangleMesh(); + + // this is null if no subdivision is happening (i.e. subdivisionLevel = 0); + private SubdivisionMesh subdivisionMesh; + + private final ArrayChangeListener meshPointsListener = (t, bln, i, i1) -> { + pointsDirty = true; + updateMesh(); + }; + private final ArrayChangeListener meshTexCoordListener = (t, bln, i, i1) -> { + texCoordsDirty = true; + updateMesh(); + }; + + private boolean pointsDirty = true; + private boolean pointsSizeDirty = true; + private boolean texCoordsDirty = true; + private boolean facesDirty = true; + + // ========================================================================= + // PROPERTIES + + /** + * Specifies the 3D mesh data of this {@code MeshView}. + * + * @defaultValue null + */ + private ObjectProperty meshProperty; + + public PolygonMesh getMesh() { + return meshProperty().get(); + } + + public void setMesh(PolygonMesh mesh) { + meshProperty().set(mesh); + } + + public ObjectProperty meshProperty() { + if (meshProperty == null) { + meshProperty = new SimpleObjectProperty(); + meshProperty.addListener((observable, oldValue, newValue) -> { + if (oldValue != null) { + oldValue.getPoints().removeListener(meshPointsListener); + oldValue.getPoints().removeListener(meshTexCoordListener); + } + + meshProperty.set(newValue); + + pointsDirty = pointsSizeDirty = texCoordsDirty = facesDirty = true; + updateMesh(); + + if (newValue != null) { + newValue.getPoints().addListener(meshPointsListener); + newValue.getTexCoords().addListener(meshTexCoordListener); + } + }); + } + return meshProperty; + } + + /** + * Defines the drawMode this {@code Shape3D}. + * + * @defaultValue DrawMode.FILL + */ + private ObjectProperty drawMode; + + public final void setDrawMode(DrawMode value) { + drawModeProperty().set(value); + } + + public final DrawMode getDrawMode() { + return drawMode == null ? DrawMode.FILL : drawMode.get(); + } + + public final ObjectProperty drawModeProperty() { + if (drawMode == null) { + drawMode = new SimpleObjectProperty(PolygonMeshView.this, "drawMode", DrawMode.FILL) { + @Override + protected void invalidated() { + meshView.setDrawMode(get()); + pointsDirty = pointsSizeDirty = texCoordsDirty = facesDirty = true; + updateMesh(); + } + }; + } + return drawMode; + } + + /** + * Defines the drawMode this {@code Shape3D}. + * + * @defaultValue CullFace.BACK + */ + private ObjectProperty cullFace; + + public final void setCullFace(CullFace value) { + cullFaceProperty().set(value); + } + + public final CullFace getCullFace() { + return cullFace == null ? CullFace.BACK : cullFace.get(); + } + + public final ObjectProperty cullFaceProperty() { + if (cullFace == null) { + cullFace = new SimpleObjectProperty(PolygonMeshView.this, "cullFace", CullFace.BACK) { + @Override + protected void invalidated() { + meshView.setCullFace(get()); + } + }; + } + return cullFace; + } + + /** + * Defines the material this {@code Shape3D}. + * The default material is null. If {@code Material} is null, a PhongMaterial + * with a diffuse color of Color.LIGHTGRAY is used for rendering. + * + * @defaultValue null + */ + private ObjectProperty materialProperty = new SimpleObjectProperty(); + + public Material getMaterial() { + return materialProperty.get(); + } + + public void setMaterial(Material material) { + materialProperty.set(material); + } + + public ObjectProperty materialProperty() { + return materialProperty; + } + + /** + * Number of iterations of Catmull Clark subdivision to apply to the mesh + * + * @defaultValue 0 + */ + private SimpleIntegerProperty subdivisionLevelProperty; + + public void setSubdivisionLevel(int subdivisionLevel) { + subdivisionLevelProperty().set(subdivisionLevel); + } + + public int getSubdivisionLevel() { + return subdivisionLevelProperty == null ? 0 : subdivisionLevelProperty.get(); + } + + public SimpleIntegerProperty subdivisionLevelProperty() { + if (subdivisionLevelProperty == null) { + subdivisionLevelProperty = new SimpleIntegerProperty(getSubdivisionLevel()) { + @Override + protected void invalidated() { + // create SubdivisionMesh if subdivisionLevel is greater than 0 + if ((getSubdivisionLevel() > 0) && (subdivisionMesh == null)) { + subdivisionMesh = new SubdivisionMesh(getMesh(), getSubdivisionLevel(), getBoundaryMode(), getMapBorderMode()); + subdivisionMesh.getOriginalMesh() + .getPoints() + .addListener((t, bln, i, i1) -> subdivisionMesh.update()); + setMesh(subdivisionMesh); + } + if (subdivisionMesh != null) { + subdivisionMesh.setSubdivisionLevel(getSubdivisionLevel()); + subdivisionMesh.update(); + } + pointsDirty = pointsSizeDirty = texCoordsDirty = facesDirty = true; + updateMesh(); + } + }; + } + return subdivisionLevelProperty; + } + + /** + * Texture mapping boundary rule for Catmull Clark subdivision applied to the mesh + * + * @defaultValue BoundaryMode.CREASE_EDGES + */ + private SimpleObjectProperty boundaryMode; + + public void setBoundaryMode(SubdivisionMesh.BoundaryMode boundaryMode) { + boundaryModeProperty().set(boundaryMode); + } + + public SubdivisionMesh.BoundaryMode getBoundaryMode() { + return boundaryMode == null ? SubdivisionMesh.BoundaryMode.CREASE_EDGES : boundaryMode.get(); + } + + public SimpleObjectProperty boundaryModeProperty() { + if (boundaryMode == null) { + boundaryMode = new SimpleObjectProperty(getBoundaryMode()) { + @Override + protected void invalidated() { + if (subdivisionMesh != null) { + subdivisionMesh.setBoundaryMode(getBoundaryMode()); + subdivisionMesh.update(); + } + pointsDirty = true; + updateMesh(); + } + }; + } + return boundaryMode; + } + + /** + * Texture mapping smoothness option for Catmull Clark subdivision applied to the mesh + * + * @defaultValue MapBorderMode.NOT_SMOOTH + */ + private SimpleObjectProperty mapBorderMode; + + public void setMapBorderMode(SubdivisionMesh.MapBorderMode mapBorderMode) { + mapBorderModeProperty().set(mapBorderMode); + } + + public SubdivisionMesh.MapBorderMode getMapBorderMode() { + return mapBorderMode == null ? SubdivisionMesh.MapBorderMode.NOT_SMOOTH : mapBorderMode.get(); + } + + public SimpleObjectProperty mapBorderModeProperty() { + if (mapBorderMode == null) { + mapBorderMode = new SimpleObjectProperty(getMapBorderMode()) { + @Override + protected void invalidated() { + if (subdivisionMesh != null) { + subdivisionMesh.setMapBorderMode(getMapBorderMode()); + subdivisionMesh.update(); + } + texCoordsDirty = true; + updateMesh(); + } + }; + } + return mapBorderMode; + } + + // ========================================================================= + // CONSTRUCTORS + + public PolygonMeshView() { + meshView.materialProperty().bind(materialProperty()); + getChildren().add(meshView); + } + + public PolygonMeshView(PolygonMesh mesh) { + this(); + setMesh(mesh); + } + + // ========================================================================= + // PRIVATE METHODS + + private void updateMesh() { + PolygonMesh pmesh = getMesh(); + if (pmesh == null || pmesh.faces == null) { + triangleMesh = new TriangleMesh(); + meshView.setMesh(triangleMesh); + return; + } + + final int pointElementSize = triangleMesh.getPointElementSize(); + final int faceElementSize = triangleMesh.getFaceElementSize(); + final boolean isWireframe = getDrawMode() == DrawMode.LINE; + if (DEBUG) System.out.println("UPDATE MESH -- " + (isWireframe ? "WIREFRAME" : "SOLID")); + final int numOfPoints = pmesh.getPoints().size() / pointElementSize; + if (DEBUG) System.out.println("numOfPoints = " + numOfPoints); + + if (isWireframe) { + // The current triangleMesh implementation gives buggy behavior when the size of faces are shrunken + // Create a new TriangleMesh as a work around + // [JIRA] (RT-31178) + if (texCoordsDirty || facesDirty || pointsSizeDirty) { + triangleMesh = new TriangleMesh(); + pointsDirty = pointsSizeDirty = texCoordsDirty = facesDirty = true; // to fill in the new triangle mesh + } + if (facesDirty) { + facesDirty = false; + // create faces for each edge + int[] facesArray = new int[pmesh.getNumEdgesInFaces() * faceElementSize]; + int facesInd = 0; + int pointsInd = pmesh.getPoints().size(); + for (int[] face : pmesh.faces) { + if (DEBUG) + System.out.println("face.length = " + (face.length / 2) + " -- " + Arrays.toString(face)); + int lastPointIndex = face[face.length - 2]; + if (DEBUG) System.out.println(" lastPointIndex = " + lastPointIndex); + for (int p = 0; p < face.length; p += 2) { + int pointIndex = face[p]; + if (DEBUG) + System.out.println(" connecting point[" + lastPointIndex + "] to point[" + pointIndex + "]"); + facesArray[facesInd++] = lastPointIndex; + facesArray[facesInd++] = 0; + facesArray[facesInd++] = pointIndex; + facesArray[facesInd++] = 0; + facesArray[facesInd++] = pointsInd / pointElementSize; + facesArray[facesInd++] = 0; + if (DEBUG) System.out.println(" facesInd = " + facesInd); + pointsInd += pointElementSize; + lastPointIndex = pointIndex; + } + } + triangleMesh.getFaces().setAll(facesArray); + triangleMesh.getFaceSmoothingGroups().clear(); + } + if (texCoordsDirty) { + texCoordsDirty = false; + // set simple texCoords for wireframe + triangleMesh.getTexCoords().setAll(0, 0); + } + if (pointsDirty) { + pointsDirty = false; + // create points and copy over points to the first part of the array + float[] pointsArray = new float[pmesh.getPoints() + .size() + pmesh.getNumEdgesInFaces() * 3]; + pmesh.getPoints().copyTo(0, pointsArray, 0, pmesh.getPoints().size()); + + // add point for each edge + int pointsInd = pmesh.getPoints().size(); + for (int[] face : pmesh.faces) { + int lastPointIndex = face[face.length - 2]; + for (int p = 0; p < face.length; p += 2) { + int pointIndex = face[p]; + // get start and end point + final float x1 = pointsArray[lastPointIndex * pointElementSize]; + final float y1 = pointsArray[lastPointIndex * pointElementSize + 1]; + final float z1 = pointsArray[lastPointIndex * pointElementSize + 2]; + final float x2 = pointsArray[pointIndex * pointElementSize]; + final float y2 = pointsArray[pointIndex * pointElementSize + 1]; + final float z2 = pointsArray[pointIndex * pointElementSize + 2]; + final float distance = Math.abs(distanceBetweenPoints(x1, y1, z1, x2, y2, z2)); + final float offset = distance / 1000; + // add new point + pointsArray[pointsInd++] = x2 + offset; + pointsArray[pointsInd++] = y2 + offset; + pointsArray[pointsInd++] = z2 + offset; + lastPointIndex = pointIndex; + } + } + triangleMesh.getPoints().setAll(pointsArray); + } + } else { + // The current triangleMesh implementation gives buggy behavior when the size of faces are shrunken + // Create a new TriangleMesh as a work around + // [JIRA] (RT-31178) + if (texCoordsDirty || facesDirty || pointsSizeDirty) { + triangleMesh = new TriangleMesh(); + pointsDirty = pointsSizeDirty = texCoordsDirty = facesDirty = true; // to fill in the new triangle mesh + } + if (facesDirty) { + facesDirty = false; + // create faces and break into triangles + final int numOfFacesBefore = pmesh.faces.length; + final int numOfFacesAfter = pmesh.getNumEdgesInFaces() - 2 * numOfFacesBefore; + int[] facesArray = new int[numOfFacesAfter * faceElementSize]; + int[] smoothingGroupsArray = new int[numOfFacesAfter]; + int facesInd = 0; + for (int f = 0; f < pmesh.faces.length; f++) { + int[] face = pmesh.faces[f]; + int currentSmoothGroup = pmesh.getFaceSmoothingGroups().get(f); + if (DEBUG) + System.out.println("face.length = " + face.length + " -- " + Arrays.toString(face)); + int firstPointIndex = face[0]; + int firstTexIndex = face[1]; + int lastPointIndex = face[2]; + int lastTexIndex = face[3]; + for (int p = 4; p < face.length; p += 2) { + int pointIndex = face[p]; + int texIndex = face[p + 1]; + facesArray[facesInd * faceElementSize] = firstPointIndex; + facesArray[facesInd * faceElementSize + 1] = firstTexIndex; + facesArray[facesInd * faceElementSize + 2] = lastPointIndex; + facesArray[facesInd * faceElementSize + 3] = lastTexIndex; + facesArray[facesInd * faceElementSize + 4] = pointIndex; + facesArray[facesInd * faceElementSize + 5] = texIndex; + smoothingGroupsArray[facesInd] = currentSmoothGroup; + facesInd++; + lastPointIndex = pointIndex; + lastTexIndex = texIndex; + } + } + triangleMesh.getFaces().setAll(facesArray); + triangleMesh.getFaceSmoothingGroups().setAll(smoothingGroupsArray); + } + if (texCoordsDirty) { + texCoordsDirty = false; + triangleMesh.getTexCoords().setAll(pmesh.getTexCoords()); + } + if (pointsDirty) { + pointsDirty = false; + triangleMesh.getPoints().setAll(pmesh.getPoints()); + } + } + + if (DEBUG) System.out.println("CREATING TRIANGLE MESH"); + if (DEBUG) + System.out.println(" points = " + Arrays.toString(((TriangleMesh) meshView.getMesh()) + .getPoints() + .toArray(null))); + if (DEBUG) + System.out.println(" texCoords = " + Arrays.toString(((TriangleMesh) meshView.getMesh()) + .getTexCoords() + .toArray(null))); + if (DEBUG) + System.out.println(" faces = " + Arrays.toString(((TriangleMesh) meshView.getMesh()) + .getFaces() + .toArray(null))); + + if (meshView.getMesh() != triangleMesh) { + meshView.setMesh(triangleMesh); + } + pointsDirty = pointsSizeDirty = texCoordsDirty = facesDirty = false; + } + + private float distanceBetweenPoints(float x1, float y1, float z1, float x2, float y2, float z2) { + return (float) Math.sqrt( + Math.pow(z2 - z1, 2) + + Math.pow(x2 - x1, 2) + + Math.pow(y2 - y1, 2)); + } +} \ No newline at end of file diff --git a/src/java/modelviewer/polygonalmesh/SubdividedPointArray.java b/src/java/modelviewer/polygonalmesh/SubdividedPointArray.java new file mode 100644 index 0000000..5ad79db --- /dev/null +++ b/src/java/modelviewer/polygonalmesh/SubdividedPointArray.java @@ -0,0 +1,168 @@ +package modelviewer.polygonalmesh; + +/* + * Copyright (c) 2010, 2014, 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. + */ + +import java.util.Arrays; + + +public class SubdividedPointArray extends SymbolicPointArray { + private final float[] controlPoints; // points of the previous subdivision level + private final int[][] controlInds; // indices corresponding to controlPoints + private final float[][] controlFactors; // factors corresponding to controlPoints + private final int[][] inds; + private final float[][] factors; + + private final SubdivisionMesh.BoundaryMode boundaryMode; + + private int currPoint = 0; + + public SubdividedPointArray(SymbolicPointArray controlPointArray, int numPoints, SubdivisionMesh.BoundaryMode boundaryMode) { + super(new float[NUM_COMPONENTS_PER_POINT * numPoints]); + + this.controlPoints = controlPointArray.data; + this.controlInds = new int[numPoints][]; + this.controlFactors = new float[numPoints][]; + this.inds = new int[numPoints][]; + this.factors = new float[numPoints][]; + + this.boundaryMode = boundaryMode; + } + + + public int addFacePoint(int[] vertices) { + controlInds[currPoint] = vertices; + controlFactors[currPoint] = new float[vertices.length]; + Arrays.fill(controlFactors[currPoint], 1.0f / vertices.length); + + inds[currPoint] = new int[0]; + factors[currPoint] = new float[0]; + + return currPoint++; + } + + public int addEdgePoint(int[] facePoints, int fromPoint, int toPoint, boolean isBoundary) { + if (isBoundary) { + controlInds[currPoint] = new int[]{fromPoint, toPoint}; + controlFactors[currPoint] = new float[]{0.5f, 0.5f}; + + inds[currPoint] = new int[0]; + factors[currPoint] = new float[0]; + } else { + int n = facePoints.length + 2; + controlInds[currPoint] = new int[]{fromPoint, toPoint}; + controlFactors[currPoint] = new float[]{1.0f / n, 1.0f / n}; + + inds[currPoint] = facePoints; + factors[currPoint] = new float[facePoints.length]; + Arrays.fill(factors[currPoint], 1.0f / n); + } + return currPoint++; + } + + public int addControlPoint(int[] facePoints, int[] edgePoints, int[] fromEdgePoints, int[] toEdgePoints, boolean[] isEdgeBoundary, int origPoint, boolean isBoundary, boolean hasInternalEdge) { + if (isBoundary) { + if ((boundaryMode == SubdivisionMesh.BoundaryMode.CREASE_EDGES) || hasInternalEdge) { + controlInds[currPoint] = new int[]{origPoint}; + controlFactors[currPoint] = new float[]{0.5f}; + + int numBoundaryEdges = 0; + for (int i = 0; i < edgePoints.length; i++) { + if (isEdgeBoundary[i]) { + numBoundaryEdges++; + } + } + inds[currPoint] = new int[numBoundaryEdges]; + factors[currPoint] = new float[numBoundaryEdges]; + int boundaryEdgeInd = 0; + for (int i = 0; i < edgePoints.length; i++) { + if (isEdgeBoundary[i]) { + inds[currPoint][boundaryEdgeInd] = edgePoints[i]; + factors[currPoint][boundaryEdgeInd] = 0.25f; + boundaryEdgeInd++; + } + } + } else { + controlInds[currPoint] = new int[]{origPoint}; + controlFactors[currPoint] = new float[]{1.0f}; + + inds[currPoint] = new int[0]; + factors[currPoint] = new float[0]; + } + } else { + int n = facePoints.length; + + controlInds[currPoint] = new int[1 + edgePoints.length * 2]; + controlFactors[currPoint] = new float[1 + edgePoints.length * 2]; + controlInds[currPoint][0] = origPoint; + controlFactors[currPoint][0] = (n - 3.0f) / n; + for (int i = 0; i < edgePoints.length; i++) { + controlInds[currPoint][1 + 2 * i] = fromEdgePoints[i]; + controlFactors[currPoint][1 + 2 * i] = 1.0f / (n * n); + controlInds[currPoint][1 + 2 * i + 1] = toEdgePoints[i]; + controlFactors[currPoint][1 + 2 * i + 1] = 1.0f / (n * n); + } + + inds[currPoint] = facePoints; + factors[currPoint] = new float[facePoints.length]; + Arrays.fill(factors[currPoint], 1.0f / (n * n)); + } + return currPoint++; + } + + @Override + public void update() { + int ci; + float f; + float x, y, z; + for (int i = 0; i < numPoints; i++) { + x = y = z = 0.0f; + for (int j = 0; j < controlInds[i].length; j++) { + ci = 3 * controlInds[i][j]; + f = controlFactors[i][j]; + x += controlPoints[ci] * f; + y += controlPoints[ci + 1] * f; + z += controlPoints[ci + 2] * f; + } + for (int j = 0; j < inds[i].length; j++) { + ci = 3 * inds[i][j]; + f = factors[i][j]; + x += data[ci] * f; + y += data[ci + 1] * f; + z += data[ci + 2] * f; + } + data[3 * i] = x; + data[3 * i + 1] = y; + data[3 * i + 2] = z; + } + } +} \ No newline at end of file diff --git a/src/java/modelviewer/polygonalmesh/SubdivisionMesh.java b/src/java/modelviewer/polygonalmesh/SubdivisionMesh.java new file mode 100644 index 0000000..9c8c56f --- /dev/null +++ b/src/java/modelviewer/polygonalmesh/SubdivisionMesh.java @@ -0,0 +1,191 @@ +package modelviewer.polygonalmesh; + +/* + * Copyright (c) 2010, 2014, 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. + */ + +import java.util.ArrayList; +import java.util.List; + +/** + * Catmull Clark subdivision surface polygon mesh + */ +public class SubdivisionMesh extends PolygonMesh { + private final PolygonMesh originalMesh; + private int subdivisionLevel; + private BoundaryMode boundaryMode; + private MapBorderMode mapBorderMode; + private final List symbolicMeshes; + + private boolean pointValuesDirty; + private boolean meshDirty; + private boolean subdivisionLevelDirty; + + /** + * Describes whether the edges and points at the boundary are treated as creases + */ + public enum BoundaryMode { + /** + * Only edges at the boundary are treated as creases + */ + CREASE_EDGES, + /** + * Edges and points at the boundary are treated as creases + */ + CREASE_ALL + } + + /** + * Describes how the new texture coordinate for the control point is defined + */ + public enum MapBorderMode { + /** + * Jeeps the same uvs for all control points + */ + NOT_SMOOTH, + /** + * Smooths uvs of points at corners + */ + SMOOTH_INTERNAL, + /** + * Smooths uvs of points at boundaries and original control points (and creases [in the future when creases are defined]) + */ + SMOOTH_ALL + } + + public SubdivisionMesh(PolygonMesh originalMesh, int subdivisionLevel, BoundaryMode boundaryMode, MapBorderMode mapBorderMode) { + this.originalMesh = originalMesh; + setSubdivisionLevelForced(subdivisionLevel); + setBoundaryModeForced(boundaryMode); + setMapBorderModeForced(mapBorderMode); + + symbolicMeshes = new ArrayList<>(4); // the polymesh is usually subdivided up to 3 times + + originalMesh.getPoints().addListener((observableArray, sizeChanged, from, to) -> { + if (sizeChanged) { + meshDirty = true; + } else { + pointValuesDirty = true; + } + }); + originalMesh.getTexCoords().addListener((observableArray, sizeChanged, from, to) -> meshDirty = true); + } + + /** + * Updates the variables of the underlying polygon mesh. + * It only updates the fields that need to be updated. + */ + public void update() { + if (meshDirty) { + symbolicMeshes.clear(); + symbolicMeshes.add(new SymbolicPolygonMesh(originalMesh)); + pointValuesDirty = true; + subdivisionLevelDirty = true; + } + + while (subdivisionLevel >= symbolicMeshes.size()) { + symbolicMeshes.add(SymbolicSubdivisionBuilder.subdivide(symbolicMeshes.get(symbolicMeshes.size()-1), boundaryMode, mapBorderMode)); + pointValuesDirty = true; + subdivisionLevelDirty = true; + } + + if (pointValuesDirty) { + for (int i = 0; i <= subdivisionLevel; i++) { + SymbolicPolygonMesh symbolicMesh = symbolicMeshes.get(i); + symbolicMesh.points.update(); + } + } + + if (pointValuesDirty || subdivisionLevelDirty) { + getPoints().setAll(symbolicMeshes.get(subdivisionLevel).points.data); + } + + if (subdivisionLevelDirty) { + faces = symbolicMeshes.get(subdivisionLevel).faces; + numEdgesInFaces = -1; + getFaceSmoothingGroups().setAll(symbolicMeshes.get(subdivisionLevel).faceSmoothingGroups); + getTexCoords().setAll(symbolicMeshes.get(subdivisionLevel).texCoords); + } + + meshDirty = false; + pointValuesDirty = false; + subdivisionLevelDirty = false; + } + + private void setSubdivisionLevelForced(int subdivisionLevel) { + this.subdivisionLevel = subdivisionLevel; + subdivisionLevelDirty = true; + } + + private void setBoundaryModeForced(BoundaryMode boundaryMode) { + this.boundaryMode = boundaryMode; + meshDirty = true; + } + + private void setMapBorderModeForced(MapBorderMode mapBorderMode) { + this.mapBorderMode = mapBorderMode; + meshDirty = true; + } + + public PolygonMesh getOriginalMesh() { + return originalMesh; + } + + public int getSubdivisionLevel() { + return subdivisionLevel; + } + + public void setSubdivisionLevel(int subdivisionLevel) { + if (subdivisionLevel != this.subdivisionLevel) { + setSubdivisionLevelForced(subdivisionLevel); + } + } + + public BoundaryMode getBoundaryMode() { + return boundaryMode; + } + + public void setBoundaryMode(BoundaryMode boundaryMode) { + if (boundaryMode != this.boundaryMode) { + setBoundaryModeForced(boundaryMode); + } + } + + public MapBorderMode getMapBorderMode() { + return mapBorderMode; + } + + public void setMapBorderMode(MapBorderMode mapBorderMode) { + if (mapBorderMode != this.mapBorderMode) { + setMapBorderModeForced(mapBorderMode); + } + } +} \ No newline at end of file diff --git a/src/java/modelviewer/polygonalmesh/SymbolicPointArray.java b/src/java/modelviewer/polygonalmesh/SymbolicPointArray.java new file mode 100644 index 0000000..60fa027 --- /dev/null +++ b/src/java/modelviewer/polygonalmesh/SymbolicPointArray.java @@ -0,0 +1,55 @@ +package modelviewer.polygonalmesh; + +/* + * Copyright (c) 2010, 2014, 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. + */ + +/** + * A 3D geometric point array that has the x, y, z coordinates of every point + * as a function of other variables. + */ +public abstract class SymbolicPointArray { + final public float[] data; + final public int numPoints; + // x, y, z as stated. + static final int NUM_COMPONENTS_PER_POINT = 3; + + protected SymbolicPointArray(float[] data) { + this.data = data; + this.numPoints = data.length / NUM_COMPONENTS_PER_POINT; + } + + /** + * Updates the variables x, y, z based on the state of the other variables + * that this symbolic point depends on. + */ + public abstract void update(); +} \ No newline at end of file diff --git a/src/java/modelviewer/polygonalmesh/SymbolicPolygonMesh.java b/src/java/modelviewer/polygonalmesh/SymbolicPolygonMesh.java new file mode 100644 index 0000000..352cb58 --- /dev/null +++ b/src/java/modelviewer/polygonalmesh/SymbolicPolygonMesh.java @@ -0,0 +1,70 @@ +package modelviewer.polygonalmesh; + +/* + * Copyright (c) 2010, 2014, 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. + */ + +/** + * Polygon mesh where the points are symbolic. That is, the values of the + * points depend on other variables and they can be updated appropriately. + */ +public class SymbolicPolygonMesh { + public SymbolicPointArray points; + public float[] texCoords; + public int[][] faces; + public int[] faceSmoothingGroups; + private int numEdgesInFaces = -1; + + public SymbolicPolygonMesh(SymbolicPointArray points, float[] texCoords, int[][] faces, int[] faceSmoothingGroups) { + this.points = points; + this.texCoords = texCoords; + this.faces = faces; + this.faceSmoothingGroups = faceSmoothingGroups; + } + + public SymbolicPolygonMesh(PolygonMesh mesh) { + this.points = new OriginalPointArray(mesh); + this.texCoords = mesh.getTexCoords().toArray(this.texCoords); + this.faces = mesh.faces; + this.faceSmoothingGroups = mesh.getFaceSmoothingGroups().toArray(null); + } + + public int getNumEdgesInFaces() { + if (numEdgesInFaces == -1) { + numEdgesInFaces = 0; + for(int[] face : faces) { + numEdgesInFaces += face.length; + } + numEdgesInFaces /= 2; + } + return numEdgesInFaces; + } +} diff --git a/src/java/modelviewer/polygonalmesh/SymbolicSubdivisionBuilder.java b/src/java/modelviewer/polygonalmesh/SymbolicSubdivisionBuilder.java new file mode 100644 index 0000000..fc2bd70 --- /dev/null +++ b/src/java/modelviewer/polygonalmesh/SymbolicSubdivisionBuilder.java @@ -0,0 +1,393 @@ +package modelviewer.polygonalmesh; + +/* + * Copyright (c) 2010, 2014, 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. + */ + + +import javafx.geometry.Point2D; + +import java.util.*; + +/** + * Data structure builder for Catmull Clark subdivision surface + */ +public class SymbolicSubdivisionBuilder { + + private SymbolicPolygonMesh oldMesh; + private Map edgeInfos; + private FaceInfo[] faceInfos; + private PointInfo[] pointInfos; + private SubdividedPointArray points; + private float[] texCoords; + private int[] reindex; + private int newTexCoordIndex; + private SubdivisionMesh.BoundaryMode boundaryMode; + private SubdivisionMesh.MapBorderMode mapBorderMode; + + public SymbolicSubdivisionBuilder(SymbolicPolygonMesh oldMesh, SubdivisionMesh.BoundaryMode boundaryMode, SubdivisionMesh.MapBorderMode mapBorderMode) { + this.oldMesh = oldMesh; + this.boundaryMode = boundaryMode; + this.mapBorderMode = mapBorderMode; + } + + public SymbolicPolygonMesh subdivide() { + collectInfo(); + + texCoords = new float[(oldMesh.getNumEdgesInFaces() * 3 + oldMesh.faces.length) * 2]; + int[][] faces = new int[oldMesh.getNumEdgesInFaces()][8]; + int[] faceSmoothingGroups = new int[oldMesh.getNumEdgesInFaces()]; + newTexCoordIndex = 0; + reindex = new int[oldMesh.points.numPoints]; // indexes incremented by 1, 0 reserved for empty + + // face points first + int newFacesInd = 0; + for (int f = 0; f < oldMesh.faces.length; f++) { + FaceInfo faceInfo = faceInfos[f]; + int[] oldFaces = oldMesh.faces[f]; + for (int p = 0; p < oldFaces.length; p += 2) { + faces[newFacesInd][4] = getPointNewIndex(faceInfo); + faces[newFacesInd][5] = getTexCoordNewIndex(faceInfo); + faceSmoothingGroups[newFacesInd] = oldMesh.faceSmoothingGroups[f]; + newFacesInd++; + } + } + // then, add edge points + newFacesInd = 0; + for (int f = 0; f < oldMesh.faces.length; f++) { + FaceInfo faceInfo = faceInfos[f]; + int[] oldFaces = oldMesh.faces[f]; + for (int p = 0; p < oldFaces.length; p += 2) { + faces[newFacesInd][2] = getPointNewIndex(faceInfo, (p / 2 + 1) % faceInfo.edges.length); + faces[newFacesInd][3] = getTexCoordNewIndex(faceInfo, (p / 2 + 1) % faceInfo.edges.length); + faces[newFacesInd][6] = getPointNewIndex(faceInfo, p / 2); + faces[newFacesInd][7] = getTexCoordNewIndex(faceInfo, p / 2); + newFacesInd++; + } + } + // finally, add control points + newFacesInd = 0; + for (int f = 0; f < oldMesh.faces.length; f++) { + FaceInfo faceInfo = faceInfos[f]; + int[] oldFaces = oldMesh.faces[f]; + for (int p = 0; p < oldFaces.length; p += 2) { + faces[newFacesInd][0] = getPointNewIndex(oldFaces[p]); + faces[newFacesInd][1] = getTexCoordNewIndex(faceInfo, oldFaces[p], oldFaces[p + 1]); + newFacesInd++; + } + } + + SymbolicPolygonMesh newMesh = new SymbolicPolygonMesh(points, texCoords, faces, faceSmoothingGroups); + return newMesh; + } + + public static SymbolicPolygonMesh subdivide(SymbolicPolygonMesh oldMesh, SubdivisionMesh.BoundaryMode boundaryMode, SubdivisionMesh.MapBorderMode mapBorderMode) { + SymbolicSubdivisionBuilder subdivision = new SymbolicSubdivisionBuilder(oldMesh, boundaryMode, mapBorderMode); + return subdivision.subdivide(); + } + + private void addEdge(Edge edge, FaceInfo faceInfo) { + EdgeInfo edgeInfo = edgeInfos.get(edge); + if (edgeInfo == null) { + edgeInfo = new EdgeInfo(); + edgeInfo.edge = edge; + edgeInfos.put(edge, edgeInfo); + } + edgeInfo.faces.add(faceInfo); + } + + private void addPoint(int point, FaceInfo faceInfo, Edge edge) { + PointInfo pointInfo = pointInfos[point]; + if (pointInfo == null) { + pointInfo = new PointInfo(); + pointInfos[point] = pointInfo; + } + pointInfo.edges.add(edge); + pointInfo.faces.add(faceInfo); + } + + private void addPoint(int point, Edge edge) { + PointInfo pointInfo = pointInfos[point]; + if (pointInfo == null) { + pointInfo = new PointInfo(); + pointInfos[point] = pointInfo; + } + pointInfo.edges.add(edge); + } + + private void collectInfo() { + edgeInfos = new HashMap<>(oldMesh.faces.length * 2); + faceInfos = new FaceInfo[oldMesh.faces.length]; + pointInfos = new PointInfo[oldMesh.points.numPoints]; + + for (int f = 0; f < oldMesh.faces.length; f++) { + int[] face = oldMesh.faces[f]; + int n = face.length / 2; + FaceInfo faceInfo = new FaceInfo(n); + faceInfos[f] = faceInfo; + if (n < 3) { + continue; + } + int from = face[(n - 1) * 2]; + int texFrom = face[(n - 1) * 2 + 1]; + double fu, fv; + double tu, tv; + double u = 0, v = 0; + fu = oldMesh.texCoords[texFrom * 2]; + fv = oldMesh.texCoords[texFrom * 2 + 1]; + for (int i = 0; i < n; i++) { + int to = face[i * 2]; + int texTo = face[i * 2 + 1]; + tu = oldMesh.texCoords[texTo * 2]; + tv = oldMesh.texCoords[texTo * 2 + 1]; + Point2D midTexCoord = new Point2D((fu + tu) / 2, (fv + tv) / 2); + Edge edge = new Edge(from, to); + faceInfo.edges[i] = edge; + faceInfo.edgeTexCoords[i] = midTexCoord; + addEdge(edge, faceInfo); + addPoint(to, faceInfo, edge); + addPoint(from, edge); + fu = tu; + fv = tv; + u += tu / n; + v += tv / n; + from = to; + texFrom = texTo; + } + faceInfo.texCoord = new Point2D(u, v); + } + + points = new SubdividedPointArray(oldMesh.points, oldMesh.points.numPoints + faceInfos.length + edgeInfos + .size(), boundaryMode); + + for (int f = 0; f < oldMesh.faces.length; f++) { + int[] face = oldMesh.faces[f]; + int n = face.length / 2; + int[] faceVertices = new int[n]; + for (int i = 0; i < n; i++) { + faceVertices[i] = face[i * 2]; + } + faceInfos[f].facePoint = points.addFacePoint(faceVertices); + } + + for (EdgeInfo edgeInfo : edgeInfos.values()) { + int[] edgeFacePoints = new int[edgeInfo.faces.size()]; + for (int f = 0; f < edgeInfo.faces.size(); f++) { + edgeFacePoints[f] = edgeInfo.faces.get(f).facePoint; + } + edgeInfo.edgePoint = points.addEdgePoint(edgeFacePoints, edgeInfo.edge.from, edgeInfo.edge.to, edgeInfo + .isBoundary()); + } + } + + private int calcControlPoint(int srcPointIndex) { + PointInfo pointInfo = pointInfos[srcPointIndex]; + int origPoint = srcPointIndex; + + int[] facePoints = new int[pointInfo.faces.size()]; + for (int f = 0; f < facePoints.length; f++) { + facePoints[f] = pointInfo.faces.get(f).facePoint; + } + int[] edgePoints = new int[pointInfo.edges.size()]; + boolean[] isEdgeBoundary = new boolean[pointInfo.edges.size()]; + int[] fromEdgePoints = new int[pointInfo.edges.size()]; + int[] toEdgePoints = new int[pointInfo.edges.size()]; + int i = 0; + for (Edge edge : pointInfo.edges) { + EdgeInfo edgeInfo = edgeInfos.get(edge); + edgePoints[i] = edgeInfo.edgePoint; + isEdgeBoundary[i] = edgeInfo.isBoundary(); + fromEdgePoints[i] = edgeInfo.edge.from; + toEdgePoints[i] = edgeInfo.edge.to; + i++; + } + int destPointIndex = points.addControlPoint(facePoints, edgePoints, fromEdgePoints, toEdgePoints, isEdgeBoundary, origPoint, pointInfo + .isBoundary(), pointInfo.hasInternalEdge()); + return destPointIndex; + } + + private void calcControlTexCoord(FaceInfo faceInfo, int srcPointIndex, int srcTexCoordIndex, int destTexCoordIndex) { + PointInfo pointInfo = pointInfos[srcPointIndex]; + boolean pointBelongsToCrease = oldMesh.points instanceof OriginalPointArray; + if ((mapBorderMode == SubdivisionMesh.MapBorderMode.SMOOTH_ALL && (pointInfo.isBoundary() || pointBelongsToCrease)) || + (mapBorderMode == SubdivisionMesh.MapBorderMode.SMOOTH_INTERNAL && !pointInfo.hasInternalEdge())) { + double u = oldMesh.texCoords[srcTexCoordIndex * 2] / 2; + double v = oldMesh.texCoords[srcTexCoordIndex * 2 + 1] / 2; + for (int i = 0; i < faceInfo.edges.length; i++) { + if ((faceInfo.edges[i].to == srcPointIndex) || (faceInfo.edges[i].from == srcPointIndex)) { + u += faceInfo.edgeTexCoords[i].getX() / 4; + v += faceInfo.edgeTexCoords[i].getY() / 4; + } + } + texCoords[destTexCoordIndex * 2] = (float) u; + texCoords[destTexCoordIndex * 2 + 1] = (float) v; + } else { + texCoords[destTexCoordIndex * 2] = oldMesh.texCoords[srcTexCoordIndex * 2]; + texCoords[destTexCoordIndex * 2 + 1] = oldMesh.texCoords[srcTexCoordIndex * 2 + 1]; + } + } + + private int getPointNewIndex(int srcPointIndex) { + int destPointIndex = reindex[srcPointIndex] - 1; + if (destPointIndex == -1) { + destPointIndex = calcControlPoint(srcPointIndex); + reindex[srcPointIndex] = destPointIndex + 1; + } + return destPointIndex; + } + + private int getPointNewIndex(FaceInfo faceInfo, int edgeInd) { + Edge edge = faceInfo.edges[edgeInd]; + EdgeInfo edgeInfo = edgeInfos.get(edge); + return edgeInfo.edgePoint; + } + + private int getPointNewIndex(FaceInfo faceInfo) { + return faceInfo.facePoint; + } + + private int getTexCoordNewIndex(FaceInfo faceInfo, int srcPointIndex, int srcTexCoordIndex) { + int destTexCoordIndex = newTexCoordIndex; + newTexCoordIndex++; + calcControlTexCoord(faceInfo, srcPointIndex, srcTexCoordIndex, destTexCoordIndex); + return destTexCoordIndex; + } + + private int getTexCoordNewIndex(FaceInfo faceInfo, int edgeInd) { + int destTexCoordIndex = newTexCoordIndex; + newTexCoordIndex++; + texCoords[destTexCoordIndex * 2] = (float) faceInfo.edgeTexCoords[edgeInd].getX(); + texCoords[destTexCoordIndex * 2 + 1] = (float) faceInfo.edgeTexCoords[edgeInd].getY(); + return destTexCoordIndex; + } + + private int getTexCoordNewIndex(FaceInfo faceInfo) { + int destTexCoordIndex = faceInfo.newTexCoordIndex - 1; + if (destTexCoordIndex == -1) { + destTexCoordIndex = newTexCoordIndex; + faceInfo.newTexCoordIndex = destTexCoordIndex + 1; + newTexCoordIndex++; + texCoords[destTexCoordIndex * 2] = (float) faceInfo.texCoord.getX(); + texCoords[destTexCoordIndex * 2 + 1] = (float) faceInfo.texCoord.getY(); + } + return destTexCoordIndex; + } + + private static class Edge { + int from, to; + + public Edge(int from, int to) { + this.from = Math.min(from, to); + this.to = Math.max(from, to); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 41 * hash + this.from; + hash = 41 * hash + this.to; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Edge other = (Edge) obj; + if (this.from != other.from) { + return false; + } + if (this.to != other.to) { + return false; + } + return true; + } + } + + private static class EdgeInfo { + Edge edge; + int edgePoint; + List faces = new ArrayList<>(2); + + /** + * an edge is in the boundary if it has only one adjacent face + */ + public boolean isBoundary() { + return faces.size() == 1; + } + } + + private class PointInfo { + List faces = new ArrayList<>(4); + Set edges = new HashSet<>(4); + + /** + * A point is in the boundary if any of its adjacent edges is in the boundary + */ + public boolean isBoundary() { + for (Edge edge : edges) { + EdgeInfo edgeInfo = edgeInfos.get(edge); + if (edgeInfo.isBoundary()) + return true; + } + return false; + } + + /** + * A point is internal if at least one of its adjacent edges is not in the boundary + */ + public boolean hasInternalEdge() { + for (Edge edge : edges) { + EdgeInfo edgeInfo = edgeInfos.get(edge); + if (!edgeInfo.isBoundary()) + return true; + } + return false; + } + } + + private static class FaceInfo { + int facePoint; + Point2D texCoord; + int newTexCoordIndex; + Edge[] edges; + Point2D[] edgeTexCoords; + + public FaceInfo(int n) { + edges = new Edge[n]; + edgeTexCoords = new Point2D[n]; + } + } +} \ No newline at end of file diff --git a/src/java/modelviewer/rs/Model.java b/src/java/modelviewer/rs/Model.java new file mode 100644 index 0000000..bd26874 --- /dev/null +++ b/src/java/modelviewer/rs/Model.java @@ -0,0 +1,759 @@ +package modelviewer.rs; + +import javafx.geometry.Point3D; +import javafx.scene.paint.Color; +import lombok.SneakyThrows; +import modelviewer.rs.buffer.Buffer; + +public class Model { + + public static Color rs2HSBToColor(short hsb, int alpha) { + + int transparency = alpha; + if (transparency <= 0) { + transparency = 255; + } + + int hue = hsb >> 10 & 0x3f; + int sat = hsb >> 7 & 0x07; + int bri = hsb & 0x7f; + java.awt.Color awtCol = java.awt.Color.getHSBColor((float) hue / 63, (float) sat / 7, (float) bri / 127); + double r = awtCol.getRed() / 255.0; + double g = awtCol.getGreen() / 255.0; + double b = awtCol.getBlue() / 255.0; + return Color.color(r, g, b, transparency / 255.0); + } + + public boolean uvBetween(int face, double uLower, double uUpper, double vLower, double vUpper) { + float u1 = textureUCoordinates[face][0]; + float u2 = textureUCoordinates[face][1]; + float u3 = textureUCoordinates[face][2]; + + float v1 = textureVCoordinates[face][0]; + float v2 = textureVCoordinates[face][1]; + float v3 = textureVCoordinates[face][2]; + return (u1 >= uLower && u1 <= uUpper) && (u2 >= uLower && u2 <= uUpper) && (u3 >= uLower && u3 <= uUpper) && (v1 >= vLower && v1 <= vUpper) && (v2 >= vLower && v2 <= vUpper) && (v3 >= vLower && v3 <= vUpper); + } + + + public static Model decode(byte[] data, int id) { + Model model = new Model(); + try { + if (data[data.length - 1] == -1 && data[data.length - 2] == -1) { + model.decodeNew(data, id); + } else { + model.decode317(data, id); + } + + } catch (Exception e) { + e.printStackTrace(); + } + /* System.out.println(Arrays.toString(model.verticesXCoordinate)); + System.out.println(Arrays.toString(model.verticesYCoordinate)); + System.out.println(Arrays.toString(model.verticesZCoordinate)); + System.out.println("-".repeat(250)); + System.out.println(Arrays.toString(model.faceIndicesA)); + System.out.println(Arrays.toString(model.faceIndicesB)); + System.out.println(Arrays.toString(model.faceIndicesC));*/ + return model; + } + + public void decodeNew(byte[] data, int id) { + System.out.println("Decoded new " + id + " | " + data.length); + modelId = id; + Buffer first = new Buffer(data); + Buffer second = new Buffer(data); + Buffer third = new Buffer(data); + Buffer fourth = new Buffer(data); + Buffer fifth = new Buffer(data); + Buffer sixth = new Buffer(data); + Buffer seventh = new Buffer(data); + + first.pos = data.length - 23; + vertices = first.getUnsignedShort(); + triangleCount = first.getUnsignedShort(); + texturedFaces = first.getUnsignedByte(); + + System.out.println("Vertices: " + vertices); + System.out.println("Triangle count: " + triangleCount); + System.out.println("Textured faces: " + texturedFaces); + + + // System.err.println("Vertices: " + vertices + " | Faces: " + faces + " | Texture faces: " + texture_faces); + + int flag = first.getUnsignedByte();//texture flag 00 false, 01+ true + boolean hasFaceTypes = (flag & 0x1) == 1; + boolean hasParticleEffects = (flag & 0x2) == 2; + boolean hasBillboards = (flag & 0x4) == 4; + boolean hasVersion = (flag & 0x8) == 8; + if (hasVersion) { + first.pos -= 7; + first.pos += 6; + } + int model_priority_opcode = first.getUnsignedByte(); + int model_alpha_opcode = first.getUnsignedByte(); + int model_muscle_opcode = first.getUnsignedByte(); + int model_texture_opcode = first.getUnsignedByte(); + int model_bones_opcode = first.getUnsignedByte(); + System.out.println("---------------> data here"); + //619, 1244, 0, true, false, true, true, true + System.out.printf("%d, %d, %d, %b, %b, %b, %b, %b\n", vertices, triangleCount, texturedFaces, flag == 1, model_priority_opcode == 1, model_alpha_opcode == 1, model_muscle_opcode == 1, model_bones_opcode == 1); + int model_vertex_x = first.getUnsignedShort(); + int model_vertex_y = first.getUnsignedShort(); + int model_vertex_z = first.getUnsignedShort(); + int model_vertex_points = first.getUnsignedShort(); + System.out.println("new model = " + vertices + ", " + triangleCount + ", " + texturedFaces + ", " + flag + ", " + model_priority_opcode + ", " + model_alpha_opcode + ", " + model_muscle_opcode + ", " + model_bones_opcode + ", " + model_vertex_x + ", " + model_vertex_y + ", " + model_vertex_z + ", " + model_vertex_points); + int model_texture_indices = first.getUnsignedShort(); + int texture_id_simple = 0; + int texture_id_complex = 0; + int texture_id_cube = 0; + int face; + System.out.println("Tex faces: " + texturedFaces); + if (texturedFaces > 0) { + textureMap = new short[texturedFaces]; + first.pos = 0; + for (face = 0; face < texturedFaces; face++) { + short opcode = textureMap[face] = first.getSignedByte(); + if (opcode == 0) { + texture_id_simple++; + } + if (opcode >= 1 && opcode <= 3) { + texture_id_complex++; + } + if (opcode == 2) { + texture_id_cube++; + } + + } + } + int pos = texturedFaces; + + int model_vertex_offset = pos; + pos += vertices; + + int model_render_type_offset = pos; + if (flag == 1) + pos += triangleCount; + + int model_face_offset = pos; + pos += triangleCount; + + int model_face_priorities_offset = pos; + if (model_priority_opcode == 255) + pos += triangleCount; + + int model_muscle_offset = pos; + if (model_muscle_opcode == 1) + pos += triangleCount; + + int model_bones_offset = pos; + if (model_bones_opcode == 1) + pos += vertices; + + int model_alpha_offset = pos; + if (model_alpha_opcode == 1) + pos += triangleCount; + + int model_points_offset = pos; + pos += model_vertex_points; + + int model_texture_id = pos; + if (model_texture_opcode == 1) + pos += triangleCount * 2; + + int model_texture_coordinate_offset = pos; + pos += model_texture_indices; + + int model_color_offset = pos; + pos += triangleCount * 2; + + int model_vertex_x_offset = pos; + pos += model_vertex_x; + + int model_vertex_y_offset = pos; + pos += model_vertex_y; + + int model_vertex_z_offset = pos; + pos += model_vertex_z; + + int model_simple_texture_offset = pos; + pos += texture_id_simple * 6; + + int model_complex_texture_offset = pos; + pos += texture_id_complex * 6; + + int model_texture_scale_offset = pos; + pos += texture_id_complex * 6; + + int model_texture_rotation_offset = pos; + pos += texture_id_complex * 2; + + int model_texture_direction_offset = pos; + pos += texture_id_complex; + + int model_texture_translate_offset = pos; + pos += texture_id_complex * 2 + texture_id_cube * 2; + + verticesXCoordinate = new int[vertices]; + verticesYCoordinate = new int[vertices]; + verticesZCoordinate = new int[vertices]; + faceIndicesA = new int[triangleCount]; + faceIndicesB = new int[triangleCount]; + faceIndicesC = new int[triangleCount]; + if (model_bones_opcode == 1) + vertexWeights = new int[vertices]; + + if (flag == 1) + triangleInfo = new int[triangleCount]; + + if (model_priority_opcode == 255) + trianglePriorities = new byte[triangleCount]; + else + modelPriority = (byte) model_priority_opcode; + + if (model_alpha_opcode == 1) + faceAlpha = new int[triangleCount]; + + if (model_muscle_opcode == 1) + triangleSkin = new int[triangleCount]; + + if (model_texture_opcode == 1) + faceMaterial = new short[triangleCount]; + + if (model_texture_opcode == 1 && texturedFaces > 0) + faceTexture = new short[triangleCount]; + + triangleColors = new short[triangleCount]; + if (texturedFaces > 0) { + textureVertexA = new short[texturedFaces]; + textureVertexB = new short[texturedFaces]; + textureVertexC = new short[texturedFaces]; + } + first.pos = model_vertex_offset; + second.pos = model_vertex_x_offset; + third.pos = model_vertex_y_offset; + fourth.pos = model_vertex_z_offset; + fifth.pos = model_bones_offset; + int start_x = 0; + int start_y = 0; + + int start_z = 0; + for (int point = 0; point < vertices; point++) { + int position_mask = first.getUnsignedByte(); + int x = 0; + if ((position_mask & 1) != 0) { + x = second.getSignedSmart(); + } + int y = 0; + if ((position_mask & 2) != 0) { + y = third.getSignedSmart(); + } + int z = 0; + if ((position_mask & 4) != 0) { + z = fourth.getSignedSmart(); + } + verticesXCoordinate[point] = start_x + x; + verticesYCoordinate[point] = start_y + y; + verticesZCoordinate[point] = start_z + z; + start_x = verticesXCoordinate[point]; + start_y = verticesYCoordinate[point]; + start_z = verticesZCoordinate[point]; + if (vertexWeights != null) + vertexWeights[point] = fifth.getUnsignedByte(); + + } + first.pos = model_color_offset; + second.pos = model_render_type_offset; + third.pos = model_face_priorities_offset; + fourth.pos = model_alpha_offset; + fifth.pos = model_muscle_offset; + sixth.pos = model_texture_id; + seventh.pos = model_texture_coordinate_offset; + for (face = 0; face < triangleCount; face++) { + triangleColors[face] = (short) (first.getUnsignedShort() & 0xFFFF); + // System.out.println("Read face color: " + triangleColors[face]); + if (flag == 1) { + triangleInfo[face] = second.getSignedByte(); + } + if (model_priority_opcode == 255) { + trianglePriorities[face] = third.getSignedByte(); + } + if (model_alpha_opcode == 1) { + faceAlpha[face] = fourth.getSignedByte(); + if (faceAlpha[face] < 0) + faceAlpha[face] = (256 + faceAlpha[face]); + + } + if (model_muscle_opcode == 1) + triangleSkin[face] = fifth.getUnsignedByte(); + + if (model_texture_opcode == 1) { + //System.out.println("Started reading face material at pos " + sixth.pos); + faceMaterial[face] = (short) (sixth.getUnsignedShort() - 1); + if (faceMaterial[face] >= 0) { + if (triangleInfo != null) { + if (triangleInfo[face] < 2 + && triangleColors[face] != 127 + && triangleColors[face] != -27075 + && triangleColors[face] != 8128 + && triangleColors[face] != 7510) { + faceMaterial[face] = -1; + } + } + } + + + if (faceMaterial[face] != -1 && faceMaterial[face] >= 0 && faceMaterial[face] <= 85) + triangleColors[face] = 127; + + } + + //System.out.println(Arrays.toString(triangleColors)); + if (faceTexture != null && faceMaterial[face] != -1) { + faceTexture[face] = (byte) (seventh.getUnsignedByte() - 1); + // System.out.println(faceTexture[face] + " ->>>>>>"); + } + } + first.pos = model_points_offset; + second.pos = model_face_offset; + int a = 0; + int b = 0; + int c = 0; + int last_coordinate = 0; + for (face = 0; face < triangleCount; face++) { + int opcode = second.getUnsignedByte(); + if (opcode == 1) { + a = first.getSignedSmart() + last_coordinate; + last_coordinate = a; + b = first.getSignedSmart() + last_coordinate; + last_coordinate = b; + c = first.getSignedSmart() + last_coordinate; + last_coordinate = c; + faceIndicesA[face] = a; + faceIndicesB[face] = b; + faceIndicesC[face] = c; + } + if (opcode == 2) { + b = c; + c = first.getSignedSmart() + last_coordinate; + last_coordinate = c; + faceIndicesA[face] = a; + faceIndicesB[face] = b; + faceIndicesC[face] = c; + } + if (opcode == 3) { + a = c; + c = first.getSignedSmart() + last_coordinate; + last_coordinate = c; + faceIndicesA[face] = a; + faceIndicesB[face] = b; + faceIndicesC[face] = c; + } + if (opcode == 4) { + int l14 = a; + a = b; + b = l14; + c = first.getSignedSmart() + last_coordinate; + last_coordinate = c; + faceIndicesA[face] = a; + faceIndicesB[face] = b; + faceIndicesC[face] = c; + } + } + first.pos = model_simple_texture_offset; + second.pos = model_complex_texture_offset; + third.pos = model_texture_scale_offset; + fourth.pos = model_texture_rotation_offset; + fifth.pos = model_texture_direction_offset; + sixth.pos = model_texture_translate_offset; + for (face = 0; face < texturedFaces; face++) { + int opcode = textureMap[face] & 0xff; + if (opcode == 0) { + textureVertexA[face] = (short) first.getUnsignedShort(); + textureVertexB[face] = (short) first.getUnsignedShort(); + textureVertexC[face] = (short) first.getUnsignedShort(); + } + if (opcode == 1) { + textureVertexA[face] = (short) second.getUnsignedShort(); + textureVertexB[face] = (short) second.getUnsignedShort(); + textureVertexC[face] = (short) second.getUnsignedShort(); + } + if (opcode == 2) { + textureVertexA[face] = (short) second.getUnsignedShort(); + textureVertexB[face] = (short) second.getUnsignedShort(); + textureVertexC[face] = (short) second.getUnsignedShort(); + } + if (opcode == 3) { + textureVertexA[face] = (short) second.getUnsignedShort(); + textureVertexB[face] = (short) second.getUnsignedShort(); + textureVertexC[face] = (short) second.getUnsignedShort(); + } + } + + } + + //*Added*// + public short[] faceMaterial; + public short[] faceTexture; + public short[] textureMap; + + + public void computeUVCoordinates() { + if (texturedFaces == 0) { + return; + } + + textureUCoordinates = new float[triangleCount][]; + textureVCoordinates = new float[triangleCount][]; + + for (int i = 0; i < triangleCount; i++) { + int coordinate = triangleInfo == null ? -1 : triangleInfo[i] >> 2; + int textureIdx; + if (triangleInfo == null || triangleInfo[i] < 2) { + textureIdx = -1; + } else { + textureIdx = triangleColors[i] & 0xFFFF; + } + + if (textureIdx != -1) { + float[] u = new float[3]; + float[] v = new float[3]; + + if (coordinate == -1) { + u[0] = 0.0F; + v[0] = 1.0F; + + u[1] = 1.0F; + v[1] = 1.0F; + + u[2] = 0.0F; + v[2] = 0.0F; + } else { + coordinate &= 0xFF; + int faceA = faceIndicesA[i]; + int faceB = faceIndicesB[i]; + int faceC = faceIndicesC[i]; + + Point3D a = new Point3D(verticesXCoordinate[faceA], verticesYCoordinate[faceA], verticesZCoordinate[faceA]); + Point3D b = new Point3D(verticesXCoordinate[faceB], verticesYCoordinate[faceB], verticesZCoordinate[faceB]); + Point3D c = new Point3D(verticesXCoordinate[faceC], verticesYCoordinate[faceC], verticesZCoordinate[faceC]); + + Point3D p = new Point3D(verticesXCoordinate[textureVertexA[coordinate]], verticesYCoordinate[textureVertexA[coordinate]], verticesZCoordinate[textureVertexA[coordinate]]); + Point3D m = new Point3D(verticesXCoordinate[textureVertexB[coordinate]], verticesYCoordinate[textureVertexB[coordinate]], verticesZCoordinate[textureVertexB[coordinate]]); + Point3D n = new Point3D(verticesXCoordinate[textureVertexC[coordinate]], verticesYCoordinate[textureVertexC[coordinate]], verticesZCoordinate[textureVertexC[coordinate]]); + + Point3D pM = m.subtract(p); + Point3D pN = n.subtract(p); + Point3D pA = a.subtract(p); + Point3D pB = b.subtract(p); + Point3D pC = c.subtract(p); + + Point3D pMxPn = pM.crossProduct(pN); + + Point3D uCoordinate = pN.crossProduct(pMxPn); + double mU = 1.0F / uCoordinate.dotProduct(pM); + + double uA = uCoordinate.dotProduct(pA) * mU; + double uB = uCoordinate.dotProduct(pB) * mU; + double uC = uCoordinate.dotProduct(pC) * mU; + + Point3D vCoordinate = pM.crossProduct(pMxPn); + double mV = 1.0 / vCoordinate.dotProduct(pN); + double vA = vCoordinate.dotProduct(pA) * mV; + double vB = vCoordinate.dotProduct(pB) * mV; + double vC = vCoordinate.dotProduct(pC) * mV; + + u[0] = (float) uA; + u[1] = (float) uB; + u[2] = (float) uC; + + v[0] = (float) vA; + v[1] = (float) vB; + v[2] = (float) vC; + } + this.textureUCoordinates[i] = u; + this.textureVCoordinates[i] = v; + } + } + } + + public float[][] textureUCoordinates; + public float[][] textureVCoordinates; + + @SneakyThrows(Exception.class) + public void decode317(byte[] data, int id) { + System.out.println("Decoded old model " + id + " | " + data.length); + modelId = id; + Buffer first = new Buffer(data); + Buffer second = new Buffer(data); + Buffer third = new Buffer(data); + Buffer fourth = new Buffer(data); + Buffer fifth = new Buffer(data); + first.pos = data.length - 18; + vertices = first.getUnsignedShort(); + triangleCount = first.getUnsignedShort(); + texturedFaces = first.getUnsignedByte(); + + + int renderTypeOpcode = first.getUnsignedByte(); + int renderPriorityOpcode = first.getUnsignedByte(); + int triangleAlphaOpcode = first.getUnsignedByte(); + int triangleSkinOpcode = first.getUnsignedByte(); + int vertexLabelOpcode = first.getUnsignedByte(); + int verticesXCoordinateOffset = first.getUnsignedShort(); + + int verticesYCoordinateOffset = first.getUnsignedShort(); + int verticesZCoordinateOffset = first.getUnsignedShort(); + int triangleIndicesOffset = first.getUnsignedShort(); + + int pos = 0; + + int vertexFlagOffset = pos; + pos += vertices; + + int triangleCompressTypeOffset = pos; + pos += triangleCount; + + int facePriorityOffset = pos; + if (renderPriorityOpcode == 255) { + pos += triangleCount; + } + + int triangleSkinOffset = pos; + if (triangleSkinOpcode == 1) { + pos += triangleCount; + } + + int renderTypeOffset = pos; + if (renderTypeOpcode == 1) { + pos += triangleCount; + } + + int vertexLabelsOffset = pos; + if (vertexLabelOpcode == 1) { + pos += vertices; + } + + int triangleAlphaOffset = pos; + if (triangleAlphaOpcode == 1) { + pos += triangleCount; + } + + int indicesOffset = pos; + pos += triangleIndicesOffset; + + int triangleColorOffset = pos; + pos += triangleCount * 2; + + int textureOffset = pos; + pos += texturedFaces * 6; + + int xOffset = pos; + pos += verticesXCoordinateOffset; + + int yOffset = pos; + pos += verticesYCoordinateOffset; + + int zOffset = pos; + + verticesXCoordinate = new int[vertices]; + verticesYCoordinate = new int[vertices]; + verticesZCoordinate = new int[vertices]; + faceIndicesA = new int[triangleCount]; + faceIndicesB = new int[triangleCount]; + faceIndicesC = new int[triangleCount]; + if (texturedFaces > 0) { + textureVertexA = new short[texturedFaces]; + textureVertexB = new short[texturedFaces]; + textureVertexC = new short[texturedFaces]; + } + + if (vertexLabelOpcode == 1) + vertexWeights = new int[vertices]; + + + if (renderTypeOpcode == 1) { + triangleInfo = new int[triangleCount]; + } + + if (renderPriorityOpcode == 255) + trianglePriorities = new byte[triangleCount]; + else + modelPriority = (byte) renderPriorityOpcode; + + if (triangleAlphaOpcode == 1) + faceAlpha = new int[triangleCount]; + + if (triangleSkinOpcode == 1) + triangleSkin = new int[triangleCount]; + + triangleColors = new short[triangleCount]; + first.pos = vertexFlagOffset; + second.pos = xOffset; + third.pos = yOffset; + fourth.pos = zOffset; + fifth.pos = vertexLabelsOffset; // 18 + + int baseX = 0; + int baseY = 0; + int baseZ = 0; + + for (int point = 0; point < vertices; point++) { + int flag = first.getUnsignedByte(); + + int x = 0; + if ((flag & 0x1) != 0) { + x = second.getSignedSmart(); + } + + int y = 0; + if ((flag & 0x2) != 0) { + y = third.getSignedSmart(); + } + int z = 0; + if ((flag & 0x4) != 0) { + z = fourth.getSignedSmart(); + } + + verticesXCoordinate[point] = baseX + x; + verticesYCoordinate[point] = baseY + y; + verticesZCoordinate[point] = baseZ + z; + baseX = verticesXCoordinate[point]; + baseY = verticesYCoordinate[point]; + baseZ = verticesZCoordinate[point]; + if (vertexLabelOpcode == 1) { + vertexWeights[point] = fifth.getUnsignedByte(); + } + } + + + first.pos = triangleColorOffset; + second.pos = renderTypeOffset; + third.pos = facePriorityOffset; + fourth.pos = triangleAlphaOffset; + fifth.pos = triangleSkinOffset; + + for (int face = 0; face < triangleCount; face++) { + int color = first.getUnsignedShort(); + triangleColors[face] = (short) color; + + if (renderTypeOpcode == 1) { + triangleInfo[face] = second.getUnsignedByte(); + } + if (renderPriorityOpcode == 255) { + trianglePriorities[face] = third.getSignedByte(); + } + + if (triangleAlphaOpcode == 1) { + faceAlpha[face] = fourth.getSignedByte(); + if (faceAlpha[face] < 0) { + faceAlpha[face] = (256 + faceAlpha[face]); + } + + } + if (triangleSkinOpcode == 1) { + triangleSkin[face] = fifth.getUnsignedByte(); + } + + } + first.pos = indicesOffset; + second.pos = triangleCompressTypeOffset; + int a = 0; + int b = 0; + int c = 0; + int offset = 0; + int coordinate; + + for (int face = 0; face < triangleCount; face++) { + int opcode = second.getUnsignedByte(); + + + if (opcode == 1) { + a = (first.getSignedSmart() + offset); + offset = a; + b = (first.getSignedSmart() + offset); + offset = b; + c = (first.getSignedSmart() + offset); + offset = c; + faceIndicesA[face] = a; + faceIndicesB[face] = b; + faceIndicesC[face] = c; + + } + if (opcode == 2) { + b = c; + c = (first.getSignedSmart() + offset); + offset = c; + faceIndicesA[face] = a; + faceIndicesB[face] = b; + faceIndicesC[face] = c; + } + if (opcode == 3) { + a = c; + c = (first.getSignedSmart() + offset); + offset = c; + faceIndicesA[face] = a; + faceIndicesB[face] = b; + faceIndicesC[face] = c; + } + if (opcode == 4) { + coordinate = a; + a = b; + b = coordinate; + c = (first.getSignedSmart() + offset); + offset = c; + faceIndicesA[face] = a; + faceIndicesB[face] = b; + faceIndicesC[face] = c; + } + + } + first.pos = textureOffset; + + for (int face = 0; face < texturedFaces; face++) { + textureVertexA[face] = (short) first.getUnsignedShort(); + textureVertexB[face] = (short) first.getUnsignedShort(); + textureVertexC[face] = (short) first.getUnsignedShort(); + } + + if (triangleInfo == null) { + triangleInfo = new int[triangleCount]; + } + // System.out.println("Tri info = " + Arrays.toString(triangleInfo)); + } + + private int modelId; + + public int getModelId() { + return modelId; + } + + public int hsbToRGB(int hsb) { + float h = hsb >> 10 & 0x3f; + float s = hsb >> 7 & 0x07; + float b = hsb & 0x7f; + return java.awt.Color.HSBtoRGB(h / 63, s / 7, b / 127); + } + + + public int vertices; + public int triangleCount; + public int[] verticesXCoordinate; + public int[] verticesYCoordinate; + public int[] verticesZCoordinate; + public int[] faceIndicesA; + public int[] faceIndicesB; + public int[] faceIndicesC; + public int[] triangleInfo; + public byte[] trianglePriorities; + public int[] faceAlpha; + public short[] triangleColors; + public byte modelPriority = 0; + public int texturedFaces; + public short[] textureVertexA; + public short[] textureVertexB; + public short[] textureVertexC; + public int[] vertexWeights; + public int[] triangleSkin; + + +} diff --git a/src/java/modelviewer/rs/buffer/Buffer.java b/src/java/modelviewer/rs/buffer/Buffer.java new file mode 100644 index 0000000..c4fdbbc --- /dev/null +++ b/src/java/modelviewer/rs/buffer/Buffer.java @@ -0,0 +1,594 @@ +package modelviewer.rs.buffer; + +import lombok.SneakyThrows; + +import java.util.Arrays; +import java.util.LinkedList; + + +public final class Buffer { + + @SneakyThrows(Exception.class) + public static Buffer create() { + synchronized (BUFFER_CACHE) { + Buffer buffer = null; + if (cache_indices > 0) { + cache_indices--; + + buffer = (Buffer) BUFFER_CACHE.pop(); + } + if (buffer != null) { + buffer.pos = 0; + return buffer; + } + } + Buffer buffer = new Buffer(); + buffer.pos = 0; + buffer.payload = new byte[5000]; + return buffer; + } + + private Buffer() { + } + + @SneakyThrows(Exception.class) + public Buffer(byte[] payload) { + this.payload = payload; + this.pos = 0; + } + + @SneakyThrows(Exception.class) + public Buffer(int length, byte initialValue) { + this.payload = new byte[length]; + Arrays.fill(payload, initialValue); + } + + @SneakyThrows(Exception.class) + public void skip(int amount) { + this.pos += amount; + } + + @SneakyThrows(Exception.class) + public void setPosition(int pos) { + this.pos = pos; + } + + @SneakyThrows(Exception.class) + public int getSmartShortMinusOne() { + final int i_118_ = this.payload[pos++] & 0xFF; + if (i_118_ < 128) { + return (this.getSignedByte() & 0xFF) - 1; + } + return (this.getShort() & 0xFFFF) - 32769; + } + + /** + * Reads a smart value from the buffer. + * + * @return the read smart value. + */ + @SneakyThrows(Exception.class) + public int readSmart() { + int value = payload[pos] & 0xff; + if (value < 128) { + return getUnsignedByte(); + } + return getUnsignedShort() - 32768; + } + + + /** + * Reads a smart value from the buffer (supports -1). + * + * @return the read smart value. + */ + @SneakyThrows(Exception.class) + public int readSmartNS() { + return readSmart() - 1; + } + + + /** + * Writes a smart value to the buffer. + * + * @param value the value to write. + */ + @SneakyThrows(Exception.class) + public void writeSmart(int value) { + if (value >= 128) { + writeShort(value + 32768); + } else { + write_byte(value); + } + } + + + /** + * Reads an unsigned smart value from the buffer. + * + * @return the read unsigned smart value. + */ + @SneakyThrows(Exception.class) + public int readUnsignedSmart() { + int value = payload[pos] & 0xff; + if (value < 128) { + return getUnsignedByte() - 64; + } + return getUnsignedShort() - 49152; + } + + + /** + * Writes an unsigned smart value to the buffer. + * + * @param value the value to write. + */ + @SneakyThrows(Exception.class) + public void writeUnsignedSmart(int value) { + if (value < 64 && value >= -64) { + write_byte(value + 64); + return; + } + if (value < 16384 && value >= -16384) { + writeShort(value + 49152); + } else { + System.out.println("Error psmart out of range: " + value); + } + } + + + /** + * Reads a smart words from the buffer. + * + * @return the read smart value. + */ + @SneakyThrows(Exception.class) + public int readSmartWords() { + int value = 0; + int incr; + for (incr = readSmart(); incr == 32767; incr = readSmart()) { + value += 32767; + } + value += incr; + return value; + } + + + /** + * Writes a smart words to the buffer. + * + * @param value the smart value to write. + */ + @SneakyThrows(Exception.class) + public void writeSmartWords(int value) { + while (value > 32767) { + writeSmart(value); + value -= 32767; + } + if (value > 0) { + writeSmart(value); + } + } + + + /** + * Reads a big smart value from the buffer. + * + * @return the read smart value. + */ + @SneakyThrows(Exception.class) + public int readBigSmart() { + if (payload[pos] < 0) { + return getInt() & 0x7fffffff; + } + return getUnsignedShort(); + } + + + /** + * Reads a big smart value from the buffer (termination value + * -1 supported). + * + * @return the read smart value. + */ + @SneakyThrows(Exception.class) + public int readBigSmartNS() { + if (payload[pos] < 0) { + return getInt() & 0x7fffffff; + } + int value = getUnsignedShort(); + if (value == 32767) { + return -1; + } + return value; + } + + + /** + * Writes a big smart value to the buffer. + * + * @param value the value to write. + */ + @SneakyThrows(Exception.class) + public void writeBigSmart(int value) { + if (value > 32767) { + writeInt(value - Integer.MAX_VALUE - 1); + } else { + writeShort(value >= 0 ? value : 32767); + } + } + + @SneakyThrows(Exception.class) + public int get_smart_b() { + int baseVal = 0; + int lastVal = 0; + while ((lastVal = getUnsignedSmart()) == 32767) { + baseVal += 32767; + } + return baseVal + lastVal; + } + + @SneakyThrows(Exception.class) + public String getNewString() { + int i = this.pos; + while (this.payload[this.pos++] != 0) + ; + return new String(this.payload, i, pos - i - 1); + } + + @SneakyThrows(Exception.class) + public void write_byte(int value) { + this.payload[this.pos++] = (byte) value; + } + + @SneakyThrows(Exception.class) + public void writeBoolean(boolean value) { + this.payload[this.pos++] = (byte) (value ? 1 : 0); + } + + @SneakyThrows(Exception.class) + public void writeShort(int value) { + this.payload[this.pos++] = (byte) (value >> 8); + this.payload[this.pos++] = (byte) value; + } + + @SneakyThrows(Exception.class) + public void writeTriByte(int value) { + this.payload[this.pos++] = (byte) (value >> 16); + this.payload[this.pos++] = (byte) (value >> 8); + this.payload[this.pos++] = (byte) value; + } + + @SneakyThrows(Exception.class) + public void writeInt(int value) { + this.payload[this.pos++] = (byte) (value >> 24); + this.payload[this.pos++] = (byte) (value >> 16); + this.payload[this.pos++] = (byte) (value >> 8); + this.payload[this.pos++] = (byte) value; + } + + @SneakyThrows(Exception.class) + public void writeLEInt(int value) { + this.payload[this.pos++] = (byte) value; + this.payload[this.pos++] = (byte) (value >> 8); + this.payload[this.pos++] = (byte) (value >> 16); + this.payload[this.pos++] = (byte) (value >> 24); + } + + @SneakyThrows(Exception.class) + public void writeString(String text) { + System.arraycopy(text.getBytes(), 0, this.payload, this.pos, text.length()); + this.pos += text.length(); + this.payload[this.pos++] = 10; + } + + @SneakyThrows(Exception.class) + public void put_bytes(byte data[], int offset, int length) { + for (int index = length; index < length + offset; index++) + this.payload[this.pos++] = data[index]; + } + + @SneakyThrows(Exception.class) + public void put_length(int length) { + this.payload[this.pos - length - 1] = (byte) length; + } + + @SneakyThrows(Exception.class) + public int getUnsignedByte() { + return this.payload[this.pos++] & 0xff; + } + + @SneakyThrows(Exception.class) + public byte getSignedByte() { + return this.payload[this.pos++]; + } + + @SneakyThrows(Exception.class) + public int getUnsignedShort() { + this.pos += 2; + return ((this.payload[this.pos - 2] & 0xff) << 8) + (this.payload[this.pos - 1] & 0xff); + } + + @SneakyThrows(Exception.class) + public int getSignedShort() { + this.pos += 2; + int value = ((this.payload[this.pos - 2] & 0xff) << 8) + (this.payload[this.pos - 1] & 0xff); + if (value > 32767) + value -= 0x10000; + + return value; + } + + @SneakyThrows(Exception.class) + public int getShort() { + this.pos += 2; + int value = ((this.payload[this.pos - 2] & 0xff) << 8) + (this.payload[this.pos - 1] & 0xff); + + if (value > 60000) + value = -65535 + value; + return value; + } + + @SneakyThrows(Exception.class) + public int get24BitInt() { + this.pos += 3; + return ((this.payload[this.pos - 3] & 0xff) << 16) + ((this.payload[this.pos - 2] & 0xff) << 8) + (this.payload[this.pos - 1] & 0xff); + } + + @SneakyThrows(Exception.class) + public int getInt() { + this.pos += 4; + return ((this.payload[this.pos - 4] & 0xff) << 24) + ((this.payload[this.pos - 3] & 0xff) << 16) + ((this.payload[this.pos - 2] & 0xff) << 8) + (this.payload[this.pos - 1] & 0xff); + } + + @SneakyThrows(Exception.class) + public long getLong() { + long msi = (long) this.getInt() & 0xffffffffL; + long lsi = (long) this.getInt() & 0xffffffffL; + return (msi << 32) + lsi; + } + + @SneakyThrows(Exception.class) + public String getString() { + + int index = this.pos; + while (this.payload[this.pos++] != 10) + ; + return new String(this.payload, index, this.pos - index - 1); + } + + private static final char[] CHARACTERS = new char[]{ + '\u20ac', '\u0000', '\u201a', '\u0192', '\u201e', '\u2026', + '\u2020', '\u2021', '\u02c6', '\u2030', '\u0160', '\u2039', + '\u0152', '\u0000', '\u017d', '\u0000', '\u0000', '\u2018', + '\u2019', '\u201c', '\u201d', '\u2022', '\u2013', '\u2014', + '\u02dc', '\u2122', '\u0161', '\u203a', '\u0153', '\u0000', + '\u017e', '\u0178' + }; + + @SneakyThrows(Exception.class) + public String getStringOSRS() { + StringBuilder sb = new StringBuilder(); + + for (; ; ) { + int ch = this.getUnsignedByte(); + + if (ch == 0) { + break; + } + + if (ch >= 128 && ch < 160) { + char var7 = CHARACTERS[ch - 128]; + if (0 == var7) { + var7 = '?'; + } + + ch = var7; + } + + sb.append((char) ch); + } + return sb.toString(); + } + + @SneakyThrows(Exception.class) + public byte[] getStringBytes() { + int index = this.pos; + while (this.payload[this.pos++] != 10) + ; + byte[] data = new byte[this.pos - index - 1]; + System.arraycopy(this.payload, index, data, 0, this.pos - 1 - index); + return data; + } + + @SneakyThrows(Exception.class) + public void getBytes(int offset, int length, byte[] data) { + for (int index = length; index < length + offset; index++) + data[index] = this.payload[this.pos++]; + } + + @SneakyThrows(Exception.class) + public void initBitAccess() { + this.bit_pos = this.pos * 8; + } + + @SneakyThrows(Exception.class) + public int getBits(int amount) { + int byte_offset = this.bit_pos >> 3; + int bit_offset = 8 - (this.bit_pos & 7); + int value = 0; + this.bit_pos += amount; + for (; amount > bit_offset; bit_offset = 8) { + value += (this.payload[byte_offset++] & BIT_MASKS[bit_offset]) << amount - bit_offset; + amount -= bit_offset; + } + if (amount == bit_offset) + value += this.payload[byte_offset] & BIT_MASKS[bit_offset]; + else + value += this.payload[byte_offset] >> bit_offset - amount & BIT_MASKS[amount]; + + return value; + } + + @SneakyThrows(Exception.class) + public void finishBitAccess() { + this.pos = (this.bit_pos + 7) / 8; + } + + @SneakyThrows(Exception.class) + public int getSignedSmart() { + int value = this.payload[this.pos] & 0xff; + if (value < 128) { + return this.getUnsignedByte() - 64; + } else { + return this.getUnsignedShort() - 49152; + } + } + + @SneakyThrows(Exception.class) + public int getUnsignedSmart() { + int value = this.payload[this.pos] & 0xff; + if (value < 128) { + return this.getUnsignedByte(); + } else { + return this.getUnsignedShort() - 32768; + } + } + + @SneakyThrows(Exception.class) + public void putAddedByte(int value) { + this.payload[this.pos++] = (byte) (value + 128); + } + + public void putNegatedByte(int value) { + this.payload[this.pos++] = (byte) (-value); + } + + public void putSubtractedByte(int value) { + this.payload[this.pos++] = (byte) (128 - value); + } + + public int getAddedByte() { + return this.payload[this.pos++] - 128 & 0xff; + } + + public int getNegatedByte() { + return -this.payload[this.pos++] & 0xff; + } + + public int getSubtractedByte() { + return 128 - this.payload[this.pos++] & 0xff; + } + + public byte getSignedAddedByte() { + return (byte) (this.payload[this.pos++] - 128); + } + + public byte getSignedNegatedByte() { + return (byte) -this.payload[this.pos++]; + } + + public byte getSignedSubtractedByte() { + return (byte) (128 - this.payload[this.pos++]); + } + + public void putLEShortDuplicate(int value) { + this.payload[this.pos++] = (byte) value; + this.payload[this.pos++] = (byte) (value >> 8); + } + + public void putShortAdded(int value) { + this.payload[this.pos++] = (byte) (value >> 8); + this.payload[this.pos++] = (byte) (value + 128); + } + + public void putLEShortAdded(int value) { + this.payload[this.pos++] = (byte) (value + 128); + this.payload[this.pos++] = (byte) (value >> 8); + } + + public int method549() {//TODO + this.pos += 2; + return ((this.payload[this.pos - 1] & 0xff) << 8) + (this.payload[this.pos - 2] & 0xff); + } + + public int method550() {//TODO + this.pos += 2; + return ((this.payload[this.pos - 2] & 0xff) << 8) + (this.payload[this.pos - 1] - 128 & 0xff); + } + + public int get_little_short() { + this.pos += 2; + return ((this.payload[this.pos - 1] & 0xff) << 8) + (this.payload[this.pos - 2] - 128 & 0xff); + } + + public int method552() {//TODO + this.pos += 2; + int value = ((this.payload[this.pos - 1] & 0xff) << 8) + (this.payload[this.pos - 2] & 0xff); + + if (value > 32767) + value -= 0x10000; + return value; + } + + public int method553() {//TODO + this.pos += 2; + int value = ((this.payload[this.pos - 1] & 0xff) << 8) + (this.payload[this.pos - 2] - 128 & 0xff); + if (value > 32767) + value -= 0x10000; + + return value; + } + + public int method555() {//TODO + this.pos += 4; + return ((this.payload[this.pos - 2] & 0xff) << 24) + + ((this.payload[this.pos - 1] & 0xff) << 16) + + ((this.payload[this.pos - 4] & 0xff) << 8) + + (this.payload[this.pos - 3] & 0xff); + } + + public int method556() {//TODO + this.pos += 4; + return ((this.payload[this.pos - 3] & 0xff) << 24) + + ((this.payload[this.pos - 4] & 0xff) << 16) + + ((this.payload[this.pos - 1] & 0xff) << 8) + + (this.payload[this.pos - 2] & 0xff); + } + + public int method557() {//TODO + this.pos += 4; + return ((this.payload[this.pos - 2] & 255) << 8) + + ((this.payload[this.pos - 4] & 255) << 24) + + ((this.payload[this.pos - 3] & 255) << 16) + + (this.payload[this.pos - 1] & 255); + } + + + public void putReverseData(byte[] data, int length, int offset) { + for (int index = (length + offset) - 1; index >= length; index--) + this.payload[this.pos++] = (byte) (data[index] + 128); + + } + + public void getReverseData(byte[] data, int offset, int length) { + for (int index = (length + offset) - 1; index >= length; index--) + data[index] = this.payload[this.pos++]; + + } + + public byte[] payload; + public int pos; + + public int bit_pos; + private static final int[] BIT_MASKS = { + 0, 1, 3, 7, 15, 31, 63, 127, 255, + 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535, 0x1ffff, 0x3ffff, + 0x7ffff, 0xfffff, 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff, + 0x1ffffff, 0x3ffffff, 0x7ffffff, 0xfffffff, 0x1fffffff, 0x3fffffff, + 0x7fffffff, -1 + }; + + private static int cache_indices; + private static final LinkedList BUFFER_CACHE = new LinkedList(); +} diff --git a/src/java/modelviewer/scene/RSMeshGroup.java b/src/java/modelviewer/scene/RSMeshGroup.java new file mode 100644 index 0000000..500bb22 --- /dev/null +++ b/src/java/modelviewer/scene/RSMeshGroup.java @@ -0,0 +1,130 @@ +package modelviewer.scene; + +import com.application.AppConstants; +import com.application.GUI; +import javafx.scene.image.Image; +import javafx.scene.paint.PhongMaterial; +import javafx.scene.shape.MeshView; +import javafx.scene.shape.TriangleMesh; +import javafx.scene.transform.Scale; +import lombok.Getter; +import modelviewer.model.Vector3i; +import modelviewer.util.ColorUtils; +import net.runelite.cache.TextureManager; +import net.runelite.cache.definitions.ModelDefinition; +import net.runelite.cache.definitions.SpriteDefinition; +import net.runelite.cache.definitions.loaders.SpriteLoader; +import net.runelite.cache.fs.Store; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@Getter +public class RSMeshGroup { + private final ModelDefinition model; + private final List meshes = new ArrayList<>(); + public final float MODEL_SCALE = 0.03f; + + private TextureManager textureManager; + + public RSMeshGroup(ModelDefinition model) { + this.model = model; + } + + private Image texture; + + public void buildMeshes() throws IOException { + if (AppConstants.cacheType.equals("Old School RuneScape")) { + Store store = new Store(new File(GUI.cacheLibrary.getPath())); + store.load(); + textureManager = new TextureManager(store); + textureManager.load(); + } + model.computeTextureUVCoordinates(); + for (int face = 0; face < model.faceCount; face++) { + TriangleMesh mesh = new TriangleMesh(); + int faceA = model.faceIndices1[face]; + int faceB = model.faceIndices2[face]; + int faceC = model.faceIndices3[face]; + + Vector3i v1 = new Vector3i(model.vertexX[faceA], model.vertexY[faceA], model.vertexZ[faceA]); + Vector3i v2 = new Vector3i(model.vertexX[faceB], model.vertexY[faceB], model.vertexZ[faceB]); + Vector3i v3 = new Vector3i(model.vertexX[faceC], model.vertexY[faceC], model.vertexZ[faceC]); + + mesh.getPoints() + .addAll(v1.x(), v1.y(), v1.z(), v2.x(), v2.y(), v2.z(), v3.x(), v3.y(), v3.z()); + + mesh.getFaces().addAll( + 0, 0, 1, 1, 2, 2 + ); + boolean textured = model.faceTextures != null; + if (textured) { + mesh.getTexCoords() + .addAll(model.faceTextureUCoordinates[face][0], model.faceTextureVCoordinates[face][0]); + mesh.getTexCoords() + .addAll(model.faceTextureUCoordinates[face][1], model.faceTextureVCoordinates[face][1]); + mesh.getTexCoords() + .addAll(model.faceTextureUCoordinates[face][2], model.faceTextureVCoordinates[face][2]); + } else { + mesh.getTexCoords().addAll(0f, 0f, 1f, 0f, 0f, 1f); + } + MeshView view = new MeshView(mesh); + view.getTransforms().add(new Scale(MODEL_SCALE, MODEL_SCALE, MODEL_SCALE)); + if (textured) { + PhongMaterial mat = new PhongMaterial(); + if (model.faceTextures != null && model.faceTextures[face] != -1 && AppConstants.cacheType.equals("Old School RuneScape")) { + texture = exportToImage(textureManager.findTexture(model.faceTextures[face]).getFileIds()[0]); + mat.setDiffuseMap(texture); + view.setMaterial(mat); + } + else { + view.setMaterial(new PhongMaterial(ColorUtils.rs2HSLToColor(model.faceColors[face], model.faceTransparencies == null ? 0 : model.faceTransparencies[face]))); + } + } else { + view.setMaterial(new PhongMaterial(ColorUtils.rs2HSLToColor(model.faceColors[face], model.faceTransparencies == null ? 0 : model.faceTransparencies[face]))); + } + initListeners(view); + meshes.add(view); + } + } + + private void initListeners(MeshView view) { + view.setOnMouseClicked(event -> { + paint(view); + }); + view.setOnMouseEntered(event -> { + if (event.isAltDown()) { + paint(view); + } + }); + } + + private void paint(MeshView view) { + PhongMaterial mat = new PhongMaterial(); + mat.setDiffuseMap(texture); + view.setMaterial(mat); + } + + public BufferedImage export(SpriteDefinition spriteDefinition) + { + BufferedImage bi = new BufferedImage(spriteDefinition.getWidth(), spriteDefinition.getHeight(), BufferedImage.TYPE_INT_ARGB); + bi.setRGB(0, 0, spriteDefinition.getWidth(), spriteDefinition.getHeight(), spriteDefinition.getPixels(), 0, spriteDefinition.getWidth()); + return bi; + } + + public Image exportToImage(int spriteID) throws IOException + { + SpriteLoader spriteLoader = new SpriteLoader(); + SpriteDefinition spriteDefinition = spriteLoader.load(spriteID, GUI.cacheLibrary.data(8, spriteID, 0))[0]; + BufferedImage image = export(spriteDefinition); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ImageIO.write(image, "png", byteArrayOutputStream); + return new Image(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); + } +} diff --git a/src/java/modelviewer/scene/camera/OrbitCamera.java b/src/java/modelviewer/scene/camera/OrbitCamera.java new file mode 100644 index 0000000..e024768 --- /dev/null +++ b/src/java/modelviewer/scene/camera/OrbitCamera.java @@ -0,0 +1,119 @@ +package modelviewer.scene.camera; + +import javafx.scene.Camera; +import javafx.scene.Group; +import javafx.scene.PerspectiveCamera; +import javafx.scene.SubScene; +import javafx.scene.transform.Rotate; +import javafx.scene.transform.Translate; +import modelviewer.model.Xform; + +public class OrbitCamera { + + private final SubScene subScene; + private final Group root3D; + + private final double MAX_ZOOM = 300.0; + + public OrbitCamera(SubScene subScene, Group root) { + this.subScene = subScene; + this.root3D = root; + init(); + } + + private void init() { + camera.setNearClip(0.1D); + camera.setFarClip(MAX_ZOOM * 1.15D); + camera.getTransforms().addAll( + yUpRotate, + cameraPosition, + cameraLookXRotate, + cameraLookZRotate + ); + + Group rotateGroup = new Group(); + rotateGroup.getChildren().addAll(cameraXform); + cameraXform.ry.setAngle(180); + cameraXform.rx.setAngle(-18); + cameraXform.getChildren().add(cameraXform2); + cameraXform2.getChildren().add(cameraXform3); + cameraXform3.getChildren().add(camera); + cameraPosition.setZ(-cameraDistance); + + root3D.getChildren().addAll(rotateGroup); + + subScene.setCamera(camera); + subScene.setOnScroll(event -> { + + double zoomFactor = 1.05; + double deltaY = event.getDeltaY(); + + if (deltaY < 0) { + zoomFactor = 2.0 - zoomFactor; + } + double z = cameraPosition.getZ() / zoomFactor; + z = Math.max(z, -MAX_ZOOM); + z = Math.min(z, 10.0); + cameraPosition.setZ(z); + }); + + subScene.setOnMousePressed(event -> { + if (!event.isAltDown()) { + mousePosX = event.getSceneX(); + mousePosY = event.getSceneY(); + mouseOldX = event.getSceneX(); + mouseOldY = event.getSceneY(); + } + }); + + subScene.setOnMouseDragged(event -> { + if (!event.isAltDown()) { + double modifier = 1.0; + double modifierFactor = 0.3; + + if (event.isControlDown()) modifier = 0.1; + if (event.isSecondaryButtonDown()) modifier = 0.035; + + mouseOldX = mousePosX; + mouseOldY = mousePosY; + mousePosX = event.getSceneX(); + mousePosY = event.getSceneY(); + mouseDeltaX = mousePosX - mouseOldX; + mouseDeltaY = mousePosY - mouseOldY; + + double flip = -1.0; + + if (event.isSecondaryButtonDown()) { + double newX = cameraXform2.t.getX() + flip * mouseDeltaX * modifierFactor * modifier * 2.0; + double newY = cameraXform2.t.getY() + 1.0 * -mouseDeltaY * modifierFactor * modifier * 2.0; + cameraXform2.t.setX(newX); + cameraXform2.t.setY(newY); + } else if (event.isPrimaryButtonDown()) { + double yAngle = cameraXform.ry.getAngle() - 1.0 * -mouseDeltaX * modifierFactor * modifier * 2.0; + double xAngle = cameraXform.rx.getAngle() + flip * mouseDeltaY * modifierFactor * modifier * 2.0; + cameraXform.ry.setAngle(yAngle); + cameraXform.rx.setAngle(xAngle); + } + } + }); + } + + + private final Camera camera = new PerspectiveCamera(true); + private final Rotate cameraXRotate = new Rotate(-20.0, 0.0, 0.0, 0.0, Rotate.X_AXIS); + private final Rotate cameraYRotate = new Rotate(-20.0, 0.0, 0.0, 0.0, Rotate.Y_AXIS); + private final Rotate cameraLookXRotate = new Rotate(0.0, 0.0, 0.0, 0.0, Rotate.X_AXIS); + private final Rotate cameraLookZRotate = new Rotate(0.0, 0.0, 0.0, 0.0, Rotate.Z_AXIS); + private final Translate cameraPosition = new Translate(0.0, 0.0, 0.0); + private Xform cameraXform = new Xform(); + private Xform cameraXform2 = new Xform(); + private Xform cameraXform3 = new Xform(); + private double cameraDistance = 25.0; + private double mousePosX = 0; + private double mousePosY = 0; + private double mouseOldX = 0; + private double mouseOldY = 0; + private double mouseDeltaX = 0; + private double mouseDeltaY = 0; + private final Rotate yUpRotate = new Rotate(0.0, 0.0, 0.0, 0.0, Rotate.X_AXIS); +} diff --git a/src/java/modelviewer/util/ColorUtils.java b/src/java/modelviewer/util/ColorUtils.java new file mode 100644 index 0000000..b0a39d6 --- /dev/null +++ b/src/java/modelviewer/util/ColorUtils.java @@ -0,0 +1,22 @@ +package modelviewer.util; + +import javafx.scene.paint.Color; + +public final class ColorUtils { + + public static Color rs2HSLToColor(short hsl, int alpha) { + int transparency = alpha; + if (transparency <= 0) { + transparency = 255; + } + + int hue = hsl >> 10 & 0x3f; + int sat = hsl >> 7 & 0x07; + int bri = hsl & 0x7f; + java.awt.Color awtCol = java.awt.Color.getHSBColor((float) hue / 63, (float) sat / 7, (float) bri / 127); + double r = awtCol.getRed() / 255.0; + double g = awtCol.getGreen() / 255.0; + double b = awtCol.getBlue() / 255.0; + return Color.color(r, g, b, transparency / 255.0); + } +} diff --git a/src/java/net/runelite/cache/TextureManager.java b/src/java/net/runelite/cache/TextureManager.java index 0b01d47..eb6747b 100644 --- a/src/java/net/runelite/cache/TextureManager.java +++ b/src/java/net/runelite/cache/TextureManager.java @@ -35,8 +35,8 @@ public class TextureManager implements TextureProvider { - private final Store store; - private final List textures = new ArrayList<>(); + public final Store store; + public final List textures = new ArrayList<>(); public TextureManager(Store store) { diff --git a/src/java/net/runelite/cache/definitions/TextureDefinition.java b/src/java/net/runelite/cache/definitions/TextureDefinition.java index 820d73b..d0c810b 100644 --- a/src/java/net/runelite/cache/definitions/TextureDefinition.java +++ b/src/java/net/runelite/cache/definitions/TextureDefinition.java @@ -42,9 +42,11 @@ public class TextureDefinition public int animationDirection; public transient int[] pixels; + public int width; public boolean method2680(double var1, int var3, SpriteProvider spriteProvider) { + width = var3; int var5 = var3 * var3; this.pixels = new int[var5]; diff --git a/src/java/rshd/ModelData.java b/src/java/rshd/ModelData.java index 4aea87c..f4249d9 100644 --- a/src/java/rshd/ModelData.java +++ b/src/java/rshd/ModelData.java @@ -54,7 +54,6 @@ public ModelDefinition load(int modelId, byte[] data) { if (data[data.length - 1] == -1 && data[data.length - 2] == -1) { decodeNewFormat(modelDefinition, data); - modelDefinition.resize(32, 32, 32); modelDefinition.setModelData(data); } /* diff --git a/src/resources/META-INF/MANIFEST.MF b/src/resources/META-INF/MANIFEST.MF index 42d8598..797d99b 100644 --- a/src/resources/META-INF/MANIFEST.MF +++ b/src/resources/META-INF/MANIFEST.MF @@ -1,3 +1,3 @@ Manifest-Version: 1.0 -Main-Class: application.Main +Main-Class: com.application.Main diff --git a/src/resources/css/darktheme.css b/src/resources/css/darktheme.css new file mode 100644 index 0000000..7826860 --- /dev/null +++ b/src/resources/css/darktheme.css @@ -0,0 +1,60 @@ +/* + * This is an adjustment of the original modena.css for a consistent dark theme. + * Original modena.css here: https://gist.github.com/maxd/63691840fc372f22f470. + */ + +/* Redefine base colors */ +.root { + -fx-base: rgb(50, 50, 50); + -fx-background: rgb(50, 50, 50); + + /* make controls (buttons, thumb, etc.) slightly lighter */ + -fx-color: derive(-fx-base, 10%); + + /* text fields and table rows background */ + -fx-control-inner-background: rgb(20, 20, 20); + /* version of -fx-control-inner-background for alternative rows */ + -fx-control-inner-background-alt: derive(-fx-control-inner-background, 2.5%); + + /* text colors depending on background's brightness */ + -fx-light-text-color: rgb(220, 220, 220); + -fx-mid-text-color: rgb(100, 100, 100); + -fx-dark-text-color: rgb(20, 20, 20); + + /* A bright blue for highlighting/accenting objects. For example: selected + * text; selected items in menus, lists, trees, and tables; progress bars */ + -fx-accent: rgb(0, 80, 100); + + /* color of non-focused yet selected elements */ + -fx-selection-bar-non-focused: rgb(50, 50, 50); +} + +/* Fix derived prompt color for text fields */ +.text-input { + -fx-prompt-text-fill: derive(-fx-control-inner-background, +50%); +} + + +/* Keep prompt invisible when focused (above color fix overrides it) */ +.text-input:focused { + -fx-prompt-text-fill: transparent; +} + + + +/* Fix scroll bar buttons arrows colors */ +.scroll-bar > .increment-button > .increment-arrow, +.scroll-bar > .decrement-button > .decrement-arrow { + -fx-background-color: -fx-mark-highlight-color, rgb(220, 220, 220); +} + +.scroll-bar > .increment-button:hover > .increment-arrow, +.scroll-bar > .decrement-button:hover > .decrement-arrow { + -fx-background-color: -fx-mark-highlight-color, rgb(240, 240, 240); +} + + +.scroll-bar > .increment-button:pressed > .increment-arrow, +.scroll-bar > .decrement-button:pressed > .decrement-arrow { + -fx-background-color: -fx-mark-highlight-color, rgb(255, 255, 255); +} \ No newline at end of file diff --git a/src/resources/fxml/ui.fxml b/src/resources/fxml/ui.fxml new file mode 100644 index 0000000..0ea559f --- /dev/null +++ b/src/resources/fxml/ui.fxml @@ -0,0 +1,10 @@ + + + + + + + + +