day3-4 three.js学习笔记

1.天空盒

一个包含 3D 世界的正方体,一共六面,像一盒子一样把我们的场景包裹在内

创建一个数组,用来存储天空盒中正方体每一面的贴图材质。然后使用纹理加载器 Texture Loader 加载图像。最后,我们将创建一个 boxGeometry 几何体,并将其与我们之前创建的数组一起用于创建一个立方体。需要将材质的 side 属性设置为 THREE.backside 

渲染天空盒的关键

scene.add(skybox);
      animate();
      function animate() {
        renderer.render(scene, camera);
        requestAnimationFrame(animate);
      }

2.OrbitControls 轨道控制摄像机

之前我们的相机是无法自己控制的,单使用这个之后则可以,

const controls = new OrbitControls(
  (object: Camera),
  (domElement: HTMLDOMElement)
);

实例:

const controls = new THREE.OrbitControls(camera); // 实例化一个 OrbitControls 对象
controls.addEventListener("change", renderer);
controls.minDistance = 100; // 设置控制的最小距离
controls.maxDistance = 3000; // 设置控制的最大距离

同时要记得引入orbit_controls.js文件,这个控件并不包括在three.js内 

3.TextureLoader 纹理加载器

实例:

let materialArray = [];
const texture_ft = new THREE.TextureLoader().load("./images/afterrain_ft.jpg");
const texture_bk = new THREE.TextureLoader().load("./images/afterrain_bk.jpg");
const texture_up = new THREE.TextureLoader().load("./images/afterrain_up.jpg");
const texture_dn = new THREE.TextureLoader().load("./images/afterrain_dn.jpg");
const texture_rt = new THREE.TextureLoader().load("./images/afterrain_rt.jpg");
const texture_lf = new THREE.TextureLoader().load("./images/afterrain_lf.jpg");

materialArray.push(new THREE.MeshBasicMaterial({ map: texture_ft }));
materialArray.push(new THREE.MeshBasicMaterial({ map: texture_bk }));
materialArray.push(new THREE.MeshBasicMaterial({ map: texture_up }));
materialArray.push(new THREE.MeshBasicMaterial({ map: texture_dn }));
materialArray.push(new THREE.MeshBasicMaterial({ map: texture_rt }));
materialArray.push(new THREE.MeshBasicMaterial({ map: texture_lf }));

for (let i = 0; i < 6; i++) materialArray[i].side = THREE.BackSide;

let skyboxGeo = new THREE.BoxGeometry(10000, 10000, 10000);
let skybox = new THREE.Mesh(skyboxGeo, materialArray);
scene.add(skybox);

4.geoJson 

geoJson 是与地理信息数据相关的 JSON 数据格式,它是基于 Javascript 对象表示法的地理空间信息数据交换格式,在three.js中,可以通过如下加载:

let loader = new THREE.FileLoader();
loader.load("getJSON.json", function (data) {
  // todos
});

实例:

// 加载地图geoJson数据
loadMapGeoJson() {
  let that = this;
  let loader = new THREE.FileLoader();
  loader.load('*****', function (data) {
      let jsonData = JSON.parse(data);
      that.initMap(jsonData);
  })
}

5.墨卡托投影

假设地球被放置在空的圆柱里,其基准纬线(即:赤道线)和圆柱相切。然后,我们再想象地球中心有一盏射灯,把地球表面的图形(即:一个个国家、地区)投影到圆柱面上,当沿着地球中心旋转投影 360 度之后,圆柱的表面就会被投影上一块块连续的图形。接着,把圆柱面展开,展开后就能看到一幅以基准纬线为参照绘制出的地图。”等角“ 的用途就是按等角的方式将经纬网投影到圆柱面上,将圆柱面展开后,就在地图上形成了经纬线。 

实例:地图就是一个 Object ,所以生成地图之前还是需要定义一个 Object3D 对象。接着,由于需要对地理空间信息数据的经纬度进行转换,我们还需要进行墨卡托投影,这里我们使用 d3 的 d3-geo 库实现。其次,每个部分都是一个 3D 模型对象,我们最终生成的地图就是由这些部分的 3D 图形组合而成。

