Thread: Building ur own Model Editor

Page 1 of 2 12 LastLast
Results 1 to 10 of 18
  1. #1 Building ur own Model Editor 
    nice


    Join Date
    Jul 2014
    Posts
    740
    Thanks given
    382
    Thanks received
    562
    Rep Power
    4239
    Alright, this tutorial will show how you can build ur own simple model editor(this is just a base) i will be including the source code below

    I will be using JavaFX for this tutorial(i was actually gonna use OpenGL first but setting up picking, simple UI and a few other things would take longer and it would be less friendly for people not familiar with OpenGL) for the following reasons:
    - I've used it for a while now(also built my model editor with it)
    - UI
    - To showcase triangle picking(JavaFX has it built in)


    The editor shown in this tutorial will have the following features:
    - RS Model Loading
    - Model Rendering
    - Simple orbit camera(can drag, zoom, move around the scene)
    - 3D grid
    - Color Painting
    - Texture Painting


    I am using Java 16 for this project

    First of all create a project and include the JavaFX modules(controls, fxml):
    Code:
    id 'org.openjfx.javafxplugin' version '0.0.9'
    
    javafx {
        version = "17-ea+11" // feel free to use an earlier version
        modules = [ 'javafx.controls', 'javafx.fxml']
    }
    Then create a new fxml file and add an AnchorPane(call it modelPane) this is where we will render our scene

    Now it's time to setup the actual JavaFX Application, simply create a main class which extends the Application class and create the scene from the fxml file, it should look something like this:
    Code:
    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    public class Main extends Application {
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/ui.fxml"));
            Scene scene = new Scene(fxmlLoader.load());
            scene.getStylesheets().add(getClass().getResource("/css/darktheme.css").toExternalForm());
            primaryStage.setScene(scene);
            primaryStage.setTitle("Simple Model Editor");
            primaryStage.show();
        }
    }
    Now lets create our main controller class(make sure you refer to it in the fxml file) It should look like this:
    Code:
    public class ModelController implements Initializable {
    
        @FXML
        private AnchorPane modelPane;
    
         //initialize method
    alright now you should be able to run the program and it should open an empty window
    Now add the model class with necessary fields and the decoder, this part is self explanatory, you can simply copy it from ur client and remove unused parts

    onto the 3d stuff now
    so, we obviously need a camera and JavaFX has 2 built in camera implementations, Perspective camera and Parallel Camera(Orthographic) one of which is for perspective and the other for orthographic projection. Feel free to read more about it here: https://en.wikipedia.org/wiki/3D_projection

    Lets setup a perspective camera with some controls now:
    Code:
    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);
    }
    Im not going to explain how all of the camera math works on this thread, but if ur curious feel free to ask me and i can explain it

    Now lets create a class that will represent our mesh, lets call it RSMeshGroup (I'll explain why it's called that way in a moment)

    Alright so to represent a model made of triangles JavaFX has a TriangleMesh class built in, which has the following properties:
    - Points(Vertices)
    - Normals
    - Texture coordinates
    - Faces(indices)
    - Smoothing Groups
    For this tutorial we'll only care about the points, faces and texture coordinates

    the RSMeshGroup class should have the following properties:
    the model itself, a list of MeshView's and the model scale, the model should be initialized inside the constructor

    now lets actually build the mesh, or actually meshes because we are going to build a mesh per triangle, now you might be wondering why in the world, isn't that super inefficient? Indeed it is but it's not always possible to build it as 1 mesh either, lets see why

    So a runescape model can either have a solid color or a texture as a material for each triangle, but triangle meshes in JavaFX can only have 1 material per mesh, so you might think "dang i guess i really have to build a mesh per triangle" fortunately not, for a mesh with only solid colors we could build a color atlas (which would be an image) and then we would use the proper UV's(texture coordinates) to give each face(triangle) the correct color.
    For a mesh with both solid colors and textures we can also optimize it by grouping the textures, for example: if we had a mesh with 200 triangles where 150 triangles have solid colors, 20 triangles texture1, 30 triangles texture2 we could build it as only 3 meshes instead of 200

    For this tutorial i will be implementing it the simple & inefficient way as i also want to showcase triangle picking(for color, texture painting) and it's just much simpler that way, however if you plan to make your own editor definitely implement what i explained above(especially when ur going to add animation support and other features where the mesh constantly changes)
    Code:
        public void buildMeshes() {
            for (int face = 0; face < model.triangleCount; face++) {
                TriangleMesh mesh = new TriangleMesh();
                int faceA = model.faceIndicesA[face];
                int faceB = model.faceIndicesB[face];
                int faceC = model.faceIndicesC[face];
    
                Vector3i v1 = new Vector3i(model.verticesXCoordinate[faceA], model.verticesYCoordinate[faceA], model.verticesZCoordinate[faceA]);
                Vector3i v2 = new Vector3i(model.verticesXCoordinate[faceB], model.verticesYCoordinate[faceB], model.verticesZCoordinate[faceB]);
                Vector3i v3 = new Vector3i(model.verticesXCoordinate[faceC], model.verticesYCoordinate[faceC], model.verticesZCoordinate[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
                );
                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));
                view.setMaterial(new PhongMaterial(ColorUtils.rs2HSLToColor(model.triangleColors[face], model.faceAlpha == null ? 0 : model.faceAlpha[face])));
                meshes.add(view);
            }
        }
    Now lets go back to our controller class and create the model:
    Code:
            byte[] modelData = Files.readAllBytes(Path.of("./9638.dat")); // fire cape
            Model model = Model.decode(modelData);
    and finally we can build our 3D scene
    Code:
     private void initScene(Model model) {
            meshGroup = new RSMeshGroup(model);
            meshGroup.buildMeshes(); // build the triangle meshes
            scene = buildScene();
            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()); // add our built meshes to the group
            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;
        }
    Running this should render the model and the camera controls should work aswell, lets also add a 3D grid
    before we build the sub scene:
    Code:
            Group grid = new Grid3D().create(48f, 1.25f);
            scene.getChildren().add(grid);
    building the grid relies on a lot of other classes which i won't include in this thread(will be included in the source code)
    Result:
    Attached image
    Looks good but we haven't added support for textured triangles yet, fortunately this is easy to do.
    For the sake of this tutorial lets just load the fire cape texture
    Code:
    private final Image texture = new Image("file:40.png");
    Now lets compute the UV coordinates from our texture coordinates in RS format(a 3D triangle)
    Code:
     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;
                }
            }
        }
    If you care about the derivation: https://nothings.org/gamedev/ray_plane.html
    Lets also call the computeUVCoordinates() method at the beginning of buildMeshes()
    Then for each triangle we check if the triangle is textured or not:
    Code:
    boolean textured = model.triangleInfo[face] >= 2;
    and based on that we set the texture coordinates, material:
    Code:
                if (textured) {
                    mesh.getTexCoords()
                            .addAll(model.textureUCoordinates[face][0], model.textureVCoordinates[face][0]);
                    mesh.getTexCoords()
                            .addAll(model.textureUCoordinates[face][1], model.textureVCoordinates[face][1]);
                    mesh.getTexCoords()
                            .addAll(model.textureUCoordinates[face][2], model.textureVCoordinates[face][2]);
                } else {
                    mesh.getTexCoords().addAll(0f, 0f, 1f, 0f, 0f, 1f);
                }
    
                if (textured) {
                    PhongMaterial mat = new PhongMaterial();
                    mat.setDiffuseMap(texture);
                    view.setMaterial(mat);
                } else {
                    view.setMaterial(new PhongMaterial(ColorUtils.rs2HSLToColor(model.triangleColors[face], model.faceAlpha == null ? 0 : model.faceAlpha[face])));
                }
    Now if we run the program again, we should see this:
    Attached image

    Alright cool we got our model to render properly, lets add support for simple editing now, ill add the following:
    - Color painting
    - Texture painting

    Also perhaps a wireframe option?

    Lets add a color picker and 2 check boxes (for texture painting and wireframe mode)

    Code:
        @FXML
        private ColorPicker colorPicker;
    
        @FXML
        private CheckBox paintTexture, wireframe;
    now lets define a SharedProperties class and add the 3 properties there

    Code:
    import javafx.beans.property.BooleanProperty;
    import javafx.beans.property.ObjectProperty;
    import javafx.beans.property.SimpleBooleanProperty;
    import javafx.beans.property.SimpleObjectProperty;
    import javafx.scene.paint.Color;
    import lombok.Getter;
    
    @Getter
    public final class SharedProperties {
    
        private final ObjectProperty<Color> selectedColor = new SimpleObjectProperty<>(Color.RED);
        private final BooleanProperty paintTexture = new SimpleBooleanProperty();
        private final BooleanProperty wireframe = new SimpleBooleanProperty();
    
        private static SharedProperties instance;
    
        public static SharedProperties getInstance() {
            if (instance == null) {
                instance = new SharedProperties();
            }
            return instance;
        }
    
        private SharedProperties() {
    
        }
    }
    Now lets bind them
    Code:
        private void initBindings() {
            sharedProperties.getSelectedColor().bind(colorPicker.valueProperty());
            sharedProperties.getPaintTexture().bind(paintTexture.selectedProperty());
            sharedProperties.getWireframe().bind(wireframe.selectedProperty());
        }
    call this before u init the scene

    Now back to our buildMeshes() method, lets bind the draw mode
    Code:
                view.drawModeProperty().bind(
                        Bindings.when(
                                sharedProperties.getWireframe())
                                .then(DrawMode.LINE)
                                .otherwise(DrawMode.FILL)
                );
    and we should have the wireframe mode working:
    Attached image

    Alright, now for the color, texture painting lets setup 2 listeners:
    Code:
        private void initListeners(MeshView view) {
            view.setOnMouseClicked(event -> {
                paint(view);
            });
            view.setOnMouseEntered(event -> {
                if (event.isAltDown()) {
                    paint(view);
                }
            });
        }
    and our paint method:
    Code:
        private void paint(MeshView view) {
            boolean paintTexture = sharedProperties.getPaintTexture().get();
            if (paintTexture) {
                PhongMaterial mat = new PhongMaterial();
                mat.setDiffuseMap(texture);
                view.setMaterial(mat);
            } else {
                Color selectedColor = sharedProperties.getSelectedColor().get();
                view.setMaterial(new PhongMaterial(selectedColor));
            }
        }
    of course this doesn't support picking & setting ur own texture coordinates but that is very easy to add and i'll leave that up to you

    Result:
    Attached image

    As you can see building ur own simple editor that renders the model and lets u paint colors and textures is not difficult at all

    oh yeah i posted this under the RS2 Client section cause i had no clue which other section would fit better

    Edit: There is another way to render the mesh even more efficiently (1 mesh for the whole model and it doesn't matter how many colors or textures there are which p much means 1 render call) simply build a texture from the mesh colors, textures. If you have meshes with out of bounds UV coordinates (models that 'abuse' texture wrapping usually use < 0, > 1 UVs) then you need to build a 3x3 square of the texture and use the center one relatively
    for storing the colors you can just build width colors per row

    Source code: https://github.com/Suicolen/Simple-JFX-Model-Editor
    Attached image
    Reply With Quote  
     


  2. #2  
    🎶 As you're falling down 🎶


    uint32_t's Avatar
    Join Date
    Feb 2015
    Posts
    1,396
    Thanks given
    6,177
    Thanks received
    776
    Rep Power
    5000
    Nice contribution, this is great.
    Quote Originally Posted by Idiot Bird View Post
    Quote Originally Posted by Velocity View Post
    lol np mate looks like the community brought ur rep down to ur IQ
    Not too sure about that, it's at 0 . It would have to go minus to even be remotely close to his IQ.
    Reply With Quote  
     

  3. Thankful user:


  4. #3  
    Extreme Donator

    Benneh's Avatar
    Join Date
    Nov 2015
    Posts
    199
    Thanks given
    133
    Thanks received
    102
    Rep Power
    464
    Nice nice Soooick
    Quote Originally Posted by Corey View Post
    Vouch for Benneh

    Worked with him for a month. He's professional and always on time with posts, always interested in how the server is doing and how he can can improve and help in any way.
    Reply With Quote  
     

  5. Thankful user:


  6. #4  
    Banned

    Join Date
    Mar 2015
    Age
    31
    Posts
    1,332
    Thanks given
    215
    Thanks received
    329
    Rep Power
    0
    another best contribution thanks suic
    Reply With Quote  
     

  7. Thankful user:


  8. #5  
    Blurite

    Corey's Avatar
    Join Date
    Feb 2012
    Age
    26
    Posts
    1,491
    Thanks given
    1,245
    Thanks received
    1,729
    Rep Power
    5000
    how far you've come

    gj thesuic
    Attached image
    Reply With Quote  
     

  9. Thankful users:


  10. #6  
    Donator

    Yuuji's Avatar
    Join Date
    Feb 2012
    Posts
    678
    Thanks given
    232
    Thanks received
    153
    Rep Power
    197
    Really nice stuff man!

    Attached image
    Attached image
    Reply With Quote  
     

  11. Thankful user:


  12. #7  
    nice


    Join Date
    Jul 2014
    Posts
    740
    Thanks given
    382
    Thanks received
    562
    Rep Power
    4239
    Quote Originally Posted by Corey View Post
    how far you've come

    gj thesuic
    thx =]
    Attached image
    Reply With Quote  
     

  13. #8  
    'Slutty McFur'

    Owain's Avatar
    Join Date
    Sep 2014
    Age
    26
    Posts
    2,894
    Thanks given
    2,360
    Thanks received
    2,200
    Rep Power
    5000
    little sooick is all grown up
    big boy stuff this, gj m8


    Spoiler for wat:
    Attached image
    Attached image

    Attached image


    Reply With Quote  
     

  14. Thankful users:


  15. #9  
    ᗪ乇尺乇乙乙乇ᗪ

    lumplum's Avatar
    Join Date
    Nov 2015
    Posts
    1,145
    Thanks given
    529
    Thanks received
    1,463
    Rep Power
    5000
    Great work my good friend thesuic12!
    Attached image
    Reply With Quote  
     

  16. Thankful user:


  17. #10  
    Registered Member

    Join Date
    Dec 2009
    Posts
    774
    Thanks given
    367
    Thanks received
    455
    Rep Power
    927
    +1 for using Javafx's observable beans properties.
    link removed
    Reply With Quote  
     

  18. Thankful user:


Page 1 of 2 12 LastLast

Thread Information
Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)


User Tag List

Similar Threads

  1. RSME - RuneScape Model Editor
    By Yarnova in forum Tools
    Replies: 122
    Last Post: 10-19-2008, 11:14 PM
  2. model-editor by bakatool
    By zen2beo in forum Downloads
    Replies: 11
    Last Post: 03-02-2008, 04:44 AM
  3. [req] kevins floor model editor download [req]
    By Mikey` in forum Tutorials
    Replies: 6
    Last Post: 02-27-2008, 08:29 AM
  4. REQ-Model Editor and Cache extractor-REQ
    By Marneros22 in forum Requests
    Replies: 1
    Last Post: 02-24-2008, 12:02 PM
  5. A New Project - Model Editor.
    By James in forum RS2 Client
    Replies: 37
    Last Post: 01-25-2008, 01:32 AM
Posting Permissions
  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •