当前位置:   article > 正文

threeJS 实现加载模型 + 页面按钮交互 + 显示css2Renderer标注_css2drenderer

css2drenderer
  1. 创建好HTML标注的提示框
  2. 创建three的场景等系列
  3. three加载GLTF模型
  4. 点击按钮选中模型 + 交互
  5. 示例(大概意思就是我点击那个按钮显示那个提示框,模型变材质 + 线)在这里插入图片描述
  6. 直接代码
<panel-group @handleSetLineChartData="handleSetLineChartData" />
        <div v-if="tagFlage == 'newVisitis'">
            <el-tag v-for="(item, index) of tagData" :key="index" :type="item.flag ? 'danger' : ''"
                @click="tagClick(item, index)">{{ item.name }}</el-tag>
        </div>

        <el-row style="background:#fff;padding:16px 16px 0; margin-bottom:32px;height: calc(100vh - 340px);">
            <canvas id="three"></canvas>
        </el-row>
        <div id="tag" style="display: none;"></div>
        <div :id="item.message" v-for="(item, index) of tagData" :key="index" style="
                visibility:hidden;
                width:230px;
                height:180px;
                position: absolute;
                color: #fff;
                z-index: 2;
                font-size: 16px;
                background: rgba(86, 183, 195, 0.47);
                box-shadow: rgba(86, 183, 195, 0.47) 0px 0px 15px 3px;"
            :style="{ left: item.x1 + 'px', top: item.y1 + 'px' }">
            <div style="position:relative;padding: 10px;">
                <div style="display: flex;">
                    <span>标识:</span>
                    <div id="granaryName" style="font-size:16px">平房仓 P_01</div>
                </div>

                <div style="display: flex;align-items: flex-end;margin-top: 5px;">
                    <span>温度:</span>
                    <span id="temperature">19</span></div>
                <div style="display: flex;align-items: flex-end;margin-top: 5px;">
                    <span>名字:</span>
                    <span id="weight">3600</span>
                </div>
                <div style="display: flex;align-items: flex-end;margin-top: 5px;">
                    <span>高度:</span>
                    <span id="granaryHeight">12</span>m
                </div>
                <div style="display: flex;align-items: flex-end;margin-top: 5px;">
                    <span>宽度:</span>
                    <span id="grainHeight">5</span> m
                </div>
            </div>
        </div>
<style lang="scss" scoped>
.dashboard-editor-container {
    padding: 32px;
    background-color: rgb(240, 242, 245);
    position: relative;

    .chart-wrapper {
        background: #fff;
        padding: 16px 16px 0;
        margin-bottom: 32px;
    }
}

@media (max-width:1024px) {
    .chart-wrapper {
        padding: 8px;
    }
}

#import-template {
    width: 100%;
    height: 100%;
}

#three {
    width: 100%;
    height: calc(591px - 32px);
}

.el-tag--medium {
    margin-right: 10px;
}
</style>
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import {
    CSS2DObject,
    CSS2DRenderer
} from 'three/examples/jsm/renderers/CSS2DRenderer.js';
 data() {
        return {
            tagFlage: null,
            scene: null, // 场景
            camera: null, // 照相机
            renderer: null, // 渲染器
            // mesh: null, // 网格
            // textureLoader: null, // 纹理加载器
            // mixer: null,
            // groupBox: null,
            // stats: null, // 性能监测
            // control: null, // 相机控件
            // scene2: null,
            // render3D: null,
            publicPath: process.env.BASE_URL,
            // clearAnim: null,
            // clock: null,
            publicPath: process.env.BASE_URL,
            granaryArr: [],
            chooseMesh: null,
            messageTags: [],
            tagData: [
                {
                    name: '场地',
                    message: 'HuaZhuang',
                    x: 10,
                    y: 10,
                    z: 15,
                    x1: -90,
                    y1: -60,
                    flag: false,
                },
                {
                    name: '建筑墙面',
                    message: 'JianZhu_ZhuTi',
                    x: 15,
                    y: 5,
                    z: 1,
                    x1: -50,
                    y1: 50,
                    flag: false,
                },
                {
                    name: '条幅',
                    message: 'TeXiao_010101',
                    x: 15,
                    y: 15,
                    z: -10,
                    x1: 80,
                    y1: -60,
                    flag: false,
                },
            ],
            idArr: ["granaryName", "temperature",
                "weight", "granaryHeight", "grainHeight"
            ],
            messageData: {
                HuaZhuang: {
                    granaryName: "数据一(HuaZhu)",
                    temperature: 30,
                    weight: 'RC100',
                    granaryHeight: 6,
                    grainHeight: 25.1
                },
                JianZhu_ZhuTi: {
                    granaryName: "数据二(JianZhu)",
                    temperature: 30,
                    weight: 'SP500',
                    granaryHeight: 66,
                    grainHeight: 23.8
                },
                TeXiao_010101: {
                    granaryName: "数据三(TeXiao)",
                    temperature: 30,
                    weight: 'DataAPI',
                    granaryHeight: 666,
                    grainHeight: 9.2
                }
            },
        }
    },

