当前位置: 首页 > wzjs >正文

毕业设计做音乐网站可以吗谷歌google play官网下载

毕业设计做音乐网站可以吗,谷歌google play官网下载,哈尔滨网站如何制作,wordpress网站定时更新没有发布3D人物关系图开发实战:Three.js实现自动旋转可视化图谱 效果核心解析场景初始化自动旋转控制器节点创建(带图片和标签)关系连线动画循环数据格式说明 代码 效果 本文将带您使用Three.js实现一个带自动旋转功能的3D人物关系图谱,核…

3D人物关系图开发实战:Three.js实现自动旋转可视化图谱

  • 效果
  • 核心解析
    • 场景初始化
    • 自动旋转控制器
    • 节点创建(带图片和标签)
    • 关系连线
    • 动画循环
    • 数据格式说明
  • 代码

效果

在这里插入图片描述

本文将带您使用Three.js实现一个带自动旋转功能的3D人物关系图谱,核心功能包括:

  • ​三维空间布局​:人物节点环形排列
  • ​动态关系线​:带箭头的红色连线和悬浮关系标签
  • ​交互控制​:支持鼠标拖拽、缩放视角
  • ​自动旋转​:场景持续缓慢旋转,增强视觉效果
  • ​自适应窗口​:响应式布局适配不同屏幕

核心解析

场景初始化

// 创建基础Three.js场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf8f8f8);// 透视相机配置
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
camera.position.set(0, 15, 30); // 初始视角// 渲染器配置
const renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

自动旋转控制器

const controls = new OrbitControls(camera, renderer.domElement);
controls.autoRotate = true;         // 启用自动旋转
controls.autoRotateSpeed = 1.0;     // 旋转速度
controls.enableDamping = true;      // 阻尼惯性效果
controls.dampingFactor = 0.05;     // 阻尼系数

节点创建(带图片和标签)

// 加载角色立绘
const textureLoader = new THREE.TextureLoader();
textureLoader.load(node.img, (texture) => {const sprite = new THREE.Sprite(material);sprite.scale.set(baseWidth, baseHeight, 1); // 保持图片比例
});// 创建信息标签
const canvas = document.createElement('canvas');
ctx.font = 'bold 24px "Microsoft YaHei"'; // 中文字体支持
ctx.fillText(node.name, 10, 30); // 绘制姓名

关系连线

const arrowHelper = new THREE.ArrowHelper(direction, sourcePos,length,0xDC143C, // 红色箭头headLength,headWidth
);

动画循环

function animate() {requestAnimationFrame(animate);controls.update(); // 持续更新控制器renderer.render(scene, camera);
}

数据格式说明

创建无名小村.json文件:

{"nodes": [{"id": 1,"name": "花四娘","img": "img/role1.png","description": "无名客栈的老板娘,有一手好厨艺,性格泼辣、刚柔并济。一个人苦苦经营无名客栈,据包打听说追她的人能排到无名小村村口,却不见有得她芳心的。"},{"id": 2,"name": "洪小七","img": "img/role1.png","description": "游荡在无名小村里的小乞丐,整日游手好闲,凭借着小偷小摸的本事,这才勉强过上饥一顿饱一顿的日子。"},{"id": 3,"name": "包打听","img": "img/role1.png","description": "无名小村里游手好闲的年轻人,平日里到处打听八卦,靠着打听到的小道消息和人换点小钱为生"},{"id": 4,"name": "白头翁","img": "img/role1.png","description": "年少时一心钻研学医,本想医术大成后救济天下人,奈何天赋不够,蹉跎半生仍旧只知皮毛,只能沦为乡里郎中,治一些风寒小病糊口。"},{"id": 5,"name": "王大锤","img": "img/role1.png","description": "无名小村的铁匠,力大如牛,有一手顶尖的打铁技巧。原先是琅琊剑阁大弟子,年轻时人称“玉面干将”,但是现在完全看不出来。因为某些事情离开剑阁,来到无名小村隐姓埋名。"},{"id": 6,"name": "刘十八","img": "img/role1.png","description": "无名小村的猎户,打猎技术高超。昔日是守卫楚襄城的杨将军部下,杨家军解散后逃到无名小村躲避,从此隐姓埋名,以打猎为生。"},{"id": 7,"name": "采石匠","img": "img/role1.png","description": "村里的采石匠,挖矿为生。"},{"id": 8,"name": "屠户","img": "img/role1.png","description": "卖肉的屠户,白白胖胖,营养过剩。"},{"id": 9,"name": "小花","img": "img/role1.png","description": "樵夫女儿,喜欢猜字谜。"},{"id": 10,"name": "小白","img": "img/role1.png","description": "樵夫的儿子,喜欢打猎。"},{"id": 11,"name": "小丫","img": "img/role1.png","description": "刘十八女儿,喜欢玩捉迷藏。"},{"id": 12,"name": "樵夫","img": "img/role1.png","description": "村里的樵夫,以砍伐木材为生。"},{"id": 13,"name": "村长","img": "img/role1.png","description": "一村之长,老秀才,守护着无名小村的秘密。"},{"id": 14,"name": "小宝","img": "img/role1.png","description": "村长三代,梦想是成为大侠。"},{"id": 15,"name": "货郎","img": "img/role1.png","description": "走南闯北,贩卖各种物品的货郎。"},{"id": 16,"name": "燕歌行","img": "img/role1.png","description": "在无名小村非要拉着收徒的怪老头,原本以为只是个不正经的老头,真实身份是老魔头楚狂生的师弟、九流门的真正创建者之一。因被仇家暗算导致武功尽失,经脉尽毁。如今已经治愈旧伤、功力恢复,准备去完成自己的毕生心愿。"}],"links": [{"source": 5,"target": 6,"relation": "不和"},{"source": 6,"target": 11,"relation": "父亲"},{"source": 6,"target": 8,"relation": "供货"},{"source": 1,"target": 5,"relation": "债主"},{"source": 3,"target": 1,"relation": "暗恋"},{"source": 13,"target": 14,"relation": "爷爷"},{"source": 12,"target": 9,"relation": "父亲"},{"source": 4,"target": 6,"relation": "救治"}]
}

代码

