//导入模型库// 旋转中心点// 创建场景// 创建相机// 创建渲染器//设置背景颜色// 创建立方体//创建文字const texts = ['左', '右', '上', '下', '前', '后'];_threejs 模型交互">
当前位置:   article > 正文

ThreeJS简单交互,_threejs 模型交互

threejs 模型交互

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

前言

一、入门学习中文网threejs文件包下载和目录简介 | Three.js中文网 (webgl3d.cn)

二、实现步骤

1.粗略封装的ThreeJs

2.实例化一个场景

3.导入模型时屏蔽了OrbitControls的旋转功(controls.enableRotate = false)所以重构了一下模型旋转

鼠标点下时记录起始位置

把canvas的坐标转换成webgl的坐标,简单讲一下,页面上的event拿到的xy跟webgl的坐标不太一样,webgl的坐标原点是画布canvas宽高的一半就是webgl的原点

鼠标移动事件 拿到的deltaRotationQuaternion相当于旋转角度,cube是左上角的组件,调用子组件函数设置camera旋转后的位置

鼠标松开

4、最后贴一下子组件cube立方体场景的代码

总结


前言

 

原本想用div做一个正方体实现立方体于模型互动,但是html的xy轴于webgl的xy轴不一样,在考虑自身水平有限的情况下,最后还是决定用两个场景去实现一个交互效果,有bug功能也没完善。


一、入门学习中文网threejs文件包下载和目录简介 | Three.js中文网 (webgl3d.cn)

webgl是前端不一样的赛道

二、实现步骤

1.粗略封装的ThreeJs

代码如下(示例):

// 引入three.js

import * as THREE from 'three';

// 引入扩展库OrbitControls.js

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

//导入模型库

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

//交互插件

import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

import { STLLoader } from 'three/examples/jsm/loaders/STLLoader';

import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";

import { FontLoader } from "three/examples/jsm/loaders/FontLoader"

import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';

import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';

import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';

import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';

// ShaderPass功能:使用后处理Shader创建后处理通道

import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';

import { Line2 } from 'three/examples/jsm/lines/Line2'

import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry'

import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'

import text from "three/examples/fonts/helvetiker_bold.typeface.json"

let that;

function init(x, y, z, bgColor, width, heigth, ref) {

    this.x = x;

    this.y = y;

    this.z = z;

    this.width = width;

    this.height = heigth;

    this.bgColor = bgColor;

    this.distance = 0;

    this.container = ref

    this.startPoint = 0;

    this.endPoint = 0;

    this.points = []

    this.line = null

    this.target = new THREE.Vector3(0, 0, 0); // 旋转中心点

    this.isDragging = false;

    this.previousMousePosition = { x: 0, y: 0 };

    const fontLoader = new FontLoader();

    this.font = fontLoader.parse(text)

    this.scene = new THREE.Scene();// 创建3D场景对象Scene

    // 执行一个渲染函数

    this.renderer = new THREE.WebGLRenderer();

    // AxesHelper:辅助观察的坐标系

    this.axesHelper = new THREE.AxesHelper(200);

    this.scene.add(this.axesHelper);

    this.s = 5;

    this.k = (this.width / this.height)

    this.reCamera()

    //测量起始点

    this.measureStart = [];

    this.measureEnd = [];

    this.isStart = true

    //垂直向量

    this.perpendicularVector = { x: 0, y: 0, z: 0 }

    //网格

    // const gridHelper = new THREE.GridHelper(300, 25, 0x004444, "#fff");

    // this.scene.add(gridHelper)

    //环境光

    this.ambient = new THREE.AmbientLight(0xffffff, 1);

    this.scene.add(this.ambient)

    // 聚光源

    // 0xffffff:光源颜色

    // 1.0:光照强度intensity

    const spotLight = new THREE.SpotLight(0xffffff, 1.0);

    spotLight.intensity = 2.0;//光照强度

    // 设置聚光光源发散角度

    spotLight.angle = Math.PI / 2;//光锥角度的二分之一

    // 设置聚光光源位置

    spotLight.position.set(0, 0, -200);

    this.scene.add(spotLight);//光源添加到场景中

    //点光源:两个参数分别表示光源颜色和光照强度

    // 参数1:0xffffff是纯白光,表示光源颜色

    // 参数2:1.0,表示光照强度,可以根据需要调整

    this.pointLight = new THREE.PointLight(0xffffff, 10);

    // //点光源位置

    this.pointLight.position.set(400, 0, 0);//点光源放在x轴上

    this.scene.add(this.pointLight); //点光源添加到场景中

    // 光源辅助观察

    this.pointLightHelper = new THREE.PointLightHelper(this.pointLight, 10);

    this.scene.add(this.pointLightHelper);

    this.raycaster = new THREE.Raycaster();

    this.mouse = new THREE.Vector2();

    //半球光

    const light = new THREE.HemisphereLight(0xB1E1FF, 0xB97A20, 1);

    this.scene.add(light)

    // 平行光

    this.directionalLight = new THREE.DirectionalLight(0xffffff, 2);

    this.directionalLight.position.set(200, 200, 200);

    this.scene.add(this.directionalLight);

    this.animate = this.animate.bind(this); // 添加这一行

    this.animate()

    this.clock = new THREE.Clock();

    that = this

}

