<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./style.css">
<script src="./index.js"></script>
</head>
<body>
<canvas id="c"></canvas>
<script type="importmap">{
"imports": {
"three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.174.0/three.module.min.js"
}
}</script>
</body>
</html>
index.html
style.css
index.js
现在支持上传本地图片了!
index.html
style.css
html, body {
height: 100%;
margin: 0;
}
#c {
width: 100%;
height: 100%;
display: block;
}
编辑器加载中
index.js
// Three.js - 动态高亮区域示例
// 自动旋转 + 弧线高亮动画
import * as THREE from 'three';
function main() {
// 创建渲染器
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({ antialias: true, canvas });
renderer.setPixelRatio(window.devicePixelRatio);
// 设置场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x333333);
// 设置相机
const camera = new THREE.PerspectiveCamera(45, 2, 0.1, 100);
camera.position.set(0, 0, 8);
// 添加光照
const ambientLight = new THREE.AmbientLight(0x444444);
scene.add(ambientLight);
const directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight1.position.set(1, 2, 3);
scene.add(directionalLight1);
const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.4);
directionalLight2.position.set(-1, -1, -2);
scene.add(directionalLight2);
// 创建材质
const baseMaterial = new THREE.MeshPhongMaterial({
color: 0xff0000, // 红色
transparent: true,
opacity: 0.4,
side: THREE.DoubleSide,
depthWrite: true, // 提高透明效果
flatShading: false // 平面着色以便更清晰地看到三角面
});
const highlightMaterial = new THREE.MeshPhongMaterial({
color: 0x0000ff, // 蓝色高亮
transparent: true,
opacity: 0.4,
side: THREE.DoubleSide,
depthWrite: true,
shininess: 100, // 增加高亮效果
emissive: 0x1144aa // 轻微自发光效果增强高亮
});
// 创建一个材质数组
const materials = [baseMaterial, highlightMaterial];
// 创建一个球体几何体
const geometry = new THREE.SphereGeometry(3, 64, 48);
// 创建网格
const mesh = new THREE.Mesh(geometry, materials);
scene.add(mesh);
// 添加线框以帮助查看三角面
const wireframe = new THREE.WireframeGeometry(geometry);
const line = new THREE.LineSegments(wireframe);
line.material.color.set(0xffffff);
line.material.transparent = true;
line.material.opacity = 0.15;
mesh.add(line);
// 设置基本组(所有面使用基本材质)
function setupBaseGroup() {
geometry.clearGroups();
const triangleCount = geometry.index.count / 3;
geometry.addGroup(0, triangleCount * 3, 0); // 所有三角形使用基本材质
}
// 初始设置
setupBaseGroup();
// 计算每个三角形的中心坐标
const triangleCenters = [];
const trianglePhiTheta = [];
function calculateTriangleData() {
const positions = geometry.attributes.position;
const index = geometry.index;
const triangleCount = index.count / 3;
// 临时变量
const v1 = new THREE.Vector3();
const v2 = new THREE.Vector3();
const v3 = new THREE.Vector3();
const center = new THREE.Vector3();
for (let i = 0; i < triangleCount; i++) {
// 获取三角形的三个顶点索引
const idx1 = index.getX(i * 3);
const idx2 = index.getX(i * 3 + 1);
const idx3 = index.getX(i * 3 + 2);
// 获取顶点坐标
v1.fromBufferAttribute(positions, idx1);
v2.fromBufferAttribute(positions, idx2);
v3.fromBufferAttribute(positions, idx3);
// 计算三角形中心点
center.copy(v1).add(v2).add(v3).divideScalar(3);
// 存储中心点
triangleCenters.push(center.clone());
// 计算中心点的球面坐标
const normalized = center.clone().normalize();
const phi = Math.acos(normalized.y); // y轴到点的角度 (0 到 π)
const theta = Math.atan2(normalized.z, normalized.x); // x-z平面上的角度 (-π 到 π)
trianglePhiTheta.push({ phi, theta });
}
}
// 计算三角形数据
calculateTriangleData();
// 更新高亮区域
function updateHighlight(time) {
const triangleCount = geometry.index.count / 3;
geometry.clearGroups();
// 基本组 - 所有三角形使用基本材质
geometry.addGroup(0, triangleCount * 3, 0);
// 创建高亮组
const highlightedTriangles = new Set();
// 弧线参数
const arcWidth = 0.4; // 弧线宽度
const arcSpeed = 0.5; // 弧线移动速度
const arcCenterPhi = (Math.sin(time * arcSpeed) + 1) * Math.PI / 2; // 弧线中心位置,从顶部到底部移动
// 波浪参数
const waveAmplitude = 0.3 * Math.PI; // 波浪幅度
const waveFrequency = 3; // 波浪频率
// 对每个三角形,检查是否在高亮弧线范围内
for (let i = 0; i < triangleCount; i++) {
const { phi, theta } = trianglePhiTheta[i];
// 生成波浪形弧线
const waveCenterPhi = arcCenterPhi + Math.sin(theta * waveFrequency) * waveAmplitude;
// 如果三角形在弧线范围内,将其添加到高亮列表
if (Math.abs(phi - waveCenterPhi) < arcWidth) {
highlightedTriangles.add(i);
}
}
// 将连续的三角形区域合并为单一group
if (highlightedTriangles.size > 0) {
const indices = Array.from(highlightedTriangles).sort((a, b) => a - b);
let currentStart = indices[0];
let currentLength = 1;
for (let i = 1; i < indices.length; i++) {
if (indices[i] === indices[i-1] + 1) {
currentLength++;
} else {
geometry.addGroup(currentStart * 3, currentLength * 3, 1);
currentStart = indices[i];
currentLength = 1;
}
}
geometry.addGroup(currentStart * 3, currentLength * 3, 1);
}
// 更新状态信息
updateStatus(highlightedTriangles.size);
}
// 调整渲染器尺寸
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
// 状态信息显示
const statusElement = document.createElement('div');
statusElement.style.position = 'absolute';
statusElement.style.bottom = '10px';
statusElement.style.left = '10px';
statusElement.style.background = 'rgba(0,0,0,0.7)';
statusElement.style.padding = '10px';
statusElement.style.borderRadius = '5px';
statusElement.style.color = 'white';
statusElement.style.fontFamily = 'monospace';
document.body.appendChild(statusElement);
// 更新状态信息
function updateStatus(highlightCount) {
const triangleCount = geometry.index.count / 3;
const highlightPercentage = (highlightCount / triangleCount * 100).toFixed(1);
statusElement.innerHTML = `
<div>总三角形数量: ${triangleCount}</div>
<div>当前高亮Group: ${highlightCount} 个 (${highlightPercentage}%)</div>
`;
}
// 添加标题
const title = document.createElement('div');
title.style.position = 'absolute';
title.style.top = '10px';
title.style.width = '100%';
title.style.textAlign = 'center';
title.style.color = 'white';
title.style.fontFamily = 'Arial, sans-serif';
title.style.fontSize = '18px';
title.style.fontWeight = 'bold';
title.style.textShadow = '1px 1px 2px black';
title.innerHTML = 'BufferGeometry Groups 实现动态高亮区域';
document.body.appendChild(title);
// 渲染循环
function render(time) {
time *= 0.001; // 转换为秒
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
// 更新高亮区域
updateHighlight(time);
// 旋转模型
mesh.rotation.y = time * 0.2;
mesh.rotation.x = Math.sin(time * 0.1) * 0.3;
renderer.render(scene, camera);
requestAnimationFrame(render);
}
// 开始渲染
requestAnimationFrame(render);
}
main();
编辑器加载中
预览页面