15 世界地图 SLG视野

相机类型

1 .其实什么类型都可以,关键是操作逻辑,要变成拖拽移动视角,并且是滑轮放大和缩小
2 .先拿node来做一下试试,感觉有别的相机

第一版,以动态地形为基准,感觉有点粗糙,镜头会变到地下面,这个是平面的,感觉还要操作,应该是只操作x,z坐标

let startPosition
                // 开始拖拽的位置

                function getPosition(){
                    let pickInfo=scene.pick(scene.pointerX,scene.pointerY,function(mesh){
                        return true
                    })
                    if(pickInfo.hit){
                        return pickInfo.pickedPoint
                    }

                }

                function pointerDown() {
                    startPosition=getPosition()
                }

                function pointerUp(){
                    startPosition=null
                }

                function pointerMove(event){
                   if(!startPosition)return 

                   let current=getPosition()
                   if(!current)return 
                   let diff=current.subtract(startPosition)
                   node.position.addInPlace(diff)
                   startPosition=current
                }



                scene.onPointerObservable.add((pointerInfo)=>{
                    switch(pointerInfo.type){
                        case BABYLON.PointerEventTypes.POINTERDOWN:
                            pointerDown()
                            break
                        case BABYLON.PointerEventTypes.POINTERUP:
                            pointerUp()
                            break
                        case BABYLON.PointerEventTypes.POINTERMOVE:
                            pointerMove(pointerInfo)
                            break
                    }
                })

第二版,以屏幕为定位

<!DOCTYPE html>
<!-- 添加小人,使用序列图 -->
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html" charset="utf-8"/>
    <title>Babylon - Getting Started</title>
    <!-- Link to the last version of BabylonJS -->
    <script src="https://preview.babylonjs.com/babylon.js"></script>
    <!-- Link to the last version of BabylonJS loaders to enable loading filetypes such as .gltf -->
    <script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
    <!-- Link to pep.js to ensure pointer events work consistently in all browsers -->
    <script src="https://code.jquery.com/pep/0.4.1/pep.js"></script>
    <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script> -->
    <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script> -->
    <!-- <script src="https://preview.babylonjs.com/gui/babylon.gui.min.js"></script> -->
    <!-- <script src="https://cdn.rawgit.com/BabylonJS/Extensions/master/DynamicTerrain/dist/babylon.dynamicTerrain.min.js"></script> -->
    <script src="./dy.js"></script>
    <script src="./noise.js"></script>
</head>
<style>
    html, body {
        overflow: hidden;
        width   : 100%;
        height  : 100%;
        margin  : 0;
        padding : 0;
    }

    #renderCanvas {
        width   : 100%;
        height  : 100%;
        touch-action: none;
    }
</style>
<body>
    <canvas id="renderCanvas" touch-action="none"></canvas>
    <script>
        const canvas = document.getElementById("renderCanvas");
        var engine = null;
        // 这里还不能用let,不然就爆炸,获取不到engine
        var scene = null;
        var sceneToRender = null;
        const createDefaultEngine = function() { return new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true,  disableWebGL2Support: false}); };
        let createScene=async function(){
            // 关键函数都写在这个里面

                var scene = new BABYLON.Scene(engine)
                var camera = new BABYLON.UniversalCamera("camera1", new BABYLON.Vector3(0, 20, 0), scene);    
                // 第二个坐标是摄像机在场景中的位置,现在还需要一个就是摄像机的朝向
                camera.rotation=new BABYLON.Vector3(0.7697057340138546,0.00772265625,0)
                camera.position.y=60
                // 高度最高现在定为60,最大值,按照这个最大值来调参数,地图大小也需要调,现在镜头外面还有没有显示的地
                

                var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0.0, 1.0, 0.0), scene);
                light.intensity = 0.75;
                light.specular = BABYLON.Color3.Black();

                // 这俩值是不是决定了地图的实际大小
                let terrainTexture=new BABYLON.Texture("https://www.babylonjs-playground.com/textures/ground.jpg",scene)

                let terrainMat=new BABYLON.StandardMaterial('tm',scene)
                terrainMat.diffuseTexture=terrainTexture
                terrainMat.diffuseTexture.UScale=1
                terrainMat.diffuseTexture.VScale=1

                var spsMaterial = new BABYLON.StandardMaterial("spsm", scene);
                var spsUrl = "https://jerome.bousquie.fr/BJS/images/uv_texture.jpg";
                var spsTexture = new BABYLON.Texture(spsUrl, scene);
                spsMaterial.diffuseTexture = spsTexture;
                
                var mapSubX = 1000;            
                var mapSubZ = 1000;              
                var seed = 0.3;                 
                var noiseScale = 0.03;         
                var elevationScale = 6.0;
                noise.seed(seed);
                var mapData = new Float32Array(mapSubX * mapSubZ * 3); 

                let SPmapData=[[],[],[]]
                // 这俩其实不是很常用,毕竟真的引入mesh这俩应该都有的吧,除非mesh仅仅是颜色不一样代表不同的物体,来做优化的时候
                var SPcolorData = [[], [],  []];
                var SPuvData = [[], [], []];

                // x位移系数:在原来的基础上移动的距离,或者说实际屏幕和地图的位移比例吧
                let xMove=0.033
                // 第一次0.5的值感觉还是有点大
                // y位移系数
                let yMove=0.053
                // 最后这个值感觉这里就差不多了,但是这个系数还是要和镜头高度有关系的

                for (var l = 0; l < mapSubZ; l++) {
                        for (var w = 0; w < mapSubX; w++) {
                            var x = (w - mapSubX * 0.5) * 2.0;
                            var z = (l - mapSubZ * 0.5) * 2.0;
                            var y = noise.simplex2(x * noiseScale, z * noiseScale);               // altitude
                            y *= (0.5 + y) * y * elevationScale; 

                            mapData[3 *(l * mapSubX + w)] = x;
                            mapData[3 * (l * mapSubX + w) + 1] = y;
                            mapData[3 * (l * mapSubX + w) + 2] = z; 

                            let index=l*mapSubX+w
                            if(Math.random()>0.85){
                                // 如果满足这个条件,就添加一个物体
                                let xp=x
                                let yp=y
                                let zp=z

                                let ry=Math.random()*3.6

                                let sx=0.5+Math.random()
                                let sy=0.5+Math.random()
                                let sz=0.5+Math.random()

                                let r = Math.abs(xp) / mapSubX * 1.2 + 0.5;
                                let g = Math.abs(zp) / mapSubZ * 1.2 + 0.5;
                                let b = Math.abs(yp) / elevationScale + 0.1;

                                // UVs
                                let u = 0.9 * Math.random();
                                let v = 0.9 * Math.random();

                                let type = index % 3;
                                SPmapData[index % 3].push(xp, yp, zp, 0, ry, 0, sx, sy, sz);
                                SPcolorData[type].push(r, g, b, 1.0); 
                                SPuvData[type].push(u, v, u + 0.1, v + 0.1);
                            }
                    }
                }   

                    let model1=BABYLON.MeshBuilder.CreateBox('m1',{size:1},scene)
                    let model2=BABYLON.MeshBuilder.CreatePolyhedron('m2',{size:0.5},scene)
                    let model3=BABYLON.MeshBuilder.CreateSphere('m3',{segments:3},scene)

                    let sps=new BABYLON.SolidParticleSystem("SPS",scene)
                    let type1=sps.addShape(model1,10000)
                    let type2=sps.addShape(model2,10000)
                    // 为什么数量少的时候,镜头了将要到达的地方物体消失,正好相反了
                    // 这里的数量是当前地形中最多可见的形状的数量,100的意思就是地图中最多可见100个model2模型
                    let type3=sps.addShape(model3,10000)
                    sps.buildMesh()
                    model1.dispose()
                    model2.dispose()
                    model3.dispose()

                    sps.mesh.material = spsMaterial;

                    var terrainSub = 160;  
                    // 这个才是镜头最大展示的数量
                    var params = {
                        mapData: mapData,               
                        mapSubX: mapSubX,               
                        mapSubZ: mapSubZ,
                        SPmapData: SPmapData,           
                        sps: sps,                       
                        terrainSub: terrainSub,
                        subToleranceX:160,
                        subToleranceZ:160,
                        // 这个更新是指所有的静态元素更新,有的时候,可能不移动镜头都需要更新计算某些值,比如地块的变化。所以还需要主动触发地块更新
                    }
                    var terrain = new BABYLON.DynamicTerrain("t", params, scene);
                    terrain.isAlwaysVisible=true
                    var terrainMaterial = new BABYLON.StandardMaterial("tm", scene);
                    terrainMaterial.diffuseTexture = terrainTexture;
                    terrain.mesh.material = terrainMaterial;

                    // terrain.updateCameraLOD = function(terrainCamera) {
                    //     // LOD value increases with camera altitude
                    //     var camLOD = Math.abs((terrainCamera.globalPosition.y / 30.0)|0);
                    //     // 这里加了动态调节,就不用别的地方加了,但是这里好像不能使用这个,因为我开大镜头,精度是不能丢失的,现在会丢失地面建筑物的精度,这个只能保证拉大镜头,地面永远都在镜头内
                    //     return camLOD;
                    // };
                terrain.update(true);
               

                let startPosition
                // 开始拖拽的位置
                let isPointerDowm=false

                function pointerDown(e) {
                    isPointerDowm=true
                }

                function pointerUp(){
                    isPointerDowm=false
                }

                function pointerMove(e){
                   if(!isPointerDowm)return 
                   let mx=e.event.movementX*xMove
                   let my=e.event.movementY*yMove
                   //这是最基础的调整,还需要调整,乘一个系数    
                    camera.position.addInPlace(new BABYLON.Vector3(-mx,0,my))
                }

                function pointerWhell(e){
                    const delta=e.event.wheelDelta
                    if(delta>0){
                        if(camera.position.y>=60)return
                        camera.position.y+=1
                        
                    }else{
                        if(camera.position.y<=20)return
                        camera.position.y-=1
                    }

                    console.log('当前相机高度',camera.position.y)
                    
                }


                scene.onPointerObservable.add((pointerInfo)=>{
                    switch(pointerInfo.type){
                        case BABYLON.PointerEventTypes.POINTERDOWN:
                            pointerDown(pointerInfo)
                            break
                        case BABYLON.PointerEventTypes.POINTERUP:
                            pointerUp(pointerInfo)
                            break
                        case BABYLON.PointerEventTypes.POINTERMOVE:
                            pointerMove(pointerInfo)
                            break
                        case BABYLON.PointerEventTypes.POINTERWHEEL:
                            pointerWhell(pointerInfo)
                            break;
                    }
                })
                let count=0
                scene.registerBeforeRender(()=>{
                    count++
                    if(count>=200){
                        // console.log('speed',camera.speed)
                        // console.log('seppd',camera.position)
                        count=0
                    }
                    

                })

                return scene
        }

    window.initFunction=async function(){
        let asyncEngineCreation=async function(){
            try{
                return createDefaultEngine()
            }catch(e){
                return createDefaultEngine()
            }
        }

        window.engine=await asyncEngineCreation()
        if(!engine){
            throw('engine should not be null')
        }
        window.scene=createScene()
        
    }

    initFunction().then(()=>{
        scene.then((returnedScene)=>{
            sceneToRender=returnedScene
        })

        let limit=60
        let count=0
        let fps=0
        engine.runRenderLoop(function(){
            count++
            if(count===limit){
                fps=Math.round(engine.getFps())
                count=0
                console.log('当前帧数是'+fps)
            }

            if(sceneToRender&&sceneToRender.activeCamera){
                sceneToRender.render()
            }
        })

    })

    window.addEventListener('resize',function(){
        engine.resize()
    })