init.prototype.AddModel = function (path) {

    return new Promise((resole, reject) => {

        const loader = new GLTFLoader();

        loader.load(path, gltf => {

            gltf.scene.traverse(child => {

                if (child.isMesh) {

                    // child是网格模型对象Mesh

                    this.mesh = child

                    console.log(child)

                    // 计算模型的边界框

                    child.geometry.computeBoundingBox();

                    let box = child.geometry.boundingBox;

                    this.box = box

                    // 计算模型的中心点

                    var center = box.getCenter(new THREE.Vector3());

                    // 将模型移动到辅助坐标中心

                    child.geometry.translate(-center.x, -center.y, -center.z);

                }

            })

            let { scene } = gltf

            // this.mesh = scene

            scene.scale.set(1, 1, 1)

            this.scene.add(scene)

            this.renderer.setSize(this.width, this.height);

            this.renderer.setClearColor(this.bgColor, 1); //设置背景颜色

            // this.renderer.render(this.scene, this.camera);

            resole(this.renderer)

        }, undefined, function (error) {

            reject(error)

        });

    })

}

init.prototype.loader = function (path, boxId) {

    this.AddModel(path).then(res => {

        document.getElementById(boxId).appendChild(this.renderer.domElement)

        this.controls = new OrbitControls(this.camera, this.renderer.domElement);

        this.controls.enableRotate = false

        this.controls.addEventListener('change', this.iniTrenderer);//监听鼠标、键盘事件

    }).catch(err => console.log(err))

}

init.prototype.setGuiObj = function (obj) {

    this.guiObj = obj

}

init.prototype.setDirPosition = function () {

    this.directionalLight.position.set(this.x, this.y, this.z)

    this.iniTrenderer()

}

init.prototype.iniTrenderer = function () {

    that.renderer.render(that.scene, that.camera);

}

init.prototype.getGui = function () {

    this.gui = new GUI();

    this.guiObj = {

        color: "red"

    }

    // .addColor()生成颜色值改变的交互界面

    this.gui.addColor(this.guiObj, 'color').onChange(value => {

        this.mesh.material.color.set(value);

        this.iniTrenderer()

    });

    // gui.addColor(obj, 'specular').onChange(function (value) {

    //     material.specular.set(value);

    // });

    // 环境光强度

    if (this.ambient) {

        this.gui.add(this.ambient, 'intensity', 0, 2).name("环境光强度").onChange(value => {

            this.ambient.intensity = value

            this.iniTrenderer()

        });

    }

    if (this.directionalLight) {

        // 平行光强度

        this.gui.add(this.directionalLight, 'intensity', 0, 2).name("平行光强度").onChange(value => {

            this.directionalLight.intensity = value

            this.iniTrenderer()

        });

        // 平行光位置

        this.gui.add(this.directionalLight.position, 'x', -400, 400).name("平行光位置x").onChange(value => {

            this.x = value

            this.setDirPosition()

        });

        this.gui.add(this.directionalLight.position, 'y', -400, 400).name("平行光位置y").onChange(value => {

            this.y = value

            this.setDirPosition()

        });

        this.gui.add(this.directionalLight.position, 'z', -400, 400).name("平行光位置z").onChange(value => {

            this.z = value

            this.setDirPosition()

        });

    }

}

init.prototype.setCanvas = function (width, height) {

    this.width = width;

    this.height = height;

    this.renderer.setSize(this.width, this.height)

}

init.prototype.setStlModel = function (path, boxId) {

    // 创建STL加载器

    const loader = new STLLoader();

    // 加载STL模型

    loader.load(path, (geometry) => {

        // 计算模型的边界框

        geometry.computeBoundingBox();

        let box = geometry.boundingBox;

        this.box = box

        // 计算模型的中心点

        var center = box.getCenter(new THREE.Vector3());

        // 将模型移动到辅助坐标中心

        geometry.translate(-center.x, -center.y, -center.z);

        this.geometry = geometry

        const textureLoader = new THREE.TextureLoader();

        const material = new THREE.MeshPhongMaterial({

            map: textureLoader.load('/maps/textures/rust_coarse_01_disp_4k.png'),

        });

        // const material = new THREE.MeshStandardMaterial({

        //     color: 0xffffff,  // 设置颜色

        //     metalness: 1,     // 设置金属度

        //     roughness: 0.5,   // 设置粗糙度 (0: 光滑, 1: 粗糙)

        // });

        this.mesh = new THREE.Mesh(this.geometry, material);

        this.scene.add(this.mesh);

        this.renderer.setSize(this.width, this.height);

        this.s = 600

        this.renderer.setClearColor(this.bgColor, 1); //设置背景颜色

        this.reCamera()

        document.getElementById(boxId).appendChild(this.renderer.domElement)

        this.controls = new OrbitControls(this.camera, this.renderer.domElement);

        this.controls.enableRotate = false

        this.controls.addEventListener('change', () => {

            this.renderer.render(this.scene, this.camera);

        });//监听鼠标、键盘事件

    });

}

init.prototype.reCamera = function () {

    // 实例化一个透视投影相机对象

    this.camera = new THREE.OrthographicCamera(-this.s * this.k, this.s * this.k, this.s, -this.s, 1, 2000);

    //相机在Three.js三维坐标系中的位置

    // 根据需要设置相机位置具体值

    this.camera.position.set(0, 0, 200);

}

init.prototype.setWgcl = function () {

    // 生成测量网格

    var edges = new THREE.EdgesGeometry(this.geometry);

    var lineMaterial = new THREE.LineBasicMaterial({ color: 0xff0000 });

    var wireframe = new THREE.LineSegments(edges, lineMaterial);

    this.scene.add(wireframe);

}

init.prototype.addObjModle = function (path, boxId) {

    var loader = new OBJLoader();

    loader.load(path, geometry => {

        this.scene.add(geometry);

        setTimeout(() => {

            this.renderer.render(this.scene, this.camera);

        }, 0)

        document.getElementById(boxId).appendChild(this.renderer.domElement)

    });

}

init.prototype.animate = function () {

    requestAnimationFrame(this.animate);

    this.renderer.render(this.scene, this.camera);

}

init.prototype.cancelAnmation = function () {

    cancelAnimationFrame(this.req)

}

init.prototype.setContainer = function (ref) {

    this.container = ref

}


 

init.prototype.getMousePosition = function (event) {

    const container = this.container;

    const rect = container.getBoundingClientRect();

    const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;

    const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;

    const vector = new THREE.Vector3(x, y, 0.5);

    vector.unproject(this.camera);

    const dir = vector.sub(this.camera.position).normalize();

    const distance = -this.camera.position.z / dir.z;

    return this.camera.position.clone().add(dir.multiplyScalar(distance));

}

init.prototype.renderMeasureTool = function () {

    if (this.startPoint && this.endPoint) {

        const geometry = new THREE.BufferGeometry();

        const vertices = [

            this.startPoint.x, this.startPoint.y, this.startPoint.z,

            this.endPoint.x, this.endPoint.y, this.endPoint.z,

            this.startPoint.x, this.startPoint.y, this.startPoint.z

        ];

        geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));

        geometry.setIndex([0, 1, 2]);

        // 创建三角形的材质

        const material = new THREE.LineBasicMaterial({ color: 0xff0000 });

        // 创建三角形的网格

        const triangle = new THREE.LineSegments(geometry, material);

        this.scene.add(triangle);

        // 创建距离标记的几何体

        const textGeometry = new TextGeometry(

            `Distance: ${this.distance.toFixed(2)}`,

            {

                // font: new THREE.Font(),

                size: 0.1,

                height: 0.01,

            }

        );

        // 创建距离标记的材质

        const textMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });

        // 创建距离标记的网格

        const textMesh = new THREE.Mesh(textGeometry, textMaterial);

        textMesh.position.copy(this.endPoint);

        textMesh.position.z += 0.1;

        this.scene.add(textMesh);

        this.iniTrenderer()

    }

}

