首页 > 其他分享 >【Three.js 分子/晶体结构解析教程】

【Three.js 分子/晶体结构解析教程】

时间:2024-07-07 15:01:53浏览次数:15  
标签:教程 const atomGroup THREE Three let atom new js

Three.js 分子/晶体结构解析教程

环境准备

目前使用的Three.js版本是0.165.0

npm i three

最终效果

结构化学式Al4As14
在这里插入图片描述

思路解析

绘制分子/晶体结构首先将这结构拆解来看,目前显示的效果整个结构三个部分
1、晶格 - 白色线框
2、原子 - 圆形
3、建 - 两个圆锥体

1、基本配置

首先需要配置基本场景、相机、渲染器

import * as THREE from 'three'
// 场景
this.scene = new THREE.Scene();
// 正交相机
this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 1, 10000000);
this.camera.position.set(0, 0, 5.5);
// 渲染器
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize(width, height);
this.renderer.setClearColor('#383838', 1);
dom.appendChild(this.renderer.domElement);
//配置环境光
this.ambientLight(this.scene)
 // 开始渲染
const animate = () => {
   requestAnimationFrame(animate);
   this.controls.update();
   // 渲染主场景
   this.renderer.clear();
   this.renderer.render(this.scene, this.camera);
   // 渲染坐标轴场景
   this.renderer.autoClear = false; // 不清除颜色缓存
   this.renderer.clearDepth(); // 清除深度缓存
   // 设置坐标轴的渲染区域
   this.renderer.setViewport(0, 0, width / 4, height / 4);
   this.renderer.render(this.Helper.axesScene, this.Helper.axesCamera);
   // 复原视口
   this.renderer.setViewport(0, 0, width, height);
   this.renderer.autoClear = true; // 恢复 autoClear
 }
 animate()

环境光函数,在这里采用了两种光

ambientLight(scene){
	// 环境光
	let AmbientLight =  new THREE.AmbientLight('#fff',2)
	scene.add(AmbientLight)
	// 创建平行光
	const DirectionalLight = new THREE.DirectionalLight(0xffffff, 1); // 颜色为白色,强度为 1
	// 设置光源位置
	DirectionalLight.position.set(1, 1, 1).normalize(); // 光源位置为 (1, 1, 1),并进行标准化处理
	// 将光源添加到场景中
	scene.add(DirectionalLight);
}

2、解析结构

在这里需要准备一个json文件,需要原子的坐标,晶格矢量,建索引
lattice - 标识晶格矢量
atom - 原子信息 l 原子名称全大写,xyz 坐标信息
band - 表示需要连健原子索引如果[2,4],表示下表2和下表4的原子进行连健

{
	"lattice": [
		[5.6,0,0],
		[0,5.6,0],
		[0,0,5.6],
		[0,0,0]
	],
	"atom": [
	{
      "l": "AL",
      "x": 0,
      "y": 0,
      "z": 0,
      "id": 0
    },
    {
      "l": "AL",
      "x": 0,
      "y": 2.8,
      "z": 2.8,
      "id": 1
    },
    {
      "l": "AL",
      "x": 2.8,
      "y": 0,
      "z": 2.8,
      "id": 2
    },
    {
      "l": "AL",
      "x": 2.8,
      "y": 2.8,
      "z": 0,
      "id": 3
    },
    {
      "l": "AS",
      "x": 0.25,
      "y": 0.25,
      "z": 0.25,
      "id": 4
    },
	],
	"band": [
	    [2,4]
   	]
}

3、进行解析

引入准备好的json文件

	import data from 'data.json'

讲个绘制的过程分成了3步骤
1、原子的绘制
2、连健
3、晶格绘制