</script>
</body>
</html>

问题1

1 .视角大小,换一种相机之后,展示的大小有问题啊
2 .默认的freeCamera的操作逻辑如下

1 .键盘,左右方向键左右移动相机,上下方向键前后移动
2 .鼠标,以相机为原点绕旋转轴旋转相机
3 .触摸-左右滑动相机左右移动,上下滑动前后移动

3 .我们想要的效果

1 .控制缩放,摄像机可以拉近和拉远,滚轮实现
2 .移动
3 .限制相机移动范围

4 .后续

1 .相机的移动需要有缓动,不能很僵硬的移动
2 .不同相机高度,移动的距离是不一样的。镜头远,滑动移动的距离大,镜头近,滑动移动的距离小
3 .要把屏幕上的移动距离和实际的体块的大小完全一致,比如屏幕上移动了10cm,地图里面的地块也应该移动了这么多
4 .按照原来的基本逻辑,按一下键盘,会移动5的距离,但是拖拽的时候就做不了这个
5 .这里改相机的speed没用,加了一个系数实现了想要的效果
6 .镜头的朝向,这个要改镜头的旋转
7 .他的渲染策略是以中心为基础渲染,现在是第三方镜头,会不会存在一些已经渲染的但是不在镜头中的呢

5 .是否需要更改相机类型为followCamera
6 .裸体一旦沦为艺术,那便是最圣洁的,道德一旦沦为虚伪,那便是最下流的

SLG地图的更新

1 .地形,地块的更新,拖动相机才会发生变化。这种是拖动相机自动完成的,这里面存储了物体的信息,这里只有拖动才会触发。
2 .不拖动相机也会发生的变化,部队,建筑物,本来是想建筑物

1 .有一些建筑物的信息不拖动也需要触发,比如建筑的升级,这种变化就需要单独触发地形更新,但是为了动态地形里面的一个建筑物,就让全部的update,感觉也有点不合理,这里要看下怎么操作
2 .部队那些不放在这里处理,需要一个专门更新mesh的函数现在的terrain.update,应该是全部会计算的

LOD不能添加

terrain.updateCameraLOD = function(terrainCamera) {
                        // LOD value increases with camera altitude
                        var camLOD = Math.abs((terrainCamera.globalPosition.y / 30.0)|0);
                        // 这里加了动态调节,就不用别的地方加了,但是这里好像不能使用这个,因为我开大镜头,精度是不能丢失的,现在会丢失地面建筑物的精度,这个只能保证拉大镜头,地面永远都在镜头内
                        return camLOD;
                    };
1 .slg里面的lod只是做一些镜头拉小,野怪消失不见,建筑物或者地形的渲染精度是不会变得

性能问题

1 .首次渲染出来,拖拽的时候会有卡顿


截屏2021-12-12 下午4.08.52.png

2 .为什么第一次pointerDown会有问题,第一次不是仅仅是添加了一个鼠标被按下的标志位么


截屏2021-12-12 下午4.13.50.png

TODO

1 .添加阴影
2 .添加各种地形
3 .添加各种建筑
4 .添加各种动画

最难的,里面的坐标也有点难搞

1 .

实践

1 .这里面好像不能添加复杂的粒子,添加一个引入的mesh很卡,但是单独的固体粒子系统里面是没问题的,而且,动态地形还需要做的是我还要管理mesh的动画和操作,这样越来越复杂的话,感觉还是要分开,那这样动态地形的优势就完全没了.还是需要管理单独的粒子系统

单独的粒子系统可以加入引入mesh的情况,选中也是可以的,播放动画呢

const scene = new BABYLON.Scene(engine);

    const camera = new BABYLON.ArcRotateCamera("ArcRotateCamera", -Math.PI / 2, Math.PI / 2.2, 50, new BABYLON.Vector3(0, 0, 0), scene);
    camera.attachControl(canvas, true);
    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);

    const SPS = new BABYLON.SolidParticleSystem("SPS", scene,{useModelMaterial:true});

    BABYLON.SceneLoader.ImportMeshAsync("", "scenes/", "skull.babylon", scene).then((result)=>{
        console.log(result)
        SPS.addShape(result.meshes[0],2)
        const mesh = SPS.buildMesh();

        SPS.initParticles = () => {
        for (let p = 0; p < SPS.nbParticles; p++) {
            const particle = SPS.particles[p];
            particle.position.x = BABYLON.Scalar.RandomRange(-50, 50);
            particle.position.y = BABYLON.Scalar.RandomRange(-50, 50);
            particle.position.z = BABYLON.Scalar.RandomRange(-50, 50);
          }
        };

        // //Update SPS mesh
        SPS.initParticles();
        SPS.setParticles();
    })
    return scene;

单独的粒子播放各自的动画

1 .这个就更不要想了
2 .现在是可以展示粒子了,但是很卡,视野内显示有1000个不行了,下一步就是要看是粒子的局限,还是这个动态系统的问题

截屏2021-12-15 上午12.18.56.png