init.prototype.displaning = function () {

    this.scene.remove(this.mesh)

    // 创建剖切平面

    const clippingPlanes = [

        new THREE.Plane(new THREE.Vector3(1, 0, 0), 0), // x轴方向的剖切平面

        new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), // y轴方向的剖切平面

        new THREE.Plane(new THREE.Vector3(0, 0, 1), 0), // z轴方向的剖切平面

    ];

    this.mesh.material.clippingPlanes = clippingPlanes;

    this.mesh.material.clipIntersection = true;

    console.log(this.mesh)

    this.scene.add(this.mesh)

    this.iniTrenderer()

}

init.prototype.Faxx = function () {

    // 设置设备像素比,避免canvas画布输出模糊

    this.renderer.setPixelRatio(window.devicePixelRatio);

    const composer = new EffectComposer(this.renderer);

    const renderPass = new RenderPass(this.scene, this.camera);

    composer.addPass(renderPass);

    const FXAAPass = new ShaderPass(FXAAShader);

    // `.getPixelRatio()`获取`renderer.setPixelRatio()`设置的值

    const pixelRatio = this.renderer.getPixelRatio();//获取设备像素比

    // width、height是canva画布的宽高度

    FXAAPass.uniforms.resolution.value.x = 1 / (this.width * pixelRatio);

    FXAAPass.uniforms.resolution.value.y = 1 / (this.height * pixelRatio);

    composer.addPass(FXAAPass);

}

init.prototype.setBoxHelper = function () {

    // 计算模型的长宽高

    const boundingBox = new THREE.Box3().setFromObject(this.mesh);

    const size = new THREE.Vector3();

    boundingBox.getSize(size);

    console.log(size)

    const length = size.x;

    const width = size.y;

    const height = size.z;

    // 计算模型中心点

    var center = new THREE.Vector3();

    boundingBox.getCenter(center);

    console.log(center)

    const vertices = [

        new THREE.Vector3(center.x - length / 2, center.y - width / 2, center.z - height / 2),

        new THREE.Vector3(center.x + length / 2, center.y - width / 2, center.z - height / 2),

        new THREE.Vector3(center.x + length / 2, center.y - width / 2, center.z + height / 2),

        new THREE.Vector3(center.x - length / 2, center.y - width / 2, center.z + height / 2),

        new THREE.Vector3(center.x - length / 2, center.y - width / 2, center.z - height / 2),

        new THREE.Vector3(center.x - length / 2, center.y - width / 2, center.z + height / 2),


 

        new THREE.Vector3(center.x - length / 2, center.y + width / 2, center.z - height / 2),

        new THREE.Vector3(center.x + length / 2, center.y + width / 2, center.z - height / 2),

        new THREE.Vector3(center.x - length / 2, center.y - width / 2, center.z - height / 2),

        new THREE.Vector3(center.x - length / 2, center.y + width / 2, center.z - height / 2),


 

        new THREE.Vector3(center.x + length / 2, center.y + width / 2, center.z + height / 2),

        new THREE.Vector3(center.x - length / 2, center.y + width / 2, center.z + height / 2),

        new THREE.Vector3(center.x - length / 2, center.y + width / 2, center.z - height / 2),

        new THREE.Vector3(center.x - length / 2, center.y + width / 2, center.z + height / 2),


 

        new THREE.Vector3(center.x - length / 2, center.y + width / 2, center.z + height / 2),

        new THREE.Vector3(center.x - length / 2, center.y - width / 2, center.z + height / 2),

        new THREE.Vector3(center.x + length / 2, center.y - width / 2, center.z - height / 2),

        new THREE.Vector3(center.x + length / 2, center.y + width / 2, center.z - height / 2),

        new THREE.Vector3(center.x + length / 2, center.y + width / 2, center.z - height / 2),

        new THREE.Vector3(center.x + length / 2, center.y + width / 2, center.z + height / 2),

        new THREE.Vector3(center.x + length / 2, center.y + width / 2, center.z + height / 2),

        new THREE.Vector3(center.x + length / 2, center.y - width / 2, center.z + height / 2),

        new THREE.Vector3(center.x + length / 2, center.y - width / 2, center.z - height / 2),

        new THREE.Vector3(center.x + length / 2, center.y - width / 2, center.z + height / 2),

    ];

    console.log(vertices, "vertices")

    // 创建线段和标签

    const lineMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00 });

    const lineGeometry = new THREE.BufferGeometry().setFromPoints(vertices);

    const lineSegments = new THREE.LineSegments(lineGeometry, lineMaterial);

    this.scene.add(lineSegments);

    let arr = []

    for (let i = 0; i < vertices.length; i++) {

        if (i % 2 == 0) {

            const start = vertices[i];

            const end = vertices[i + 1];

            const segmentLength = start.distanceTo(end);

            let falg = deWeight(arr, segmentLength)

            // if (!falg) {

            //     continue;

            // } else {

            //     arr.push(segmentLength)

            // }

            // 创建长度标签

            const labelMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });

            const labelGeometry = new TextGeometry(` ${segmentLength.toFixed(2)}`, {

                font: this.font, // 字体文件

                size: 20, // 字体大小

                height: 1, // 字体厚度

            });

            const labelMesh = new THREE.Mesh(labelGeometry, labelMaterial);

            // 设置标签位置为线段的中点

            const midpoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);

            labelMesh.position.copy(midpoint);

            this.scene.add(labelMesh);

        }

    }

    this.iniTrenderer()

}