1)原子部分
 // 原子
  drawAtom(atoms){
    const atomGroup = new THREE.Group();
    atomGroup.name = 'atom'
    const ballConstruction = (color,name) => {
      let radius = 0.3; // 球体的半径
      let widthSegments = 32; // 水平方向的分段数
      let heightSegments = 16; // 垂直方向的分段数
      let geometry = new THREE.SphereGeometry(radius, widthSegments, heightSegments);
      let newColor = new THREE.Color();
      newColor.setStyle(color);
      let material = new THREE.MeshStandardMaterial ({
        color: newColor, // 设置球体颜色
        roughness: 0, // 设置粗糙度
        metalness: 0, // 设置金属度
      });
      let sphere = new THREE.Mesh(geometry, material);
      sphere.name = name
      return sphere
    }
    atoms.forEach(atom => {
      const initAtom = ballConstruction(atomColor[atom.l.toLowerCase()],atom.l)
      initAtom.position.set(atom.x,atom.y,atom.z)
      atomGroup.add(initAtom);
    })
    return atomGroup
  }
2)连健部分

连健需要的是2个原子的索引,因为两个原子的种类可能不同需要绘制两个圆柱。

// 连键
  drawBand(atom,band){
    const bandGroup = new THREE.Group();
    bandGroup.name = 'band'
    const bandConstruction = (point1,point2,color) => {
      // 计算两个点之间的距离
      const distance = point1.distanceTo(point2);
      // 计算圆柱体的高度和位置
      const height = distance; // 圆柱体的高度为两点之间的距离
      const position = point1.clone().add(point2).divideScalar(2); // 圆柱体的位置为两点之间的中点
      // 计算圆柱体的方向向量
      const direction = point2.clone().sub(point1).normalize();
      // 创建圆柱体的参数
      const radiusTop = 0.1; // 顶部半径
      const radiusBottom = 0.1; // 底部半径
      const radialSegments = 32; // 周向分段数
      const heightSegments = 1; // 高度分段数
      const openEnded = false; // 是否封闭
      // 创建圆柱体的几何体
      const geometry = new THREE.CylinderGeometry(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded);
      // 创建圆柱体的材质
      let newColor = new THREE.Color();
      newColor.setStyle(color);
      const material = new THREE.MeshStandardMaterial({
        color: newColor, // 设置球体颜色
        roughness: 0, // 设置粗糙度
        metalness: 0, // 设置金属度
      });
      // 创建圆柱体的网格对象
      const cylinder = new THREE.Mesh(geometry, material);
      // 设置圆柱体的位置和方向
      cylinder.position.copy(position);
      cylinder.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
      return cylinder
    }
    band.forEach(keys => {
      let start = atom[keys[0]]
      let end = atom[keys[1]]
      // 两个原子的坐标
      const point1 = new THREE.Vector3(start.x, start.y, start.z); // 替换 x1、y1 和 z1 为第一个点的坐标
      const point2 = new THREE.Vector3(end.x, end.y, end.z); // 替换 x2、y2 和 z2 为第二个点的坐标
      // 计算两个原子的中间点坐标
      const midpoint = new THREE.Vector3().addVectors(point1, point2).multiplyScalar(0.5);
      // 需要添加两个圆柱
      // 原子1到中间
      const band1 = bandConstruction(point1,midpoint,atomColor[atom[keys[0]].l.toLowerCase()])
      band1.name = `band_left_${atom[keys[0]].l}_${atom[keys[0]].id}`
      // 原子2到中间
      const band2 = bandConstruction(midpoint,point2,atomColor[atom[keys[1]].l.toLowerCase()])
      band1.name = `band_right_${atom[keys[1]].l}_${atom[keys[1]].id}`
      bandGroup.add(band1)
      bandGroup.add(band2)
      console.log(midpoint);
    })
    return bandGroup
  }
3)晶格部分

晶格需要根据json文件的适量去计算所有顶点的坐标