3 .直接拿固体粒子发现是可以的,3000以下是无压力的,所以说地形就应该干地形应该做的事情,或者粒子不是那么多,现在可能许多点都是都是很密的,把密度往下压一压试试
4 .要看粒子的里面用这种mesh能不能播自己的动画
5 .粒子是否真的能干这个东西,还是要使用最经典的克隆https://playground.babylonjs.com/#TWQZAU#1或者这个是什么原理 瘦实例来实现的,粒子是完全有用的,所以还是瘦实例是对的.这里应该只是用来摆山,城之类不动的,玩家主城

解决方法

1 .减少密度,仅仅只是永远不变的物体,加入粒子,比如一些固定的城,山之类的
2 .类似土地之类的大面积的,压根就不让他展示渲染,换另一种逻辑算了,操作不了。尤其是带动画的这种,感觉更加不行。粒子,只能用来实现一些简单的,操作,这种算是复杂的是不行的.
3 .https://doc.babylonjs.com/divingDeeper/particles/particle_system/animation 这里看来是可以实现动画粒子的,但是。只是纹理之类的,也不算是真的mesh动作的那种吧,这里只是可以用来实现复杂的粒子效果而存在。比如火,烟之类的

减小密度,看是否卡顿

for (var l = 0; l < mapSubZ; l++) {
                        for (var w = 0; w < mapSubX; w++) {
                            var x = (w - mapSubX * 0.5) * 2.0;
                            var z = (l - mapSubZ * 0.5) * 2.0;
                            var y = noise.simplex2(x * noiseScale, z * noiseScale);               // altitude
                            y *= (0.5 + y) * y * elevationScale; 

                            mapData[3 *(l * mapSubX + w)] = x;
                            mapData[3 * (l * mapSubX + w) + 1] = y;
                            mapData[3 * (l * mapSubX + w) + 2] = z; 

                            let index=l*mapSubX+w
                            let random=Math.random()
                            if(random>0.99999){
                                // 如果满足这个条件,就添加一个物体
                                let xp=x
                                let yp=y
                                let zp=z

                                let ry=Math.random()*3.6

                                let sx=0.5+Math.random()
                                let sy=0.5+Math.random()
                                let sz=0.5+Math.random()

                                let type = index % 3;
                                SPmapData[index % 3].push(xp, yp, zp, 0, ry, 0, sx, sy, sz);
                                console.log(SPmapData,'现在的粒子数',random)
                            }
                            
                    }
                }  
//这样看起来完全不卡顿啊

虚拟地图的摄像机

1 .想做类似于MOBA的地图视角,但是发现地图生成的时候就是按照摄像机在正中间,如果简单的来偏转,就需要重新渲染更多的无用的出来
[图片上传失败...(image-d10f5-1639585054269)]
2 .最后结论,虚拟地形上的元素,只适合非常小的,不变的东西,比如草,没了,别的东西都不适合,虽然较少粒子数也支持一些复杂的东西,但是也不支持mesh的动画啥的,投一些宝箱啥的,但是一旦东西多了。粒子是会创建每个实体的,不能用来节省资源。所以还是要用瘦实例的方式来操作
3 .上面是很多的粒子实体,但是实际上同屏最多展示100块地。试下100块这些会不会卡
4 .如何查看当前画面有多少粒子呢?
5 .100188 '现在的粒子数'三倍的这个粒子数,都是可以跑满60帧的
6 .复杂的这些同屏显示500个就会卡顿,但是正常的slg游戏,一个屏幕最多显示100块地,那就是说这种方法还是可以用的

<!DOCTYPE html>
<!-- 添加小人,使用序列图 -->
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html" charset="utf-8"/>
    <title>Babylon - Getting Started</title>
    <!-- Link to the last version of BabylonJS -->
    <script src="https://preview.babylonjs.com/babylon.js"></script>
    <!-- Link to the last version of BabylonJS loaders to enable loading filetypes such as .gltf -->
    <script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
    <!-- Link to pep.js to ensure pointer events work consistently in all browsers -->
    <script src="https://code.jquery.com/pep/0.4.1/pep.js"></script>
    <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script> -->
    <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script> -->
    <!-- <script src="https://preview.babylonjs.com/gui/babylon.gui.min.js"></script> -->
    <!-- <script src="https://cdn.rawgit.com/BabylonJS/Extensions/master/DynamicTerrain/dist/babylon.dynamicTerrain.min.js"></script> -->
    <script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylonjs.proceduralTextures.min.js"></script>
    <script src="https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script>
    <script src="./dy.js"></script>
    <script src="./noise.js"></script>
    <!-- <script src="./tree.js"></script> -->
</head>
<style>
    html, body {
        overflow: hidden;
        width   : 100%;
        height  : 100%;
        margin  : 0;
        padding : 0;
    }

    #renderCanvas {
        width   : 100%;
        height  : 100%;
        touch-action: none;
    }