<!DOCTYPE html>
<html lang="zh-cn">
<head><meta charset="UTF-8"><title>3D人物关系图(自动旋转版)</title><style>body { margin: 0; }canvas { display: block; }</style><!-- importmap 配置 --><script type="importmap">{"imports": {"three": "./js/three.js/build/three.module.js","three/addons/": "./js/three.js/examples/jsm/"}}</script>
</head>
<body>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';fetch('无名小村.json').then(response => response.json()).then(data => {const scene = new THREE.Scene();scene.background = new THREE.Color(0xf8f8f8);const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);const renderer = new THREE.WebGLRenderer({antialias: true});renderer.setSize(window.innerWidth, window.innerHeight);renderer.toneMapping = THREE.NoToneMapping;renderer.outputColorSpace = THREE.SRGBColorSpace;document.body.appendChild(renderer.domElement);// 控制器配置(新增自动旋转参数)const controls = new OrbitControls(camera, renderer.domElement);controls.autoRotate = true;         // 启用自动旋转controls.autoRotateSpeed = 1.0;      // 旋转速度(默认1.0)controls.enableDamping = true;       // 启用阻尼惯性controls.dampingFactor = 0.05;       // 阻尼系数controls.minDistance = 15;          // 最小缩放距离controls.maxDistance = 50;          // 最大缩放距离// 调整初始摄像机位置(新增斜视角)camera.position.set(0, 15, 30);      // x, y, z坐标controls.update();// 节点布局(简单环形)const radius = 10;const nodeMeshes = {};data.nodes.forEach((node, i) => {const angle = (i / data.nodes.length) * Math.PI * 2;const x = radius * Math.cos(angle);const y = radius * Math.sin(angle);const z = (Math.random() - 0.5) * 5;node.position = {x, y, z};// 立绘图片节点const textureLoader = new THREE.TextureLoader();textureLoader.load(node.img, (texture) => {texture.colorSpace = THREE.SRGBColorSpace;const material = new THREE.SpriteMaterial({map: texture,transparent: true,premultipliedAlpha: false,blending: THREE.NormalBlending,depthWrite: false,depthTest: true,sizeAttenuation: true,color: 0xffffff});material.toneMapped = false;const sprite = new THREE.Sprite(material);sprite.position.set(x, y, z);// --- 根据图片实际尺寸计算缩放比例 ---const image = texture.image;if (image) {const aspectRatio = image.naturalWidth / image.naturalHeight;// 定义一个基础高度(例如,所有立绘在场景中的基础高度为 4 个单位)const baseHeight = 4;// 根据宽高比计算宽度const baseWidth = baseHeight * aspectRatio;sprite.scale.set(baseWidth, baseHeight, 1);} else {// 如果图片尺寸信息获取失败,使用默认值sprite.scale.set(3, 4, 1);}// --- 缩放计算结束 ---scene.add(sprite);nodeMeshes[node.id] = sprite;});// 节点标签const canvas = document.createElement('canvas');canvas.width = 256;canvas.height = 128;const ctx = canvas.getContext('2d');ctx.clearRect(0, 0, canvas.width, canvas.height);ctx.fillStyle = 'rgba(200,220,255,0.8)';ctx.fillRect(0, 0, canvas.width, canvas.height);// 名称ctx.font = 'bold 24px "Microsoft YaHei", "微软雅黑", sans-serif';ctx.fillStyle = 'black';ctx.fillText(node.name, 10, 30);// 描述ctx.font = 'bold 16px "Microsoft YaHei", "微软雅黑", sans-serif';const description = node.description || "暂无人物介绍";const maxWidth = 240;const lineHeight = 20;let textY = 60;for (let i = 0; i < description.length; i += 20) {const chunk = description.substr(i, 20);ctx.fillText(chunk, 10, textY);textY += lineHeight;if (textY > canvas.height - 10) break;}const labelTexture = new THREE.CanvasTexture(canvas);const labelMaterial = new THREE.SpriteMaterial({map: labelTexture,transparent: true,depthTest: false,depthWrite: false});const labelSprite = new THREE.Sprite(labelMaterial);labelSprite.scale.set(5, 3, 1);// 将 y + 3.5 修改为 y - 3.5 (或其他负值)labelSprite.position.set(x, y - 3.5, z);scene.add(labelSprite);});// 绘制连线和关系标签data.links.forEach(link => {const sourceNode = data.nodes.find(n => n.id === link.source);const targetNode = data.nodes.find(n => n.id === link.target);// 确保节点和位置存在if (!sourceNode || !sourceNode.position || !targetNode || !targetNode.position) {console.warn('Skipping link due to missing node or position:', link);return;}const sourcePos = new THREE.Vector3(sourceNode.position.x, sourceNode.position.y, sourceNode.position.z);const targetPos = new THREE.Vector3(targetNode.position.x, targetNode.position.y, targetNode.position.z);// --- 绘制带箭头的连线 ---const direction = new THREE.Vector3().subVectors(targetPos, sourcePos);const length = direction.length(); // 获取向量长度,即连线长度direction.normalize(); // 标准化方向向量// 定义箭头参数const arrowColor = 0xDC143C;const headLength = 1; // 箭头头部长度,可调整const headWidth = 0.5; // 箭头头部宽度,可调整// 创建 ArrowHelperconst arrowHelper = new THREE.ArrowHelper(direction,  // 箭头方向(标准化向量)sourcePos,  // 箭头起点length,     // 箭头总长度(从起点到终点)arrowColor, // 箭头颜色headLength, // 箭头头部长度headWidth   // 箭头头部宽度);scene.add(arrowHelper);// --- 箭头连线结束 ---// --- 添加关系标签 ---const relationText = link.relation || ''; // 获取关系文本,如果不存在则为空if (relationText) {const canvas = document.createElement('canvas');const context = canvas.getContext('2d');// 增大字体大小const fontSize = 24; // <--- 增大字体context.font = `bold ${fontSize}px Arial`;const textWidth = context.measureText(relationText).width;// 根据文本内容调整Canvas大小,并增加更多边距const padding = 20; // <--- 增加边距canvas.width = textWidth + padding * 2;canvas.height = fontSize + padding; // 上下边距可以少一点// 重新设置字体和样式context.font = `bold ${fontSize}px Arial`; // <--- 保持一致context.fillStyle = 'rgba(0, 0, 0, 0.7)';context.fillRect(0, 0, canvas.width, canvas.height);context.fillStyle = 'white';context.textAlign = 'center';context.textBaseline = 'middle';// 绘制文本位置也要相应调整context.fillText(relationText, canvas.width / 2, canvas.height / 2);const texture = new THREE.CanvasTexture(canvas);// 可以尝试不同的过滤方式,但通常提高分辨率效果更好// texture.minFilter = THREE.LinearFilter;// texture.magFilter = THREE.LinearFilter; // 或者 THREE.NearestFilter 看效果const spriteMaterial = new THREE.SpriteMaterial({map: texture,transparent: true,depthTest: false,depthWrite: false,sizeAttenuation: true // 确保 Sprite 大小随距离变化});const sprite = new THREE.Sprite(spriteMaterial);// 计算标签位置(线段中点稍微偏移一点)const midPoint = new THREE.Vector3().addVectors(sourcePos, targetPos).multiplyScalar(0.5);midPoint.y += 0.5; // 稍微向上偏移sprite.position.copy(midPoint);// 可能需要重新调整 scaleFactor 以匹配新的 Canvas 尺寸和字体大小const scaleFactor = 0.05; // <--- 可能需要减小 scaleFactorsprite.scale.set(canvas.width * scaleFactor, canvas.height * scaleFactor, 1.0);scene.add(sprite);}// --- 关系标签结束 ---});// 渲染循环(新增自动旋转逻辑)function animate() {requestAnimationFrame(animate);controls.update(); // 必须调用才能启用自动旋转renderer.render(scene, camera);}animate();// 窗口大小自适应window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});});
</script>
</body>
</html>
http://www.dtcms.com/wzjs/124226.html

