preface

Today, 3D models are used in a variety of different fields. They are used in the medical industry to make accurate models of organs; The film industry uses them for moving people, objects and realistic movies; The video game industry uses them as resources for computers and video games; They are used in science as precise models of compounds; The construction industry uses them to show proposed buildings or landscapes; Engineering uses them to design new devices, vehicles, structures and other applications. In recent decades, 3-d geological models have begun to be constructed in the earth sciences, and 3-d models are often animated, for example, in feature films and computer and video games. They can be used in 3d modeling tools or by themselves. In order to make it easy to animate, some extra data is often added to the model. For example, some 3d models of humans or animals have complete skeletal systems so that the movement looks more realistic and can be controlled through joints and bones.

All of these make us front-end developers feel that it would be nice if we could realize 3D effects without learning Unity3D or other game development tools and accurately control movement or direction by code. Therefore, I made use of 3D components in HT For Web to achieve a small example, using most of the functions of 3D components in HT. To make this example, I want to have a good grasp of 3D components and try to put them into an example, so that others can refer to them when they need.

Let’s take a look at the overall implementation:

Creating a 3D scene

With HT for Web, the existing 3D template is not a problem to create a three-layer backplane, the problem is how to put the “computer” and “cabinet components” of the first layer in the picture on it? I went down the file in OBJ format on the Internet, and then I used the function HT.default. loadObj(objUrl, mtlUrl, params) in HT to load the model into it. Params part can refer to www.hightopo.com/guide/guide… The code is as follows:

ht.Default.loadObj('OBj/Cabinet Component 1. Obj'.'OBJ/Cabinet Component 1. MTL', {  // Load the obj file
    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: 'box'.// 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){  
			device2 = createNode('box', floor1);  // Create a node on the first layer "floor"        device2.p3([x1- 120., y1+13, z1+60]);  // Set the node coordinates        device2.s3(rawS3);// Set the node size        createEdge(device1, device2);// Create a connection        device3 = createNode('box', floor1);         device3.s3(rawS3);         device3.p3([x1+120, y1+13, z1+60]);         createEdge(device1, device3); }}});Copy the code

The three parameters of finishiFunc function are defined as follows:

  • ModelMap: Returns the parsed value after calling HT.default. parseObj. The return value is null if loading or parsing fails
  • Array: An array of all material models
  • RawS3: Contains the original dimensions of all models

In practice, we usually set the size of the primitives to the original size of the model.

Warning model modeling

“Computer” of a red can rotating above the “warning”, is relying on the ht. The Default. The setShape3dModel function (ht) for Web modeling manual registered a 3 d model, in ht, encapsulated modeling function has a lot of, is the basis of is a sphere, cylinder, Cube and so on. Here I’m using the circle method createRingModel to generate the outer ring of the warning, and the top part of the exclamation mark is the sphere created with createSmoothSphereModel, The lower part of the exclamation point is to use createSmoothCylinderModel to construct the cylinder. I started using the 3 d model directly encapsulation of good function, lead to later don’t know what parameter is used in the function for, but also don’t understand is how to form a 3 d model, and then again to see themselves in front of the “model”, just know originally a surface that is used by the 3 d model is the most basic triangle face, Later, the complex surface is also formed by multiple triangular surfaces, which are rotated around a specific axis. Of course, this axis is up to you. Different axes can generate different shapes. As for how to rotate the 3D model, addScheduleTask(Task) method is packaged in HT. I called setRotation, a rotation function packaged in HT, in the third-layer Task to set the rotation sequence and direction, and specify the rotation object. Here’s how to customize the “warning” 3D model (note: since the model in this example is a custom combination, use the “all.blend” style property to set the color of the whole model) :

function createAlarm(device, formPane) {
    var ringModel = ht.Default.createRingModel([ 8.1.10.1.10.- 1.8.- 1.8.1].null.null.false.false.100); // Create a 3D model around the xy plane.
    var sphereModel = ht.Default.createSmoothSphereModel(8.8.0.Math.PI*2.0.Math.PI, 2); // Build the smooth sphere model
    var cylinderModel = ht.Default.createSmoothCylinderModel(8.true.true.1.2.0.Math.PI*2.8); // Build a smooth cylinder model
    
    var alarmArr = [ // The combined model is composed of three ringModel, sphereModel and cylinderModel
        {
          shape3d: ringModel, // Define the model type
          r3: [Math.PI/2.0.0].// Set the rotation Angle
          color: { // Set the model color
            func: '[email protected]'.// The all.blend property in the data binding style, which can be obtained and set with data.s()
          }
        },{
          shape3d: sphereModel,
          t3: [0.4.0].color: {
            func: '[email protected]',}}, {shape3d: cylinderModel,
          t3: [0.- 3.0].color: {
            func: '[email protected]',}}]; ht.Default.setShape3dModel('alarm', { // Register custom 3D models
      shape3d: alarmArr
    });

    var alarmTip = createNode('alarm', device); // Create a shape3d alarm node
    alarmTip.s3([2.2.2]); // Set the node size
    alarmTip.p3(device.p3()[0], device.p3()[1] +60, device.p3()[2]);
    alarmTip.s('all.blend'.'red'); // Changing this property can change the color of the model because the model was already data-bound when it was created

    return alarmTip;
}
Copy the code

Now let’s see how to make the “alarm” node “blink”. I’m binding the animation directly to the node so that I can control the animation directly from the node. So when we create the alarm model above, we can bind the animation directly to the node:

if(formPane){
    alarmNode.scaleFunc = function() { // Set the size change animation
        var size = alarmNode.s3(); // Get the size of the node
        if (size[0= = =2 && size[1= = =2 && size[2= = =2) alarmNode.s3([1.1.1]);
        else alarmNode.s3([2.2.2]);
        alarmNode.scaleTimer = setTimeout(alarmNode.scaleFunc, formPane.v('scaleInterval')); // Set the animation
    }
    alarmNode.blinkFunc = function(){ // Set flashing animation
        var color = alarmNode.s('all.blend'); // Get the node's style
        if (color === 'red') alarmNode.s({'all.blend': 'yellow'}); // If the node color is red, set it to yellow
        else alarmNode.s({'all.blend': 'red'});
        alarmNode.blinkTimer = setTimeout(alarmNode.blinkFunc, formPane.v('blinkInterval'));
    }
    alarmNode.rotateFunc = function() { // Set the rotation animation
        alarmNode.setRotation(alarmNode.getRotation() + Math.PI/20); // Get the current rotation Angle of the node, and add math.pi /20 angles to this rotation Angle
        alarmNode.rotateTimer = setTimeout(alarmNode.rotateFunc, formPane.v('rotInterval')); }}Copy the code

I set the above animation can control the speed of the node flashing through the properties of the form panel, as well as the animation of the flashing node, etc., mainly talk about the realization of this function in the form form:

formPane.addRow([ // Add a row to the Form panel
    {
        checkBox: { / / check box
            label: 'Enable Blink'.// The text content corresponding to the checkbox
            selected: true.// Set select the check box
            onValueChanged: function(){ // The function to call back when the checkbox value changes
                var data = dataModel.getDataByTag('colorAlarm'); // Get the node using the tag
                if (this.getValue()) { // Get the current value of the checkbox true/false
                    data.blinkTimer = setTimeout(data.blinkFunc, formPane.v('blinkInterval')); // Set the animation directly by setting the blinkTimer of the node
                }
                else {
                    clearTimeout(data.blinkTimer); // Clear the animation}}}}, {id: 'blinkInterval'.// Form can get the value of this item by using getValue (v)
        slider: { // The HT will automatically build an HT.Widget. Slider based on the property value and store it on the Element property
            min: 0.// Minimum slider value
            max: 1000.// Maximum slider value
            step: 50.// Slider step
            value: 500.// The current slider value}}], [0.1.0.1]); // Sets the ratio of the two item elements in this line whose width is less than 1
Copy the code

The flow of a ball in a pipe

Finally, let’s talk about the ball flow part on the 3D pipeline. This function is really very practical, and the effect is really good to share with you

First, create a wire connecting the start node and end node and set the style of the wire. Using HT. Edge, you can attach the wire to the start node and end node, so that moving either of the two nodes will change the wire according to the position of the node. Very convenient:

var polyline = new ht.Edge(source, target); // Create a connection
dataModel.add(polyline); // Add wires to the data container
polyline.s({
    'edge.width': 5.// Line width
    'edge.type': 'points'.// The direction of the line is determined by the edge.points property, which is used to draw polylines
    'edge.points': [ Ht. List {x:100, y:100} array of points. Edge. type is used when points
        {x: source.getPosition3d()[0] +200.y: source.getPosition3d()[2].e: source.getPosition3d()[1] {},x: target.getPosition3d()[0] +400.y: target.getPosition3d()[2].e: target.getPosition3d()[1]}],'edge.segments': [1.4].// Is used to describe the dot join style. Array elements are integer values
    'shape3d': 'cylinder'./ / cylindrical
    'shape3d.color': 'rgba (242, 200, 40, 0.4)'.'shape3d.resolution': 30.// The number of segments determines the smoothness of the curve
    'edge.source.t3': [20.0.0].[tx, ty, tz], null by default
    'edge.target.t3': [20.0.0] [tx, ty, tz], null by default
});
Copy the code

Since the points we set when creating the line are only two points on the curve, if we want to obtain the points formed by the curve at present, source and target are missing. We reset an array and add these two points, which will be used when obtaining all points on the curve later:

var list = new ht.List();
list.push({x: source.getPosition3d()[0].y: source.getPosition3d()[2].e: source.getPosition3d()[1]}); // Add source points to the array
polyline.s('edge.points').each(function(item){ // Add the two points already set in the style property
    list.push(item);
});
list.push({x: target.getPosition3d()[0].y: target.getPosition3d()[2].e: target.getPosition3d()[1]}); // Add the target point
Copy the code

Then create a ball node that slides on the pipeline. This is just to set up the node and add it to the data container dataModel when the coordinates of the ball need to be set. If the position of the node is not set, add the node to the data container. The initial position of the node is the position [0, 0, 0] right in the center of the 3D scene. The ball sliding animation code is as follows:

var ball = new ht.Node(); // Create a ball node
ball.s({ // Set the style of the ball node
    'shape3d': 'sphere'.// Set the 3d model of the ball to spherical
    'shape3d.color': 'rgba (40, 90, 240, 0.4)' // Set the color of the 3D model
});

var delta = 10, flag = 0;
setInterval(function(){
    flag++;
    var length = (polyline.a('total') | |0) % polyline.a('length') + delta; // The current curve length of the ball
    var cache = ht.Default.getLineCacheInfo(list, polyline.s('edge.segments')); // Get information about points on the curve
    var lineLength = ht.Default.getLineLength(cache); // Get the total length of the curve
    polyline.a('length', lineLength - 50); // Because I set edge's T3 (equivalent to offset in 2D), the line length is not that long
    var offset = ht.Default.getLineOffset(cache, length); // The offset of the curve according to the information of the points on the curve
    ball.setPosition3d(offset.point.x + 10, offset.point.y, offset.point.z); // Set the coordinates of the node
    polyline.a('total', length);
    if(flag === 1) dataModel.add(ball); // At this point, the node already has coordinates and can be added to the data container
}, 10);
Copy the code

Special polygon

We can also see that on the second layer there are two special polygons, the parallelogram and the trapezoid. The parallelogram is created by the createParallelogramModel function, which is a simpler function, CreateExtrusionModel (array, segments, top, bottom, Resolution, repeatUVLength, tall, elevation) Segments refer to the HT for Web shape manual. Top and bottom allow you to choose whether there is a top or a bottom. The resolution differential is the number of segments. When we draw a curve, we may only need to confirm several individual points and divide it into multiple segments on the line between each two points, so that the line segment will become smooth. Ht encapsulates this parameter for users to operate these lines easily, and repeatUVLength is blank by default. After setting the value, the top and bottom maps will be repeated according to the specified length value. The height of the TALL model is 5 by default, and the Y-axis position of the center of the Elevation model is 0 by default. Setting this value can make the plane on Xz rotate around the Y-axis.

The circular placement of equipment

The effect of a ring at the bottom is realized by an algorithm. The ring has to confirm how many elements there are on the ring, and then calculate the Angle between each two. The position of each element is calculated by sine and cosine, and the following code is obtained:

names = ['equipment 2'.'the equipment'.'equipment 4'.'equipment 5'.'equipment 6'.'equipment 7'.'equipment 8'.'equipment 9'];  
names.forEach(function(name, index) {  
    x = 400, y = 200, angle = 45, r = 120;  
    x = x3 + Math.sin((2 * Math.PI / 360) * angle * index) * r;  
    y = z3 + Math.cos((2 * Math.PI / 360) * angle * index) * r;  
    device = createRect([x, y3 + 15, y], [w * 0.1.15, h * 0.1].' '.' ', floor3);  
    createEdge(device5, device);  
}); 
Copy the code

If there are other parts that you do not understand, you can go to the official website (hightopo.com) to check the corresponding manual, or leave a message.

Finally attach example: www.hightopo.com/demo/3DTopo…