</style>
<body>
    <canvas id="renderCanvas" touch-action="none"></canvas>
    <script>
        const canvas = document.getElementById("renderCanvas");
        var engine = null;
        // 这里还不能用let,不然就爆炸,获取不到engine
        var scene = null;
        var sceneToRender = null;
        const createDefaultEngine = function() { return new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true,  disableWebGL2Support: false}); };

        let createScene=async function(){
            // 关键函数都写在这个里面

                var scene = new BABYLON.Scene(engine)
                var camera = new BABYLON.UniversalCamera("camera1", new BABYLON.Vector3(0, 20, 0), scene);    
                // 第二个坐标是摄像机在场景中的位置,现在还需要一个就是摄像机的朝向
                camera.rotation=new BABYLON.Vector3(0.8697057340138546,0.00772265625,0)
                camera.position.y=100
                // 高度最高现在定为60,最大值,按照这个最大值来调参数,地图大小也需要调,现在镜头外面还有没有显示的地

                
                let tree=await BABYLON.SceneLoader.ImportMeshAsync('','https://playground.babylonjs.com/scenes/Elf/','Elf.gltf',scene)
                console.log('tree',tree)
                var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0.0, 1.0, 0.0), scene);
                light.intensity = 0.75;
                light.specular = BABYLON.Color3.Black();

                // 这俩值是不是决定了地图的实际大小
                let terrainTexture=new BABYLON.Texture("https://www.babylonjs-playground.com/textures/ground.jpg",scene)

                let terrainMat=new BABYLON.StandardMaterial('tm',scene)
                terrainMat.diffuseTexture=terrainTexture
                terrainMat.diffuseTexture.UScale=1
                terrainMat.diffuseTexture.VScale=1

                var spsMaterial = new BABYLON.StandardMaterial("spsm", scene);
                var spsUrl = "https://jerome.bousquie.fr/BJS/images/uv_texture.jpg";
                var spsTexture = new BABYLON.Texture(spsUrl, scene);
                spsMaterial.diffuseTexture = spsTexture;
                
                var mapSubX = 1000;            
                var mapSubZ = 1000;              
                var seed = 0.3;                 
                var noiseScale = 0.03;         
                var elevationScale = 6.0;
                noise.seed(seed);
                var mapData = new Float32Array(mapSubX * mapSubZ * 3); 

                let SPmapData=[[],[],[]]
                // 这俩其实不是很常用,毕竟真的引入mesh这俩应该都有的吧,除非mesh仅仅是颜色不一样代表不同的物体,来做优化的时候
                var SPcolorData = [[], [],  []];
                var SPuvData = [[], [], []];

                // x位移系数:在原来的基础上移动的距离,或者说实际屏幕和地图的位移比例吧
                let xMove=0.033
                // 第一次0.5的值感觉还是有点大
                // y位移系数
                let yMove=0.053
                // 最后这个值感觉这里就差不多了,但是这个系数还是要和镜头高度有关系的

                for (var l = 0; l < mapSubZ; l++) {
                        for (var w = 0; w < mapSubX; w++) {
                            var x = (w - mapSubX * 0.5) * 2.0;
                            var z = (l - mapSubZ * 0.5) * 2.0;
                            var y = noise.simplex2(x * noiseScale, z * noiseScale);               // altitude
                            y *= (0.5 + y) * y * elevationScale; 

                            mapData[3 *(l * mapSubX + w)] = x;
                            mapData[3 * (l * mapSubX + w) + 1] = y;
                            mapData[3 * (l * mapSubX + w) + 2] = z; 

                            let index=l*mapSubX+w
                            let random=Math.random()
                            if(Math.random()>0.9){
                                // 如果满足这个条件,就添加一个物体
                                let xp=x
                                let yp=y
                                let zp=z

                                let ry=Math.random()*3.6

                                let sx=0.5+Math.random()
                                let sy=0.5+Math.random()
                                let sz=0.5+Math.random()

                                let type = index % 3;
                                SPmapData[type].push(xp, yp, zp, 0, ry, 0, sx, sy, sz);
                                // console.log(SPmapData,'现在的粒子数',random)
                            }
                            
                    }
                }   

                console.log(SPmapData[0].length/3,'现在的粒子数')
                let model2=BABYLON.MeshBuilder.CreatePolyhedron('m2',{size:0.5},scene)
                let model3=BABYLON.MeshBuilder.CreateSphere('m3',{segments:3},scene)

                let sps=new BABYLON.SolidParticleSystem("SPS",scene)
                let type1=sps.addShape(tree.meshes[1],100)
                // let type1=sps.addShape(model2,1000)
                let type2=sps.addShape(model2,100)
                // 为什么数量少的时候,镜头了将要到达的地方物体消失,正好相反了
                // 这里的数量是当前地形中最多可见的形状的数量,100的意思就是地图中最多可见100个model2模型
                let type3=sps.addShape(model3,100)

                sps.buildMesh()

                tree.meshes[1].dispose()
                model2.dispose()
                model3.dispose()
                // sps.mesh.material = spsMaterial;

                var terrainSub = 160;  
                // 这个才是镜头最大展示的数量
                var params = {
                    mapData: mapData,               
                    mapSubX: mapSubX,               
                    mapSubZ: mapSubZ,
                    SPmapData: SPmapData,           
                    sps: sps,                       
                    terrainSub: terrainSub,
                    subToleranceX:160,
                    subToleranceZ:160,
                    // 这个更新是指所有的静态元素更新,有的时候,可能不移动镜头都需要更新计算某些值,比如地块的变化。所以还需要主动触发地块更新
                }
                var terrain = new BABYLON.DynamicTerrain("t", params, scene);
                terrain.isAlwaysVisible=true
                var terrainMaterial = new BABYLON.StandardMaterial("tm", scene);
                terrainMaterial.diffuseTexture = terrainTexture;
                terrain.mesh.material = terrainMaterial;

                    // terrain.updateCameraLOD = function(terrainCamera) {
                    //     // LOD value increases with camera altitude
                    //     var camLOD = Math.abs((terrainCamera.globalPosition.y / 30.0)|0);
                    //     // 这里加了动态调节,就不用别的地方加了,但是这里好像不能使用这个,因为我开大镜头,精度是不能丢失的,现在会丢失地面建筑物的精度,这个只能保证拉大镜头,地面永远都在镜头内
                    //     return camLOD;
                    // };
                terrain.update(true);
               

                let startPosition
                // 开始拖拽的位置
                let isPointerDowm=false

                function pointerDown(e) {
                    isPointerDowm=true
                }

                function pointerUp(){
                    isPointerDowm=false
                }

                function pointerMove(e){
                   if(!isPointerDowm)return 
                   let mx=e.event.movementX*xMove
                   let my=e.event.movementY*yMove
                   //这是最基础的调整,还需要调整,乘一个系数    
                    camera.position.addInPlace(new BABYLON.Vector3(-mx,0,my))
                }

                function pointerWhell(e){
                    const delta=e.event.wheelDelta
                    if(delta>0){
                        // if(camera.position.y>=100)return
                        // camera.position.y+=1

                        // 用这里来调试下摄像机吧.现在的摄像机老是感觉很奇怪
                        camera.rotation.y+=0.1
                        // console.log(camera.rotation.x)
                        
                    }else{
                        // if(camera.position.y<=20)return
                        // camera.position.y-=1

                        camera.rotation.y-=0.1
                    }

                    // console.log('当前相机高度',camera.position.y)
                    console.log(camera.rotation)
                    
                }

                scene.onPointerObservable.add((pointerInfo)=>{
                    switch(pointerInfo.type){
                        case BABYLON.PointerEventTypes.POINTERDOWN:
                            pointerDown(pointerInfo)
                            break
                        case BABYLON.PointerEventTypes.POINTERUP:
                            pointerUp(pointerInfo)
                            break
                        case BABYLON.PointerEventTypes.POINTERMOVE:
                            pointerMove(pointerInfo)
                            break
                        case BABYLON.PointerEventTypes.POINTERWHEEL:
                            pointerWhell(pointerInfo)
                            break;
                    }
                })
                let count=0
                scene.registerBeforeRender(()=>{
                    count++
                    if(count>=200){
                        // console.log('speed',camera.speed)
                        // console.log('seppd',camera.position)
                        count=0
                    }
                    

                })

                return scene
        }

    window.initFunction=async function(){
        let asyncEngineCreation=async function(){
            try{
                return createDefaultEngine()
            }catch(e){
                return createDefaultEngine()
            }
        }

        window.engine=await asyncEngineCreation()
        if(!engine){
            throw('engine should not be null')
        }
        window.scene=createScene()
        
    }

    initFunction().then(()=>{
        scene.then((returnedScene)=>{
            sceneToRender=returnedScene
        })

        let limit=60
        let count=0
        let fps=0
        engine.runRenderLoop(function(){
            count++
            if(count===limit){
                fps=Math.round(engine.getFps())
                count=0
                console.log('当前帧数是'+fps)
            }

            if(sceneToRender&&sceneToRender.activeCamera){
                sceneToRender.render()
            }
        })

    })

    window.addEventListener('resize',function(){
        engine.resize()
    })