init.prototype.setModleLine = function () {

    // 获取模型的顶点数据

    const vertices = [];

    console.log(this.mesh)

    const positionAttribute = this.mesh.geometry.getAttribute('position');

    for (let i = 0; i < positionAttribute.count; i++) {

        const vertex = new THREE.Vector3();

        vertex.fromBufferAttribute(positionAttribute, i);

        vertices.push(vertex);

    }

    // 创建线段并贴合模型

    const lineMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00 });

    const lineGeometry = new THREE.BufferGeometry().setFromPoints(vertices);

    const lineSegments = new THREE.LineSegments(lineGeometry, lineMaterial);

    this.scene.add(lineSegments);

    for (let i = 0; i < vertices.length; i++) {

        if (i % 2 == 0) {

            const start = vertices[i];

            const end = vertices[i + 1];

            const segmentLength = start.distanceTo(end);

            // if (!falg) {

            //     continue;

            // } else {

            //     arr.push(segmentLength)

            // }

            // 创建长度标签

            const labelMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });

            const labelGeometry = new TextGeometry(` ${segmentLength.toFixed(2)}`, {

                font: this.font, // 字体文件

                size: 6, // 字体大小

                height: 1, // 字体厚度

            });

            const labelMesh = new THREE.Mesh(labelGeometry, labelMaterial);

            // 设置标签位置为线段的中点

            const midpoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);

            labelMesh.position.copy(midpoint);

            this.scene.add(labelMesh);

        }

    }

}

init.prototype.setSpin = function (deltaRotationQuaternion) {

    this.camera.position.sub(this.target); // 将相机位置减去旋转中心点

    this.camera.position.applyQuaternion(deltaRotationQuaternion); // 应用旋转

    this.camera.position.add(this.target); // 将旋转后的相机位置加上旋转中心点

    this.camera.lookAt(this.target); // 相机朝向旋转中心点

}

