- 效果图
描述: 星球数据围绕着球心自转,空间内有多个星球,切换星球展示对应星球的数据并自转
- 代码实现
1.初始化场景
const init = () => {
dom.value = document.getElementById("planetBox"); //获取dom
width.value = dom.value.offsetWidth;
height.value = dom.value.offsetHeight;
// 场景
scene = new THREE.Scene();
// 创建透视相机(视角、长宽比、近面、远面)
camera = new THREE.PerspectiveCamera(
45,
width.value / height.value,
20,
10000
);
camera.position.set(0, 600, 800); // 设置相机位置
camera.lookAt(0, 0, 0);
// 创建渲染器
state.renderer = new THREE.WebGLRenderer({
antialias: true, //抗锯齿
alpha: true, //透明
});
state.renderer.setSize(width.value, height.value); // 设置渲染区域尺寸
if (dom.value.children && dom.value.children.length) {
dom.value.removeChild(dom.value.children[0]);
}
dom.value.appendChild(state.renderer.domElement); // 将渲染器添加到dom中形成canvas
// 坐标系
// const axesHelper = new THREE.AxesHelper(1000);
// scene.add(axesHelper);
createOrbitControls(); //创建鼠标控制器
createLight(); //创建光源
initSpheres(); // 初始化数据
render(); //渲染
}
2.创建鼠标控制器
const createOrbitControls = () => {
orbitControls = new OrbitControls(camera, state.renderer.domElement);
orbitControls.enablePan = true; //右键平移拖拽
orbitControls.enableZoom = true; //鼠标缩放
orbitControls.enableDamping = true; //滑动阻尼
orbitControls.dampingFactor = 0.05; //(默认.25)
orbitControls.minDistance = 100; //相机距离目标最小距离
orbitControls.maxDistance = 2700; //相机距离目标最大距离
orbitControls.autoRotate = true; //自转(相机)
orbitControls.autoRotateSpeed = 0; //自转速度
}
3.创建光源
const createLight = () => {
let ambient = new THREE.AmbientLight(new THREE.Color(0xffffff)); //环境光
scene.add(ambient);
let pointLight = new THREE.PointLight(new THREE.Color(0xffff00), 2, 1, 0); //点光源
pointLight.visible = true;
pointLight.position.set(400, 200, 300); //点光源在原点充当太阳
scene.add(pointLight); //点光源添加到场景中
}
4.初始化数据(涉及到的创建球心、创建球心上的文字及创建球体数据等方法在4.1、4.2、4.3)
const initSpheres = () => {
resultList.value.forEach((item, index) => {
let star = createStar(item);
let container = getDataSphereRotation(star, item.data);
container.userData.id = item.id; // 标识
if (index === 0) {
// 默展示第一个球
let group =
container &&
container.children &&
container.children.length &&
container.children[1]; // 取出group
showData(star, group, false);
}
// 当前球添加数据
scene.add(container);
});
}
4.1 利用canvas创建文字
const makeTextCanvas = (width, height, data, font, isLevel) => {
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.translate(width / 2, height / 2);
ctx.fillStyle = "#ffffff"; //文本填充颜色
ctx.font = font + "px Arial"; //字体样式设置
ctx.textBaseline = "middle"; //文本与 fillText 定义的纵坐标
ctx.textAlign = "center"; //文本居中(以 fillText 定义的横坐标)
ctx.fillText(data.name, 0, 0);
// 排名标识
if (isLevel) {
ctx.beginPath();
ctx.arc(0, -40, 2 * (10 - data.level), 0, 360, false);
ctx.fillStyle = levelColor.value[data.level - 1]
? levelColor.value[data.level - 1]
: "rgba(144, 78, 84)";
ctx.fill(); //画实心圆
ctx.closePath();
}
return canvas;
}
4.2 创建球心并将文字放到球心上
const createStar = (data) => {
const geometry = new THREE.SphereGeometry(50, 45, 45);
const material = new THREE.MeshBasicMaterial({
color: "yellow",
opacity: 0.2,
transparent: true,
});
const sphere = new THREE.Mesh(geometry, material);
sphere.position.set(data.position[0], data.position[1], data.position[2]);
sphere.userData.isRotate = false; // 禁止自转
sphere.userData.dataList = data.data; // 数据
// 文字放到球心
const canvasText = makeTextCanvas(80, 24, data, 18, false);
const texture = new THREE.CanvasTexture(canvasText);
texture.generateMipmaps = false;
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
const sprite = new THREE.Sprite(
new THREE.SpriteMaterial({
map: texture,
})
);
sprite.scale.set(80, 40, 1);
sprite.position.set(0, -2, 0);
sphere.add(sprite);
return sphere;
}
4.3 创建星球上的数据,并将数据和球心放到容器group中
const getDataSphereRotation = (sphere, data) => {
let group = new THREE.Group();
const len = data && data.length;
data.forEach((item, i) => {
const phi = Math.acos(-1 + (2 * i) / len);
const theta = Math.sqrt(len * Math.PI) * phi;
let text = makeTextCanvas(160, 120, item, 30, true);
var texture = new THREE.CanvasTexture(text);
texture.generateMipmaps = false;
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
let pinMaterial = new THREE.SpriteMaterial({
map: texture,
});
let sprite = new THREE.Sprite(pinMaterial);
sprite.scale.set(120, 80, 2);
sprite.position.setFromSphericalCoords(250, phi, theta);
sprite.visible = false; // 初始化隐藏数据
sprite.userData = item;
group.add(sprite);
});
let container = new THREE.Object3D();
container.add(sphere);
container.add(group);
return container;
}
5.渲染
const render = () => {
//请求动画帧,屏幕每刷新一次调用一次,绑定屏幕刷新频率
requestAnimationFrame(render);
orbitControls.update(); //鼠标控件实时更新
state.renderer.render(scene, camera);
TWEEN.update();
// 当前group自转
if (state.curSphere && state.curSphere.userData.isRotate) {
curGroup.rotation.y += 0.001;
curGroup.rotation.x += 0.001;
}
}
6.切换球体(点击事件在7)
const switchToCurrentSphere = (clickSphere) => {
// 隐藏当前球体的数据 && 更改当前球体数据
hiddenData();
// 切换动画
startRotationAndZoom(clickSphere);
// 展示点击球体的数据
showData(clickSphere, null, true);
}
6.1 隐藏当前球体的数据 && 更改当前球体数据
const hiddenData = () => {
// 更改数据
state.curSphere.userData.isRotate = false;
// 隐藏数据
const curGroupChildren = curGroup && curGroup.children;
if (curGroupChildren && curGroupChildren.length) {
curGroupChildren.forEach((i) => {
i.visible = false;
});
}
}
6.2 缓动动画,将相机拉近点击的球体
const startRotationAndZoom = (targetSphere) => {
// 点击球体的位置
const targetPosition = targetSphere.position.clone();
new TWEEN.Tween(scene.position)
.to(targetPosition.negate(), 2000)
.easing(TWEEN.Easing.Quadratic.Out)
.start();
}
6.3 展示点击球体的数据
const showData = (clickSphere, group, isMove) => {
clickSphere.userData.isRotate = true;
state.curSphere = clickSphere;
let clickSphereGroup = group
? group
: clickSphere.parent &&
clickSphere.parent.children &&
clickSphere.parent.children.length &&
clickSphere.parent.children[1];
curGroup = clickSphereGroup;
if (
clickSphereGroup &&
clickSphereGroup.type === "Group" &&
clickSphereGroup.children &&
clickSphereGroup.children.length
) {
const clickSprites = clickSphereGroup.children;
clickSprites.forEach((i) => {
i.visible = true;
});
// 移动group
if (isMove) {
curGroup.position.set(
clickSphere.position.x,
clickSphere.position.y,
clickSphere.position.z
);
}
}
}
7. 球体上数据的点击事件(因为全局监听的点击事件,在里面区分了点击的对象是球体上的数据,还是点击的球心进行星球切换)
// 监听点击事件
dom.value.addEventListener("click", moduleEvent)
const moduleEvent = (e) => {
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
mouse.x = (e.offsetX / width.value) * 2 - 1;
mouse.y = -(e.offsetY / height.value) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
let intersectsMesh = raycaster.intersectObjects(scene.children, true); // 点击球体
let intersectsSprite = raycaster.intersectObjects(curGroup.children, true); // 点击姓名
// 优先判断点击的是不是人名 => 点击的是不是球体
if (intersectsSprite && intersectsSprite.length) {
state.curSprite =
intersectsSprite[0] && intersectsSprite[0].object.userData;
} else if (intersectsMesh && intersectsMesh.length) {
// 点击球体
intersectsMesh.forEach((item) => {
const object = item.object;
if (
object.userData &&
!object.userData.isRotate &&
object.type === "Mesh"
) {
switchToCurrentSphere(object);
}
});
}
}
ending!!!