</script>
</body>
</html>

总结

1 .也就是说,最小的渲染粒度是100块地,现在可以保证这里是完全正确的
2 .总的地图数据我们可以一次全加载进来,甚至包括点的数据,但是同时最多显示的数据则是要严格要求的.这样就保证了不会卡顿.在虚拟地形里面,同时操作100块地都会卡顿,无法60帧

截屏2021-12-19 下午11.12.46.png

3 .粒子系统和这个分来来展示,单独这个显示是可以4040,就同一个展示来说,那么他们唯一的不同,就还没算,移动相机的时候需要新算的地块,因为就展示来说,5050都不会卡,关键是拖拽之后,还有新的数据需要显示.

1 .其实这里如果我们要做的就是每次移动完,拿到当前摄像机里面的所有的数据然后渲染出来.这样感觉的话,这种也是要崩的啊,其实有点像这种话操作,每次鼠标抬起的时候,就渲染新的数据,甚至move的时候也渲染
function pointerUp(e){
                    isPointerDowm=false

                    let xCount=20
                    let yCount=20

                    console.log(e)

                    // 循环添加树
                    for(let x=0;x<xCount;x++){
                        for(let y=0;y<yCount;y++){
                            let newTree=tree.meshes[0].clone(`tree-${x}-${y}`)
                            newTree.position.x=10*x+e.pickInfo.pickedPoint.x
                            newTree.position.z=10*y+e.pickInfo.pickedPoint.z
                        }
                    }
                }
//果然不行,40*40着这种情况根本拖动不了.而且还仅仅是up的时候不算
//进过测试,up这种操作,仅仅支持20*20这种量级,这里还可以优化,一次拖拽,并不需要全部生成新的,仅仅是需要diff,这还有需要操作的东西.也就是说,每次move,都要算出remove,add的数据,然后做删除,增加,先增加,然后删除.这个好像还很麻烦,但是虚拟列表那里已经自带了.还是想下那个行不行,如果move的时候性能最后和虚拟布局的性能一样,那还不如用那个来做,不过,这个还是最简单的clone的操作,可能从实现方面已经是最拉胯的方式了

move的时候

最关键的操作

1 .最关键的现在感觉是地图的摄像机高度,角度,这些决定了真的应该渲染的元素的数量,比如这样的图,这个高度,这个角度,决定了地图内显示的真正数量的数据,


截屏2021-12-20 上午12.22.11.png
var terrainSub = 50;  
                // 这个才是镜头最大展示的数量,这个才是关键,这个是50的话,一点也不卡
                var params = {
                    mapData: mapData,               
                    mapSubX: mapSubX,               
                    mapSubZ: mapSubZ,
                    SPmapData: SPmapData,           
                    sps: sps,                       
                    terrainSub: terrainSub,
                    subToleranceX:terrainSub,
                    subToleranceZ:terrainSub,
                    // 这个更新是指所有的静态元素更新,有的时候,可能不移动镜头都需要更新计算某些值,比如地块的变化。所以还需要主动触发地块更新
                }

