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.

rendering

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) the palette = new ht. The widget. The palette (); / / (http://hightopo.com/guide/guide/plugin/palette/ht-palette-guide.html) splitView = new panel component 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; Splitview.addtodom (); splitView.addTodom (); splitView.addTodom (); // Add the split component to the bodyCopy 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 component's bottom div style = view.style; document.body.appendChild(view); // Add the component's underlying div to the body. Left ='0'; // Ht sets position for all components to absolute. Right ='0';
    style.top = '0';
    style.bottom = '0';
    window.addEventListener('resize'.function () { self.iv(); }, false); // Window size change 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 size of the node node.r3(0, 0, math.pi /4); // Set the rotation Angle of the node. The rotation Angle is learned and depends on the position of the drag.'3d.movable'.false); Dm.add (node); // Set the node to be immovable on 3d because the node is only a reference. // Add the node to the data containerCopy the code

Left content building

The Palette is similar to the GraphView, powered by HT. DataModel, which displays the groups in HT. Group and the button elements in HT. Node. I’ve packaged the primitive function that loads the Palette as initPalette, defined as follows:

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 nameArrfor(var i = 0; i < arrNode.length; i++) { var name = nameArr[i]; var vName = arrNode[i]; arrNode[i] = new ht.Group(); // Palette will be in "palette", then add palette to "palette".dm().add(arrNode[I]); // Add group element arrNode[I].setExpanded(to the Palette componenttrue); ArrNode [I].setName(name); Var imageArr = []; Switch (I){// Set the different primiples in each group according to the different groupscase 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'; Var name = imageName. Slice (imageName. LastIndexOf ('/')+1, imageName.lastIndexOf('. ')); // Take the last/and. Var URL = imageName. Slice (imageName. IndexOf ('/')+1, imageName.lastIndexOf('. ')); // Take the first/and the last. 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:

functionCreateNode (name, image, parent, urlName, jsonUrl) {// Create a node on the Palette component var node = new ht.node (); palette.dm().add(node); node.setName(name); // Set the node name for the text to be displayed in the palette using this property. // 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 the drawing mode of node picture'label': ' '// Set the label of the node to empty, so that even if the name is set, it is not displayed under the model in 3D}); node.a('urlName', urlName); //a Sets the user - defined property 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 function g3d.getView().adDeventListener ()"dragover".function(e) {/ / drag and drop event e.d ataTransfer. DropEffect ="copy";
        handleOver(e);
    });
    g3d.getView().addEventListener("drop".function(e) {// Drop the mouse handleDrop(e); }); }functionhandleOver(e) { e.preventDefault(); // Cancels the default action for the event. }functionHandleDrop (e) {// e.preventDefault(); // Cancels the default action for the event. var paletteNode = palette.dm().sm().ld(); // Get the last selected node in the Paletteif (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}}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:

functionLoadObjFunc (objUrl, mtlUrl, jsonUrl, p3) {var node = new ht.node (); var shape3d = jsonUrl.slice(jsonUrl.lastIndexOf('/')+1, jsonUrl.lastIndexOf('. ')); Ht.default. loadObj(objUrl, mtlUrl, {// ht loads obj model cube by loadObj function:true,// Whether to scale the model to unit 1. Defaultfalse
        center: true,// Whether the model is centeredfalse, is set totrueShape3d: shape3d,// If the shape3D name is specified, HT will automatically register all the parsed material models as an array, with that name finishFunc:function(modelMap, array, rawS3) {// For post-load callback processingif(modelMap) {node.s({// Sets 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 will not be displayed below the node}); g3d.dm().add(node); // Add the node to the data container node.s3(rawS3); Node.p3 (p3); // Set the size of the rawS3 model. // Set the 3d coordinates of the node node.setname (shape3d); // Set the node name node.setelevation (node.s3()[1]/2); G3d.sm ().ss(Node); g3d.sm().ss(Node); // Set the current node to g3d.setfocus (node); // Set the focus on the current nodereturnnode; }}}); }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.