// 初始化地图
initMap(geoJson) {
  let that = this;
  that.map = new THREE.Object3D();
  // 墨卡托投影变换
  const projection = d3.geoMercator().center([140.0, 37.5]).scale(80).translate([0, 0]);
  geoJson.features.forEach(item => {
    const province = new THREE.Object3D();
    // 每个对象的坐标数组
    const coordinates = item.geometry.coordinates;
    // 循环坐标数组
    coordinates.forEach(multiPolygon => {
        multiPolygon.forEach(polygon => {
            const shape = new THREE.Shape();
            // 定义线的材质
            const lineMaterial = new THREE.LineBasicMaterial({color: '#28b4e1'});
            // 定义线几何体
            const lineGeometry = new THREE.Geometry();

            for (let i = 0; i < polygon.length; i++) {
              // 将经纬度转为坐标值
                const [x, y] = projection(polygon[i]);
                if (i === 0) {
                    shape.moveTo(x, -y);
                }
                shape.lineTo(x, -y); // 使用这些坐标值合成路径THREE.Shape
                lineGeometry.vertices.push(new THREE.Vector3(x, -y, 4.01));
            }

            const extrudeSettings = {
                depth: 4,
                bevelEnabled: false,

            }
            // 定义一个 ExtrudeGeometry 挤压几何体,生成一个有深度的地图形状
            const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
            // 分别定义两种材质
            const material1 = new THREE.MeshBasicMaterial({color: '#206199', transparent: true, opacity: 0.6});
            const material2 = new THREE.MeshBasicMaterial({color: '#28b4e1', transparent: true, opacity: 0.5});
            // 定义网格
            const mesh = new THREE.Mesh(geometry, [material1, material2]);
            // 定义地图的边界线条
            const line = new THREE.Line(lineGeometry, lineMaterial);
            province.add(mesh);
            province.add(line);
        })
    })
    province.properties = item.properties;
    if (item.properties.contorid) {
        const [x, y] = projection(item.properties.contorid);
        province.properties._centroid = [x, y];
    }
    that.map.add(province);
  })
  that.scene.add(that.map);
}

 鼠标悬浮的效果还需要学一学

首先,需要监听鼠标的 MouseMove 事件,其次,需要计算鼠标的 x 与 y 值,用于控制显示部分的名称信息框。那要如何让鼠标移动到部分的区块上面时能够拿到名称数据?THREE 提供的 Raycaster (光射线投射),可以获取鼠标经过哪个物体上。使用 mousemove 监听鼠标的位置,然后在动画函数中计算经过哪些物体。

实例:

setMouseMove() {
  let that = this;
  that.raycaster = new THREE.Raycaster(); // 光射线投射
  that.mouse = new THREE.Vector2();
  that.eventOffset = {};

  function onMouseMove(event) {
    that.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    that.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
    that.eventOffset.x = event.clientX;
    that.eventOffset.y = event.clientY;
    infoBox.style.left = that.eventOffset.x + 2 + 'px'; // 动态设置提示框的位置
    infoBox.style.top = that.eventOffset.y + 2 + 'px';  // 动态设置提示框的位置

  }
    window.addEventListener('mousemove', onMouseMove, false);
}

animate() {
  requestAnimationFrame(this.animate.bind(this));
  this.raycaster.setFromCamera(this.mouse, this.camera); // 用新的原点和方向来更新射线
  // 计算与拾取射线相交的对象
  let intersects = this.raycaster.intersectObjects(this.scene.children, true); // 判断指定对象有没有被这束光线击中,返回被击中对象的信息,相交的结果会以一个数组的形式返回,其中的元素依照距离排序,越近的排在越前
  if (this.activeInstersect && this.activeInstersect.length > 0) { // 将上一次选中的恢复颜色
      this.activeInstersect.forEach(item => {
          item.object.material[0].color.set('#206199');
          item.object.material[1].color.set('#28b4e1');
      });
  }
  this.activeInstersect = []; // 设置为空
  for (let i = 0; i < intersects.length; i++) {
      if (intersects[i].object.material && intersects[i].object.material.length === 2) {
          this.activeInstersect.push(intersects[i]);
          intersects[i].object.material[0].color.set(0xffe500);
          intersects[i].object.material[1].color.set(0xffe500);
          break; // 只取第一个
      }
  }
  this.createProvinceInfo();
  this.renderer.render(this.scene, this.camera);
}
createProvinceInfo() {
  if (this.activeInstersect.length !== 0 && this.activeInstersect[0].object.parent.properties.name) {
    let properties = this.activeInstersect[0].object.parent.properties;
    // 设置信息框的名称内容
    this.infoBox.textContent = properties.name;
    // 显示信息框
    this.infoBox.style.visibility = 'visible';
  } else {
    // 隐藏信息框
    this.infoBox.style.visibility = 'hidden';
  }
}

6.纹理

实例:

const earthMaterial = new THREE.MeshPhongMaterial({
  map: new THREE.ImageUtils.loadTexture(
    "*******"
  ),
  color: 0xaaaaaa,
  specular: 0x333333,
  shininess: 25,
});

让物体自身产生旋转

const render = function () {
  earth.rotation.y -= 0.0005;

  renderer.render(scene, camera);
  requestAnimationFrame(render);
};
render();

天空盒的另一种实现方式:用一个半径比现在大得多的实体覆盖现在的实体

//星际
const starGeometry = new THREE.SphereGeometry(1000, 50, 50);
const starMaterial = new THREE.MeshPhongMaterial({
  map: new THREE.ImageUtils.loadTexture(
    "*************"
  ),
  side: THREE.DoubleSide,
  shininess: 0,
});
const starField = new THREE.Mesh(starGeometry, starMaterial);
scene.add(starField);

动画其实就是让物体动起来,让摄像机旋转起来,这点在我们上面的代码中也有显现 

 

 

 

 

你可能感兴趣的:(学习,笔记,three.js)