init.prototype.recordMouse = function (event) {

    // 计算鼠标位置的归一化设备坐标

    this.mouse.x = (event.clientX / this.width) * 2 - 1;

    this.mouse.y = -(event.clientY / this.height) * 2 + 1;

    if (!this.isStart) {

        // 通过鼠标位置和相机创建射线

        this.raycaster.setFromCamera(this.mouse, this.camera);

        // 计算射线与模型的交叉情况

        var intersects = this.raycaster.intersectObject(this.mesh);

        if (intersects.length > 0) {

            if (this.recorLine) {

                this.scene.remove(this.recorLine)

            }

            // // 创建两个向量表示两个点的坐标

            // var point1 = new THREE.Vector3(this.measureStart.x, this.measureStart.y, this.measureStart.z); // 第一个点的坐标

            // var point2 = new THREE.Vector3(this.measureEnd.x, this.measureEnd.y, this.measureEnd.z); // 第二个点的坐标

            // // 计算两个向量的差向量

            // var differenceVector = point2.clone().sub(point1);

            // // 计算垂直于差向量的向量(通过向量的叉乘操作)

            // var perpendicularVector = new THREE.Vector3(-differenceVector.y, differenceVector.x, 0);

            // 打印结果

            this.measureEnd = intersects[0].point

            const vertices = [...this.measureStart, ...this.measureEnd]

            var geometry = new LineGeometry()

            geometry.setPositions(vertices)

            var material = new LineMaterial({

                color: 0x00ff00,

                linewidth: 6

            })

            material.resolution.set(window.innerWidth, window.innerHeight)

            this.recorLine = new Line2(geometry, material)

            this.recorLine.computeLineDistances()

            this.scene.add(this.recorLine)

            const start = this.measureStart;

            const end = this.measureEnd;

            const segmentLength = start.distanceTo(end);

            const labelMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });

            const labelGeometry = new TextGeometry(` ${segmentLength.toFixed(2)}`, {

                font: this.font, // 字体文件

                size: 20, // 字体大小

                height: 1, // 字体厚度

            });

            if (this.measureLabel) {

                this.scene.remove(this.measureLabel)

            }

            this.measureLabel = new THREE.Mesh(labelGeometry, labelMaterial);

            // 设置标签位置为线段的中点

            const midpoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);

            this.measureLabel.position.copy(midpoint);

            this.scene.add(this.measureLabel);

        }

    } else {

        this.measureClick = false

    }

}


 

init.prototype.recordClick = function () {

    // 通过鼠标位置和相机创建射线

    this.raycaster.setFromCamera(this.mouse, this.camera);

    // 计算射线与模型的交叉情况

    var intersects = this.raycaster.intersectObject(this.mesh);

    if (intersects.length > 0) {

        if (this.isStart) {

            this.measureStart = intersects[0].point

        }

        this.isStart = !this.isStart

        // 鼠标点击点在模型内部

        console.log('模型内部');

        if (!this.isStart) {

            return true

        } else {

            return false

        }

    } else {

        // 鼠标点击点在模型外部

        console.log('模型外部!');

    }

}

//鼠标高亮

init.prototype.highlight = function () {

    this.renderer.domElement.addEventListener("mousedown", (event) => {

        this.mouse.x = (event.clientX / this.width) * 2 - 1;

        this.mouse.y = -(event.clientY / this.height) * 2 + 1;

        this.raycaster.setFromCamera(this.mouse, this.camera);

        var intersects = this.raycaster.intersectObject(this.mesh);

        if (intersects.length > 0) {

            var intersectedObject = intersects[0].object;

            var intersectedFaceIndex = intersects[0].faceIndex;

            // 获取相交面的几何体

            var geometry = intersectedObject.geometry;

            if (geometry instanceof THREE.BufferGeometry) {

                // 创建一个新的颜色属性数组

                var colors = new Float32Array(geometry.attributes.position.count * 3);

                // for (let i = 0; i <= colors.length; i += 3) {

                //     colors[i] = 1;

                //     colors[i + 1] = 1;

                //     colors[i + 2] = 0;

                // }

                // 将点击的面的颜色设置为红色

                colors[500 * 3] = 0;

                colors[500 * 3 + 1] = 1;

                colors[500 * 3 + 2] = 0;

                console.log(colors)

                // 将新的颜色属性数组设置回几何体

                geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

                var material = new THREE.MeshBasicMaterial({ vertexColors: THREE.VertexColors });

                // 创建网格并将材质应用到几何体

                this.mesh = new THREE.Mesh(geometry, material);

                // 创建一个基于顶点颜色的材质

            }

        } else {

            console.log("没点中")

        }

    })

}


 

function deWeight(arr, event) {

    let falg = true

    arr.forEach(item => {

        if (event == item) {

            falg = false

        }

    })

    return falg

}


 

export default init

2.实例化一个场景

代码如下:

three = new initThree(50, 100, 50, "#e7e9e9", 800, 800)

three.setContainer(containerBox.value)

three.loader("/modle/scene.gltf", "test2")

3.导入模型时屏蔽了OrbitControls的旋转功(controls.enableRotate = false)所以重构了一下模型旋转