mounted() {
        this.initThree();
},
methods: {
	initThree() {
            let this_ = this
            this_.scene = new THREE.Scene();
            this_.scene.background = new THREE.Color(0xeeeeee);
            this_.scene.fog = new THREE.Fog(0xa0a0a0, 200, 1000);

            const canvas = document.querySelector("#three");
            this_.renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
            this_.renderer.shadowMap.enabled = true;

            this.camera = new THREE.PerspectiveCamera(
                40,
                window.innerWidth / window.innerHeight,
                1,
                1000
            );
            const controls = new OrbitControls(this.camera, this.renderer.domElement);

            const gltfLoader = new GLTFLoader();
            gltfLoader.load(`${this.publicPath}factory/XXX.gltf`, (gltf) => {
                let model = gltf.scene;
                model.scale.set(1, 1, 1);
                controlsBox(model);
                gltf.scene.traverse(function (object) {
                    if (object.type === 'Mesh') {
                        // 批量更改所有Mesh的材质
                        object.material = new THREE.MeshLambertMaterial({
                            map: object.material.map, //获取原来材质的颜色贴图属性值
                            color: object.material.color, //读取原来材质的颜色
                        })
                    }
                })
                gltf.scene.children.forEach((obj, index) => {
                    if (obj.type === 'Mesh') {
                        this_.granaryArr.push(obj);
                    }
                })

                this_.scene.add(model);
            }, undefined, (error) => console.error(error));
            
            // 将模型的中心点设置到canvas坐标系的中心点,保证模型显示是居中的
            function controlsBox(model) {
                let box = new THREE.Box3().setFromObject(model); // 获取模型的包围盒
                let mdlen = box.max.x - box.min.x; // 模型长度
                let mdwid = box.max.z - box.min.z; // 模型宽度
                let mdhei = box.max.y - box.min.y; // 模型高度
                let xPoiition = box.min.x + mdlen / 2; // 模型中心点坐标X
                let yPoiition = box.min.y + mdhei / 2; // 模型中心点坐标Y
                let zPoiition = box.min.z + mdwid / 2; // 模型中心点坐标Z
                // 获取模型整体对角线长度,这里获取模型模型对角线的目的是为了保证模型可以完全的展示在视线范围内
                let diagonal = Math.sqrt(Math.pow(Math.sqrt(Math.pow(mdlen, 2) + Math.pow(mdwid, 2)), 2) + Math.pow(mdhei, 2));
                // 假设我们需要的进入视角为45度
                // 设置相机位置,向上偏移,确定可以包裹整个模型
                controls.object.position.set(box.max.x + diagonal / 2, (diagonal * 2) / Math.tan(Math.PI / 180 * 45) + Math.abs(box.max.y), box.max.z + diagonal / 2);
                // 设置相机的视角方向,看向模型的中心点
                controls.target.set(xPoiition, yPoiition, zPoiition);
                controls.update(); // 更新相机
            }

            const hemLight = new THREE.HemisphereLight(0xffffff, 0xffffff);
            hemLight.position.set(0, 20, 0);
            this_.scene.add(hemLight);
            const dirLight = new THREE.DirectionalLight(0xffffff, 0.6);
            //光源等位置
            dirLight.position.set(0, 20, 10);
            //可以产生阴影
            dirLight.castShadow = true;
            dirLight.shadow.mapSize = new THREE.Vector2(1024, 1024);
            this_.scene.add(dirLight);
            // 添加地板
            let floorGeometry = new THREE.PlaneGeometry(8000, 3000);
            let floorMaterial = new THREE.MeshPhongMaterial({
              color: 0x5cb3cc,
              shininess: 0,
            });
            let floor = new THREE.Mesh(floorGeometry, floorMaterial);
            floor.rotation.x = -0.5 * Math.PI;
            floor.receiveShadow = true;
            floor.position.y = -0.001;
            // this_.scene.add(floor);

            controls.enableDamping = true;
            this.tagData.forEach((item) => {
                this.labelRenderer = new CSS2DRenderer();
                let three = document.querySelector('#three')
                this.labelRenderer.setSize(three.getBoundingClientRect().width,three.getBoundingClientRect().height);
                this.labelRenderer.domElement.style.position = 'absolute';
                this.labelRenderer.domElement.style.top = '320px';
                this.labelRenderer.domElement.style.left = '250px';
                this.labelRenderer.domElement.style.pointerEvents = 'none';
                document.body.appendChild(this.labelRenderer.domElement);
                var messageTag = this.tag(item.message); //创建粮仓标注的标签
                this.scene.add(messageTag);
                this.messageTags.push(messageTag)
            })
            function animate() {
                controls.update();
                this_.renderer.render(this_.scene, this_.camera);
                this_.labelRenderer.render(this_.scene, this_.camera); //渲染HTML标签对象
                requestAnimationFrame(animate);

                if (resizeRendererToDisplaySize(this_.renderer)) {
                    const canvas = this_.renderer.domElement;
                    this_.camera.aspect = canvas.clientWidth / canvas.clientHeight;
                    this_.camera.updateProjectionMatrix();
                }
            }
            animate();
            function resizeRendererToDisplaySize(renderer) {
                const canvas = renderer.domElement;
                var width = window.innerWidth;
                var height = window.innerHeight;
                var canvasPixelWidth = canvas.width / window.devicePixelRatio;
                var canvasPixelHeight = canvas.height / window.devicePixelRatio;
                const needResize = canvasPixelWidth !== width || canvasPixelHeight !== height;
                if (needResize) {
                    renderer.setSize(width, height, false);
                }
                return needResize;
            }
        },

}
tagClick(item, index) {
            let this_ = this
            var messageTag = this.tag(item.message);
            let visibilitys = messageTag.element.style.visibility
            let chooseMeshData = null
            chooseMeshData = this_.choose(event, messageTag, 2, item.message); //执行射线拾取的代码
            // 选中不同粮仓,HTML标签信息跟着改变
            if (chooseMeshData) {
                //批量更新粮仓chooseMesh的标签信息
                this_.idArr.forEach(function (id) {
                    var dom = document.querySelector(`#${item.message} #${id}`)
                    dom.innerHTML = this_.messageData[chooseMeshData.name][id];
                })
                this.messageTags.forEach(function (messageTag) {
                    // 判断是否为当前需要更新的弹窗对象
                    if (document.querySelector(`#${item.message}`).id == messageTag.element.id) { // 替判断当前弹窗对象的条件
                        if (visibilitys == 'visible') {
                            messageTag.element.style.visibility = 'hidden';
                            this_.tagData[index].flag = false;
                        } else {
                            messageTag.element.style.visibility = 'visible';
                            this_.tagData[index].flag = true;
                        }
                        

                        // 创建线条材质
                        var lineMaterial = new THREE.LineBasicMaterial({ color: 0xff0000, linewidth: 100 });
                        // 获取点击模型和弹窗的位置坐标
                        var modelPosition = chooseMeshData.position.clone();
                        var newPositions = new THREE.Vector3().copy(messageTag.position); // 创建新的Vector3对象并复制点击对象的坐标
                        messageTag.position.copy(newPositions);
                        // var popupPosition = messageTag.position.clone();
                        var popupPosition = messageTag.position.copy({
                            x: item.x,
                            y: item.y,
                            z: item.z
                        });
                        // 创建线条的顶点数组
                        var positions = [];
                        positions.push(modelPosition.x, modelPosition.y, modelPosition.z);
                        positions.push(popupPosition.x, popupPosition.y, popupPosition.z);
                        // 创建线条的属性数组
                        var geometry = new THREE.BufferGeometry();
                        geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
                        // 创建线条对象
                        var line = new THREE.LineSegments(geometry, lineMaterial);
                        // 添加线条到场景中
                        line.name = item.message
                        this_.scene.add(line);
                        // 获取场景中的所有线段  
                        var allLineSegments = [];
                        this_.scene.traverse(function (child) {
                            if (child instanceof THREE.LineSegments) {
                                allLineSegments.push(child);
                            }
                        });
                        // 打印所有线段  
                        if (visibilitys == 'visible') {
                            allLineSegments.forEach((node) => {
                                if (node.name == item.message) {
                                    this_.scene.remove(node);
                                }
                            })
                        }
                    }
                });

            }
        },
        tag(domID) {
            var dom = document.getElementById(domID);
            var label = new CSS2DObject(dom);
            label.name = domID
            dom.style.pointerEvents = 'none';
            return label;
        },
        choose(event, messageTag, type, name) {
            let chooseMesh = null
            if (type == 1) {
            //这个逻辑里用不上,正常是点击模型里的 后来改成点击按钮了
                if (this.chooseMesh) {
                    this.chooseMesh.material.color.set(0xffffff); // 把上次选中的mesh设置为原来的颜色
                }
                var Sx = event.clientX;
                var Sy = event.clientY;
                let mainCanvas = document.querySelector('canvas')
                var x = ((event.clientX - mainCanvas.getBoundingClientRect().left) / mainCanvas.offsetWidth) * 2 - 1;
                var y = -((event.clientY - mainCanvas.getBoundingClientRect().top) / mainCanvas.offsetHeight) * 2 + 1;

                var raycaster = new THREE.Raycaster();
                raycaster.setFromCamera(new THREE.Vector2(x, y), this.camera);

                var intersects = raycaster.intersectObjects(this.granaryArr);
                console.log("射线器返回的对象", intersects);
                if (intersects.length > 0) {
                    this.chooseMesh = intersects[0].object;
                    this.chooseMesh.material.color.set(0x00ffff); //选中改变颜色,这样材质颜色贴图.map和color颜色会相乘
                    this.chooseMesh.point = intersects[0].point;
                } else {
                    this.chooseMesh = null;
                }
            } else {
                this.granaryArr.forEach((item) => {
                    if (item.name == name) {
                        console.log(item, 'd点击后的item', item.material.color.getHex())
                        chooseMesh = item;
                        if (item.material.color.getHex() == 16777215) {
                            chooseMesh.material.color.set(0xff0000); //选中改变颜色,这样材质颜色贴图.map和color颜色会相乘
                        } else {
                            chooseMesh.material.color.set(0xffffff); // 把上次选中的mesh设置为原来的颜色
                        }
                        chooseMesh.point = item.position;
                        // chooseMesh.point = item.point;
                        // this.chooseMesh.point = {
                        //     x: item.x,
                        //     y: item.y,
                        //     z: item.z
                        // }
                    }
                })
            }
            return chooseMesh

        },
        handleSetLineChartData(type) {
            console.log(type)
            this.tagFlage = type
        },


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425

以上就是完整的,
弹窗的位置,和线的结束点是我写死的,因为我不知道创建的弹窗的位置该怎么设置
新手three 有大佬的话 可以给些建议

后续补充了一点新的 弹窗位置和线条的起始结束点
给我的当前弹窗点击拿到后设置
var popupPosition = messageTag.position.copy({
x: item.x,
y: item.y,
z: item.z
});
这组就是线条的结束点 push到一个存起点和结束点的数组里然后放到THREE.Float32BufferAttribute

数据的坐标点基本都这样的
{
name: ‘XXX’,
message: ‘XXX’,
x: -2,
y: 0.8,
z: 1,
x1: -43,
y1: -45,
flag: false,
},

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/135224
推荐阅读
相关标签
  

闽ICP备14008679号