相关文章:

  • 什么是无主体新增网站以网红引流促业态提升
  • 这几年做哪个网站能致富seo关键技术有哪些
  • 公司主页怎么写武汉谷歌seo
  • 大数据与网站开发技术腾讯网qq网站
  • 互联网创意网站有哪些公司网站建设多少钱
  • 营销型网站建设一般要多少钱公司网页
  • 做网站是要编程吗java培训学费多少钱
  • 天河建设网站技术成都有实力的seo团队
  • html公益网站模板网站软文是什么
  • 网站建设优化推广哈尔滨seo的实现方式
  • 推进政府网站建设培训班主持词seo建站需求
  • 广州网站建设案例福鼎网站优化公司
  • 网站建设思想重视不够百度商城官网首页
  • 重庆市做网站的公司有哪些英文站友情链接去哪里查
  • 网站建设与网页制作案例泰州seo
  • 苹果应用商店网站seo搜索引擎优化案例
  • 中国企业转让网最新seo新手教程
  • 衡阳微信网站电子商务平台
  • 东莞做网站公司郴州网站定制
  • 无锡加盟网站建设关键词采集网站
  • 武汉网站制作平台南昌seo计费管理
  • 最优做网站免费网站服务器安全软件下载
  • 网站seo相关设置优化seo网站诊断报告
  • 浦项建设公司员工网站广告营销包括哪些方面
  • 美国疫情最新消息今天又封了网络优化工程师骗局
  • 做网站还是app省钱凡科建站
  • 西苑做网站公司免费二级域名分发网站
  • 企业官方网站建设目的百度广告推广价格
  • 南昌市有帮做网站的吗免费推广产品平台有哪些
  • wordpress开启多站点功大数据获客系统