// 晶格
  drawLattice(vectors) {
    const atomGroup = new THREE.Group();
    atomGroup.name = 'lattice'
    const [a1, a2, a3, origin] = vectors;
    const points = [];
    // 遍历所有可能的 n1, n2, n3 组合
    for (let n1 = 0; n1 <= 1; n1++) {
      for (let n2 = 0; n2 <= 1; n2++) {
        for (let n3 = 0; n3 <= 1; n3++) {
          const x = n1 * a1[0] + n2 * a2[0] + n3 * a3[0];
          const y = n1 * a1[1] + n2 * a2[1] + n3 * a3[1];
          const z = n1 * a1[2] + n2 * a2[2] + n3 * a3[2];
          points.push(...[x, y, z]);
        }
      }
    }
    // 计算后的八个顶点
    const vertices = new Float32Array(points);
    // 计算后顶点的链接关系
    const edges = new Uint16Array([
      0, 1,
      0, 2,
      0, 4,
      1, 3,
      1, 5,
      2, 3,
      2, 6,
      3, 7,
      4, 5,
      4, 6,
      5, 7,
      6, 7
    ]);
    // 创建几何体并添加顶点和边
    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
    geometry.setIndex(new THREE.BufferAttribute(edges, 1));
    // 创建线条材质
    const material = new THREE.LineBasicMaterial({ color: 0xffffff });
    // 创建线条
    const lineSegments = new THREE.LineSegments(geometry, material);
    atomGroup.add(lineSegments)
    const OlineText = (toArr, text, color) => {
      const label = this.createTextLabel(text, color)
      label.position.set(...toArr);
      atomGroup.add(label);
    }
    // 画OA,OB,OC
    const Oline = (toArr,color,text) => {
      // 定义线条的几何体
      const points = [];
      points.push(new THREE.Vector3( 0,0, 0));
      points.push(new THREE.Vector3(toArr[0],toArr[1],toArr[2]));
      const geometry = new THREE.BufferGeometry().setFromPoints(points);
      // 定义线条的材质
      const material = new THREE.LineBasicMaterial({ color });
      // 创建线条并添加到场景中
      const line = new THREE.Line(geometry, material);
      atomGroup.add(line);
      OlineText([toArr[0] - 0.2, toArr[1] - 0.2, toArr[2] - 0.2], text, color)
    }
    // OA
    Oline(vectors[0],'red','a')
    // OB
    Oline(vectors[1],'green','b')
    // OC
    Oline(vectors[2],'blue','c')
    // O
    OlineText([-0.2,-0.2,-0.2],'o','#fff')
    // 将最后绘制完成的内容进行返回
    return atomGroup
  }
   // 坐标轴的文字
  createTextLabel (labelText, color,size = 1.5) {
    const canvas = document.createElement('canvas');
    canvas.width = 200; // 调整 canvas 大小以容纳文字
    canvas.height = 200;
    const context = canvas.getContext('2d');
    context.clearRect(0, 0, canvas.width, canvas.height);
    context.font = '120px Times New Roman'; // 增大字体尺寸
    context.fillStyle = color;
    context.fillText(labelText, canvas.width / 2, canvas.height / 2);
    const texture = new THREE.CanvasTexture(canvas); // 使用 CanvasTexture
    texture.premultiplyAlpha = false;
    texture.needsUpdate = true;
    const material = new THREE.SpriteMaterial({ map: texture ,alphaTest: 0.2 , transparent: true});
    const sprite = new THREE.Sprite(material);
    sprite.scale.set(size, size, size); // 调整标签尺寸
    return sprite;
  }
4)绘制位置调整

因为绘制到画布如果不进行调整那么他的效果是这样的,并不在正中心,那么需要进行调整
在这里插入图片描述

// 将结构调整到中间位置
setMolCenter(content){
   const {maxX,maxY,maxZ} = this.getMinAndMAxPosition()
   content.position.set(
     parseFloat(-Math.abs(maxX)).toFixed(2) / 2,
     parseFloat(-Math.abs(maxY)).toFixed(2) / 2,
     parseFloat(-Math.abs(maxZ )).toFixed(2) / 2
   )
}
  // 获取内容的最大和最小坐标
getMinAndMAxPosition(){
  // 初始化一个空的 Box3
  const boundingBox = new THREE.Box3();
  // 遍历 atomGroup 中的所有子对象并更新 boundingBox
  this.atomGroup.children.forEach(child => {
    const childBoundingBox = new THREE.Box3().setFromObject(child);
    boundingBox.union(childBoundingBox);
  });
  const maxX = boundingBox.max.x;
  const maxY = boundingBox.max.y;
  const maxZ = boundingBox.max.z;
  const minX = boundingBox.min.x;
  const minY = boundingBox.min.y;
  const minZ = boundingBox.min.z;
  return {
    maxX,maxY,maxZ,
    minX,minY,minZ
  }
}
5)绘制

