|
|
<!DOCTYPE html>
|
|
|
<html lang="zh-CN">
|
|
|
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
<title>three</title>
|
|
|
<script src="./js/vue.global.min.js"></script>
|
|
|
<script src="./js/antd.min.js"></script>
|
|
|
<link rel="stylesheet" href="./css/antd.min.css">
|
|
|
<link rel="stylesheet" href="./css/element-plus.css">
|
|
|
<script src="./js/element-plus.js"></script>
|
|
|
<!-- 引入 Element Plus 中文语言包 -->
|
|
|
<script src="./js/zh-cn.min.js"></script>
|
|
|
<script src="./js/echarts.min.js"></script>
|
|
|
<script type="text/javascript" src="./js/axios.min.js"></script>
|
|
|
<!-- 引入three -->
|
|
|
<script type="text/javascript" src="./three/three.min.js"></script>
|
|
|
<script type="text/javascript" src="./three/OrbitControls.js"></script>
|
|
|
<script type="text/javascript" src="./three/BufferGeometryUtils.js"></script>
|
|
|
<script type="text/javascript" src="./three/Lut.js"></script>
|
|
|
<style>
|
|
|
body {
|
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
|
padding: 10px;
|
|
|
background-color: #f5f5f5;
|
|
|
}
|
|
|
|
|
|
.content {
|
|
|
height: 98vh;
|
|
|
width: 100%;
|
|
|
background-color: #fff;
|
|
|
}
|
|
|
|
|
|
#tree-box {
|
|
|
width: 400px;
|
|
|
height: 350px;
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
<div id="app">
|
|
|
<div class="content">
|
|
|
<div ref="prpsBoxRef" id="tree-box"></div>
|
|
|
<a-button type="primary" @click="onStarPrps" style="margin-right: 12px;">开始 prps</a-button>
|
|
|
<div ref="prpdBoxRef" id="tree-box"></div>
|
|
|
<a-button type="primary" @click="onStarPrpd">开始 prpd</a-button>
|
|
|
</div>
|
|
|
<script>
|
|
|
const { createApp, ref, onMounted, unref, watch, reactive, computed, nextTick } = Vue;
|
|
|
const { message } = antd;
|
|
|
const fontStyles = [
|
|
|
0,
|
|
|
{
|
|
|
w: 55,
|
|
|
h: 25,
|
|
|
size: 16,
|
|
|
},
|
|
|
{
|
|
|
w: 55,
|
|
|
h: 25,
|
|
|
size: 16,
|
|
|
},
|
|
|
{
|
|
|
w: 75,
|
|
|
h: 25,
|
|
|
ch: 9,
|
|
|
cw: 5.7,
|
|
|
size: 20.3,
|
|
|
},
|
|
|
{
|
|
|
w: 85,
|
|
|
h: 32,
|
|
|
size: 22.3,
|
|
|
},
|
|
|
{
|
|
|
w: 110,
|
|
|
h: 40,
|
|
|
size: 25.3,
|
|
|
},
|
|
|
]
|
|
|
// 生成指定范围内的随机整数
|
|
|
function getRandomInt(min, max) {
|
|
|
const randomNumber = Math.random() * (max - min) + min;
|
|
|
const roundedNumber = randomNumber.toFixed(1);
|
|
|
return parseFloat(roundedNumber);
|
|
|
}
|
|
|
const generateArray = (period, phase) => {
|
|
|
let counter = 1, allArray = [];
|
|
|
for (let i = 0; i < period; i++) {
|
|
|
const newArray = [];
|
|
|
const step = 360 / phase;
|
|
|
for (let i = 1; i <= phase; i++) {
|
|
|
newArray.push([counter, parseInt(i * step), getRandomInt(10, 70)]);
|
|
|
}
|
|
|
counter = (counter % period) + 1;
|
|
|
allArray.push(newArray);
|
|
|
}
|
|
|
|
|
|
return allArray;
|
|
|
}
|
|
|
const formatNumber = (num, decimalPlaces = 1) => {
|
|
|
if (Number.isInteger(num)) {
|
|
|
// 如果是整数,直接返回
|
|
|
return num.toString();
|
|
|
} else {
|
|
|
// 如果是小数,使用 toFixed 方法
|
|
|
return num.toFixed(decimalPlaces);
|
|
|
}
|
|
|
}
|
|
|
const drawPlan = (obj) => {
|
|
|
const { size, color } = obj;
|
|
|
let geometry = new THREE.PlaneGeometry(size, 360); //创建一个Buffer类型几何体对象
|
|
|
geometry.rotateX(Math.PI / 2).translate(size / 2, 0, 180);
|
|
|
// 三角面(网格)渲染模式
|
|
|
let material = new THREE.MeshBasicMaterial({
|
|
|
color, //三角面颜色
|
|
|
transparent: true,//透明
|
|
|
opacity: 0.2,
|
|
|
side: THREE.DoubleSide, //两面可见
|
|
|
}); //材质对象
|
|
|
let mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
|
|
|
mesh.name = 'plan';
|
|
|
//scene.add(mesh);
|
|
|
return mesh
|
|
|
}
|
|
|
const drawLine = (obj) => {
|
|
|
let { line, color, position, margin, num } = obj
|
|
|
let geometry = new THREE.BufferGeometry(); //创建一个Buffer类型几何体对象
|
|
|
//3个为一组,表示一个顶点的xyz坐标
|
|
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute(line, 3))
|
|
|
// 线条渲染模式
|
|
|
let material = new THREE.LineBasicMaterial({
|
|
|
color, //线条颜色
|
|
|
});//材质对象
|
|
|
let LineList = [];
|
|
|
let modelLine = new THREE.Line(geometry, material);//线条模型对象
|
|
|
LineList.push(modelLine)
|
|
|
for (let i = 1; i < num; i++) {
|
|
|
let lineChildren = modelLine.clone()['translate' + position](i * margin)
|
|
|
LineList.push(lineChildren);
|
|
|
}
|
|
|
return LineList
|
|
|
};
|
|
|
const draw2DText = (obj) => {
|
|
|
let scale = 1;//window.devicePixelRatio;
|
|
|
let { content, w, h, color, font, xyz, size, cw, ch, rotate } = obj;
|
|
|
cw = cw ? cw : 6;
|
|
|
ch = ch ? ch : 8;
|
|
|
let canvas = document.createElement('canvas')
|
|
|
canvas.style.width = w * scale;
|
|
|
canvas.style.height = h * scale;
|
|
|
if (rotate) {
|
|
|
canvas.width = w * cw * scale * 3;
|
|
|
canvas.height = h * ch * scale * 2;
|
|
|
} else {
|
|
|
canvas.width = w * cw * scale * 2;
|
|
|
canvas.height = h * ch * scale * 3;
|
|
|
}
|
|
|
|
|
|
let ctx = canvas.getContext('2d');
|
|
|
ctx.fillStyle = color;
|
|
|
ctx.strokeStyle = '#000';
|
|
|
ctx.font = font;
|
|
|
ctx.direction = "ltr"; // 文本方向从左向右
|
|
|
ctx.textAlign = "left"; // 左对齐
|
|
|
ctx.textBaseline = 'middle'//基线对齐选项,决定文字垂直方向的对齐方式
|
|
|
if (rotate) {
|
|
|
ctx.translate(canvas.width / 2, canvas.height);
|
|
|
ctx.rotate(-Math.PI / 2);
|
|
|
ctx.fillText(content, h, 0);
|
|
|
ctx.strokeText(content, h, 0);
|
|
|
} else {
|
|
|
for (let i = 0; i < 1; i++) {
|
|
|
ctx.fillText(content[i], 0, canvas.height / 2);
|
|
|
ctx.strokeText(content[i], 0, canvas.height / 2);
|
|
|
}
|
|
|
}
|
|
|
let texture = new THREE.CanvasTexture(canvas)
|
|
|
// texture.needsUpdate = true//如果编码类型在纹理已被一个材质使用之后发生了改变, 你需要来设置Material.needsUpdate为true来使得材质重新编译
|
|
|
let geometry = new THREE.BufferGeometry(); //创建一个Buffer类型几何体对象
|
|
|
//3个为一组,表示一个顶点的xyz坐标
|
|
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute(xyz, 3));
|
|
|
const material = new THREE.PointsMaterial({
|
|
|
map: texture,
|
|
|
transparent: true,//材质透明
|
|
|
size: size ? size : 16.0, //点对象像素尺寸
|
|
|
sizeAttenuation: false
|
|
|
});
|
|
|
|
|
|
const pointsText = new THREE.Points(geometry, material);
|
|
|
pointsText.name = 'pointsText';
|
|
|
return pointsText;
|
|
|
}
|
|
|
const drawSinLine = (obj) => {
|
|
|
let { line, color } = obj, list = [];
|
|
|
line.forEach(item => {
|
|
|
list.push(new THREE.Vector3(...item));
|
|
|
})
|
|
|
let geometry = new THREE.BufferGeometry(); //声明一个几何体对象Geometry
|
|
|
// 三维样条曲线 Catmull-Rom算法
|
|
|
let curve = new THREE.CatmullRomCurve3(list);
|
|
|
//getPoints是基类Curve的方法,返回一个vector3对象作为元素组成的数组
|
|
|
let points = curve.getPoints(200); //分段数100,返回101个顶点
|
|
|
// setFromPoints方法从points中提取数据改变几何体的顶点属性vertices
|
|
|
geometry.setFromPoints(points);
|
|
|
//材质对象
|
|
|
let material = new THREE.LineBasicMaterial({
|
|
|
color,
|
|
|
linewidth: 1,
|
|
|
});
|
|
|
//线条模型对象
|
|
|
let modelLine = new THREE.Line(geometry, material);
|
|
|
return modelLine
|
|
|
//scene.add(line); //线条对象添加到场景中
|
|
|
};
|
|
|
const createAxisInfo = (size, minSize = 0, newPeriod = 50) => {
|
|
|
const textY = minSize ? (360 / (size + (-minSize))) * minSize - 30 : -30;
|
|
|
let data = {};
|
|
|
let prps = [
|
|
|
{
|
|
|
type: 'plan',
|
|
|
size: newPeriod,
|
|
|
color: 0x32494B,
|
|
|
},
|
|
|
{
|
|
|
type: 'curveLine',
|
|
|
line: [
|
|
|
[0, minSize ? 0 : size / 2, 0],
|
|
|
[0, minSize ? minSize : 0, 90],
|
|
|
[0, size, 270],
|
|
|
[0, minSize ? 0 : size / 2, 360],
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
},
|
|
|
{
|
|
|
type: 'line',
|
|
|
line: [
|
|
|
0, minSize, 0,
|
|
|
0, size, 0,
|
|
|
0, size, 360,
|
|
|
0, minSize, 360,
|
|
|
0, minSize, 0,
|
|
|
|
|
|
0, 0, 0,
|
|
|
newPeriod, 0, 0,
|
|
|
newPeriod, 0, 360,
|
|
|
0, 0, 360,
|
|
|
0, 0, 0,
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
},
|
|
|
{
|
|
|
type: 'line',
|
|
|
line: [
|
|
|
0, 0, 90,
|
|
|
newPeriod, 0, 90,
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
position: 'Z',
|
|
|
num: 3,
|
|
|
margin: 90,
|
|
|
},
|
|
|
{
|
|
|
type: 'line',
|
|
|
line: [
|
|
|
newPeriod / 4, 0, 0,
|
|
|
newPeriod / 4, 0, 360,
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
position: 'X',
|
|
|
num: 3,
|
|
|
margin: newPeriod / 4,
|
|
|
},
|
|
|
{
|
|
|
type: 'line',
|
|
|
line: [
|
|
|
0, (minSize ? minSize + (size / 2) : size / 4), 0,
|
|
|
0, (minSize ? minSize + (size / 2) : size / 4), 360,
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
position: 'Y',
|
|
|
num: 3,
|
|
|
margin: minSize ? size / 2 : size / 4,
|
|
|
},
|
|
|
{
|
|
|
type: 'line',
|
|
|
line: [
|
|
|
0, minSize, 90,
|
|
|
0, size, 90,
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
position: 'Z',
|
|
|
num: 3,
|
|
|
margin: 90,
|
|
|
},
|
|
|
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: [formatNumber(newPeriod)],
|
|
|
w: 55,
|
|
|
h: 25,
|
|
|
color: '#ffffff',
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [360, -40, 360],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: [formatNumber(newPeriod / 2)],
|
|
|
color: '#ffffff',
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [180, -40, 360],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['0'],
|
|
|
color: '#ffffff',
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [0, -40, 360],
|
|
|
},
|
|
|
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['0'],
|
|
|
w: 55,
|
|
|
h: 25,
|
|
|
color: '#ffffff',
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [390, -40, 330],
|
|
|
textAlign: 'right',
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['90'],
|
|
|
w: 55,
|
|
|
h: 25,
|
|
|
color: '#ffffff',
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [380, -40, 250],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['180'],
|
|
|
w: 75,
|
|
|
h: 25,
|
|
|
ch: 9,
|
|
|
cw: 5.7,
|
|
|
color: '#ffffff',
|
|
|
size: 20.3,
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [380, -40, 160],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['270'],
|
|
|
w: 75,
|
|
|
h: 25,
|
|
|
ch: 9,
|
|
|
cw: 5.7,
|
|
|
color: '#ffffff',
|
|
|
size: 20.3,
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [380, -40, 70],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['360'],
|
|
|
w: 75,
|
|
|
h: 25,
|
|
|
ch: 9,
|
|
|
cw: 5.7,
|
|
|
color: '#ffffff',
|
|
|
size: 20.3,
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [380, -40, -20],
|
|
|
},
|
|
|
]
|
|
|
let prpd = [
|
|
|
{
|
|
|
type: 'curveLine',
|
|
|
line: [
|
|
|
[0, minSize ? 0 : size / 2, 0],
|
|
|
[0, minSize ? minSize : 0, 90],
|
|
|
[0, size, 270],
|
|
|
[0, minSize ? 0 : size / 2, 360],
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
},
|
|
|
{
|
|
|
type: 'line',
|
|
|
line: [
|
|
|
0, minSize, 0,
|
|
|
0, size, 0,
|
|
|
0, size, 360,
|
|
|
0, minSize, 360,
|
|
|
0, minSize, 0,
|
|
|
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
},
|
|
|
{
|
|
|
type: 'line',
|
|
|
line: [
|
|
|
0, (minSize ? minSize + (size / 2) : size / 4), 0,
|
|
|
0, (minSize ? minSize + (size / 2) : size / 4), 360,
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
position: 'Y',
|
|
|
num: 3,
|
|
|
margin: minSize ? size / 2 : size / 4,
|
|
|
},
|
|
|
{
|
|
|
type: 'line',
|
|
|
line: [
|
|
|
0, minSize, 90,
|
|
|
0, size, 90,
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
position: 'Z',
|
|
|
num: 3,
|
|
|
margin: 90,
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['0'],
|
|
|
w: 40,
|
|
|
h: 25,
|
|
|
color: '#ffffff',
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [0, textY, 340],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['90'],
|
|
|
w: 47,
|
|
|
h: 25,
|
|
|
color: '#ffffff',
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [0, textY, 270],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['180'],
|
|
|
w: 75,
|
|
|
h: 25,
|
|
|
ch: 9,
|
|
|
cw: 5.7,
|
|
|
color: '#ffffff',
|
|
|
size: 22.3,
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [0, textY - 2, 180],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['270'],
|
|
|
w: 75,
|
|
|
h: 25,
|
|
|
ch: 9,
|
|
|
cw: 5.7,
|
|
|
color: '#ffffff',
|
|
|
size: 22.3,
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [0, textY - 2, 90],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['360'],
|
|
|
w: 75,
|
|
|
h: 25,
|
|
|
ch: 9,
|
|
|
cw: 5.7,
|
|
|
color: '#ffffff',
|
|
|
size: 22.3,
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [0, textY - 2, 0],
|
|
|
},
|
|
|
];
|
|
|
let prpd3d = [
|
|
|
{
|
|
|
type: 'plan',
|
|
|
size,
|
|
|
color: 0x32494B,
|
|
|
},
|
|
|
{
|
|
|
type: 'curveLine',
|
|
|
line: [
|
|
|
[minSize ? 0 : size / 2, 0, 0],
|
|
|
[size, 0, 90],
|
|
|
[minSize, 0, 270],
|
|
|
[minSize ? 0 : size / 2, 0, 360],
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
},
|
|
|
{
|
|
|
type: 'line',
|
|
|
line: [
|
|
|
minSize, 0, 0,
|
|
|
size, 0, 0,
|
|
|
size, 0, 360,
|
|
|
minSize, 0, 360,
|
|
|
minSize, 0, 0,
|
|
|
|
|
|
0, 0, 0,
|
|
|
0, newPeriod, 0,
|
|
|
0, newPeriod, 360,
|
|
|
0, 0, 360,
|
|
|
0, 0, 0,
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
},
|
|
|
{
|
|
|
type: 'line',
|
|
|
line: [
|
|
|
minSize, 0, 90,
|
|
|
size, 0, 90,
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
position: 'Z',
|
|
|
num: 3,
|
|
|
margin: 90,
|
|
|
},
|
|
|
{
|
|
|
type: 'line',
|
|
|
line: [
|
|
|
(minSize ? minSize + (size / 2) : size / 4), 0, 0,
|
|
|
(minSize ? minSize + (size / 2) : size / 4), 0, 360,
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
position: 'X',
|
|
|
num: 3,
|
|
|
margin: (minSize ? size / 2 : size / 4),
|
|
|
},
|
|
|
{
|
|
|
type: 'line',
|
|
|
line: [
|
|
|
0, 0, 90,
|
|
|
0, newPeriod, 90,
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
position: 'Z',
|
|
|
num: 3,
|
|
|
margin: 90,
|
|
|
},
|
|
|
{
|
|
|
type: 'line',
|
|
|
line: [
|
|
|
0, newPeriod / 4, 0,
|
|
|
0, newPeriod / 4, 360,
|
|
|
],
|
|
|
color: 0xffffff,
|
|
|
position: 'Y',
|
|
|
num: 3,
|
|
|
margin: newPeriod / 4,
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['0'],
|
|
|
color: '#ffffff',
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [0, 0, 380],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: [formatNumber(newPeriod / 2)],
|
|
|
color: '#ffffff',
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [0, 180, 400],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: [formatNumber(newPeriod)],
|
|
|
color: '#ffffff',
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [0, 360, 400],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['0'],
|
|
|
w: 40,
|
|
|
h: 25,
|
|
|
color: '#ffffff',
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [minSize ? 200 : 400, -40, 330],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['90'],
|
|
|
w: 47,
|
|
|
h: 25,
|
|
|
color: '#ffffff',
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [minSize ? 200 : 400, -40, 230],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['180'],
|
|
|
w: 75,
|
|
|
h: 25,
|
|
|
ch: 9,
|
|
|
cw: 5.7,
|
|
|
color: '#ffffff',
|
|
|
size: 22.3,
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [minSize ? 200 : 400, -40, 140],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['270'],
|
|
|
w: 75,
|
|
|
h: 25,
|
|
|
ch: 9,
|
|
|
cw: 5.7,
|
|
|
color: '#ffffff',
|
|
|
size: 22.3,
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [minSize ? 200 : 400, -40, 50],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
content: ['360'],
|
|
|
w: 75,
|
|
|
h: 25,
|
|
|
ch: 9,
|
|
|
cw: 5.7,
|
|
|
color: '#ffffff',
|
|
|
size: 22.3,
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [minSize ? 200 : 400, -40, -40],
|
|
|
},
|
|
|
];
|
|
|
prps = prps.map(i => i.type !== 'text' ? i : { ...i, ...fontStyles[i.content[0].length] });
|
|
|
prpd3d = prpd3d.map(i => i.type !== 'text' ? i : { ...i, ...fontStyles[i.content[0].length] })
|
|
|
let set = [360 / newPeriod, 360 / (size + (-minSize)), 1];
|
|
|
let isGIS = size <= 80;
|
|
|
let YAxisText = minSize ? [
|
|
|
[size, 180],
|
|
|
[size / 2, 90],
|
|
|
[0, 0],
|
|
|
[minSize / 2, -90],
|
|
|
[minSize, -180],
|
|
|
] : [
|
|
|
[isGIS ? size == 80 ? 0 : `-${80 - size}` : size],
|
|
|
[isGIS ? `-${80 - (size * 0.75)}` : size * 0.75,],
|
|
|
[isGIS ? `-${80 - (size * 0.5)}` : size * 0.5,],
|
|
|
[isGIS ? `-${80 - (0.25 * size)}` : size * 0.25,],
|
|
|
[isGIS ? `-80` : 0,],
|
|
|
];
|
|
|
let infoNum = [
|
|
|
{
|
|
|
w: 40,
|
|
|
h: 20,
|
|
|
size: 16,
|
|
|
}, {
|
|
|
w: 55,
|
|
|
h: 25,
|
|
|
size: 22.3,
|
|
|
}, {
|
|
|
w: 70,
|
|
|
h: 25,
|
|
|
size: 22.3,
|
|
|
}, {
|
|
|
w: 100,
|
|
|
h: 30,
|
|
|
size: 26.3,
|
|
|
}, {
|
|
|
w: 115,
|
|
|
h: 32,
|
|
|
size: 28.3,
|
|
|
}
|
|
|
]
|
|
|
YAxisText.forEach((item, index) => {
|
|
|
item[0] = Math.ceil(item[0]).toString();
|
|
|
let length = item[0].length;
|
|
|
let info = {
|
|
|
type: 'text',
|
|
|
content: item,
|
|
|
w: infoNum[length - 1].w,
|
|
|
h: infoNum[length - 1].h,
|
|
|
cw: length >= 3 ? 5.7 : '',
|
|
|
ch: length >= 3 ? 9 : '',
|
|
|
size: infoNum[length - 1].size,
|
|
|
color: '#ffffff',
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [index, minSize ? item[1] : 360 - (index * 90), length === 1 ? 410 : 430],
|
|
|
textAlign: 'right',
|
|
|
};
|
|
|
let infoPrpd = {
|
|
|
type: 'text',
|
|
|
content: item,
|
|
|
w: infoNum[length - 1].w,
|
|
|
h: infoNum[length - 1].h,
|
|
|
cw: length >= 3 ? 5.7 : '',
|
|
|
ch: length >= 3 ? 9 : '',
|
|
|
size: infoNum[length - 1].size,
|
|
|
color: '#ffffff',
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [0, minSize ? item[1] : 360 - (index * 90), length === 1 ? 385 : 395],
|
|
|
textAlign: 'right',
|
|
|
};
|
|
|
let info3d = {
|
|
|
type: 'text',
|
|
|
content: item,
|
|
|
w: infoNum[length - 1].w,
|
|
|
h: infoNum[length - 1].h,
|
|
|
cw: length >= 3 ? 5.7 : '',
|
|
|
ch: length >= 3 ? 9 : '',
|
|
|
size: infoNum[length - 1].size,
|
|
|
color: '#ffffff',
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [minSize ? item[1] - 20 : 360 - (index * 90), -40, 380],
|
|
|
textAlign: 'right',
|
|
|
};
|
|
|
prps.push(info);
|
|
|
prpd.push(infoPrpd);
|
|
|
prpd3d.push(info3d);
|
|
|
})
|
|
|
data[size] = { set, prps, prpd, prpd3d }
|
|
|
return data;
|
|
|
}
|
|
|
createApp({
|
|
|
setup() {
|
|
|
const testPrpsData = ref([])
|
|
|
const testPrpdData = ref([])
|
|
|
const prpsBoxRef = ref();
|
|
|
const prpdBoxRef = ref();
|
|
|
const currentPrpsData = ref([])
|
|
|
const currentPrpdData = ref([])
|
|
|
const timer = ref([])
|
|
|
let renderer = null
|
|
|
let scene = null
|
|
|
let camera = null
|
|
|
let controls = null
|
|
|
let axis = new THREE.Group()
|
|
|
let meshList = new THREE.Group() //图例柱子
|
|
|
meshList.name = 'chartsMesh'
|
|
|
let pointsList = new THREE.Group()
|
|
|
pointsList.name = 'chartsPoint'
|
|
|
let geometry = '' //InstancedMesh的初始几何体
|
|
|
let initInstancedMesh = ''
|
|
|
let pointPositions = ''
|
|
|
let pointsColor = '' //Points实例的位置及颜色的集合
|
|
|
let dummy = new THREE.Object3D()
|
|
|
let lut = new THREE.Lut()
|
|
|
lut.addColorMap('axis', [
|
|
|
[0.0, 0x00ee00],
|
|
|
[0.25, 0xeeee00],
|
|
|
[0.75, 0xee0000],
|
|
|
[1.0, 0x4e0211],
|
|
|
])
|
|
|
lut.setColorMap('axis', 32)
|
|
|
const maxNum = 80
|
|
|
const minNum = 0
|
|
|
const range = 100
|
|
|
let count = 0
|
|
|
let period = 50
|
|
|
let phase = 128
|
|
|
// prpd相关参数
|
|
|
let count1 = 0
|
|
|
let geometry1 = ''
|
|
|
let pointPositions1 = ''
|
|
|
let pointsColor1 = ''
|
|
|
let initInstancedMesh1 = ''
|
|
|
let renderer1 = null
|
|
|
let scene1 = null
|
|
|
let camera1 = null
|
|
|
let controls1 = null
|
|
|
let axis1 = new THREE.Group()
|
|
|
let meshList1 = new THREE.Group() //图例柱子
|
|
|
meshList1.name = 'chartsMesh1'
|
|
|
let dummy1 = new THREE.Object3D()
|
|
|
const delay = (id, callBack, ms) => {
|
|
|
return new Promise((resolve) => {
|
|
|
timer.value[id] = setTimeout(() => {
|
|
|
callBack();
|
|
|
resolve();
|
|
|
}, ms);
|
|
|
});
|
|
|
};
|
|
|
const onStarPrps = async () => {
|
|
|
console.log('[ unref(testPrpsData) ] >', unref(testPrpsData))
|
|
|
const data = unref(testPrpsData);
|
|
|
for (let [index, item] of data.entries()) {
|
|
|
let callBack = () => {
|
|
|
currentPrpsData.value = item;
|
|
|
};
|
|
|
await delay(index, callBack, 20);
|
|
|
}
|
|
|
// 等待所有延迟完成后再次开始
|
|
|
onStarPrps();
|
|
|
};
|
|
|
const onStarPrpd = () => {
|
|
|
let index = 0;
|
|
|
// 每1秒执行一次
|
|
|
const intervalId = setInterval(() => {
|
|
|
currentPrpdData.value = unref(testPrpdData)[index];
|
|
|
index = (index + 1) % unref(testPrpdData).length; // 循环索引
|
|
|
}, 1000);
|
|
|
|
|
|
// 需要停止时调用 clearInterval(intervalId);
|
|
|
}
|
|
|
const drawAxis = (size, minNum, type) => {
|
|
|
if (type === 1) {
|
|
|
if (!scene) {
|
|
|
return
|
|
|
}
|
|
|
const options = ['prps', 'prpd', 'prpd3d']
|
|
|
let key = options[type - 1]
|
|
|
const data = createAxisInfo(size, minNum, 50)
|
|
|
let [x, y, z] = data[size]['set'] // 坐标比例
|
|
|
scene.remove(...scene.children.filter((item) => item.name === 'axisNumberText'))
|
|
|
if (axis.children.length) {
|
|
|
axis.remove(...axis.children)
|
|
|
}
|
|
|
let axisText = new THREE.Group()
|
|
|
axisText.name = 'axisNumberText'
|
|
|
scene.add(axisText)
|
|
|
axis.scale.set(x, y, z)
|
|
|
meshList.scale.set(x, y, z)
|
|
|
pointsList.scale.set(x, y, z)
|
|
|
data[size][key].forEach(async (item) => {
|
|
|
switch (item.type) {
|
|
|
case 'plan':
|
|
|
axis.add(drawPlan(item)) // 有可能默认有面
|
|
|
break
|
|
|
case 'line':
|
|
|
axis.add(...drawLine(item))
|
|
|
break
|
|
|
case 'curveLine':
|
|
|
axis.add(drawSinLine(item))
|
|
|
break
|
|
|
case 'text':
|
|
|
axisText.add(draw2DText(item))
|
|
|
}
|
|
|
})
|
|
|
} else {
|
|
|
if (!scene1) {
|
|
|
return
|
|
|
}
|
|
|
const options = ['prps', 'prpd', 'prpd3d']
|
|
|
let key = options[type - 1]
|
|
|
const data = createAxisInfo(size, minNum, 50)
|
|
|
let [x, y, z] = data[size]['set'] // 坐标比例
|
|
|
scene1.remove(...scene1.children.filter((item) => item.name === 'axisNumberText'))
|
|
|
if (axis1.children.length) {
|
|
|
axis1.remove(...axis1.children)
|
|
|
}
|
|
|
let axisText1 = new THREE.Group()
|
|
|
axisText1.name = 'axisNumberText'
|
|
|
scene1.add(axisText1)
|
|
|
axis1.scale.set(x, y, z)
|
|
|
meshList.scale.set(x, y, z)
|
|
|
pointsList.scale.set(x, y, z)
|
|
|
data[size][key].forEach(async (item) => {
|
|
|
switch (item.type) {
|
|
|
case 'plan':
|
|
|
axis1.add(drawPlan(item)) // 有可能默认有面
|
|
|
break
|
|
|
case 'line':
|
|
|
axis1.add(...drawLine(item))
|
|
|
break
|
|
|
case 'curveLine':
|
|
|
axis1.add(drawSinLine(item))
|
|
|
break
|
|
|
case 'text':
|
|
|
axisText1.add(draw2DText(item))
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
const initChart = () => {
|
|
|
count = period * phase
|
|
|
geometry = new THREE.BoxGeometry(0.6, 1, 2.5) // 高度初始化比例为1
|
|
|
initInstancedMesh = new THREE.InstancedMesh(geometry, new THREE.MeshBasicMaterial(), count)
|
|
|
let k = 0
|
|
|
let initList = generateArray(period, phase)
|
|
|
for (let j = 0; j < initList.length; j++) {
|
|
|
for (let i = 0; i < initList[j].length; i++) {
|
|
|
const [a, b, c] = initList[j][i]
|
|
|
dummy.position.set(j, 99999, b)
|
|
|
dummy.scale.y = c
|
|
|
dummy.updateMatrix()
|
|
|
initInstancedMesh.setMatrixAt(k, dummy.matrix)
|
|
|
const color = lut.getColor(Math.abs(c) / maxNum)
|
|
|
initInstancedMesh.setColorAt(k, color)
|
|
|
k++
|
|
|
}
|
|
|
}
|
|
|
initInstancedMesh.instanceMatrix.needsUpdate = true
|
|
|
initInstancedMesh.instanceColor.needsUpdate = true
|
|
|
const object = meshList.getObjectByName('initInstancedMesh')
|
|
|
if (!object) {
|
|
|
meshList.add(initInstancedMesh)
|
|
|
}
|
|
|
}
|
|
|
const draw2DText = (obj) => {
|
|
|
let scale = 1;//window.devicePixelRatio;
|
|
|
let { content, w, h, color, font, xyz, size, cw, ch, rotate } = obj;
|
|
|
cw = cw ? cw : 6;
|
|
|
ch = ch ? ch : 8;
|
|
|
let canvas = document.createElement('canvas')
|
|
|
canvas.style.width = w * scale;
|
|
|
canvas.style.height = h * scale;
|
|
|
if (rotate) {
|
|
|
canvas.width = w * cw * scale * 3;
|
|
|
canvas.height = h * ch * scale * 2;
|
|
|
} else {
|
|
|
canvas.width = w * cw * scale * 2;
|
|
|
canvas.height = h * ch * scale * 3;
|
|
|
}
|
|
|
|
|
|
let ctx = canvas.getContext('2d');
|
|
|
ctx.fillStyle = color;
|
|
|
ctx.strokeStyle = '#000';
|
|
|
ctx.font = font;
|
|
|
ctx.direction = "ltr"; // 文本方向从左向右
|
|
|
ctx.textAlign = "left"; // 左对齐
|
|
|
ctx.textBaseline = 'middle'//基线对齐选项,决定文字垂直方向的对齐方式
|
|
|
if (rotate) {
|
|
|
ctx.translate(canvas.width / 2, canvas.height);
|
|
|
ctx.rotate(-Math.PI / 2);
|
|
|
ctx.fillText(content, h, 0);
|
|
|
ctx.strokeText(content, h, 0);
|
|
|
} else {
|
|
|
for (let i = 0; i < 1; i++) {
|
|
|
ctx.fillText(content[i], 0, canvas.height / 2);
|
|
|
ctx.strokeText(content[i], 0, canvas.height / 2);
|
|
|
}
|
|
|
}
|
|
|
let texture = new THREE.CanvasTexture(canvas)
|
|
|
// texture.needsUpdate = true//如果编码类型在纹理已被一个材质使用之后发生了改变, 你需要来设置Material.needsUpdate为true来使得材质重新编译
|
|
|
let geometry = new THREE.BufferGeometry(); //创建一个Buffer类型几何体对象
|
|
|
//3个为一组,表示一个顶点的xyz坐标
|
|
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute(xyz, 3));
|
|
|
const material = new THREE.PointsMaterial({
|
|
|
map: texture,
|
|
|
transparent: true,//材质透明
|
|
|
size: size ? size : 16.0, //点对象像素尺寸
|
|
|
sizeAttenuation: false
|
|
|
});
|
|
|
|
|
|
const pointsText = new THREE.Points(geometry, material);
|
|
|
pointsText.name = 'pointsText';
|
|
|
return pointsText;
|
|
|
}
|
|
|
// 坐标轴绘制
|
|
|
const drawAxisText = (type) => {
|
|
|
const setTextMap = type === 1 ? {
|
|
|
amplitude: '幅值',
|
|
|
period: '周期',
|
|
|
phase: '相位',
|
|
|
} : {
|
|
|
amplitude: '幅值',
|
|
|
phase: '相位',
|
|
|
}
|
|
|
if (!scene) {
|
|
|
return
|
|
|
}
|
|
|
const isPrpd = type == 2
|
|
|
let axisText = [
|
|
|
{
|
|
|
type: 'text',
|
|
|
lang: true,
|
|
|
content: 'amplitude',
|
|
|
rotate: 90,
|
|
|
color: '#ffffff',
|
|
|
size: 30,
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [
|
|
|
isPrpd ? 0 : -40,
|
|
|
isPrpd ? 165 : 120,
|
|
|
400,
|
|
|
],
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
lang: true,
|
|
|
content: 'period',
|
|
|
xyz: [250, -120, 400],
|
|
|
color: '#ffffff',
|
|
|
size: 30,
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
},
|
|
|
{
|
|
|
type: 'text',
|
|
|
lang: true,
|
|
|
content: 'phase',
|
|
|
color: '#ffffff',
|
|
|
size: 30,
|
|
|
font: 'normal Bold 500px Arial,sans-serif',
|
|
|
xyz: [
|
|
|
isPrpd ? 0 : 460,
|
|
|
isPrpd ? -75 : -100,
|
|
|
170,
|
|
|
],
|
|
|
},
|
|
|
]
|
|
|
let infoNum = [
|
|
|
{},
|
|
|
{
|
|
|
w: 60,
|
|
|
h: 32,
|
|
|
cw: 9,
|
|
|
ch: 8,
|
|
|
size: 26.3,
|
|
|
},
|
|
|
{},
|
|
|
{
|
|
|
w: 125,
|
|
|
h: 50,
|
|
|
size: 38.3,
|
|
|
},
|
|
|
{
|
|
|
w: 125,
|
|
|
h: 50,
|
|
|
size: 38.3,
|
|
|
},
|
|
|
{
|
|
|
w: 130,
|
|
|
h: 50,
|
|
|
size: 38.3,
|
|
|
},
|
|
|
{
|
|
|
w: 165,
|
|
|
h: 122,
|
|
|
size: 68.3,
|
|
|
},
|
|
|
{},
|
|
|
{
|
|
|
w: 165,
|
|
|
h: 122,
|
|
|
size: 68.3,
|
|
|
},
|
|
|
]
|
|
|
let setList = (list) => {
|
|
|
let newList = []
|
|
|
for (let i of list) {
|
|
|
let str = setTextMap[i.content]
|
|
|
if (str) {
|
|
|
i.content = [str]
|
|
|
let { w, h, cw, ch, size } = infoNum[str.length - 1]
|
|
|
i.w = i.rotate ? h : w
|
|
|
i.h = i.rotate ? w : h
|
|
|
i.cw = i.rotate ? ch : cw
|
|
|
i.ch = i.rotate ? cw : ch
|
|
|
i.size = size
|
|
|
if (str.length == 9 && !i.rotate) {
|
|
|
i.w = 200
|
|
|
i.h = 100
|
|
|
}
|
|
|
if (i.rotate) {
|
|
|
i.xyz[2] = 445 + size / 2
|
|
|
}
|
|
|
const mesh = draw2DText(i)
|
|
|
if (isPrpd && str.length > 2 && i.rotate) {
|
|
|
mesh.position.z += 40
|
|
|
}
|
|
|
newList.push(mesh)
|
|
|
}
|
|
|
}
|
|
|
return newList
|
|
|
}
|
|
|
if (isPrpd) {
|
|
|
let textList1 = scene1.getObjectByName('axisText')
|
|
|
if (textList1) {
|
|
|
textList1.children.map((i) => {
|
|
|
i.material.dispose()
|
|
|
i.geometry.dispose()
|
|
|
textList1.remove(i)
|
|
|
})
|
|
|
scene1.remove(textList1)
|
|
|
}
|
|
|
let axisTextGroup1 = new THREE.Group()
|
|
|
axisTextGroup1.name = 'axisText'
|
|
|
axisTextGroup1.add(...setList(axisText))
|
|
|
scene1.add(axisTextGroup1)
|
|
|
} else {
|
|
|
let textList = scene.getObjectByName('axisText')
|
|
|
if (textList) {
|
|
|
textList.children.map((i) => {
|
|
|
i.material.dispose()
|
|
|
i.geometry.dispose()
|
|
|
textList.remove(i)
|
|
|
})
|
|
|
scene.remove(textList)
|
|
|
}
|
|
|
let axisTextGroup = new THREE.Group()
|
|
|
axisTextGroup.name = 'axisText'
|
|
|
axisTextGroup.add(...setList(axisText))
|
|
|
scene.add(axisTextGroup)
|
|
|
}
|
|
|
}
|
|
|
const render = () => {
|
|
|
//获取摄像机的视锥体
|
|
|
const frustum = new THREE.Frustum()
|
|
|
const cameraViewProjectionMatrix = new THREE.Matrix4()
|
|
|
camera.updateMatrixWorld() // 确保摄像机的世界矩阵已更新
|
|
|
cameraViewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)
|
|
|
frustum.setFromProjectionMatrix(cameraViewProjectionMatrix)
|
|
|
// 遍历场景中的每个对象,并根据视锥体进行剔除
|
|
|
scene.traverse((object) => {
|
|
|
if (object.isMesh) {
|
|
|
object.visible = frustum.intersectsObject(object)
|
|
|
}
|
|
|
})
|
|
|
requestAnimationFrame(render)
|
|
|
renderer.render(scene, camera)
|
|
|
}
|
|
|
const render1 = () => {
|
|
|
const frustum = new THREE.Frustum()
|
|
|
const cameraViewProjectionMatrix = new THREE.Matrix4()
|
|
|
camera1.updateMatrixWorld() // 确保摄像机的世界矩阵已更新
|
|
|
cameraViewProjectionMatrix.multiplyMatrices(camera1.projectionMatrix, camera1.matrixWorldInverse)
|
|
|
frustum.setFromProjectionMatrix(cameraViewProjectionMatrix)
|
|
|
// 遍历场景中的每个对象,并根据视锥体进行剔除
|
|
|
scene1.traverse((object) => {
|
|
|
if (object.isMesh) {
|
|
|
object.visible = frustum.intersectsObject(object)
|
|
|
}
|
|
|
})
|
|
|
requestAnimationFrame(render1)
|
|
|
renderer1.render(scene1, camera1)
|
|
|
}
|
|
|
// 初始化prps
|
|
|
const initPrps = () => {
|
|
|
let box = prpsBoxRef.value
|
|
|
let width = box.clientWidth
|
|
|
let height = box.clientHeight //box.clientHeight
|
|
|
renderer = new THREE.WebGLRenderer({ antialias: true })
|
|
|
renderer.autoClear = false
|
|
|
renderer.localClippingEnabled = true
|
|
|
let constant = (360 / (maxNum + -minNum)) * maxNum + 1
|
|
|
const planes = [
|
|
|
new THREE.Plane(new THREE.Vector3(0, -1, 0), constant), //y轴正值截取
|
|
|
new THREE.Plane(new THREE.Vector3(0, 1, 0), constant), //y轴负值截取
|
|
|
]
|
|
|
renderer.clippingPlanes = planes
|
|
|
renderer.setSize(width, height) //设置渲染区域尺寸
|
|
|
renderer.setPixelRatio(window.devicePixelRatio) //设备像素比,优化渲染效果
|
|
|
box.append(renderer.domElement) //body元素中插入canvas对象
|
|
|
scene = new THREE.Scene()
|
|
|
scene.background = new THREE.Color(0x000d13)
|
|
|
scene.add(axis, meshList)
|
|
|
drawAxis(maxNum, minNum, 1)
|
|
|
const setCamera = [500, 300, 500]
|
|
|
let k = width / height //窗口宽高比
|
|
|
let s = 420 //三维场景显示范围控制系数,系数越大,显示的范围越大
|
|
|
//创建相机对象
|
|
|
camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 0.1, 1000)
|
|
|
camera.position.set(...setCamera) //设置相机位置
|
|
|
camera.lookAt(scene.position) //设置相机方向(指向的场景对象)
|
|
|
controls = new THREE.OrbitControls(camera, renderer.domElement)
|
|
|
// 设置左右方向的最大、最小角度限制为 90 度和 0 度
|
|
|
controls.minAzimuthAngle = 0
|
|
|
controls.maxAzimuthAngle = Math.PI / 2
|
|
|
// 设置上下方向的最大、最小角度限制为 90 度和 0 度
|
|
|
controls.minPolarAngle = 0
|
|
|
controls.maxPolarAngle = Math.PI / 2
|
|
|
//设置控制器中心点
|
|
|
controls.target.set(25, 0, 180)
|
|
|
controls.enableZoom = true //启用或禁用摄像机的缩放
|
|
|
controls.maxZoom = 2;
|
|
|
controls.minZoom = 0.5;
|
|
|
initChart()
|
|
|
drawAxisText(1)
|
|
|
render()
|
|
|
}
|
|
|
// 处理数据
|
|
|
const handlePrpsData = (data) => {
|
|
|
const isClear = data.length ? false : true
|
|
|
const max = maxNum * (range / 100)
|
|
|
if (isClear) {
|
|
|
let k = 0,
|
|
|
initList = generateArray(period, phase)
|
|
|
for (let j = 0; j < initList.length; j++) {
|
|
|
for (let i = 0; i < initList[j].length; i++) {
|
|
|
const [a, b, c] = initList[j][i]
|
|
|
dummy.position.set(j, 99999, b)
|
|
|
dummy.scale.y = c
|
|
|
dummy.updateMatrix()
|
|
|
initInstancedMesh.setMatrixAt(k, dummy.matrix)
|
|
|
k++
|
|
|
}
|
|
|
}
|
|
|
initInstancedMesh.instanceMatrix.needsUpdate = true
|
|
|
return
|
|
|
}
|
|
|
let k = 0
|
|
|
let list = JSON.parse(JSON.stringify(data))
|
|
|
list = list.reverse().map((i) => ({
|
|
|
h: i[2],
|
|
|
color: lut.getColor(Math.abs(i[2] / maxNum).toFixed(2)),
|
|
|
}))
|
|
|
|
|
|
for (let j = 0; j < period; j++) {
|
|
|
for (let i = 0; i < list.length; i++) {
|
|
|
initInstancedMesh.getMatrixAt(k, dummy.matrix)
|
|
|
dummy.position.setFromMatrixPosition(dummy.matrix)
|
|
|
dummy.scale.setFromMatrixScale(dummy.matrix)
|
|
|
if (dummy.position.x > 0) {
|
|
|
dummy.position.x -= 1
|
|
|
} else {
|
|
|
let { h, color } = list[i]
|
|
|
dummy.position.x = period - 1
|
|
|
dummy.position.y = Math.abs(h) / 2 // 居中
|
|
|
dummy.scale.y = Math.abs(h)
|
|
|
initInstancedMesh.setColorAt(k, color)
|
|
|
}
|
|
|
|
|
|
dummy.updateMatrix()
|
|
|
initInstancedMesh.setMatrixAt(k, dummy.matrix)
|
|
|
k++
|
|
|
}
|
|
|
}
|
|
|
initInstancedMesh.instanceMatrix.needsUpdate = true
|
|
|
initInstancedMesh.instanceColor.needsUpdate = true
|
|
|
data = null
|
|
|
|
|
|
}
|
|
|
const initChart1 = () => {
|
|
|
let maxYPoints = 800
|
|
|
count1 = maxYPoints * phase
|
|
|
geometry1 = new THREE.BufferGeometry()
|
|
|
pointPositions1 = new Float32Array(count1 * 3) // x, y, z
|
|
|
pointsColor1 = new Float32Array(count1 * 3) // r, g, b
|
|
|
// 初始化点的位置和颜色
|
|
|
for (let i = 0; i < count1; i++) {
|
|
|
const x = 30
|
|
|
const y = period
|
|
|
const z = period
|
|
|
pointPositions1[i * 3] = x
|
|
|
pointPositions1[i * 3 + 1] = y
|
|
|
pointPositions1[i * 3 + 2] = z
|
|
|
pointsColor1[i * 3] = 0
|
|
|
pointsColor1[i * 3 + 1] = 0
|
|
|
pointsColor1[i * 3 + 2] = 0
|
|
|
}
|
|
|
geometry1.setAttribute('position', new THREE.BufferAttribute(pointPositions1, 3))
|
|
|
geometry1.setAttribute('color', new THREE.BufferAttribute(pointsColor1, 3))
|
|
|
// 创建 ShaderMaterial
|
|
|
const material = new THREE.PointsMaterial({ size: 2, vertexColors: true }) //双面显示
|
|
|
// 创建 Points 对象
|
|
|
initInstancedMesh1 = new THREE.Points(geometry1, material)
|
|
|
const object = meshList1.getObjectByName('initInstancedPoints')
|
|
|
if (!object) {
|
|
|
meshList1.add(initInstancedMesh1)
|
|
|
}
|
|
|
}
|
|
|
const initPrpd = () => {
|
|
|
const box = prpdBoxRef.value
|
|
|
const width = box.clientWidth
|
|
|
const height = box.clientHeight //box.clientHeight
|
|
|
renderer1 = new THREE.WebGLRenderer({ antialias: true })
|
|
|
renderer1.autoClear = false
|
|
|
renderer1.localClippingEnabled = true
|
|
|
let constant = (360 / (maxNum + -minNum)) * maxNum + 1
|
|
|
const planes = [
|
|
|
new THREE.Plane(new THREE.Vector3(0, -1, 0), constant), //y轴正值截取
|
|
|
new THREE.Plane(new THREE.Vector3(0, 1, 0), constant), //y轴负值截取
|
|
|
]
|
|
|
renderer1.clippingPlanes = planes
|
|
|
renderer1.setSize(width, height) //设置渲染区域尺寸
|
|
|
renderer1.setPixelRatio(window.devicePixelRatio) //设备像素比,优化渲染效果
|
|
|
box.append(renderer1.domElement) //body元素中插入canvas对象
|
|
|
scene1 = new THREE.Scene()
|
|
|
scene1.background = new THREE.Color(0x000d13)
|
|
|
scene1.add(axis1, meshList1)
|
|
|
drawAxis(maxNum, minNum, 2)
|
|
|
const setCamera = [600, 0, 0]
|
|
|
let k = width / height //窗口宽高比
|
|
|
let s = 280 //三维场景显示范围控制系数,系数越大,显示的范围越大
|
|
|
//创建相机对象
|
|
|
camera1 = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 0.1, 1000)
|
|
|
camera1.position.set(...setCamera) //设置相机位置
|
|
|
const isPrpd = true
|
|
|
camera1.left = -s * k - (isPrpd ? 200 : 20)
|
|
|
camera1.right = s * k - (isPrpd ? 200 : 20)
|
|
|
camera1.top = s + (isPrpd ? (minNum ? -30 : 155) : isPrps3d ? 60 : minNum ? -80 : 60)
|
|
|
camera1.bottom = -s + (isPrpd ? (minNum ? -30 : 155) : isPrps3d ? 60 : minNum ? -80 : 60)
|
|
|
camera1.aspect = k
|
|
|
camera1.updateProjectionMatrix()
|
|
|
camera1.lookAt(scene1.position) //设置相机方向(指向的场景对象)
|
|
|
controls1 = new THREE.OrbitControls(camera1, renderer1.domElement)
|
|
|
// 设置左右方向的最大、最小角度限制为 90 度和 0 度
|
|
|
controls1.minAzimuthAngle = 0
|
|
|
controls1.maxAzimuthAngle = Math.PI / 2
|
|
|
// 设置上下方向的最大、最小角度限制为 90 度和 0 度
|
|
|
controls1.minPolarAngle = 0
|
|
|
controls1.maxPolarAngle = Math.PI / 2
|
|
|
//设置控制器中心点
|
|
|
controls1.target.set(25, 0, 180)
|
|
|
controls1.enableZoom = true //启用或禁用摄像机的缩放
|
|
|
controls1.maxZoom = 2;
|
|
|
controls1.minZoom = 0.5;
|
|
|
controls1.enableRotate = false //启用或禁用摄像机水平或垂直旋转
|
|
|
controls1.enableZoom = false //启用或禁用摄像机的缩放
|
|
|
controls1.enableZoom = false //启用或禁用摄像机的缩放
|
|
|
initChart1()
|
|
|
drawAxisText(2)
|
|
|
render1()
|
|
|
}
|
|
|
const handlePrpdData = (data) => {
|
|
|
const isClear = data.length ? false : true
|
|
|
const max = maxNum * (range / 100)
|
|
|
const colors = [
|
|
|
{
|
|
|
color: new THREE.Color(0x50a3ba),
|
|
|
num: 5,
|
|
|
},
|
|
|
{
|
|
|
color: new THREE.Color(0x9db578),
|
|
|
num: 7,
|
|
|
},
|
|
|
{
|
|
|
color: new THREE.Color(0xeac736),
|
|
|
num: 10,
|
|
|
},
|
|
|
{
|
|
|
color: new THREE.Color(0xe28b4a),
|
|
|
num: 14,
|
|
|
},
|
|
|
]
|
|
|
data.forEach((item, index) => {
|
|
|
// 更新颜色
|
|
|
const color =
|
|
|
item[2] <= 3
|
|
|
? new THREE.Color(0x0082df)
|
|
|
: item[2] >= 15
|
|
|
? new THREE.Color(0xd94e5d)
|
|
|
: colors.find((i) => item[2] <= i.num).color
|
|
|
pointPositions1[index * 3] = 1 // X
|
|
|
pointPositions1[index * 3 + 1] = item[0] //> max ? max : item[0] // Y
|
|
|
pointPositions1[index * 3 + 2] = 360 - item[1] // Z
|
|
|
pointsColor1[index * 3] = color.r //R
|
|
|
pointsColor1[index * 3 + 1] = color.g //G
|
|
|
pointsColor1[index * 3 + 2] = color.b //B
|
|
|
})
|
|
|
for (let i = data.length - 1; i < count1; i++) {
|
|
|
pointPositions1[i * 3 + 1] = 9999999 // Y//移到y轴高出,隐藏多余的点
|
|
|
}
|
|
|
// 通知 Three.js 更新 BufferGeometry
|
|
|
geometry1.attributes.position.needsUpdate = true
|
|
|
geometry1.attributes.color.needsUpdate = true
|
|
|
geometry1.computeBoundingSphere() //重新计算边界球体 解决顶点的位置超出了原来的范围,而默认的渲染器无法正确处理这种变化,导致points不可见的bug
|
|
|
}
|
|
|
// 监听当前prps
|
|
|
watch(currentPrpsData, (data) => {
|
|
|
handlePrpsData(data)
|
|
|
}, { deep: true })
|
|
|
// 监听当前prpd
|
|
|
watch(currentPrpdData, (data) => {
|
|
|
console.log('[ data ] >', data)
|
|
|
handlePrpdData(data)
|
|
|
}, { deep: true })
|
|
|
onMounted(() => {
|
|
|
initPrps();
|
|
|
initPrpd();
|
|
|
// 加载prps数据
|
|
|
fetch('./three/prps.json') // JSON文件路径
|
|
|
.then(response => response.json()) // 解析为JSON对象
|
|
|
.then(data => {
|
|
|
testPrpsData.value = data;
|
|
|
})
|
|
|
.catch(error => console.error('加载JSON失败:', error));
|
|
|
// 加载prpd数据
|
|
|
fetch('./three/prpd.json') // JSON文件路径
|
|
|
.then(response => response.json()) // 解析为JSON对象
|
|
|
.then(data => {
|
|
|
console.log('[ data ] >', data)
|
|
|
testPrpdData.value = data
|
|
|
})
|
|
|
.catch(error => console.error('加载JSON失败:', error));
|
|
|
})
|
|
|
return {
|
|
|
prpsBoxRef,
|
|
|
prpdBoxRef,
|
|
|
testPrpsData,
|
|
|
testPrpdData,
|
|
|
currentPrpsData,
|
|
|
currentPrpdData,
|
|
|
onStarPrps,
|
|
|
onStarPrpd,
|
|
|
timer
|
|
|
};
|
|
|
}
|
|
|
})
|
|
|
.use(antd)
|
|
|
.use(ElementPlus, {
|
|
|
locale: ElementPlusLocaleZhCn // 使用中文语言包
|
|
|
})
|
|
|
.mount('#app');
|
|
|
</script>
|
|
|
</body>
|
|
|
|
|
|
</html> |