3D model bevel generation based on HTML5 WebGL

Keywords: Front-end JSON less Windows

Preface

There are not only horizontal faces in 3D scene, but also countless faces in space. So we may place objects on any face. How to determine the faces in space? We know that a face in space can be made up of a point and a normal. The left side of the Demo is the panel. Drag the object from the panel to the 3D scene on the right. Of course, the position I drag the mouse to is the point where the object is placed. But this time, our focus is how to place the model on the slope.

Design sketch:

code generation

Create scene

dm = new ht.DataModel(); // Data model (http://hightopo.com/guide/guide/core/datamodel/ht-datamodel-guide.html)
g3d = new ht.graph3d.Graph3dView(dm); // 3D scene component (http://hightopo.com/guide/guide/core/3d/ht-3d-guide.html)
palette = new ht.widget.Palette(); // Panel components (http://hightopo.com/guide/guide/plugin/palette/ht-palette-guide.html)
splitView = new ht.widget.SplitView(palette, g3d, 'h', 0.2); // Split components. The third parameter is the split method. h is left and right, v is up and down. The fourth parameter is the split proportion. The value greater than 1 is the absolute width, and the value less than 1 is the proportion.
splitView.addToDOM();//Add the split component to the body

The definitions of these components can be viewed in the corresponding links. It is necessary to explain the addToDOM function that adds the split component to the body (I mentioned it every time, this is really important!).

HT The components of are usually embedded in containers such as BorderPane, SplitView and TabView, while the outermost HT component requires the user to manually add the underlying div element returned by getView() to the DOM element of the page. Note that when the size of the parent container changes, if the parent container is the HT predefined container components such as BorderPane and SplitView, then H T's container will automatically recursively call the child component invalidate function to notify of updates. However, if the parent container is a native html element, the HT component cannot know that it needs to be updated. Therefore, the outermost HT component generally needs to listen for window size change events and call the invalidate function of the outermost component to update.

For the convenience of loading and filling the window with the outermost components, all components of HT have addToDOM functions. The implementation logic is as follows, where iv is the abbreviation of invalidate:

addToDOM = function(){
    var self = this,
        view = self.getView(), // Get the underlying div of the component
        style = view.style;
    document.body.appendChild(view); // Add the component's underlying div to the body
    style.left = '0'; // ht sets the position of all components to absolute by default.
    style.right = '0';
    style.top = '0';
    style.bottom = '0';
    window.addEventListener('resize', function () { self.iv(); }, false); // Window size change event, call refresh function
}

You may notice that the slope I added in the scene is actually a ht.Node node. As a reference to the ground plane, the stereo sense will be stronger in this comparison. Here is the definition of this node:

node = new ht.Node();
node.s3(1000, 1, 1000); // Set the size of the node
node.r3(0, 0, Math.PI/4); // It is learned to set the rotation angle of node rotation, which is related to the position of drag and drop to be set below.
node.s('3d.movable', false); // Set that the node cannot be moved on 3d, because this node is only a reference, it is recommended not to move.
dm.add(node); // Add node to data container

Content building on the left

Palette is similar to GraphView, driven by ht.DataModel, displaying groups with ht.Group and button elements with ht.Node. I encapsulate the primitive function in the loading palette panel as initPalette, which is defined as follows:

function initPalette() { // Loading elements in palette panel components
    var arrNode = ['displayDevice', 'cabinetRelative', 'deskChair', 'temperature', 'indoors', 'monitor','others'];
    var nameArr = ['Exhibition facilities', 'Cabinet related', 'Tables and chairs store', 'temperature control', 'indoor', 'Video surveillance', 'Other']; // index in arrNode corresponds to one-to-one in nameArr
    
    for (var i = 0; i < arrNode.length; i++) {
        var name = nameArr[i];
        var vName = arrNode[i];

        arrNode[i] = new ht.Group(); // palette panel is to divide elements into groups, and then add elements to groups.
        palette.dm().add(arrNode[i]); // Add group elements to palette panel components
        arrNode[i].setExpanded(true); // Set group to on
        arrNode[i].setName(name); // Set the group name to display on the group
        
        var imageArr = [];
        switch(i){ // Set different elements in each group according to different groups
            case 0:
                imageArr = ['models/Computer room/Exhibition facilities/Big screen.png'];
                break;
            case 1: 
                imageArr = ['models/Computer room/Cabinet related/Power Distribution Box.png', 'models/Computer room/Cabinet related/Outdoor antenna.png', 'models/Computer room/Cabinet related/Cabinet 1.png', 
                            'models/Computer room/Cabinet related/Cabinet 2.png', 'models/Computer room/Cabinet related/Cabinet 3.png', 'models/Computer room/Cabinet related/Cabinet 4.png', 
                            'models/Computer room/Cabinet related/Battery cabinet.png'];
                break;
            case 2: 
                imageArr = ['models/Computer room/Tables and chairs store/Lockers.png', 'models/Computer room/Tables and chairs store/Table.png', 'models/Computer room/Tables and chairs store/Chair.png'];
                break;
            case 3: 
                imageArr = ['models/Computer room/temperature control/Air conditioning streamlining.png', 'models/Computer room/Fire protection facilities/Fire fighting equipment.png'];
                break;
            case 4:
                imageArr = ['models/indoor/Simple desk.png', 'models/indoor/book.png', 'models/indoor/Desk image.png', 'models/indoor/Office chair.png'];
                break;
            case 5:
                imageArr = ['models/Computer room/Video surveillance/Camera side.png', 'models/Computer room/Video surveillance/Intercom maintenance camera.png', 'models/Computer room/Video surveillance/Micro camera.png'];
                break;
            default: 
                imageArr = ['models/Other/Signal tower.png'];
                break;
        }
        setPalNode(imageArr, arrNode[i]); // Create nodes on palette, set name, display picture, parent-child relationship
    }
}

I made some name settings in the setPalNode function, mainly to set the name of the model and the path of different files in different folders according to the path name I passed in the initPalette function above:

function setPalNode(imageArr, arr) {
    for (var j = 0; j < imageArr.length; j++) {
        var imageName = imageArr[j];
        var jsonUrl = imageName.slice(0, imageName.lastIndexOf('.')) + '.json'; // json path in shape3d
        var name = imageName.slice(imageName.lastIndexOf('/')+1, imageName.lastIndexOf('.')); // Take the string between the last / and. To set the node name
        var url = imageName.slice(imageName.indexOf('/')+1, imageName.lastIndexOf('.')); // The string between the first / and last. Is used to set the path of dragging to generate the model obj file.
        
        createNode(name, imageName, arr, url, jsonUrl); // Create a node, which is displayed on the palette panel
    }
}

createNode the function to create a node is relatively simple:

function createNode(name, image, parent, urlName, jsonUrl) { // Create nodes on palette panel components
    var node = new ht.Node();
    palette.dm().add(node);
    node.setName(name); // Setting the node name is also used to set the name of the text displayed on the palette panel.
    node.setImage(image); // Set the picture of the node
    node.setParent(parent); // Set up the parent node
    node.s({
        'draggable': true, // Set nodes to drag
        'image.stretch': 'centerUniform', // Set how node pictures are drawn
        'label': '' // Set the label of the node to be empty, so that even if name is set, it will not be displayed under the model in 3d.
    });
    node.a('urlName', urlName); // a set user-defined properties
    node.a('jsonUrl', jsonUrl);
    return node;
}

Although it is simple, it should be mentioned that draggable: true is to set the node to be draggable, otherwise the node cannot be dragged; node.s is the default style setting method encapsulated by HT. If the user needs to add his own method, he can add it through node.a method. Parameter 1 is the user-defined name, parameter 2 is the user-defined value, which can not only pass constant, but also pass constant. Variables, objects, and functions! Another very powerful function.