调用上述代码,将所有的内容放到atomGroup 中,将atomGroup 添加到场景即可实现完整逻辑

	const {lattice, atom, band} = data
	
	this.atomGroup = new THREE.Group();
	this.atomGroup.name = 'structure'
	
	// 原子绘制
	if (atom && atom.length){
	  const initAtom = this.drawAtom(atom)
	  this.atomGroup.add(initAtom)
	}
	
	// 连键
	if (band && band.length){
	  const initBand = this.drawBand(atom,band)
	  this.atomGroup.add(initBand)
	}
	
	// 晶格绘制
	if (lattice && lattice.length){
	  const initLattice = this.drawLattice(lattice)
	  this.atomGroup.add(initLattice)
	}
	
	//  讲内容调整到画布中间位置
	this.setMolCenter(this.atomGroup)

标签:教程,const,atomGroup,THREE,Three,let,atom,new,js
From: https://blog.csdn.net/chenyong_1997/article/details/140245082

相关文章

  • JSP静态Webshell编码
    #0x00 背景介绍JSP静态Webshell分析,包括unicode、html、cdata、特殊字体编码等,相互结合以达到欺骗效果。#0x01unicode编码JSP中可以解析unicode编码,通过contentType字段定义。原始一句话如下:Stringcmd=request.getParameter("cmd");Processprocess=Runtime.getRu......
  • vite+vue3整合less教程
    1、安装依赖pnpminstall-Dlessless-loader2、定义全局css变量文件src/assets/css/global.less:root{--public_background_font_Color:red;--publicHouver_background_Color:#fff;--header_background_Color:#fff;--menu_background:#fff;}3、......
  • 最新扣子(Coze)实战案例:图像流工具之空间风格化,完全免费教程
    ......
  • Node.js之Express
    Express介绍Express是一个简洁、灵活的node.jsWeb应用开发框架,是目前最流行的基于Node.js的Web开发框架.它提供一系列强大的功能,比如:模板解析静态文件服务中间件路由控制还可以使用其他模块来帮助你创建各种Web和移动设备应用使用express本地安装$npminstallexp......
  • intellij idea安装教程和项目创建
     安装教程:https://blog.csdn.net/2401_84239901/article/details/137701540 IDEA下载地址:https://www.jetbrains.com//idea/download/#section=windows 创建一个简单项目    点击运行之后,多出来target目录 右击运行 也就是都可以运行,这两处 写了个J......
  • CesiumJS【Basic】- #073 日月矢量
    文章目录日月矢量1目标2代码日月矢量1目标绘制对日矢量和对月矢量2代码/**@Author:alan.lau*@Date:2024-06-1611:15:48*@LastEditTime:2024-06-1611:43:02*@LastEditors:alan.lau*@Description:*@FilePath:\my-cesium......
  • node.js_HTTP协议
    HypertextTransferProtocol  超文本传输协议1.HTTP报文 请求行 请求头请求体 它的内容形式很灵活,可以设置任意内容  2.HTTP响应报文响应状态码响应状态的描述遇到陌生的状态码可以参考一下这个网址:https://developer.mozilla.org/zh-CN/docs/Web/HT......
  • 会声会影2024官方破解版下载全攻略方法教程最新
    《会声会影2024破解版》是一款流行的视频编辑软件,其正式版通常需付费使用。然而,由于正版费用较高,一些用户可能寻求破解版以免费享受该软件的全部功能。这种需求反映了普通用户在面对昂贵软件时的经济考量与版权意识之间的冲突。会声会影全版本绿色安装包获取链接:(抓紧保存以防......
  • 最新扣子(Coze)实战案例:使用扩图功能,让你的图任意变换,完全免费教程
    ......
  • 最新扣子(Coze)实战案例:使用图像流做超分,模糊图片秒变清晰,完全免费教程
    ......