three.renderer.domElement.addEventListener('mousemove', onMouseMove);

three.renderer.domElement.addEventListener('mousedown', onMouseDown);

three.renderer.domElement.addEventListener('mouseup', onMouseUp);

鼠标点下时记录起始位置

const onMouseDown = (event) => {

    three.previousMousePosition = getMousePosInWebgl(event)//

};

把canvas的坐标转换成webgl的坐标,简单讲一下,页面上的event拿到的xy跟webgl的坐标不太一样,webgl的坐标原点是画布canvas宽高的一半就是webgl的原点

function getMousePosInWebgl({ clientX, clientY }) {

    //鼠标在画布中的css位置

    const { left, top, width, height } = three.renderer.domElement.getBoundingClientRect();

    const [cssX, cssY] = [clientX - left, clientY - top];

    //解决坐标原点位置的差异

    const [halfWidth, halfHeight] = [width / 2, height / 2];

    const [xBaseCenter, yBaseCenter] = [

        cssX - halfWidth,

        cssY - halfHeight,

    ];

    // 解决y 方向的差异

    const yBaseCenterTop = -yBaseCenter;

    //解决坐标基底的差异

    return {

        x: xBaseCenter / halfWidth,

        y: yBaseCenterTop / halfHeight

    }

}

鼠标移动事件 拿到的deltaRotationQuaternion相当于旋转角度,cube是左上角的组件,调用子组件函数设置camera旋转后的位置

const onMouseMove = (event) => {

if (three.isDragging) {

            const deltaRotationQuaternion = text(event)

            three.camera.position.sub(three.target); // 将相机位置减去旋转中心点

            three.camera.position.applyQuaternion(deltaRotationQuaternion); // 应用旋转

            three.camera.position.add(three.target); // 将旋转后的相机位置加上旋转中心点

            three.camera.lookAt(three.target); // 相机朝向旋转中心点

            cube.value.setCamera(deltaRotationQuaternion)

        }

}

const text = function (event) {

    const { x, y } = getMousePosInWebgl(event)

    const deltaMove = {

        x: x - three.previousMousePosition.x,

        y: y - three.previousMousePosition.y

    };

    // const deltaMove = {

    //     x: event.clientX - three.previousMousePosition.x,

    //     y: event.clientY - three.previousMousePosition.y

    // };

    const deltaRotationQuaternion = new THREE.Quaternion().setFromEuler(

        new THREE.Euler(

            toRadians(deltaMove.y * 1.5),

            toRadians(deltaMove.x * 1.5),

            0,

            'XYZ'

        )

    );

    for (let key in deltaRotationQuaternion) {

        // deltaRotationQuaternion[key] *= 1.01

    }

    return deltaRotationQuaternion

}

鼠标松开

const onMouseUp = (event) => {

   three.isDragging = false;

};

4、最后贴一下子组件cube立方体场景的代码

<template>

    <div id="container" ref="container" style="

    width: 200px;

    height: 200px;

    margin-left: 0px;

"></div>

</template>

<script setup>

import { ref, onMounted } from 'vue';

import * as THREE from 'three';

//导入模型库

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

import text from "three/examples/fonts/helvetiker_bold.typeface.json"

import { FontLoader } from "three/examples/jsm/loaders/FontLoader"

import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const container = ref(null);

let scene, camera, renderer, cube, model, font;

const props = defineProps(["width", "heigth", "bgcColor",])

const emit = defineEmits(["spin"])


 

let isDragging = false;

let previousMousePosition = { x: 0, y: 0 };

const target = new THREE.Vector3(0, 0, 0); // 旋转中心点

let controls;

// 创建场景

scene = new THREE.Scene();

let fontLoader = new FontLoader();

font = fontLoader.parse(text)

// 创建相机

camera = new THREE.OrthographicCamera(-2 * 1, 2 * 1, 2, -2, 1, 2000);

camera.position.set(0, 0, 2);

// 创建渲染器

renderer = new THREE.WebGLRenderer({ antialias: true });

renderer.setSize(200, 200);

renderer.setClearColor(props.bgcColor, 1); //设置背景颜色

const axesHelper = new THREE.AxesHelper(200);

scene.add(axesHelper);

// 创建立方体

const geometry = new THREE.BoxGeometry(1, 1, 1);

//创建文字