3 .现在的镜头高度下,需要70才能完全盖住视野,所以最后渲染的就是70个宽度内的虚拟列表内的东西,一切都和摄像机高度有关系,但是65的时候发现帧数就已经不满60了,所以这个最后还是一个协调的东西,互相妥协,也就是高度,视野内渲染的数量,然后去决定到底使用那种方案
4 .

<!DOCTYPE html>
<!-- 添加小人,使用序列图 -->
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html" charset="utf-8"/>
    <title>Babylon - Getting Started</title>
    <!-- Link to the last version of BabylonJS -->
    <script src="https://preview.babylonjs.com/babylon.js"></script>
    <!-- Link to the last version of BabylonJS loaders to enable loading filetypes such as .gltf -->
    <script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
    <!-- Link to pep.js to ensure pointer events work consistently in all browsers -->
    <script src="https://code.jquery.com/pep/0.4.1/pep.js"></script>
    <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script> -->
    <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script> -->
    <!-- <script src="https://preview.babylonjs.com/gui/babylon.gui.min.js"></script> -->
    <!-- <script src="https://cdn.rawgit.com/BabylonJS/Extensions/master/DynamicTerrain/dist/babylon.dynamicTerrain.min.js"></script> -->
    <script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylonjs.proceduralTextures.min.js"></script>
    <script src="https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script>
    <script src="./dy.js"></script>
    <script src="./noise.js"></script>
    <!-- <script src="./tree.js"></script> -->
</head>
<style>
    html, body {
        overflow: hidden;
        width   : 100%;
        height  : 100%;
        margin  : 0;
        padding : 0;
    }

    #renderCanvas {
        width   : 100%;
        height  : 100%;
        touch-action: none;
    }