Drag and drop function

Drag basically responds to the dragover and drop events of windows. To create a model when you release the mouse, you need to generate a model when the event triggers:

function dragAndDrop() { // Drag and drop function
    g3d.getView().addEventListener("dragover", function(e) { // Drag and drop events
        e.dataTransfer.dropEffect = "copy";
        handleOver(e);
    });
    g3d.getView().addEventListener("drop", function(e) { // Release mouse event
        handleDrop(e);
    });
}

function handleOver(e) {
    e.preventDefault(); // Cancels the default action for an event.
}

function handleDrop(e) { // When the mouse is released
    e.preventDefault(); // Cancels the default action for an event.
    
    var paletteNode = palette.dm().sm().ld(); // Get the last selected node in the palette panel
    if (paletteNode) {
        loadObjFunc('assets/objs/' + paletteNode.a('urlName') + '.obj', 'assets/objs/' + paletteNode.a('urlName') + '.mtl', 
                             paletteNode.a('jsonUrl'), g3d.getHitPosition(e, [0, 0, 0], [-1, 1, 0])); // Load obj model
    }
}

It's absolutely necessary to explain here that the focus of this Demo is coming! The last parameter in the loadobjfunction function is the position3d coordinate of the generated model. The method g3d.getHitPosition has three parameters in total. The first parameter is the event type. If the second and third parameters are not set, the default is the center point of the horizontal plane, i.e., [0, 0], and the normal is the y-axis, i.e., [0, 1, 0]. One normal and one point can be determined. A face, so we use this method to set which face this node is to be placed on. I set the node node to rotate 45 ° around the z axis in front of me, so we need to think about how to set the normal here. This is a mathematical problem, and we need to think about it.

Loading model

HT loads the model through the ht.Default.loadObj function, but only if there is a node on which to load the model:

function loadObjFunc(objUrl, mtlUrl, jsonUrl, p3) { // Load obj model
    var node = new ht.Node();
    var shape3d = jsonUrl.slice(jsonUrl.lastIndexOf('/')+1, jsonUrl.lastIndexOf('.'));
    
    ht.Default.loadObj(objUrl, mtlUrl, { // HT loads obj model through loadObj function
        cube: true, // Whether to scale the model to the size range of unit 1, default to false
        center: true, // Whether the model is centered. The default value is false. If it is set to true, the model position will be moved to center its content.
        shape3d: shape3d, // If the shape3d name is specified, HT will automatically build all the material models after loading and parsing into an array, and register with that name.
        finishFunc: function(modelMap, array, rawS3) { // Used for callback processing after loading
            if (modelMap) {
                node.s({ // Set node style
                    'shape3d': jsonUrl, // jsonUrl is the json file path of obj model
                    'label': '' // Set the label to null, and the priority of the label is higher than name, so even if name is set, the name will not be displayed below the node.
                });
                g3d.dm().add(node); // Add node to data container

                node.s3(rawS3); // Set node size raw size of rawS3 model
                node.p3(p3); // Set 3D coordinates of nodes
                node.setName(shape3d); // Set node name
                node.setElevation(node.s3()[1]/2); // Control the y-axis position of the 3D coordinate system where the Node entity center is located
                g3d.sm().ss(node); // Set the current node selected
                g3d.setFocus(node); // Set focus on current node
                return node;
            }
        }
    });
}

End of code!

summary

In fact, this Demo is really very easy. The difficulty may lie in the ability of spatial thinking. First, confirm the normal and point, and then find the side according to the normal and point. This side has a contrast in my way, and it can be understood. If it's a fantasy, it may be easy to string. This Demo is easy mainly because the encapsulated hitPosition function is simple and easy to use. This is really a work.

Posted by keithh0427 on Thu, 24 Oct 2019 19:50:24 -0700