Summary: Most 3D programming involves ground elements. In the scene, we use the ground as the bearing base of other objects, and also use the ground to limit the movement range of users in the scene. We can also set the corresponding calculation rules for different positions in the scene by setting the properties of the plot. This article explores and implements two ground constructs on the WebGL platform with the help of the Babyl.js library. In addition to the two definitive constructs, this article also includes a discussion of some other options and some thoughts on video game art. Advice before reading this article, learn 3 d programming primer and Babylon. Js official introductory tutorial, the former can be space.bilibili.com/25346426/ch…

1. We first make 5 standard plots:

Standard blocks are all cubes with side length of 1. Each standard block uses corresponding texture to represent specific landforms. We can splice copies of these standard blocks into complex ground structures. In this paper, I used the cube as the standard block to generate the terrain vertically. You can also use other shapes like hexagons as the standard block, or rotate the block around the Y-axis by some angles to arrange the block. The code for making a standard plot is as follows:

1 var size_per=1; Var obj_landType ={}; 3 / / build grid 4 var box_grass = new BABYLON. MeshBuilder. CreateBox (" box_grass, "{size: size_per}, scene). 5 var box_tree=new BABYLON.MeshBuilder.CreateBox("box_tree",{size:size_per},scene); 6 var box_stone=new BABYLON.MeshBuilder.CreateBox("box_stone",{size:size_per},scene); 7 var box_shallowwater=new BABYLON.MeshBuilder.CreateBox("box_shallowwater",{size:size_per},scene); 8 var box_deepwater=new BABYLON.MeshBuilder.CreateBox("box_deepwater",{size:size_per},scene); 9 box_grass.renderingGroupId = 2; 10 box_tree.renderingGroupId = 2; 11 box_stone.renderingGroupId = 2; 12 box_shallowwater.renderingGroupId = 2; 13 box_deepwater.renderingGroupId = 2; 14 box_grass.position.y=-100*size_per; 15 box_tree.position.y=-101*size_per; 16 box_stone.position.y=-102*size_per; 17 box_shallowwater.position.y=-103*size_per; 18 box_deepwater.position.y=-104*size_per; 19 obj_landtype.box_grass=box_grass; 20 obj_landtype.box_tree=box_tree; 21 obj_landtype.box_stone=box_stone; 22 obj_landtype.box_shallowwater=box_shallowwater; 23 obj_landtype.box_deepwater=box_deepwater; 24 OptimizeMesh(box_grass); 25 OptimizeMesh(box_tree); 26 OptimizeMesh(box_stone); 27 OptimizeMesh(box_shallowwater); 28 OptimizeMesh(box_deepwater); 5 var mat_grass = new BABYLON.StandardMaterial("mat_grass", scene); //1 31 mat_grass.diffuseTexture = new BABYLON.Texture(".. /.. /ASSETS/IMAGE/LANDTYPE/grass.jpg", scene); 32 mat_grass.freeze(); 33 box_grass.material=mat_grass; 34 var mat_tree = new BABYLON.StandardMaterial("mat_tree", scene); //1 35 mat_tree.diffuseTexture = new BABYLON.Texture(".. /.. /ASSETS/IMAGE/LANDTYPE/yulin.png", scene); 36 mat_tree.freeze(); 37 box_tree.material=mat_tree; 38 var mat_stone = new BABYLON.StandardMaterial("mat_stone", scene); //1 39 mat_stone.diffuseTexture = new BABYLON.Texture(".. /.. /ASSETS/IMAGE/LANDTYPE/stone.png", scene); 40 mat_stone.freeze(); 41 box_stone.material=mat_stone; 42 var mat_shallowwater = new BABYLON.StandardMaterial("mat_shallowwater", scene); //1 43 mat_shallowwater.diffuseTexture = new BABYLON.Texture(".. /.. /ASSETS/IMAGE/LANDTYPE/lake.png", scene); 44 mat_shallowwater.freeze(); 45 box_shallowwater.material=mat_shallowwater; 46 var mat_deepwater = new BABYLON.StandardMaterial("mat_deepwater", scene); //1 47 mat_deepwater.diffuseTexture = new BABYLON.Texture(".. /.. /ASSETS/IMAGE/LANDTYPE/sea.png", scene); 48 mat_deepwater.freeze(); 49 box_deepwater.material=mat_deepwater;Copy the code

This code creates five standard plots of “grass”, “forest”, “rock”, “shallow water” and “deep water”. For the grid of plots, some display optimizations are performed using the OptimizeMesh method. The OptimizeMesh method is as follows:

1 function OptimizeMesh(mesh) 2 { 3 mesh.convertToFlatShadedMesh(); FreezeWorldMatrix (); // Use vertex color calculation instead of slice color calculation. / / 5 / / mesh to freeze the world coordinate system. The material. The needDepthPrePass = true; / / enabled depth prediction by 6 / / mesh. ConvertToUnIndexedMesh (); // Use triangle draw instead of index draw 7}Copy the code

For block materials, the freeze method is used to freeze the properties of the material object, preventing the rendering engine from constantly refreshing the material state.

2. Next, we put together the simplest plain terrain with grass plots. The terrain rendering effect is as follows:

 

The grassland is a patchwork of 10,201 grassland plots, and here I use OpenGL’s “multi-instance rendering” technique to reduce the computational cost of drawing a large number of repeated objects, encapsulated in the createInstance method in the Babylon. Js library:

1 var arr_instance=[]; 2 var segs_x=100; Var segs_y=100; // Take height 0 as sea level and xy00 as geodetic origin 6 // Form the initial block :101*101 cells, the center of the center cell is the origin 7 for(var I =0; i<=segs_x; i++) 8 { 9 arr_instance[i]=[]; 10 for(var j=0; j<=segs_y; j++) 11 { 12 arr_instance[i][j]=[]; 13 var instance=obj_landtype.box_grass.createInstance("ground_"+i+"_"+j+"_0"); 14 instance.mydata={i:i,j:j,k:0,landclass:obj_landtype.box_grass}; 15 instance.position=new BABYLON.Vector3((i-(segs_x/2))*size_per,0,(j-(segs_y/2))*size_per); [I][j]. Push (instance); // Store each instance as a global object 17} 18}Copy the code

Here we set a myData property for each instance object and store some information about the plot in this property for future scene interaction.

3. Mark the index in the XZ direction for the cell

Now each plot is neatly arranged along the X and Z axes. For ease of differentiation, we call each square position on the XZ plane a “cell”, and each cell may have multiple plot instances. The position of each cell is represented by an index on its X and z axes, and we now need a way to display this index value. Here we try to each cell shows a text index, rendering effects as follows: (can be accessed at https://ljzc002.github.io/EmptyTalk/HTML/TEST/testfloor.html)

This approach may not be suitable for the current cell marking requirements, but the techniques included may be useful elsewhere:

A, first create a Sprite manager and a picture containing numbers and minus signs

Var can_temp=document.createElement("canvas"); var can_temp=document.createElement("canvas"); 3 can_temp.width=132//264; 4 can_temp.height=24; 5 var context=can_temp.getContext("2d"); 6 the context. FillStyle = "rgba (0,0,0,0)"; FillRect (0,0,can_temp.width,can_temp.height); 8 context.fillStyle = "#ffffff"; 9 context.font = "bold 24px monospace"; 10 for(var i=0; i<10; I ++) 11 {12 context.filltext (I, I *12,24); 13} 14 context. FillText (" - ", 120, 24); 15 / / context. FillText (" 0123456789-0, 24); 16 var PNG =can_temp.toDataURL("image/ PNG "); 18 var spriteManager = new BABYLON.SpriteManager("spriteManager", PNG, (Segs_x +1)*(Segs_y +1)*7, 24, scene); 19 spriteManager.renderingGroupId=2; 20 spriteManager.cellWidth=12; 21 spriteManager.cellHeight=24;Copy the code

B. Add Sprite generation code to the loop that generates instance blocks:

2 var number1 = new BABYLON.Sprite("number1", spriteManager); 3 var number2 = new BABYLON.Sprite("number2", spriteManager); 4 var number3 = new BABYLON.Sprite("number3", spriteManager); 5 var number4 = new BABYLON.Sprite("number4", spriteManager); 6 var number5 = new BABYLON.Sprite("number5", spriteManager); 7 var number6 = new BABYLON.Sprite("number6", spriteManager); 8 var number7 = new BABYLON.Sprite("number7", spriteManager); Stri =(I +1000+"").substr(1); 11 strj=(j+1000+"").substr(1); 12 13 number1.cellIndex=parseInt(stri[0]); 14 number2.cellIndex=parseInt(stri[1]); 15 number3.cellIndex=parseInt(stri[2]); 16 number4.cellIndex=10; CellIndex =parseInt(STRJ [0]); 18 number6.cellIndex=parseInt(strj[1]); 19 number7.cellIndex=parseInt(strj[2]); Number1.size =0.2*size_per; number1.size=0.2*size_per; 22 number1.position=instance.position.clone(); 23 number1.position.y=2*size_per; 24 number1. Position. X + = 0.3 * size_per; 25 number1. Position. + z = 0.3 * size_per; 26 and 27 number2. Size = 0.2 * size_per; 28 number2.position=instance.position.clone(); 29 number2. Position. Y = 1.8 * size_per; 30 number2. Position. X + = 0.3 * size_per; 31 number2. Position. + z = 0.3 * size_per; 32 number3. Size = 0.2 * size_per; 33 number3.position=instance.position.clone(); 34 number3. Position. Y = 1.6 * size_per; 35 number3. Position. X + = 0.3 * size_per; 36 number3. Position. + z = 0.3 * size_per; 37 number4. Size = 0.2 * size_per; 38 number4.position=instance.position.clone(); 39 number4. Position. Y = 1.4 * size_per; 40 number4. Position. X + = 0.3 * size_per; 41 number4. Position. + z = 0.3 * size_per; 42 number5. Size = 0.2 * size_per; 43 number5.position=instance.position.clone(); 44 number5. Position. Y = 1.2 * size_per; 45 number5. Position. X + = 0.3 * size_per; 46 number5. Position. + z = 0.3 * size_per; 47 number6. Size = 0.2 * size_per; 48 number6.position=instance.position.clone(); 49 number6. Position. Y = 1.0 * size_per; 50 number6. Position. X + = 0.3 * size_per; 51 number6. Position. + z = 0.3 * size_per; 52 number7. Size = 0.2 * size_per; 53 number7.position=instance.position.clone(); 54 number7. Position. Y = 0.8 * size_per; 55 number7. Position. X + = 0.3 * size_per; 56 number7. Position. + z = 0.3 * size_per;Copy the code

Considering the computing performance, here as a carrier of the text, use the elves, but when we have established more than 70000 elves, frame rate is reduced a lot), because the horizontal arrangement of elves keep out each other in camera will move horizontally, vertically so elves to reduce the impact, perhaps can be reset position of rotation of the elves to the thorough settlement of this issue.

In addition to indexing each plot, we can also use a small map with the index, display a close-up of the selected plot in a separate viewport, generate markers next to the selected plot, etc. The plot index will be marked by placing references in the scene.

We raised the two cells in the lower left corner of the Xz plane and set these two cells as “rock” landforms:

 

A. First establish two tool methods:

1 {4 var len=arr_instance[I][j]. Length; 2 {5 var len=arr_instance[I]. 5 for(var k=0; k<len; k++) 6 { 7 var instance=arr_instance[i][j][k]; 8 instance.dispose(); 9 instance=null; CreateCube (0,0,2,obj_landtype.box_stone) 15 // I, j must be integers, Function createCube(I,j,k,landclass) 17 {18 var instance=landclass.createInstance("ground_"+i+"_"+j+"_"+k); 19 instance.mydata={i:i,j:j,k:k,landclass:landclass}; 20 instance.position=new BABYLON.Vector3((i-(segs_x/2))*size_per,k*size_per,(j-(segs_y/2))*size_per); // All stack from negative to positive? 21 //arr_instance[I][j]. Push (instance); 22 arr_instance[i][j].unshift(instance); 23}Copy the code

B. Next modify some of the selected cells

We put the selected cells in an array called Configuration Array.

1 // Fill all xZ cells on a path with the corresponding square, first empty the original square in the cell, then create a square 2 // at the specified height, then compare the height of all surrounding squares (compare the four directions), fill the missing part, pay attention to the lower square in the array when filling. 3 / / createCubePath ([{j I: 0:0, k: 1, landclass: obj_landtype. Box_stone}, {I: 1, j: 1, k: 2.5, landclass: obj_landtype. Box_stone}]) 4 5 function createCubePath(cubepath) 6 { 7 var len=cubepath.length; 8 for(var i=0; i<len; I ++)// For each xz cell 9 {10 var cube= cubePath [I]; 11 disposeCube(cube.i,cube.j); 12 createCube(cube.i,cube.j,cube.k,cube.landclass); For (var index=0; var index=0; index<len; index++) 16 { 17 var cube=cubepath[index]; 18 var i=cube.i; 19 var j=cube.j; 20 var k=cube.k; Var k1=999; 24 if(arr_instance[i]) 25 { 26 var arr1=arr_instance[i][j+1]; 27 if(arr1) 28 { 29 var ins_cube1=arr1[arr1.length-1]; 30 k1=ins_cube1.mydata.k; 31 } 32 } 33 var k2=999; 34 if(arr_instance[i+1]) 35 { 36 var arr2=arr_instance[i+1][j]; 37 if(arr2) { 38 var ins_cube2 = arr2[arr2.length - 1]; 39 k2=ins_cube2.mydata.k; 40 } 41 } 42 var k3=999; 43 if(arr_instance[i]) 44 { 45 var arr3=arr_instance[i][j-1]; 46 if(arr3) { 47 var ins_cube3=arr3[arr3.length-1]; 48 k3=ins_cube3.mydata.k; 49 } 50 } 51 var k4=999; 52 if(arr_instance[i-1]) 53 { 54 var arr4=arr_instance[i-1][j+1]; 55 if(arr4) { 56 var ins_cube4=arr4[arr4.length-1]; 57 k4=ins_cube4.mydata.k; Min (k1,k2,k3,k4); min(k1,k2, k4); 63 64 var len2=Math.floor((k-mink)/size_per); 65 for(var index2=1; index2<=len2; index2++) 66 { 67 createCube(i,j,k-index2,cube.landclass); 68 //arr_instance[i][j].unshift() 69 } 70 } 71 }Copy the code

This code consists of two loops. The first loop places the highest plot in the selected cell, and the second loop fills the support below the highest plot, such as the mountain side of a high mountain or the wall of a deep valley. This padding is achieved by comparing the height of the selected cell with that of the four sides of the cell. A drawback of this algorithm is that when a valley needs to be generated, a circle of flat land with constant height around the valley also needs to be put into the configuration array, otherwise the terrain will break. Directly in the browser console to perform createCubePath ([{I: 0, j: 0, k: 1, landclass: obj_landtype. Box_stone}, {I: 1, j: 1, k: 2.5, landclass: obj_landtype. Box_sto Ne}]) command to change the terrain. Here you might want to see the “drag the mouse around the scene and the terrain will rise and fall” effect, but I think this runtime code injection control is a big advantage of WebGL technology over traditional desktop 3D applications, allowing us to achieve much more fine-grained control than traditional UIs.

Can visit https://ljzc002.github.io/EmptyTalk/HTML/TEST/testfloor2.html for testing

In summary, we have written a simple terrain generation method, but there is still more work to be done. We need some according to certain rules to generate some configuration of the array method, according to the rules in the same cell of different height distribution plot method (about terrain rules formulated perhaps can refer to this article on the randomly generated terrain surface of the planet at https://www.cnblogs.com/ljzc002/p/9134272 .html), and consider writing a useful interactive UI for users who are not used to console input. This spatially discrete terrain is good for writing time-discrete turn-based 3D scenes, and we’ll discuss continuous terrain that is better suited for real-time 3D scenes.

Second, the second method — improved ground grid

Babylu.js has a built-in flat ground grid and a height map ground grid, but these two grids have some disadvantages: a: can only set the same number of X-direction segments and z-direction segments b: There is no xz index information in the ground grid, so terrain can only be changed by modifying the position of the bottom vertices

To elaborate on problem C, the vertices of the ground grid are arranged as shown below:

The user will find that he cannot generate a vertical cliff between the HAOG cell and the ABCO cell, because the ground grid uses a “simplified grid” with only one vertex at each AO, unable to represent the upper and lower sides of the cliff. In this case, the user has to simulate a cliff by using as small a cell as possible to generate as steep a slope as possible (this is exactly the opposite of the block stitching method that does not generate a slope). On the other hand, the angles between the six corrugated lines around each vertex (such as vertex O) are not uniform, making it difficult to produce a regular shape.

It is planned to replace the ground mesh with a “strip mesh” to solve problems A and B, but the vertex arrangement of the strip mesh is similar to that of the ground mesh, and problem C still exists. After observation, problem C is only obvious when the terrain changes dramatically, so it is decided to use the stripted grid to show the gentle terrain general trend (named ground_base in the code), and place some specially customized “terrain attached grid” (i.e., models) on the ground_base to show the terrain with drastic changes. If necessary, And somehow integrate the ground_base with the terrain attachment.

2, generated a similar method of flat grasslands terrain, and mark xz index rendering results are as follows: (can visit https://ljzc002.github.io/EmptyTalk/HTML/TEST/testframe2.html to check the effect)

The parentheses are the current indicator coordinates, followed by the XZ index value

A. Setting texture repetition is different from the block Mosaic method. By default, a texture map is used to cover all vertices in the strip network. In order to display blocks one by one as in method 1, the block material code is modified as follows:

1 mat_grass = new BABYLON.StandardMaterial("mat_grass", scene); //1 2 mat_grass.diffuseTexture = new BABYLON.Texture(".. /.. /ASSETS/IMAGE/LANDTYPE/grass.jpg", scene); 3 mat_grass.diffuseTexture.uScale = segs_x+1; / / texture repeat effect 4 mat_grass. DiffuseTexture. VScale = segs_z + 1; 5 mat_grass.freeze();Copy the code

B. Generate striped grid ground

1 var arr_path=[]; For (var I =0; i<=segs_x+1; i++) 3 { 4 var posx=(i-((segs_x+1)/2))*size_per; 5 var path=[]; 6 for(var j=0; j<=segs_z+1; j++) 7 { 8 var posz=(j-((segs_z+1)/2))*size_per; 9 path.push(new BABYLON.Vector3(posx,0,posz)); 10 } 11 arr_path.push(path); 12 } 13 ground_base=BABYLON.MeshBuilder.CreateRibbon("ground_base" 14 ,{pathArray:arr_path,updatable:true,closePath:false,closeArray:false,sideOrientation:BABYLON.Mesh.DOUBLESIDE}); 15 ground_base.sideOrientation=BABYLON.Mesh.DOUBLESIDE; 16 ground_base.material=mat_grass; 17 ground_base.renderingGroupId=2; 18 ground_base.metadata={}; 19 ground_base.metadata.arr_path=arr_path; 20 obj_ground.ground_base=ground_base;Copy the code

Note that the updatable property in the CreateRibbon method parameter must be set to true, otherwise the terrain will not be modified after the stripe mesh is created.

C. Make some blue balls as terrain reference:

1 / / 5 blue ball 2 var mesh_sphereup = new BABYLON. MeshBuilder. CreateSphere (" mesh_sphereup, "{diameter: 0.5}, scene). 3 mesh_sphereup.material=mat_blue; 4 mesh_sphereup.renderingGroupId=2; 5 mesh_sphereup. Direction = new BABYLON. Vector3 (0, 1, 2); 6 mesh_sphereup.isPickable=false; 7 mesh_sphereup.rayHelper = null; 8 obj_plane.mesh_sphereup=mesh_sphereup; 9 var mesh_sphereright = new BABYLON. MeshBuilder. CreateSphere (" mesh_sphereright, "{diameter: 0.5}, scene). 10 mesh_sphereright.material=mat_blue; 11 mesh_sphereright.renderingGroupId=2; 12 mesh_sphereright. Direction = new BABYLON. Vector3 (2, 1, 0); 13 mesh_sphereright.isPickable=false; 14 mesh_sphereright.rayHelper = null; 15 obj_plane.mesh_sphereright=mesh_sphereright; 16 var mesh_spheredown = new BABYLON. MeshBuilder. CreateSphere (" mesh_spheredown, "{diameter: 0.5}, scene). 17 mesh_spheredown.material=mat_blue; 18 mesh_spheredown.renderingGroupId=2; 19 mesh_spheredown.direction=new BABYLON.Vector3(0,-1,-2); 20 mesh_spheredown.isPickable=false; 21 mesh_spheredown.rayHelper = null; 22 obj_plane.mesh_spheredown=mesh_spheredown; 23 var mesh_sphereleft = new BABYLON. MeshBuilder. CreateSphere (" mesh_sphereleft, "{diameter: 0.5}, scene). 24 mesh_sphereleft.material=mat_blue; 25 mesh_sphereleft.renderingGroupId=2; 26 mesh_sphereleft. Direction = new BABYLON. Vector3 (2, 1, 0). 27 mesh_sphereleft.isPickable=false; 28 mesh_sphereleft.rayHelper = null; 29 obj_plane.mesh_sphereleft=mesh_sphereleft; 30 var mesh_spheremiddle = new BABYLON. MeshBuilder. CreateSphere (" mesh_spheremiddle, "{diameter: 0.5}, scene). 31 mesh_spheremiddle.material=mat_blue; 32 mesh_spheremiddle.renderingGroupId=2; 33 mesh_spheremiddle. Direction = new BABYLON. Vector3 (0, 1, 0); 34 mesh_spheremiddle.isPickable=false; 35 mesh_spheremiddle.rayHelper = null; 36 obj_plane.mesh_spheremiddle=mesh_spheremiddle; 38 for(var key in obj_plane) 39 {40 var label = new Boiling.gui.Rectangle(key); 41 label.background = "black"; 42 label.height = "30px"; 43 label. Alpha = 0.5; 44 label.width = "240px"; 45 label.cornerRadius = 20; 46 label.thickness = 1; 47 label.linkOffsetY = 30; // Position offset?? 48 fsUI.addControl(label); 49 label.linkWithMesh(obj_plane[key]); 50 var text1 = new BABYLON.GUI.TextBlock(); 51 text1.text = ""; 52 text1.color = "white"; 53 label.addControl(text1); 54 label.isVisible=true; 55 //label.layerMask=2; 56 label.text=text1; 57 obj_plane[key].lab=label; 58}Copy the code

Can visit https://www.cnblogs.com/ljzc002/p/7699162.html to view Babylon. Js GUI function of Chinese documents

D. Modify the position of the road sign according to the position of the camera:

1 scene. RegisterAfterRender (2 function () {3 / / update the position of the five markers ball 4 var origin = camera0. Position; 5 var length=200; 6 for(key in obj_plane) 7 { 8 var mesh=obj_plane[key]; 9 var direction=mesh.direction; 10 var ray = new BABYLON.Ray(origin, direction, length); 11 /*if(mesh.rayHelper) 12 { 13 mesh.rayHelper.dispose(); 14 }*/ 15 //mesh.rayHelper = new BABYLON.RayHelper(ray); 16 / / / / at this time has not yet been _renderLine attribute mesh. RayHelper. _renderLine. RenderingGroupId = 2; 17 //mesh.rayHelper.show(scene); // Using show twice in a row crashes? 18 // Can only use one pick in a frame? 19 //console.log(key); 20 var hit = scene.pickWithRay(ray,predicate); 21 if (hit.pickedMesh){ 22 //console.log(key+"2"); 23 mesh.isVisible=true; 24 var posp=hit.pickedPoint; 25 mesh.position=posp.clone(); 26 mesh.lab.isVisible=true; 27 var index_x= math.floor ((posp.x+(segs_x+1)*size_per/2)/size_per); 29 var index_z=Math.floor((posp.z+(segs_z+1)*size_per/2)/size_per); 30 mesh.lab.text.text="("+posp.x.toFixed(2)+","+posp.y.toFixed(2)+","+posp.z.toFixed(2)+")*" 31 +index_x+"-"+index_z; 32} 33 else 34 {// The road sign is not displayed if the ground is lost. 35 mesh.lab.isVisible=false; 36 mesh.isVisible=false; 37} 38} 39 40 41 42} 43) 44 function predicate(mesh){// Only rays are allowed to hit the ground mesh, 45 if (mesh.name.substr(0,6)=="ground"){46 return true; 47 } 48 else 49 { 50 return false; 51} 52 53}Copy the code

This code, executed after each rendering, fires five rays from the camera in five directions, using the intersection of the rays and the ground as a signpost, and modifies the GUI text. I have tried to use RayHelper function to display rays here, but found that RayHelper cannot set the rendering group before rendering, and RayHelper needs to be replaced with a new object immediately after rendering, and the old render group attribute is invalid. I hope the official can optimize the use of RayHelper, if it is necessary to display rays. Maybe you can use Line instead of rayHelper.

E. Apply matrix variations to some selected vertices:

In the console to perform TransVertex (obj_ground ground_base, [[0, 0], [0, 1], [1, 0]], BABYLON, Matrix. The Translation (0, 0)) to lift the lower left corner of the three vertices:

TransVertex methods are as follows

1 function TransVertex(mesh,arr,matrix) 2 { 3 var len=arr.length; 4 var arr_path=mesh.metadata.arr_path; 5 for(var i=0; i<len; I++)// move each vertex in the path array 6 {// note that we are manipulating the path array instead of the underlying vertex data 7 arr_path[arr[i][0]][arr[i][1]]=BABYLON.Vector3.TransformCoordinates(arr_path[arr[i][0]][arr[i][1]],matrix); 8 } 9 mesh=BABYLON.MeshBuilder.CreateRibbon(mesh.name 10 ,{pathArray:arr_path,updatable:true,instance:mesh,closePath:false,closeArray:false,sideOrientation:BABYLON.Mesh.DOUBLESI DE}); 11 of 12}Copy the code

Similar to method 1’s block lift, there is a need for some way to generate “configuration arrays” and allocate variations, and a few simple ones will be written below.

2. Generate round hills with random ups and downs

The rendering looks like this:

A, select a certain range of vertices:

1 // Select a region, convert the region condition to a path index, Here there should be a variety of selection methods 2 // Select the vertex at a certain distance from a point 3 //FindZoneBYDistance(obj_ground. Ground_base,new BABYLON.Vector3(-50,0,-50),45) 4 function FindZoneBYDistance(mesh,pos,distance) 5 { 6 var arr_res=[]; 7 var arr_path=mesh.metadata.arr_path; 8 var len=arr_path.length; 9 for(var i=0; i<len; I ++)// For each path 10 {11 var path=arr_path[I]; 12 var len2=path.length; 13 for(var j=0; j<len2; Var vec=path[j]; 16 var length=pos.clone().subtract(vec).length(); Arr_res.push ([I,j,length]); arr_res.push([I,j,length]); 20 } 21 } 22 } 23 return arr_res; Function FindZoneBYDistanceXZ(mesh,pos,distance) 27 {28 var arr_res=[]; 29 var arr_path=mesh.metadata.arr_path; 30 var len=arr_path.length; 31 for(var i=0; i<len; I ++)// For each path 32 {33 var path=arr_path[I]; 34 var len2=path.length; 35 for(var j=0; j<len2; Var vec=path[j]; Subtract (vec) 39 var length= math.pow (vec2.x*vec2.x+vec2.z*vec2.z,0.5); Arr_res.push ([I,j,length]); // If (length<=(distance)) 43 } 44 } 45 } 46 return arr_res; 47}Copy the code

B. Use gradient random method to generate undulating mountains in line with the general trend:

1 // Perform matrix transformation according to certain rules: There should be a variety of interpolation methods 2 // This is the closer to pos the more improved, Function TransVertexGradiently(mesh, ARR,arr_gradient) 4 {5 var len=arr.length; 6 var len2=arr_gradient.length; 7 var arr_path=mesh.metadata.arr_path; 8 for(var i=0; i<len; I++)// for each vertex to transform 9 {10 var matrix=null; 11 var arr2=arr[i]; 12 var vec=arr_path[arr2[0]][arr2[1]]; // VeC is not base, but why can't it be changed directly? 13 var dis=arr2[2]; 14 if(dis<arr_gradient[0][0]) 15 { 16 dis=arr_gradient[0][0]; 17 } 18 else if(dis>arr_gradient[len2-1][0]) 19 { 20 dis=arr_gradient[len2-1][0]; For (var j=1; var j=1; j<len2; j++) 24 { 25 var gradient=arr_gradient[j]; 26 if(dis<=gradient[0]) 27 {// Calculate gradient0=arr_gradient[J-1]; Var ratio=(gradient0[0])/(gradient0[0] -gradient0[0])); Gradient0 [1]+(gradient0[1] - Gradient0 [1])* gradient; Gradient0 [2]+(gradient0[2] - Gradient0 [2])* gradient; Var c=b-a; 38 var res=a+c*Math.random(); 39 matrix=new BABYLON.Matrix.Translation(0,res,0); 40 break; 41 } 42 } 43 if(matrix) 44 { 45 arr_path[arr2[0]][arr2[1]]=BABYLON.Vector3.TransformCoordinates(arr_path[arr2[0]][arr2[1]],matrix); 46 } 47 } 48 mesh=BABYLON.MeshBuilder.CreateRibbon(mesh.name 49 ,{pathArray:arr_path,updatable:true,instance:mesh,closePath:false,closeArray:false,sideOrientation:BABYLON.Mesh.DOUBLESI DE}); 50}Copy the code

The gradient stochastic algorithm in the code can be found in the chapter on particle systems in the InitialBaby.js tutorial.

The commands used to achieve the effect in the figure are:

1 TransVertexGradiently(obj_ground.ground_base,FindZoneBYDistance(obj_ground.ground_base,new BABYLON. Vector3 (-, 0, 50-50), 45) 2, [,29,30 [0], [15,14,15], [30 final three], [1] 45]); 3 TransVertexGradiently(obj_ground.ground_base,FindZoneBYDistance(obj_ground.ground_base,new BABYLON. Vector3 (- 50,0,50), 30) 4, [[0,14,15], [15, four, five], [1] 30]); 5 TransVertexGradiently(obj_ground.ground_base,FindZoneBYDistance(obj_ground.ground_base,new BABYLON. Vector3 (, 0, 50-50), 30) 6, [[0,14,15], [15, four, five], [1] 30]); 7 TransVertexGradiently(obj_ground.ground_base,FindZoneBYDistance(obj_ground.ground_base,new BABYLON. Vector3 (- 0, 50), 8, 30) [,14,15 [0], [15, four, five], [1] 30]); 9 TransVertexGradiently(obj_ground.ground_base,FindZoneBYDistance(obj_ground.ground_base,new BABYLON. Vector3 (0, 0, - 50), 10, 30) [,14,15 [0], [15, four, five], [1] 30]);Copy the code

You can also write these commands directly in the terrain initialization section of the program, which is faster than runtime injection code. After processing the ground_base, we started adding the terrain fixtures.

3. The effect of forests and lakes that fit hilly terrain and remain horizontal is shown below:

Trees grow with the terrain, so the forest should have the same relief as the hills, and the water should be as smooth as a mirror, keeping the terrain flat regardless of underwater conditions. At the same time these two terrain attachments should also have texture repetition synchronized with ground_base. Code implementation:

1 // The key difficulty lies in how to extract and recombine vertices, indexes and UVs of terrain mesh. The joint surface rendering texture can also be used in model 2 function MakeLandtype1 (mesh, arr, mat, name, sameheight, height) 3 {4/5 var/ground_base vertex data vb=mesh.geometry._vertexBuffers; Var data_pos=vb.position._buffer._data; var data_pos=vb.position._buffer. Var data_index=mesh.geometry._indices; Var data_uv=vb.uv._buffer._data; Var len_index=data_index.length; 10 11 var len=arr.length; 12 var arr_path=mesh.metadata.arr_path; Var arr_index=[]; var arr_index=[]; 16 var data_pos2=[]; 17 var data_index2=[]; Var data_uv2=[]; 19 console.log(" Start generating terrain attachments "); 20 for(var I =0; i<len; I ++){var int0=arr[I][0]; 24 var int1=arr[i][1]; 25 var vec=arr_path[int0][int1]; // Get a Vector3 object from the path array 26 // There are two ways to do this: one is to start with the vertex data and completely reproduce the height of the terrain; Secondly, starting from the path index of the strip, the polygon outline of the attachment can be generated more closely, but the height may not be accurate (not fit). 27 //-> Combine the two? 28 // Assuming that the path array and the vertex data are one-to-one, and assuming that each path is the same length as the first, 29 var index_v=int0*arr_path[0].length+int1// The vertex index is 30 31 data_pos2.push(vec.x); 32 if(sameheight)// If all vertices are equal height, 33 {34 data_pos2.push(height); 35} 36 else 37 {38 data_pos2.push(vec.y); 39} 40 data_pos2.push(vec.z); 41 Data_uv2. push(data_uv[index_V *2]); 42 data_uv2.push(data_uv[index_V *2+1]); 43 44} 45 // Generate an index array of attachments 46 Len =arr_index.length; 47 console.log(" start setting terrain attachment index "); 48 for(var I =0; I <len; I ++)// For each vertex index, 49 {50 console.log(I +"/"+len); 51 var index_v=arr_index[I]; 52 for(var) J <len_index;j+=3)// traverses the index array of ground_base, Var num2=-1; 55 var num3=-1; 56 var arr_temp=[]; 57 var flag_type=null; 58 var flag_type=null If (index_v==data_index[j])// If (index_v==data_index[j])// The first vertex of the triangle 59 { Num2 =data_index[j+1];//*3; Flag_type ==data_index[j+ 2]; 62 flag_type=1; 63} 64 else if(index_v==data_index[j+1])// the second vertex of the triangle 65 {66 If (index_v==data_index[j]; 0 else if(index_v==data_index[j]; 0 else if(index_v==data_index[j]; 0 else}) 72 num2=data_index[j]; 73 num3=data_index[j+1]; 74 flag_type=3; 75 } 76 if(num2!=-1&&num3!=-1) 77 {// If num2 and num3 are not in the selected vertex range, then they are not in the attachment to draw this triangle 78 For (var i2=0;i2<len;i2++) 82 {83 var index2=arr_index[i2]; 84 var index2=arr_index[i2]; 84 If (index2==num2) 85 {86 flag2=i2; if(index2==num3) 89 {90 flag3=i2; 91} 92 If (flag2!=-1&& FLAG3!=-1) 93 {94 break if(flag_type==1) 100 { 101 data_index2.push(i); 102 data_index2.push(flag2); 103 data_index2.push(flag3); 104 } 105 else  if(flag_type==2) 106 { 107 data_index2.push(flag2); 108 data_index2.push(i); 109 data_index2.push(flag3); 110 } 111 else if(flag_type==3) 112 { 113 data_index2.push(flag2); 114 data_index2.push(flag3); 115 data_index2.push(i); 116 } 117 118} 119} 120} 121} 122 To start generating 123 var geometry normals = []; 124 BABYLON.VertexData.Com puteNormals (data_pos2 data_index2, // Calculate normals 125 BABYLON.vertexdata. _ComputeSides(0, data_pos2, data_index2, normals, Var vertexData= new BABYLON.VertexData(); vertexdata.indices = data_index2;// Index 128 Positions = data_pos2; 129 vertexdata. normals = normals;// Position Changes normals also change !!!! 130 vertexdata. uvs = data_uv2;  131 132 var mesh=new BABYLON.Mesh(name,scene); 133 vertexData.applyToMesh(mesh, true); 134 mesh.vertexData=vertexData; 135 mesh.renderingGroupId=2; 136 mesh.material=mat; 137 obj_ground[name]=mesh; 138}Copy the code

There are many optional algorithms when extracting the terrain of ground_base. Assume that the vertex layout of ground_base is as shown in the figure below:

If all vertices on the upper right side of ABF, including ABF, are selected, we may only extract the cells with diagonals, we may extract all triangles on the upper right side of ACF (with more ABC parts than the former), and we may extract all regions on the upper right side of AF (in which case additional triangles need to be added, assuming that JIHG is different in height from CDEF, Or extract all cells adjacent to ABCDEF… Different extraction methods will produce different effects of terrain details, so I choose the second extraction method here.

When dealing with different texture border there are a variety of different options, such as mutual crisscross, based on the actual location of the triangle let texture or on the border between using the transition of mixing two kinds of texture map texture and so on, here I am simple to keep the original texture of each landscape, drawn after use, cover drawing first, with a block away from the camera near the camera.

The command to generate the image above is as follows:

1 MakeLandtype1 (obj_ground ground_base FindZoneBYDistanceXZ (obj_ground ground_base, new BABYLON. Vector3 (- 50,0,10), 30) 2 ,mat_tree,"ground_tree1"); 3 MakeLandtype1 (obj_ground ground_base FindZoneBYDistanceXZ (obj_ground ground_base, new BABYLON. Vector3 (0, 0), (35) 4 ,mat_shallowwater,"ground_shallowwater1",true,0);Copy the code

 

4. Import the prefabricated model into the terrain to generate drastically changed terrain. Add a “crashed spaceship” model to the scene:

From below :(the left side only shows the forest texture not because the grass triangle does not exist, but because the later triangle will overwrite the first one when the mesh position and render group are the same)

It is difficult to generate the backslope topography using a simple ground grid. The code to import the Babylon format model is as follows:

1 // If you load a TXT file with the same content, you will receive a warning, but it seems to have been successfully imported!! 2 function ImportMesh(objname,filepath,filename,obj_p) 3 { 4 BABYLON.SceneLoader.ImportMesh(objname, filepath, Filename, scene 5, function (newMeshes, particleSystems, Skeletons) 6 {7 var mesh=newMeshes[0]; 8 mesh.position=obj_p.position; 9 mesh.rotation=obj_p.rotation; 10 mesh.scaling=obj_p.scaling; 11 mesh.name=obj_p.name; 12 mesh.id=obj_p.name; 13 var mat=obj_p.material.clone(); 14 mat.backFaceCulling=false; 15 mat.name=obj_p.material.name; 16 mesh.material=mat; 17 mesh.renderingGroupId=2; 18 mesh.sideOrientation=BABYLON.Mesh.DOUBLESIDE; 19 obj_ground[obj_p.name]=mesh; 20} 21); 22}Copy the code

The call command is as follows:

1 ImportMesh("",".. /.. / ASSETS/SCENE/SpaceCraft. ", "Babylon" 2, {position: new Babylon, Vector3 (2, 10, 10 -), rotation: new BABYLON.Vector3(0, -math.pi /4, math.pi /6) 3,scaling:new BABYLON.Vector3(1,1,1),name:"ground_spacecraft" 4 ,material:mat_stone});Copy the code

You may want to save the current scene for reloading after you’ve done a few things.

A. The archive code is as follows:

2 function ExportObjGround() 3 {4 var obj_scene= makebasicGround (); 2 {5 var obj_scene=MakeBasicBabylon(); 5 For (key in obj_ground)// No material is configured in the Babylon file. Can the material ID in the new scene be automatically imported? 6 {7 var obj_mesh={}; 8 var mesh=obj_ground[key]; 9 obj_mesh.name=mesh.name; 10 obj_mesh.id=mesh.id; 11 obj_mesh.materialId=mesh.material.name; 12 obj_mesh.position=[mesh.position.x,mesh.position.y,mesh.position.z]; 13 obj_mesh.rotation=[mesh.rotation.x,mesh.rotation.y,mesh.rotation.z]; 14 obj_mesh.scaling=[mesh.scaling.x,mesh.scaling.y,mesh.scaling.z]; 15 obj_mesh.isVisible=true; 16 obj_mesh.isEnabled=true; 17 obj_mesh.checkCollisions=false; 18 obj_mesh.billboardMode=0; 19 obj_mesh.receiveShadows=true; 20 obj_mesh.renderingGroupId=mesh.renderingGroupId; 21 obj_mesh.metadata=mesh.metadata; 22 obj_mesh.sideOrientation=mesh.sideOrientation; 24 {25 var vb=mesh.geometry._vertexBuffers; 26 obj_mesh.positions=BuffertoArray2(vb.position._buffer._data); 27 obj_mesh.normals=BuffertoArray2(vb.normal._buffer._data); 28 obj_mesh.uvs= BuffertoArray2(vb.uv._buffer._data); 29 obj_mesh.indices=BuffertoArray2(mesh.geometry._indices); 30 obj_mesh.subMeshes=[{ 31 'materialIndex': 0, 32 'verticesStart': 0, 33 'verticesCount': mesh.geometry._totalVertices, 34 'indexStart': 0, 35 'indexCount': mesh.geometry._indices.length, 36 }]; 37 obj_mesh.parentId=mesh.parent? mesh.parent.id:null; Positions =[]; obj_mesh.positions=[]; 42 obj_mesh.normals=[]; 43 obj_mesh.uvs=[]; 44 obj_mesh.indices=[]; 45 obj_mesh.subMeshes=[{ 46 'materialIndex': 0, 47 'verticesStart': 0, 48 'verticesCount': 0, 49 'indexStart': 0, 50 'indexCount': 0 51 }]; 52 obj_mesh.parentId=null; 53 } 54 obj_scene.meshes.push(obj_mesh); 55 } 56 var str_data=JSON.stringify(obj_scene); Var tmpDown = new Blob([s2ab(str_data)] 59,{60 type: "61} 62); 63 saveAs(tmpDown,"ObjGround.babylon") 64 }Copy the code

The MakeBasicBabylon method holds the data needed for the most basic scene object:

Function MakeBasicBabylon() 3 {4 var obj_scene= 5 {// Select 'autoClear' from 'autoClear': True, 7 'clearColor: 0, 0 and 3, 8' ambientColor ': 0, 0, 9' gravity ': [0, 9.81, 0], 10' cameras' : [], 11 'activeCamera' : null, 12 'lights':[], 13 'materials':[], 14 'geometries': {}, 15 'meshes': [], 16 'multiMaterials': [], 17 'shadowGenerators': [], 18 'skeletons': [], 19 'sounds': []//, 20 //'metadata':{'walkabilityMatrix':[]} 21 }; 22 return obj_scene; 23}Copy the code

BuffertoArray2 is a method that converts buffer data into arrays:

1 function BuffertoArray2(arr) 2 { 3 var arr2=[]; 4 var len=arr.length; 5 for(var i=0; i<len; i++) 6 { 7 arr2.push(arr[i]); 8 } 9 return arr2; 10}Copy the code

Save the Babylon model file using www.jianshu.com/p/9a465d7d1…

1 function s2ab(s) { 2 if (typeof ArrayBuffer ! == 'undefined') { 3 var buf = new ArrayBuffer(s.length); 4 var view = new Uint8Array(buf); 5 for (var i = 0; i ! = s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; 6 return buf; 7 } else { 8 var buf = new Array(s.length); 9 for (var i = 0; i ! = s.length; ++i) buf[i] = s.charCodeAt(i) & 0xFF; 10 return buf; 11 } 12 } 13 saveAs=function(obj, fileName) 14 { 15 var tmpa = document.createElement("a"); 16 tmpa. Download = fileName | | "download". 17 tmpa.href = URL.createObjectURL(obj); 18 tmpa.click(); 19 setTimeout(function () { 20 URL.revokeObjectURL(obj); 21}, 100); 22};Copy the code

 

6. Summary:

Above content can visit https://ljzc002.github.io/EmptyTalk/HTML/TEST/testframe3.html for testing

In previous studies based on programming model editor (www.cnblogs.com/ljzc002/p/9…

Three,

1, First import the archive we saved, run the following command to import the archive we made earlier:

ImportObjGround(“.. /.. /ASSETS/SCENE/”,”ObjGround.babylon”,webGLStart3);

The code for ImportObjGround is as follows:

1 function ImportObjGround(filepath,filename,func) 2 { 3 BABYLON.SceneLoader.ImportMesh("", filepath, filename, Scene 4, function (newMeshes, particleSystems, Skeletons) 5 {var len=newMeshes. 7 for(var i=0; i<len; i++) 8 { 9 var mesh=newMeshes[i]; 10 mesh.renderingGroupId=2; 11 mesh.sideOrientation=BABYLON.Mesh.DOUBLESIDE; 12 obj_ground[mesh.name]=mesh; 13 if(mesh. Name =="ground_base") 14 {// Declare the vertex position to be changeable!! 15 mesh. MarkVerticesDataAsUpdatable (BABYLON. VertexBuffer. PositionKind / / is actually "position", in addition to "normal" and so on the 16th, true); 17} 18 if(mesh.metadata&&mesh.metadata.arr_path) 19 {// To change the array back to Vector3!!!! 20 var arr_path=mesh.metadata.arr_path; 21 var len1=arr_path.length; 22 for(var j=0; j<len1; j++) 23 { 24 var path=arr_path[j]; 25 var len2=path.length; 26 for(var k=0; k<len2; k++) 27 { 28 var vec=path[k]; 29 var vec2=new BABYLON.Vector3(vec.x,vec.y,vec.z); 30 path[k]=vec2; 31 } 32 } 33 } 34 } 35 func(); // Go to 36} 37); 38}Copy the code

To note here is that after importing the model grid updatable attributes will automatically become false, then you need to use markVerticesDataAsUpdatable method to activate the grid update ability. At the same time, the Vector3 Object of Babibs.js will automatically degenerate into a JavaScript Object when it is converted into a JSON string. We need to convert it into a Vector3 again when loading.

2. Prepare for rain

Rainfall algorithm is not complicated, thinking from a camera may observe direction from ray, if ray hits a triangle, put the three vertices of the triangle “wet” (all wet a subset of “wet array”), in the rain, after the completion of out without getting wet vertex and the corresponding indexes, the model is simplified. Compared with the traditional mesh fusion method, the rainfall method does not generate a large number of uncontrolled triangles at the mesh interface. Compared with traditional mesh simplification methods, visible triangle details are not discarded. The downside is that it takes a long time to compute (maybe optimized?). First prepare for rain:

1 function PrepareRain() 2 {3 console.log(" Prepare for rain "); 4 mesh_DropFrom=new BABYLON.Mesh("mesh_DropFrom",scene); 5 for(var key in obj_ground) 6 { 7 var mesh=obj_ground[key]; 8 var obj={}; 9 obj.vb=mesh.geometry._vertexBuffers; 10 obj.data_pos=obj.vb.position._buffer._data; Obj. data_index= mes.geometry._indices; // Grid index data 12 obj.data_uv=obj.vb.uv._buffer._data; Obj.len_index =obj.data_index.length; 14 obj.len_pos=obj.data_pos.length/3; 15 obj.data_wet=[]; For (var I =0; i<obj.len_pos; i+=1) 17 { 18 obj.data_wet.push(0); 19 } 20 obj.arr_index=[]; 21 obj.data_pos2=[]; 22 obj.data_index2=[]; // Fill the second loop with 23 obj.data_uv2=[]; 24 obj_wet[key]=obj; 25} 26 console.log(" Ready "); 27} 28 function PrepareRain2() 29 {30 console.log(" Read local wet arrays "); 31 mesh_DropFrom=new BABYLON.Mesh("mesh_DropFrom",scene); 32 for(var key in obj_ground) 33 { 34 var mesh=obj_ground[key]; 35 var obj={}; 36 obj.vb=mesh.geometry._vertexBuffers; Obj.data_pos =obj.vb.position._buffer._data; Obj. data_index= mes.geometry._indices; // Grid index data 39 obj.data_uv=obj.vb.uv._buffer._data; Obj.len_index =obj.data_index. Length; 41 obj.len_pos=obj.data_pos.length/3; 42 obj.data_wet=localStorage.getItem(key); // Whether each vertex is wet 43 obj.arr_index=[]; 44 obj_wet[key]=obj; 45} 46 console.log(" Ready "); 47}Copy the code

The first method extracts the data for each grid in the ground object required for rainfall, where obJ. data_wet is an all-zero set of numbers of the same length as the vertex array, with 0 indicating no wet. The second method reads the calculated wet array from the browser’s local storage and saves the time needed for rain with the pre-calculated data. The mesh_DropFrom grid is the reference grid used for subsequent rainfall.

3. It starts to rain (this is a very time-consuming calculation)

1 // width segment, depth segment, block size (there are four rays in this size), "ray" length, center of starting point for all rays 2 //DropRain(100,100,1,100,new BABYLON.Vector3(0,50,0),new 5 //DropRain(200,200,0.5,100,new BABYLON.Vector3(0,50,0),new BABYLON DropRain(count_x,count_z,size,length,from,to) 5 { 6 mesh_DropFrom.position=from; 7 mesh_DropFrom.lookAt(to); // The WorldMatrix and AbsoulutPosition of the grid have not been changed!! 8 // The negative Y direction of the grid points to!!!! The final effect of this matrix should be x, y, z left a 9 mesh_DropFrom.com puteWorldMatrix (); 10 var matrix=mesh_DropFrom.getWorldMatrix(); Var size41=size/4; 12 var direction=to.subtract(from); 14 console.log(" It starts raining "); 15 for(var i=0; i<=count_x; i++) 16 { 17 for(var j=0; j<=count_z; j++) 18 { 19 console.log(i+"/"+count_x+"_"+j+"/"+count_z); 20 var arr_wet=[]; 21 var pos0=new BABYLON.Vector3((j-(count_z/2))*size,(i-(count_x/2))*size,0); // Move one bit to the right? 22 // Set up four rays, In the local coordinate system transformation 24 var pos1 = BABYLON. Vector3. TransformCoordinates (pos0. The clone (). The add (new BABYLON. Vector3 (size41 - size41, 0)), matrix); 25 var pos2 = BABYLON. Vector3. TransformCoordinates (pos0. The clone (). The add (new BABYLON. Vector3 (size41 size41, 0)), matrix). 26 var pos3 = BABYLON. Vector3. TransformCoordinates (pos0. The clone (). The add (new BABYLON. Vector3 (- size41 size41, 0)), matrix). 27 var pos4 = BABYLON. Vector3. TransformCoordinates (pos0. The clone (). The add (new BABYLON. Vector3 (- size41, - size41, 0)), matrix). 28 //var ray=new BABYLON.Ray(new BABYLON.Vector3(0,-1,0), new BABYLON. 29 var ray1 = new BABYLON.Ray(pos1, direction, length); 30 var ray2 = new BABYLON.Ray(pos2, direction, length); 31 var ray3 = new BABYLON.Ray(pos3, direction, length); 32 var ray4 = new BABYLON.Ray(pos4, direction, length); 33 // For each ray, if the first grid is not hit by "ground_alpha", only the first grid is wetted 34 //, otherwise the second is checked in the same way, the results for all four rays are placed in the same array 35 //, Hit the ground_base and other ground in the same position, and select other ground 36 testRay(Ray1,size) first; // Check the ray wet mesh 37 testRay(ray2,size); 38 testRay(ray3,size); 39 testRay(ray4,size); 43 for(key in obj_wet) 44 {45 localstorage.setitem (key,obj_wet[key].data_wet); 46} 47 console.log(" Rainfall ends "); 48}Copy the code

To allow for oblique rain, we use a matrix transform to adjust the starting and direction of each rain line. In the local coordinate system of the mesh_DropFrom, the starting point of each rain line is arranged flat like a block cell. Using lookAt to tilt the mesh_DropFrom a bit, all rain lines are tilted. Note that lookAt defaults to pointing the “front” of the mesh_DropFrom to the target, whereas we want to point the “below” of the mesh_DropFrom to the target. This difference will shift the final world coordinate one bit to the left. That is, (x,y,z) becomes (y,z,x), so when we evaluate in the local coordinate system of mesh_DropFrom, we pre-shift the coordinates one bit to the right.

Schematic diagram of rainfall:

Four rays are emitted from each “cell” of the mesh_DropFrom, and the contact of these rays with the ground grid is determined using the testRay method:

1 function sort_compare(a,b) 2 { 3 return a.distance-b.distance; 4 } 5 function testRay(ray,size) 6 { 7 var arr=scene.multiPickWithRay(ray,predicate); // Multiple selection of rays, so that the arR is not sorted in distance order !!!! 8 var len=arr.length; 10 var lastHit=null; 10 var lastHit=null; 11 for(var k=0; k<len; K ++)// For each triangle hit by this ray 12 {13 var hit=arr[k]; 14 var mesh=hit.pickedMesh; 15 var distance=hit.distance; 16 if(mesh) 17 {18 if(lastHit)// There is already a layer 19 {// If the layer is translucent, the next layer must be wet, if the layer is ground_base, Depends on the distance between the two layers of 20 if (lastHit. PickedMesh. Name. Substr (0, 10) = = "ground_base") {21 to 22 If ((distance-lasthit. Distance)>(size/1000)) 25} 26 else// If close, priority is given to wet terrain attachment 27 {28 getWet(hit); 33 {34 if(mesh.name. Substr (0,11)! ="ground_base")// if it is ground grid, it is not sure whether it will getWet, other grids must getWet. 37} 38 else if(k==(len-1)) 39 {40 getWet(hit); 41 } 42 } 43 var name=mesh.name; 44 the if (name && (name. Substr (0, 12) = = "ground_alpha" | | name. The substr (0, 10) = = "ground_base")) 45 {46 lastHit = hit; 47 } 48 else 49 { 50 lastHit=null; 51 break; 52} 53} 54 else 55 {56 lastHit=null; 54 else 55 {56 lastHit=null; 57 break; 58} 59} 60}Copy the code

The possible terrain grids are divided into three categories: Semi-transparent terrain grid (with ground_alpha as the prefix), base terrain grid (with ground_base as the prefix), and terrain attachment grid (with ground as the prefix). It is stipulated that the semi-transparent grid will not block rain, and the attachment grid has priority to wet when the distance between the attachment grid and the base grid is close. After confirming that it will getWet, use getWet to put the vertices of the triangle into the wet array:

1 function getWet(hit) 2 { 3 var mesh=hit.pickedMesh; 4 var name=mesh.name; 5 var faceId=hit.faceId; 6 var indices = mesh.getIndices(); 7 var index0 = indices[faceId * 3]; 8 var index1 = indices[faceId * 3 + 1]; 9 var index2 = indices[faceId * 3 + 2]; 10 var wet=obj_wet[name]; // This vertex is wetted 11 wet.data_wet[index0]=1; 12 wet.data_wet[index1]=1; 13 wet.data_wet[index2]=1; 14}Copy the code

4. Eliminate triangles according to the wet array :(this is a more time-consuming calculation)

1 function SpliceRain(obj_ground) 2 {3 for(var key in obj_ground) 4 {5 console.log(" clean "+key); 6 var obj=obj_wet[key]; 7 var len=obj.len_pos; 8 var data_wet=obj.data_wet; 9 var data_pos=obj.data_pos; Var count_splice=0; var count_splice=0; var count_splice=0; 13 for(var i=0; i<data_wet.length; I++)// for each vertex, the order must be careful here 14 {// if the vertex is not wet, to clear the vertex, if not clear the index, can not be faster? 15 console.log(i+"/"+data_wet.length); 16 if(! Data_pos.splice (I *3,3); data_pos.splice(I *3,3); 19 data_uv. Splice (I * 2, 2); 20 data_wet.splice(i,1); 21 22 //count_splice++; 23 var len2=obj.len_index; 24 for(var j=0; j<obj.len_index; J ++) 25 {26 if(data_index[j]> I) 27 {28 data_index[j]-=1; //count_splice; 29} 30 else if(data_index[j]== I) 31 {32 var int_temp=j%3; If (int_temp==0)// the first vertex of the triangle 34 {35 data_index.splice(j,3); 36 j-=1; 39} 38 else if(int_temp==1)// the second vertex of the triangle 39 {40 data_index.splice(j-1,3); 41 j-=2; 42} 43 else if(int_temp==2)// the third vertex of the triangle 44 {45 data_index.splice(j-2,3); 46 j-=3; 47 } 48 } 49 } 50 i--; Var normals=[]; var normals=[]; 55 BABYLON.VertexData.ComputeNormals(data_pos, data_index, normals); // Calculate normals 56 surplibs.vertexdata. _ComputeSides(0, data_pos, data_index, normals, data_uv); // Assign texture orientation according to normal 57 var vertexData= new babybabylon.VertexData(); 58 vertexData.indices = data_index; Positions = data_pos; // Index 59 vertexdata.positions = data_pos; 60 vertexData.normals = normals; //position changes normals to also change !!!! 61 vertexData.uvs = data_uv; 62 63 var mesh=obj_ground[key]; 64 var mat=mesh.material; 65 var pos=mesh.position; 66 var rot=mesh.rotation; 67 var scal=mesh.scaling; 68 mesh.dispose(); 69 mesh=new BABYLON.Mesh(key,scene); 70 //mesh 71 //mesh=new BABYLON.Mesh(name,scene); 72 vertexData.applyToMesh(mesh, true); 73 mesh.vertexData=vertexData; 74 mesh.sideOrientation=BABYLON.Mesh.DOUBLESIDE; 75 mesh.renderingGroupId=2; 76 mesh.material=mat; 77 mesh.position=pos; 78 mesh.rotation=rot; 79 mesh.scaling=scal; 80 obj_ground[key]=mesh; 82 81}}Copy the code

We need to remove wet vertices from the vertex array and vertex index array, and we need to reduce all values in the vertex index array that exceed the deleted vertices as the number of vertices changes. Other calculations are similar to generating terrain attachments. The rendering effect after elimination is shown in the figure below:

DropRain(100,100,1,100,new BABYLON.Vector3(0,50,0),new BABYLON.Vector3(0,0,0))

As you can see, the shallow-water triangles that didn’t get wet were removed, on the other hand, because the ship’s grid was denser than the rain, many of the grids didn’t get wet.

DropRain(200,200,0.5,100,new BABYLON.Vector3(0,50,0),new BABYLON.Vector3(0,0,0))

You can see that there are still some holes in the model, which can be solved by adding some small rainfall in other directions. In addition to this “directional light” rain method, other rain methods can be programmed according to actual needs, such as referring to a “point light source” that emits rays from a point around it, Can also use some boundary judgment method direct them to a range of all the vertices (border judgment method can refer to www.cnblogs.com/ljzc002/p/1…

Conclusion:

Now that the foundation has been laid, it’s time to add various characters to the scene and interact.