preface

Horizontal plane is not the only plane in 3D scene. Space is composed of countless planes, so we may place objects on any plane. How to determine the plane in space? We know that a surface in space can consist of a point and a normal line. In this Demo, there is a panel on the left. Drag objects from the panel to the 3D scene on the right. Of course, where I drag the mouse is where the objects are placed, but this time we will focus on how to place the model on the inclined plane.

Effect:

Code generation

Create a scenario

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

The definition of these components can be found in the corresponding link, but the addToDOM function that adds the split component to the body needs to be explained (I mention this every time, it’s really important!). .

While HT components are typically embedded in containers such as BorderPane, SplitView, and TabView, the outermost HT components require the user to manually add the underlying div element returned by getView() to the DOM element of the page. When the parent container size changes, if the parent container is a predefined container component such as BorderPane and SplitView, the HT container automatically recursively calls the child component invalidate function to notify the update. 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 usually needs to listen for the window size change event and call the invalidate function of the outermost component to update.

For ease of loading and filling Windows for the outermost components, all components of HT have addToDOM functions that implement the following logic, where iv is short for 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 changes event, call refresh function
}
Copy the code

As you may have noticed, the inclined plane I added to the scene is actually an HT. Node, which is used as a reference to the ground plane, which gives it a more three-dimensional feel. Here is the definition of this node:

node = new ht.Node();
node.s3(1000.1.1000); // Set the node size
node.r3(0.0.Math.PI/4); // The rotation Angle of the node is learned and depends on where the drag is placed
node.s('3d.movable'.false); // Set the node to be immovable in 3D because the node is only a reference, the recommendation is not to allow movement
dm.add(node); // Add the node to the data container
Copy the code

Left content building

function initPalette() { // Load an icon from the Palette component
    var arrNode = ['displayDevice'.'cabinetRelative'.'deskChair'.'temperature'.'indoors'.'monitor'.'others'];
    var nameArr = ['Display Facilities'.'Cabinet Related'.'Table and chair storage'.'Temperature control'.'interior'.'Video surveillance'.'other']; // Index in arrNode corresponds to nameArr
    
    for (var i = 0; i < arrNode.length; i++) {
        var name = nameArr[i];
        var vName = arrNode[i];

        arrNode[i] = new ht.Group(); // Palette is used to select items from "groups", and then add items to "Groups"
        palette.dm().add(arrNode[i]); // Add group primitives to the Palette component
        arrNode[i].setExpanded(true); // Set the group to open
        arrNode[i].setName(name); // Set the group name to be displayed on the group
        
        var imageArr = [];
        switch(i){ // Set different elements in each group according to different groups
            case 0:
                imageArr = ['Models/machine room/Display Facilities/large screen.png'];
                break;
            case 1: 
                imageArr = ['Models/Machine room/Cabinet/power distribution Box.png'.'Models/Machine room/Cabinet/Outdoor antenna.png'.'Models/machine room/Cabinet related/cabinet 1.png'.'Models/machine room/Cabinet related/cabinet 2.png'.'Models/machine room/Cabinet related/cabinet 3.png'.'Models/machine room/Cabinet related/cabinet 4.png'.'Models/Machine room/Cabinet/Battery Cabinet. PNG'];
                break;
            case 2: 
                imageArr = ['Models/computer room/Desk and chair storage/locker.png'.'Models/machine room/Desk/table.png'.'Models/computer room/Table and chair storage/chair. PNG'];
                break;
            case 3: 
                imageArr = ['Models/Machine Room/Temperature Control/Air conditioning simplified. PNG'.'Models/Machine Room/Fire Fighting Equipment/fire fighting equipment.png'];
                break;
            case 4:
                imageArr = ['Models/Interior/Desk easy.png'.'Models/interior/book.png'.'Models/Interior/desk image.png'.'Models/Interior/office chair. PNG'];
                break;
            case 5:
                imageArr = ['Models/machine room/Video Surveillance/webcam.png'.'Models/Machine room/Video Surveillance/Intercom maintenance Camera.png'.'Models/Computer Room/Video Surveillance/microcamera.png'];
                break;
            default: 
                imageArr = ['Models/other/Signal tower.png'];
                break;
        }
        setPalNode(imageArr, arrNode[i]); Create a node on the Palette and set the name, display image, parent-child relationship}}Copy the code