</style>
<body>
    <canvas id="renderCanvas" touch-action="none"></canvas>
    <script>
        const canvas = document.getElementById("renderCanvas");
        var engine = null;
        // 这里还不能用let,不然就爆炸,获取不到engine
        var scene = null;
        var sceneToRender = null;
        const createDefaultEngine = function() { return new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true,  disableWebGL2Support: false}); };

        let createScene=async function(){
            // 关键函数都写在这个里面

                var scene = new BABYLON.Scene(engine)
                var camera = new BABYLON.UniversalCamera("camera1", new BABYLON.Vector3(0, 20, 0), scene);    
                // 第二个坐标是摄像机在场景中的位置,现在还需要一个就是摄像机的朝向
                camera.rotation=new BABYLON.Vector3( 1.8697057340138552,0.00772265625,0)
                camera.position.y=70
                // 高度最高现在定为60,最大值,按照这个最大值来调参数,地图大小也需要调,现在镜头外面还有没有显示的地

                
                // let tree=await BABYLON.SceneLoader.ImportMeshAsync('','https://playground.babylonjs.com/scenes/Elf/','Elf.gltf',scene)
                let tree=await BABYLON.SceneLoader.ImportMeshAsync('',"http://192.168.1.102:8080/source/glb/","tree.babylon",scene)
                console.log('tree',tree.meshes[0].rotation)
                var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0.0, 1.0, 0.0), scene);
                light.intensity = 0.75;
                light.specular = BABYLON.Color3.Black();

                // 这俩值是不是决定了地图的实际大小
                let terrainTexture=new BABYLON.Texture("https://www.babylonjs-playground.com/textures/ground.jpg",scene)

                let terrainMat=new BABYLON.StandardMaterial('tm',scene)
                terrainMat.diffuseTexture=terrainTexture
                terrainMat.diffuseTexture.UScale=1
                terrainMat.diffuseTexture.VScale=1

                var spsMaterial = new BABYLON.StandardMaterial("spsm", scene);
                var spsUrl = "https://jerome.bousquie.fr/BJS/images/uv_texture.jpg";
                var spsTexture = new BABYLON.Texture(spsUrl, scene);
                spsMaterial.diffuseTexture = spsTexture;
                
                var mapSubX = 1000;            
                var mapSubZ = 1000;              
                var seed = 0.3;                 
                var noiseScale = 0.03;         
                var elevationScale = 6.0;
                noise.seed(seed);
                var mapData = new Float32Array(mapSubX * mapSubZ * 3); 

                let SPmapData=[[],[],[]]
                // 这俩其实不是很常用,毕竟真的引入mesh这俩应该都有的吧,除非mesh仅仅是颜色不一样代表不同的物体,来做优化的时候
                var SPcolorData = [[], [],  []];
                var SPuvData = [[], [], []];

                // x位移系数:在原来的基础上移动的距离,或者说实际屏幕和地图的位移比例吧
                let xMove=0.033
                // 第一次0.5的值感觉还是有点大
                // y位移系数
                let yMove=0.053
                // 最后这个值感觉这里就差不多了,但是这个系数还是要和镜头高度有关系的

                for (var l = 0; l < mapSubZ; l++) {
                        for (var w = 0; w < mapSubX; w++) {
                            var x = (w - mapSubX * 0.5) * 2.0;
                            var z = (l - mapSubZ * 0.5) * 2.0;
                            var y = noise.simplex2(x * noiseScale, z * noiseScale);               // altitude
                            y *= (0.5 + y) * y * elevationScale; 

                            mapData[3 *(l * mapSubX + w)] = x;
                            mapData[3 * (l * mapSubX + w) + 1] = y;
                            mapData[3 * (l * mapSubX + w) + 2] = z; 

                            let index=l*mapSubX+w
                            let random=Math.random()
                            if(Math.random()>0.9){
                                // 如果满足这个条件,就添加一个物体
                                let xp=x
                                let yp=y
                                let zp=z

                                let ry=Math.random()*3.6



                                let type = index % 3;
                                SPmapData[type].push(xp, yp, zp, -1.57, 0, 0, 10,10,10);
                                // console.log(SPmapData,'现在的粒子数',random)
                            } 
                    }
                }   

                console.log(SPmapData[0].length/3,'现在的粒子数')
                let model2=BABYLON.MeshBuilder.CreatePolyhedron('m2',{size:0.5},scene)
                let model3=BABYLON.MeshBuilder.CreateSphere('m3',{segments:3},scene)

                let sps=new BABYLON.SolidParticleSystem("SPS",scene,{useModelMaterial:true})
                let type1=sps.addShape(tree.meshes[0],1000)
                // let type1=sps.addShape(model2,1000)
                let type2=sps.addShape(model2,1)
                // 为什么数量少的时候,镜头了将要到达的地方物体消失,正好相反了
                // 这里的数量是当前地形中最多可见的形状的数量,100的意思就是地图中最多可见100个model2模型
                let type3=sps.addShape(model3,1)

                sps.buildMesh()

                // tree.meshes[0].dispose()
                model2.dispose()
                model3.dispose()
                // sps.mesh.material = spsMaterial;

                var terrainSub = 65;  
                // 这个才是镜头最大展示的数量
                var params = {
                    mapData: mapData,               
                    mapSubX: mapSubX,               
                    mapSubZ: mapSubZ,
                    SPmapData: SPmapData,           
                    sps: sps,                       
                    terrainSub: terrainSub,
                    subToleranceX:terrainSub,
                    subToleranceZ:terrainSub,
                    // 这个更新是指所有的静态元素更新,有的时候,可能不移动镜头都需要更新计算某些值,比如地块的变化。所以还需要主动触发地块更新
                }
                var terrain = new BABYLON.DynamicTerrain("t", params, scene);
                terrain.isAlwaysVisible=true
                var terrainMaterial = new BABYLON.StandardMaterial("tm", scene);
                terrainMaterial.diffuseTexture = terrainTexture;
                terrain.mesh.material = terrainMaterial;

                    // terrain.updateCameraLOD = function(terrainCamera) {
                    //     // LOD value increases with camera altitude
                    //     var camLOD = Math.abs((terrainCamera.globalPosition.y / 30.0)|0);
                    //     // 这里加了动态调节,就不用别的地方加了,但是这里好像不能使用这个,因为我开大镜头,精度是不能丢失的,现在会丢失地面建筑物的精度,这个只能保证拉大镜头,地面永远都在镜头内
                    //     return camLOD;
                    // };
                terrain.update(true);
               

                let startPosition
                // 开始拖拽的位置
                let isPointerDowm=false

                function pointerDown(e) {
                    isPointerDowm=true
                }

                function pointerUp(){
                    isPointerDowm=false
                }

                function pointerMove(e){
                   if(!isPointerDowm)return 
                   let mx=e.event.movementX*xMove
                   let my=e.event.movementY*yMove
                   //这是最基础的调整,还需要调整,乘一个系数    
                    camera.position.addInPlace(new BABYLON.Vector3(-mx,0,my))
                }

                function pointerWhell(e){
                    const delta=e.event.wheelDelta
                    if(delta>0){
                        if(camera.position.y>=100)return
                        camera.position.y+=1

                        // 用这里来调试下摄像机吧.现在的摄像机老是感觉很奇怪
                        // camera.rotation.x+=0.1
                        // console.log(camera.rotation.x)
                        
                    }else{
                        if(camera.position.y<=20)return
                        camera.position.y-=1

                        // camera.rotation.x-=0.1
                    }

                    // console.log('当前相机高度',camera.position.y)
                    console.log(camera.rotation)
                    
                }

                scene.onPointerObservable.add((pointerInfo)=>{
                    switch(pointerInfo.type){
                        case BABYLON.PointerEventTypes.POINTERDOWN:
                            pointerDown(pointerInfo)
                            break
                        case BABYLON.PointerEventTypes.POINTERUP:
                            pointerUp(pointerInfo)
                            break
                        case BABYLON.PointerEventTypes.POINTERMOVE:
                            pointerMove(pointerInfo)
                            break
                        case BABYLON.PointerEventTypes.POINTERWHEEL:
                            pointerWhell(pointerInfo)
                            break;
                    }
                })
                let count=0
                scene.registerBeforeRender(()=>{
                    count++
                    if(count>=200){
                        // console.log('speed',camera.speed)
                        // console.log('seppd',camera.position)
                        count=0
                    }
                    

                })

                return scene
        }

    window.initFunction=async function(){
        let asyncEngineCreation=async function(){
            try{
                return createDefaultEngine()
            }catch(e){
                return createDefaultEngine()
            }
        }

        window.engine=await asyncEngineCreation()
        if(!engine){
            throw('engine should not be null')
        }
        window.scene=createScene()
        
    }

    initFunction().then(()=>{
        scene.then((returnedScene)=>{
            sceneToRender=returnedScene
        })

        let limit=60
        let count=0
        let fps=0
        engine.runRenderLoop(function(){
            count++
            if(count===limit){
                fps=Math.round(engine.getFps())
                count=0
                console.log('当前帧数是'+fps)
            }

            if(sceneToRender&&sceneToRender.activeCamera){
                sceneToRender.render()
            }
        })

    })

    window.addEventListener('resize',function(){
        engine.resize()
    })

</script>
</body>
</html>

自己那个合成的mesh不行的,可以使用gltf导出器,把已经合并好的mesh,连带着材质,合成为一个mesh。然后到处成为一个gltf,不就可以了么,这样也不需要每次都用代码加载了.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容