const materials = [];

const texts = ['左', '右', '上', '下', '前', '后'];

for (let i = 0; i < 6; i++) {

    const canvas = document.createElement('canvas');

    const context = canvas.getContext('2d');

    canvas.width = 256;

    canvas.height = 256;

    context.fillStyle = 'white';

    context.fillRect(0, 0, canvas.width, canvas.height);

    context.font = 'Bold 48px Arial';

    context.fillStyle = 'black';

    context.textAlign = 'center';

    context.textBaseline = 'middle';

    context.fillText(texts[i], canvas.width / 2, canvas.height / 2);

    const texture = new THREE.CanvasTexture(canvas);

    const material = new THREE.MeshBasicMaterial({ map: texture });

    materials.push(material);

}

// 将材质应用到立方体的各个面上

cube = new THREE.Mesh(geometry, materials);

scene.add(cube);

// 鼠标拖动事件

const onMouseMove = (event, flag) => {

    const { x, y } = getMousePosInWebgl(event)

    const deltaMove = {

        x: x - previousMousePosition.x,

        y: y - previousMousePosition.y

    };

    // const deltaMove = {

    //     x: event.clientX - previousMousePosition.x,

    //     y: event.clientY - previousMousePosition.y

    // };

    if (isDragging || flag) {

        const deltaRotationQuaternion = new THREE.Quaternion().setFromEuler(

            new THREE.Euler(

                toRadians(deltaMove.y * 1.5),

                toRadians(deltaMove.x * 1.5),

                0,

                'XYZ'

            )

        );

        camera.position.sub(target); // 将相机位置减去旋转中心点

        camera.position.applyQuaternion(deltaRotationQuaternion); // 应用旋转

        camera.position.add(target); // 将旋转后的相机位置加上旋转中心点

        camera.lookAt(target); // 相机朝向旋转中心点

        // cube.quaternion.multiplyQuaternions(deltaRotationQuaternion, cube.quaternion);

        emit("spin", deltaRotationQuaternion, isDragging)

    }

};

const onMouseDown = (event) => {

    event.stopPropagation();

    isDragging = true;

    console.log(event)

    previousMousePosition = getMousePosInWebgl(event);

    // previousMousePosition = {

    //     x: event.clientX,

    //     y: event.clientY

    // };

};

function getMousePosInWebgl({ clientX, clientY }) {

    //鼠标在画布中的css位置

    const { left, top, width, height } = renderer.domElement.getBoundingClientRect();

    const [cssX, cssY] = [clientX - left, clientY - top];

    //解决坐标原点位置的差异

    const [halfWidth, halfHeight] = [width / 2, height / 2];

    const [xBaseCenter, yBaseCenter] = [

        cssX - halfWidth,

        cssY - halfHeight,

    ];

    // 解决y 方向的差异

    const yBaseCenterTop = -yBaseCenter;

    //解决坐标基底的差异

    return {

        x: xBaseCenter / halfWidth,

        y: yBaseCenterTop / halfHeight

    }

}


 

const onMouseUp = () => {

    isDragging = false;

};

const setCamera = (deltaRotationQuaternion) => {

    camera.position.sub(target); // 将相机位置减去旋转中心点

    camera.position.applyQuaternion(deltaRotationQuaternion); // 应用旋转

    camera.position.add(target); // 将旋转后的相机位置加上旋转中心点

    camera.lookAt(target); // 相机朝向旋转中心点

}

// 输出组件的方法,让外部组件可以调用

defineExpose({

    setCamera,

})



 

onMounted(() => {

    container.value.appendChild(renderer.domElement);

    renderer.domElement.addEventListener('mousemove', onMouseMove);

    renderer.domElement.addEventListener('mousedown', onMouseDown);

    renderer.domElement.addEventListener('mouseup', onMouseUp);

    // 动画循环

    const animate = () => {

        requestAnimationFrame(animate);

        renderer.render(scene, camera);

    };

    animate();

});

// 辅助函数,将角度转换为弧度

const toRadians = (angle) => {

    return angle * (Math.PI / 180);

};

</script>

<style>

#container {

    width: 100px;

    height: 100px;

}

</style>


总结

重复代码有点多,cube场景是可以new出来的,这旋转起始是摄像机围绕模型中心点转,不是模型本身的自旋转

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

闽ICP备14008679号