I did some name Settings in the setPalNode function, mainly to set the name of the model and the path in different files in different folders according to the path name I passed in to 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 last/and. Is used to set the node name
        var url = imageName.slice(imageName.indexOf('/') +1, imageName.lastIndexOf('. ')); // Take the first/and the last. Is used to set the path to drag and drop the generated model OBj file
        
        createNode(name, imageName, arr, url, jsonUrl); // Create a node, which is displayed in the Palette}}Copy the code

CreateNode creates a node using a simple function:

function createNode(name, image, parent, urlName, jsonUrl) { // Create a node on the Palette palette component
    var node = new ht.Node();
    palette.dm().add(node);
    node.setName(name); // Sets the name of the node to appear in the palette through this property
    node.setImage(image); // Set the image of the node
    node.setParent(parent); // Set the parent node
    node.s({
        'draggable': true.// Set the node to be draggable
        'image.stretch': 'centerUniform'.// Set how to draw the node image
        'label': ' ' // Set the label of the node to empty, so that even if the name is set, it does not appear under the model in 3D
    });
    node.a('urlName', urlName); // a Sets user-defined attributes
    node.a('jsonUrl', jsonUrl);
    return node;
}
Copy the code

Draggable: true sets the node to be draggable, otherwise it cannot be draggable. Node. s is the HT default encapsulated style setting method, if the user needs to add their own method, you can use node.a method to add, parameter 1 is user-defined name, parameter 2 is user-defined value, not only can pass constant, also can pass variables, objects, but also can pass function! Another very powerful feature.

Drag and drop functionality

Drag is basically a response to the Windows dragover and drop events. To create a model when the mouse is released, generate the model when the event is triggered:

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

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

function handleDrop(e) { // When the mouse is released
    e.preventDefault(); // Cancels the default action for the event.
    
    var paletteNode = palette.dm().sm().ld(); // Get the last selected node in the Palette
    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 the obj model}}Copy the code

It is absolutely necessary to clarify here, the main point of this Demo! The last parameter in the loadObjFunc function is the position3D coordinates of the generated model. The g3d.getHitPosition method takes three parameters in total. The first parameter is the event type. By default, the center point of the horizontal plane is [0, 0, 0] and the normal line is the Y-axis, that is, [0, 1, 0]. A normal line and a point can determine a plane, so we use this method to set which plane the node is to be placed on. I set the node node to rotate 45 degrees around the Z-axis, so I need to think about how to set the normal line here. This is a math problem.

Load model

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

function loadObjFunc(objUrl, mtlUrl, jsonUrl, p3) { // Load the obj model
    var node = new ht.Node();
    var shape3d = jsonUrl.slice(jsonUrl.lastIndexOf('/') +1, jsonUrl.lastIndexOf('. '));
    
    ht.Default.loadObj(objUrl, mtlUrl, { // HT uses the loadObj function to load the obj model
        cube: true.// Whether to scale the model to unit 1. Default is false
        center: true.// Whether the model is centered. The default is false, and true moves the model to center its contents
        shape3d: shape3d, // If the shape3D name is specified, HT will automatically register all the parsed material models as an array
        finishFunc: function(modelMap, array, rawS3) { // used for post-load callback processing
            if (modelMap) {
                node.s({ // Set the node style
                    'shape3d': jsonUrl, // jsonUrl is the json file path of the obj model
                    'label': ' ' // Set label to empty. Label has a higher priority than name, so even if name is set, the name name is not displayed below the node
                });
                g3d.dm().add(node); // Add the node to the data container

                node.s3(rawS3); // Set node size the original size of the rawS3 model
                node.p3(p3); // Set the 3d coordinates of the node
                node.setName(shape3d); // Set the node name
                node.setElevation(node.s3()[1] /2); // Controls the Y-axis position of the 3D coordinate system where the center of the Node pixel is located
                g3d.sm().ss(node); // Select the current node
                g3d.setFocus(node); // Set the focus on the current node
                returnnode; }}}); }Copy the code

End of code!

conclusion

To be honest, this Demo is really very easy, the difficulty may lie in the ability of spatial thinking, first confirm the normal line and point, and then according to the normal line and point to find that surface, this surface according to my way to have a comparison is more able to understand, true fantasy, may be easy to string. The main reason this Demo is so easy is because the packaged hitPosition function is so simple and easy to use.