草地
This commit is contained in:
161
src/utils/plane_grass.js
Normal file
161
src/utils/plane_grass.js
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import * as THREE from "three";
|
||||||
|
const BLADE_WIDTH = 0.1
|
||||||
|
const BLADE_HEIGHT = 0.8
|
||||||
|
const BLADE_HEIGHT_VARIATION = 0.6
|
||||||
|
const BLADE_VERTEX_COUNT = 5
|
||||||
|
const BLADE_TIP_OFFSET = 0.1
|
||||||
|
|
||||||
|
function interpolate(val, oldMin, oldMax, newMin, newMax) {
|
||||||
|
return ((val - oldMin) * (newMax - newMin)) / (oldMax - oldMin) + newMin
|
||||||
|
}
|
||||||
|
|
||||||
|
class GrassGeometry extends THREE.BufferGeometry {
|
||||||
|
constructor(size, count) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
const positions = []
|
||||||
|
const uvs = []
|
||||||
|
const indices = []
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const surfaceMin = (size / 2) * -1
|
||||||
|
const surfaceMax = size / 2
|
||||||
|
|
||||||
|
// ✅ 直接在正方形范围内取随机点
|
||||||
|
const x = surfaceMin + Math.random() * size
|
||||||
|
const y = surfaceMin + Math.random() * size
|
||||||
|
|
||||||
|
uvs.push(
|
||||||
|
...Array.from({ length: BLADE_VERTEX_COUNT }).flatMap(() => [
|
||||||
|
interpolate(x, surfaceMin, surfaceMax, 0, 1),
|
||||||
|
interpolate(y, surfaceMin, surfaceMax, 0, 1)
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
const blade = this.computeBlade([x, 0, y], i)
|
||||||
|
positions.push(...blade.positions)
|
||||||
|
indices.push(...blade.indices)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.setAttribute(
|
||||||
|
'position',
|
||||||
|
new THREE.BufferAttribute(new Float32Array(positions), 3)
|
||||||
|
)
|
||||||
|
this.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(uvs), 2))
|
||||||
|
this.setIndex(indices)
|
||||||
|
this.computeVertexNormals()
|
||||||
|
}
|
||||||
|
computeBlade(center, index = 0) {
|
||||||
|
const height = BLADE_HEIGHT + Math.random() * BLADE_HEIGHT_VARIATION
|
||||||
|
const vIndex = index * BLADE_VERTEX_COUNT
|
||||||
|
|
||||||
|
// Randomize blade orientation and tip angle
|
||||||
|
const yaw = Math.random() * Math.PI * 2
|
||||||
|
const yawVec = [Math.sin(yaw), 0, -Math.cos(yaw)]
|
||||||
|
const bend = Math.random() * Math.PI * 2
|
||||||
|
const bendVec = [Math.sin(bend), 0, -Math.cos(bend)]
|
||||||
|
|
||||||
|
// Calc bottom, middle, and tip vertices
|
||||||
|
const bl = yawVec.map((n, i) => n * (BLADE_WIDTH / 2) * 1 + center[i])
|
||||||
|
const br = yawVec.map((n, i) => n * (BLADE_WIDTH / 2) * -1 + center[i])
|
||||||
|
const tl = yawVec.map((n, i) => n * (BLADE_WIDTH / 4) * 1 + center[i])
|
||||||
|
const tr = yawVec.map((n, i) => n * (BLADE_WIDTH / 4) * -1 + center[i])
|
||||||
|
const tc = bendVec.map((n, i) => n * BLADE_TIP_OFFSET + center[i])
|
||||||
|
|
||||||
|
// Attenuate height
|
||||||
|
tl[1] += height / 2
|
||||||
|
tr[1] += height / 2
|
||||||
|
tc[1] += height
|
||||||
|
|
||||||
|
return {
|
||||||
|
positions: [...bl, ...br, ...tr, ...tl, ...tc],
|
||||||
|
indices: [
|
||||||
|
vIndex,
|
||||||
|
vIndex + 1,
|
||||||
|
vIndex + 2,
|
||||||
|
vIndex + 2,
|
||||||
|
vIndex + 4,
|
||||||
|
vIndex + 3,
|
||||||
|
vIndex + 3,
|
||||||
|
vIndex,
|
||||||
|
vIndex + 2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Grass extends THREE.Mesh {
|
||||||
|
constructor(size, count) {
|
||||||
|
const geometry = new GrassGeometry(size, count)
|
||||||
|
const material = new THREE.ShaderMaterial({
|
||||||
|
uniforms: {
|
||||||
|
uCloud: { value: "" },
|
||||||
|
offsetX: { value: 0.5 },
|
||||||
|
offsetY: { value: 0.3 },
|
||||||
|
uTime: { value: 0 },
|
||||||
|
},
|
||||||
|
side: THREE.DoubleSide,
|
||||||
|
vertexShader: ` uniform float uTime;
|
||||||
|
uniform float offsetX;
|
||||||
|
uniform float offsetY;
|
||||||
|
|
||||||
|
varying vec3 vPosition;
|
||||||
|
varying vec2 vUv;
|
||||||
|
varying vec3 vNormal;
|
||||||
|
|
||||||
|
float wave(float waveSize, float tipDistance, float centerDistance) {
|
||||||
|
// Tip is the fifth vertex drawn per blade
|
||||||
|
bool isTip = (gl_VertexID + 1) % 5 == 0;
|
||||||
|
|
||||||
|
float waveDistance = isTip ? tipDistance : centerDistance;
|
||||||
|
return sin((uTime / 500.0) + waveSize) * waveDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vPosition = position;
|
||||||
|
vUv = uv;
|
||||||
|
|
||||||
|
// Cloud shadow move
|
||||||
|
vUv.x += uTime * 0.0001 * offsetX;
|
||||||
|
vUv.y += uTime * 0.0001 * offsetY;
|
||||||
|
|
||||||
|
vNormal = normalize(normalMatrix * normal);
|
||||||
|
if (vPosition.y < 0.0) {
|
||||||
|
vPosition.y = 0.0;
|
||||||
|
} else {
|
||||||
|
vPosition.x += wave(uv.x * 10.0, 0.3, 0.1);
|
||||||
|
}
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(vPosition, 1.0);
|
||||||
|
}`,
|
||||||
|
fragmentShader: ` uniform sampler2D uCloud;
|
||||||
|
uniform float uTime;
|
||||||
|
varying vec3 vPosition;
|
||||||
|
varying vec2 vUv;
|
||||||
|
varying vec3 vNormal;
|
||||||
|
|
||||||
|
vec3 green = vec3(0.2, 0.6, 0.3);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 color = mix(green * 0.7, green, vPosition.y);
|
||||||
|
color = mix(color, texture2D(uCloud, vUv).rgb, 0.4);
|
||||||
|
float lighting = normalize(dot(vNormal, vec3(10)));
|
||||||
|
gl_FragColor = vec4(color + lighting * 0.03, 1.0);
|
||||||
|
}`,
|
||||||
|
})
|
||||||
|
super(geometry, material)
|
||||||
|
const floor = new THREE.Mesh(
|
||||||
|
new THREE.PlaneGeometry(50, 50).rotateX(Math.PI / 2),
|
||||||
|
material
|
||||||
|
)
|
||||||
|
floor.position.y = -Number.EPSILON
|
||||||
|
this.add(floor)
|
||||||
|
|
||||||
|
}
|
||||||
|
update(time) {
|
||||||
|
this.material.uniforms.uTime.value = time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const planeGrass = new Grass(50, 100000)
|
||||||
|
export { planeGrass }
|
||||||
@@ -1,638 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="container">
|
|
||||||
<div ref="threeDiv" id="webgl" class="webgl"></div>
|
|
||||||
|
|
||||||
<div class="legend_box">
|
|
||||||
<div class="legend_head"><span class="text_gradual head_text">显示选择</span></div>
|
|
||||||
<div class="legend_content">
|
|
||||||
<div class="legend_item" v-for="(v, k) in legendList" :key="k">
|
|
||||||
<div :class="{ icon_check: true, icon_checked: v.visible }" @click="handleCheck(v, k)"></div>
|
|
||||||
<!-- <img src="@/assets/bigScreen/point/icon_check.png" alt="" @click="handleCheck(v, k)">
|
|
||||||
<img src="@/assets/bigScreen/point/icon_check.png" alt="" @click="handleCheck(v,k)"> -->
|
|
||||||
<span :class="{ span_checked: v.visible }">{{ v.name }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// 引入three.js
|
|
||||||
import * as THREE from 'three';
|
|
||||||
// // 引入扩展库OrbitControls.js
|
|
||||||
// import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
||||||
// // 引入扩展库GLTFLoader.js
|
|
||||||
// import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
||||||
// 导入轨道控制器
|
|
||||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
|
||||||
|
|
||||||
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
|
|
||||||
|
|
||||||
import { Water } from 'three/examples/jsm/objects/Water.js';
|
|
||||||
|
|
||||||
|
|
||||||
import { drawAxes } from "@/utils/three.js";
|
|
||||||
|
|
||||||
import Scene from "@/components/Scene/index.vue";
|
|
||||||
export default {
|
|
||||||
name: 'Home',
|
|
||||||
props: {
|
|
||||||
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
Scene
|
|
||||||
},
|
|
||||||
data(){
|
|
||||||
return {
|
|
||||||
scene: null, // 场景
|
|
||||||
camera: null, // 相机
|
|
||||||
renderer: null, // 渲染器
|
|
||||||
|
|
||||||
// 从图片中提取的数据
|
|
||||||
xAxisLabels: ['A', 'B', 'C', 'D', 'E'],
|
|
||||||
zAxisLabels: ['A', 'B', 'C', 'D', 'E'],
|
|
||||||
labelRenderer: null,
|
|
||||||
legendList: [
|
|
||||||
{ label: '红色通道', val: '1', checked: true },
|
|
||||||
{ label: '绿色通道', val: '2', checked: true },
|
|
||||||
{ label: '蓝色通道', val: '3', checked: true },
|
|
||||||
{ label: '灰色通道', val: '4', checked: true },],
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted(){
|
|
||||||
// console.log(THREE.Scene);
|
|
||||||
// this.init()
|
|
||||||
|
|
||||||
const sceneBox = document.getElementById('webgl');
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
gridSize: { x: 50, y: 50, z: 50 }, // 三维网格尺寸
|
|
||||||
// gridSize: { x: 100, y: 100, z: 100 }, // 三维网格尺寸
|
|
||||||
// grid_uv_up = [5, 5],
|
|
||||||
// grid_uv_down = [5, 5],
|
|
||||||
grid_uv_left: [10, 10],
|
|
||||||
// grid_uv_right = [5, 4],
|
|
||||||
// grid_uv_front = [5, 4],
|
|
||||||
grid_uv_back: [10, 10],
|
|
||||||
show_water: true, // 是否显示水面
|
|
||||||
water_height: 5, // 水面厚度
|
|
||||||
data: {
|
|
||||||
红球: [
|
|
||||||
[20, 40, 0],
|
|
||||||
[20, 40, 10],
|
|
||||||
[20, 40, 20],
|
|
||||||
[20, 40, 30],
|
|
||||||
[20, 40, 40],
|
|
||||||
],
|
|
||||||
绿球: [
|
|
||||||
[10, 10, 0],
|
|
||||||
[10, 10, 10],
|
|
||||||
[10, 10, 20],
|
|
||||||
],
|
|
||||||
蓝球: [
|
|
||||||
[20, 20, 0],
|
|
||||||
[20, 20, 10],
|
|
||||||
[20, 20, 20],
|
|
||||||
[40, 20, 20],
|
|
||||||
[40, 20, 0],
|
|
||||||
],
|
|
||||||
灰球: [
|
|
||||||
[30, 30, 0],
|
|
||||||
[30, 30, 10],
|
|
||||||
[30, 30, 20],
|
|
||||||
[40, 30, 0],
|
|
||||||
[40, 30, 10],
|
|
||||||
],
|
|
||||||
白球: [
|
|
||||||
[20, 50, 0],
|
|
||||||
[20, 50, 10],
|
|
||||||
[30, 50, 20],
|
|
||||||
[30, 50, 0],
|
|
||||||
[30, 50, 10],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// labels_left: ["", "A", "B", "C", "D", "E"],
|
|
||||||
labels_left: ["", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J"],
|
|
||||||
// labels_back: ["", "A", "B", "C", "D", "E"],
|
|
||||||
labels_back: ["", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J"],
|
|
||||||
};
|
|
||||||
const demo1 = drawAxes(sceneBox, options,(val)=> {
|
|
||||||
this.legendList = val
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleCheck(v,k){
|
|
||||||
if (this.legendList[k]) this.legendList[k].visible = !this.legendList[k].visible;
|
|
||||||
},
|
|
||||||
init(){
|
|
||||||
// 创建场景
|
|
||||||
this.scene = new THREE.Scene();
|
|
||||||
|
|
||||||
|
|
||||||
// // 网格辅助线 -- 地表格(网格宽度、等分数、中心线颜色,网格线颜色)
|
|
||||||
// const grid = new THREE.GridHelper(12, 12, 0x3a506b, 0x1a3a5a);
|
|
||||||
// scene.add(grid);
|
|
||||||
// this.createGrids()
|
|
||||||
// 3. 创建网格辅助平面(只显示正方向)
|
|
||||||
this.createPositivePlane(this.scene, 'XY', 0x6db1fc, 5, 5); // 红色XY平面
|
|
||||||
this.createPositivePlane(this.scene, 'XZ', 0x6db1fc, 5, 5); // 绿色XZ平面
|
|
||||||
this.createPositivePlane(this.scene, 'YZ', 0x6db1fc, 5, 5); // 蓝色YZ平面
|
|
||||||
|
|
||||||
// 三维辅助线(长度)-- 三维坐标系
|
|
||||||
// const axisHelper = new THREE.AxesHelper(5);
|
|
||||||
// this.scene.add(axisHelper);
|
|
||||||
this.addPositiveAxes(6)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.createWaterPool()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//定义一个几何体
|
|
||||||
// const geometry = new THREE.SphereGeometry(2, 30, 30);
|
|
||||||
//创建一个长方体几何对象Geometry
|
|
||||||
const geometry = new THREE.BoxGeometry(100, 100, 100);
|
|
||||||
// //创建一个材质对象Material
|
|
||||||
const material = new THREE.MeshBasicMaterial({
|
|
||||||
color: 0xff0000,//0xff0000设置材质颜色为红色
|
|
||||||
});
|
|
||||||
//定义一种材质,显示为线框
|
|
||||||
// const material = new THREE.MeshBasicMaterial({ color: 0xB3DD, wireframe: true });
|
|
||||||
//网孔(Mesh)是用来承载几何模型的一个对象,可以把材料应用到它上面
|
|
||||||
const ball = new THREE.Mesh(geometry, material);
|
|
||||||
//把几何模型添加到场景中,对象被添加到原点(0,0,0)坐标。
|
|
||||||
this.scene.add(ball);
|
|
||||||
this.scene.position.set(-2.5, -2.5, -2.5); // 修改 场景 中心点
|
|
||||||
|
|
||||||
//创建透视投影相机,视角45度,画幅比例 宽比高,近平面距离0.1,远平面1000
|
|
||||||
this.camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
|
|
||||||
//移动相机位置
|
|
||||||
// this.camera.position.z = 8
|
|
||||||
this.camera.position.set(8, 6, 5);
|
|
||||||
//相机观察目标指向Threejs 3D空间中某个位置
|
|
||||||
this.camera.lookAt(0, 0, 0); //坐标原点;
|
|
||||||
|
|
||||||
//创建渲染器
|
|
||||||
this.renderer = new THREE.WebGLRenderer({
|
|
||||||
alpha: true, // 启用透明度
|
|
||||||
// antialias: true
|
|
||||||
});
|
|
||||||
this.renderer.setClearColor(0x000000, 0); // 设置透明背景
|
|
||||||
//渲染器canvas宽高设为与窗口一致
|
|
||||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
||||||
this.renderer.render(this.scene, this.camera);
|
|
||||||
//将渲染器对应的dom元素添加到body中
|
|
||||||
const sceneBox = document.getElementById('webgl');
|
|
||||||
sceneBox.appendChild(this.renderer.domElement);
|
|
||||||
|
|
||||||
|
|
||||||
// function render() {
|
|
||||||
// //渲染循环,以每秒60次的频率来绘制场景
|
|
||||||
// requestAnimationFrame(render);
|
|
||||||
// //设置球体绕y轴旋转
|
|
||||||
// ball.rotation.y += 0.005;
|
|
||||||
// this.renderer.render(this.scene, this.camera);
|
|
||||||
// }
|
|
||||||
// render();
|
|
||||||
|
|
||||||
|
|
||||||
// // 创建CSS2D渲染器用于标签
|
|
||||||
// this.labelRenderer = new CSS2DRenderer();
|
|
||||||
// this.labelRenderer.setSize(
|
|
||||||
// sceneBox.clientWidth,
|
|
||||||
// sceneBox.clientHeight
|
|
||||||
// );
|
|
||||||
// this.labelRenderer.domElement.style.position = 'absolute';
|
|
||||||
// this.labelRenderer.domElement.style.top = '0';
|
|
||||||
// this.labelRenderer.domElement.style.pointerEvents = 'none';
|
|
||||||
// sceneBox.appendChild(this.labelRenderer.domElement);
|
|
||||||
|
|
||||||
// this.addGridHelpers()
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建轨道控制器(OrbitControls)
|
|
||||||
* 可以使得相机围绕目标进行轨道运动
|
|
||||||
*/
|
|
||||||
const controls = new OrbitControls(this.camera, this.renderer.domElement);
|
|
||||||
controls.addEventListener('change',()=> {
|
|
||||||
this.renderer.render(this.scene, this.camera);
|
|
||||||
})
|
|
||||||
// controls.autoRotate = true; // 相机是否自动旋转
|
|
||||||
// controls.autoRotateSpeed = 3; // 自转速度
|
|
||||||
// // // 设置控制器阻尼,让控制器更有真实效果,必须在动画循环render()中调用controls.update()()
|
|
||||||
// controls.enableDamping = true;
|
|
||||||
|
|
||||||
// 监听尺寸变化实现自适应画面
|
|
||||||
window.addEventListener("resize", () => {
|
|
||||||
// console.log("画面变化了");
|
|
||||||
// 更新摄像头
|
|
||||||
this.camera.aspect = window.innerWidth / window.innerHeight;
|
|
||||||
// 更新摄像机的投影矩阵
|
|
||||||
this.camera.updateProjectionMatrix();
|
|
||||||
// 更新渲染器
|
|
||||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
||||||
// 设置渲染器的像素比
|
|
||||||
this.renderer.setPixelRatio(window.devicePixelRatio);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
// 创建坐标轴
|
|
||||||
async addPositiveAxes(axisLength) {
|
|
||||||
// axisLength 坐标轴长度
|
|
||||||
const tickInterval = 1; // 刻度间隔
|
|
||||||
|
|
||||||
// 加载刻度图片(替换为你的图片路径)
|
|
||||||
// const tickTexture = await loadTexture('/path/to/your/tick-image.png');
|
|
||||||
const tickTextureX = [require('@/assets/icon_xz/x01.png'), require('@/assets/icon_xz/x02.png'),
|
|
||||||
require('@/assets/icon_xz/x03.png'), require('@/assets/icon_xz/x04.png'), require('@/assets/icon_xz/x05.png')
|
|
||||||
]
|
|
||||||
const tickTextureZ = [require('@/assets/icon_xz/z01.png'), require('@/assets/icon_xz/z02.png'),
|
|
||||||
require('@/assets/icon_xz/z03.png'), require('@/assets/icon_xz/z04.png'), require('@/assets/icon_xz/z05.png')
|
|
||||||
]
|
|
||||||
// const tickTextureX = ['@/assets/icon/x01.png', '@/assets/icon/x02.png',
|
|
||||||
// '@/assets/icon/x03.png', '@/assets/icon/x04.png', '@/assets/icon/x05.png'
|
|
||||||
// ]
|
|
||||||
// const tickTextureZ = ['@/assets/icon/z01.png', '@/assets/icon/z02.png',
|
|
||||||
// '@/assets/icon/z03.png', '@/assets/icon/z04.png', '@/assets/icon/z05.png'
|
|
||||||
// ]
|
|
||||||
// 创建X轴(红色,只显示正向)
|
|
||||||
const xAxis = new THREE.ArrowHelper(
|
|
||||||
new THREE.Vector3(1, 0, 0), // 方向
|
|
||||||
new THREE.Vector3(0, 0, 0), // 起点
|
|
||||||
axisLength, // 长度
|
|
||||||
0xff0000, // 颜色(红色)
|
|
||||||
0.2, // 头部长度
|
|
||||||
0.1 // 头部宽度
|
|
||||||
);
|
|
||||||
this.scene.add(xAxis);
|
|
||||||
|
|
||||||
// 创建Y轴(绿色,只显示正向)
|
|
||||||
const yAxis = new THREE.ArrowHelper(
|
|
||||||
new THREE.Vector3(0, 1, 0),
|
|
||||||
new THREE.Vector3(0, 0, 0),
|
|
||||||
axisLength,
|
|
||||||
0x00ff00, // 颜色(绿色)
|
|
||||||
0.2,
|
|
||||||
0.1
|
|
||||||
);
|
|
||||||
this.scene.add(yAxis);
|
|
||||||
|
|
||||||
// 创建Z轴(蓝色,只显示正向)
|
|
||||||
const zAxis = new THREE.ArrowHelper(
|
|
||||||
new THREE.Vector3(0, 0, 1),
|
|
||||||
new THREE.Vector3(0, 0, 0),
|
|
||||||
axisLength,
|
|
||||||
0x0000ff, // 颜色(蓝色)
|
|
||||||
0.2,
|
|
||||||
0.1
|
|
||||||
);
|
|
||||||
this.scene.add(zAxis);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const texLoader = new THREE.TextureLoader();
|
|
||||||
// 添加X轴刻度图片
|
|
||||||
for (let i = tickInterval; i <= axisLength; i += tickInterval) {
|
|
||||||
// .load()方法加载图像,返回一个纹理对象Texture
|
|
||||||
// const texture = await new Promise(resolve => {
|
|
||||||
// texLoader.load(tickTextureX[i - 1], resolve);
|
|
||||||
// });
|
|
||||||
const texture = texLoader.load(tickTextureX[i - 1]);
|
|
||||||
const tick = this.createImageTick(texture, i, 0, 0, 'x');
|
|
||||||
this.scene.add(tick);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加Z轴刻度图片
|
|
||||||
for (let j = tickInterval; j <= axisLength; j += tickInterval) {
|
|
||||||
// .load()方法加载图像,返回一个纹理对象Texture
|
|
||||||
const texture = texLoader.load(tickTextureZ[j -1])
|
|
||||||
const tick = this.createImageTick(texture, 0, 0, j, 'z');
|
|
||||||
this.scene.add(tick);
|
|
||||||
}
|
|
||||||
// 添加轴标签
|
|
||||||
this.addAxisLabel('X', new THREE.Vector3(axisLength * 1.1, 0, 0), 0xff0000);
|
|
||||||
this.addAxisLabel('Y', new THREE.Vector3(0, axisLength * 1.1, 0), 0x00ff00);
|
|
||||||
this.addAxisLabel('Z', new THREE.Vector3(0, 0, axisLength * 1.1), 0x0000ff);
|
|
||||||
|
|
||||||
|
|
||||||
this.addAxisLabel('A', new THREE.Vector3(1, 5.3, 0), 0x6db1fc);
|
|
||||||
this.addAxisLabel('B', new THREE.Vector3(2, 5.3, 0), 0x6db1fc);
|
|
||||||
this.addAxisLabel('C', new THREE.Vector3(3, 5.3, 0), 0x6db1fc);
|
|
||||||
this.addAxisLabel('D', new THREE.Vector3(4, 5.3, 0), 0x6db1fc);
|
|
||||||
this.addAxisLabel('E', new THREE.Vector3(5, 5.3, 0), 0x6db1fc);
|
|
||||||
|
|
||||||
this.addAxisLabel('A', new THREE.Vector3(0, 5.3, 1), 0x6db1fc);
|
|
||||||
this.addAxisLabel('B', new THREE.Vector3(0, 5.3, 2), 0x6db1fc);
|
|
||||||
this.addAxisLabel('C', new THREE.Vector3(0, 5.3, 3), 0x6db1fc);
|
|
||||||
this.addAxisLabel('D', new THREE.Vector3(0, 5.3, 4), 0x6db1fc);
|
|
||||||
this.addAxisLabel('E', new THREE.Vector3(0, 5.3, 5), 0x6db1fc);
|
|
||||||
|
|
||||||
},
|
|
||||||
// 创建图片刻度
|
|
||||||
createImageTick(texture, x, y, z, axis){
|
|
||||||
const tickSize = 0.8; // 刻度大小
|
|
||||||
|
|
||||||
const material = new THREE.SpriteMaterial({ map: texture, transparent: true });
|
|
||||||
const sprite = new THREE.Sprite(material);
|
|
||||||
|
|
||||||
// 根据轴调整位置和旋转
|
|
||||||
if (axis === 'x') {
|
|
||||||
sprite.position.set(x, y, z);
|
|
||||||
} else if (axis === 'z') {
|
|
||||||
sprite.position.set(x, y, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
sprite.scale.set(tickSize, tickSize, 1);
|
|
||||||
return sprite;
|
|
||||||
},
|
|
||||||
// 添加轴标签
|
|
||||||
addAxisLabel(text, position, color){
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = 128;
|
|
||||||
canvas.height = 128;
|
|
||||||
const context = canvas.getContext('2d');
|
|
||||||
context.fillStyle = 'rgba(0, 0, 0, 0)';
|
|
||||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
||||||
context.font = 'Bold 48px Arial';
|
|
||||||
context.textAlign = 'center';
|
|
||||||
context.textBaseline = 'middle';
|
|
||||||
context.fillStyle = `#${color.toString(16).padStart(6, '0')}`;
|
|
||||||
context.fillText(text, canvas.width / 2, canvas.height / 2);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const texture = new THREE.CanvasTexture(canvas);
|
|
||||||
const material = new THREE.SpriteMaterial({ map: texture, transparent: true });
|
|
||||||
const sprite = new THREE.Sprite(material);
|
|
||||||
sprite.position.copy(position);
|
|
||||||
sprite.scale.set(0.8, 0.8, 0.8);
|
|
||||||
this.scene.add(sprite);
|
|
||||||
},
|
|
||||||
// 创建水池几何体 - 宽度和深度可以根据需要调整
|
|
||||||
createWaterPool() {
|
|
||||||
const width = 5;
|
|
||||||
const depth = 5;
|
|
||||||
const height = 0.5; // 水池高度0.5
|
|
||||||
|
|
||||||
const geometry = new THREE.BoxGeometry(width, height, depth);
|
|
||||||
|
|
||||||
// 创建半透明材质
|
|
||||||
const material = new THREE.MeshPhongMaterial({
|
|
||||||
color: 0x8ddefe,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.3,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
specular: 0x111111, // 高光
|
|
||||||
shininess: 100, // 光泽度
|
|
||||||
refractionRatio: 0.85 // 折射率
|
|
||||||
});
|
|
||||||
|
|
||||||
// 创建水池网格
|
|
||||||
const pool = new THREE.Mesh(geometry, material);
|
|
||||||
pool.position.set(2.5, -0.2, 2.5);
|
|
||||||
pool.receiveShadow = true; // 接收阴影
|
|
||||||
pool.castShadow = true; // 投射阴影
|
|
||||||
|
|
||||||
// // 将水池放置在XZ平面,Y轴位置为高度的一半(因为几何体中心在原点)
|
|
||||||
// pool.position.y = height / 2;
|
|
||||||
|
|
||||||
this.scene.add(pool);
|
|
||||||
|
|
||||||
// 添加一些光照
|
|
||||||
const ambientLight = new THREE.AmbientLight(0x404040);
|
|
||||||
this.scene.add(ambientLight);
|
|
||||||
|
|
||||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
|
||||||
directionalLight.position.set(1, 1, 1);
|
|
||||||
directionalLight.castShadow = true;
|
|
||||||
this.scene.add(directionalLight);
|
|
||||||
// this.addWaterEffect(pool)
|
|
||||||
// this.createAdvancedWater()
|
|
||||||
},
|
|
||||||
// 水流动效果实现
|
|
||||||
addWaterEffect(pool) {
|
|
||||||
// 创建着色器材质来实现水波效果
|
|
||||||
const waterUniforms = {
|
|
||||||
time: { type: "f", value: 0.0 },
|
|
||||||
waterColor: { type: "c", value: new THREE.Color(0x8ddefe) }
|
|
||||||
};
|
|
||||||
|
|
||||||
const waterMaterial = new THREE.ShaderMaterial({
|
|
||||||
uniforms: waterUniforms,
|
|
||||||
vertexShader: `
|
|
||||||
uniform float time;
|
|
||||||
varying vec3 vPosition;
|
|
||||||
varying vec2 vUv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vUv = uv;
|
|
||||||
vPosition = position;
|
|
||||||
|
|
||||||
// 创建波浪效果
|
|
||||||
float waveHeight = sin(position.x * 2.0 + time) * 0.05;
|
|
||||||
waveHeight += sin(position.z * 1.5 + time * 1.5) * 0.03;
|
|
||||||
|
|
||||||
vec3 newPosition = position;
|
|
||||||
newPosition.y += waveHeight;
|
|
||||||
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
uniform vec3 waterColor;
|
|
||||||
varying vec3 vPosition;
|
|
||||||
varying vec2 vUv;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// 创建水的颜色变化
|
|
||||||
float ripple = sin(vPosition.x * 10.0 + vPosition.z * 10.0) * 0.1;
|
|
||||||
vec3 color = waterColor + vec3(ripple * 0.2);
|
|
||||||
|
|
||||||
// 透明度随波浪变化
|
|
||||||
float alpha = 0.3 + ripple * 0.1;
|
|
||||||
|
|
||||||
gl_FragColor = vec4(color, alpha);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
transparent: true,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
wireframe: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// 替换原来的材质
|
|
||||||
pool.material = waterMaterial;
|
|
||||||
|
|
||||||
// 动画循环中更新time uniform
|
|
||||||
const animate = () => {
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
waterUniforms.time.value += 0.05;
|
|
||||||
};
|
|
||||||
animate();
|
|
||||||
},
|
|
||||||
|
|
||||||
// 水流动效果实现 方式 2
|
|
||||||
createAdvancedWater() {
|
|
||||||
const waterGeometry = new THREE.PlaneGeometry(5, 5);
|
|
||||||
const water = new Water(
|
|
||||||
waterGeometry,
|
|
||||||
{
|
|
||||||
textureWidth: 512,
|
|
||||||
textureHeight: 512,
|
|
||||||
waterNormals: new THREE.TextureLoader().load('textures/waternormals.jpg', function (texture) {
|
|
||||||
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
|
|
||||||
}),
|
|
||||||
sunDirection: new THREE.Vector3(),
|
|
||||||
sunColor: 0xffffff,
|
|
||||||
waterColor: 0x8ddefe,
|
|
||||||
distortionScale: 3.7,
|
|
||||||
fog: this.scene.fog !== undefined
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
water.rotation.x = -Math.PI / 2;
|
|
||||||
// water.position.y = -0.2;
|
|
||||||
water.position.set(2.5, -0.2, 2.5);
|
|
||||||
|
|
||||||
this.scene.add(water);
|
|
||||||
|
|
||||||
// 在动画循环中更新水
|
|
||||||
const animate = () => {
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
water.material.uniforms.time.value += 0.05;
|
|
||||||
};
|
|
||||||
animate();
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建正方向网格平面
|
|
||||||
* @param {THREE.Scene} scene - 场景对象
|
|
||||||
* @param {string} planeType - 平面类型: 'XY', 'XZ', 'YZ'
|
|
||||||
* @param {number} color - 网格颜色
|
|
||||||
* @param {number} size - 网格尺寸
|
|
||||||
* @param {number} divisions - 网格分割数
|
|
||||||
*/
|
|
||||||
createPositivePlane(scene, planeType, color, size, divisions) {
|
|
||||||
const step = size / divisions;
|
|
||||||
const vertices = [];
|
|
||||||
|
|
||||||
// 根据平面类型生成顶点数据
|
|
||||||
switch (planeType) {
|
|
||||||
case 'XY': // Z=0 平面 (X正方向, Y正方向)
|
|
||||||
for (let i = 0; i <= divisions; i++) {
|
|
||||||
// 平行于X轴的线 (Y变化)
|
|
||||||
vertices.push(i * step , 0, 0, i * step, size, 0);
|
|
||||||
// 平行于Y轴的线 (X变化)
|
|
||||||
vertices.push(0, i * step, 0, size, i * step, 0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'XZ': // Y=0 平面 (X正方向, Z正方向)
|
|
||||||
for (let i = 0; i <= divisions; i++) {
|
|
||||||
// 平行于X轴的线 (Z变化)
|
|
||||||
vertices.push(i * step, 0, 0, i * step, 0, size);
|
|
||||||
// 平行于Z轴的线 (X变化)
|
|
||||||
vertices.push(0, 0, i * step, size, 0, i * step);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'YZ': // X=0 平面 (Y正方向, Z正方向)
|
|
||||||
for (let i = 0; i <= divisions; i++) {
|
|
||||||
// 平行于Y轴的线 (Z变化)
|
|
||||||
vertices.push(0, i * step, 0, 0, i * step, size);
|
|
||||||
// 平行于Z轴的线 (Y变化)
|
|
||||||
vertices.push(0, 0, i * step, 0, size, i * step);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// 创建几何体和材质
|
|
||||||
const geometry = new THREE.BufferGeometry();
|
|
||||||
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
|
|
||||||
|
|
||||||
const material = new THREE.LineBasicMaterial({
|
|
||||||
color: color,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.6
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加到场景
|
|
||||||
const grid = new THREE.LineSegments(geometry, material);
|
|
||||||
scene.add(grid);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.container{
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
.webgl{
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.legend_box{
|
|
||||||
position: absolute;
|
|
||||||
top: 120px;
|
|
||||||
right: 150px;
|
|
||||||
width: 195px;
|
|
||||||
height: fit-content;
|
|
||||||
.legend_head{
|
|
||||||
width: 100%;
|
|
||||||
height: 38px;
|
|
||||||
background-image: url('~@/assets/bigScreen/point/icon_bg_1.png');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 100% 100%;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 38px;
|
|
||||||
user-select: none;
|
|
||||||
.head_text{
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.legend_content{
|
|
||||||
width: 100%;
|
|
||||||
height: fit-content;
|
|
||||||
// height: 316px;
|
|
||||||
background-image: url('~@/assets/bigScreen/point/icon_bg_2.png');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 100% 100%;
|
|
||||||
padding: 30px 20px;
|
|
||||||
.legend_item{
|
|
||||||
width: 100%;
|
|
||||||
height: 42px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #99bbd7;
|
|
||||||
.icon_check{
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
background-image: url('~@/assets/bigScreen/point/icon_check.png');
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 100% 100%;
|
|
||||||
}
|
|
||||||
.icon_checked{
|
|
||||||
background-image: url('~@/assets/bigScreen/point/icon_checked.png');
|
|
||||||
}
|
|
||||||
span{
|
|
||||||
margin-left: 15px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
.span_checked{
|
|
||||||
color: #69ffdd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -55,6 +55,7 @@ import { lastData } from "@/api/bigScreen/api.js";
|
|||||||
import { drawAxes } from "@/utils/three.js";
|
import { drawAxes } from "@/utils/three.js";
|
||||||
|
|
||||||
import { connectWebsocket, closeWebsocket } from "@/utils/websocket.js";
|
import { connectWebsocket, closeWebsocket } from "@/utils/websocket.js";
|
||||||
|
import { planeGrass } from "@/utils/plane_grass.js";
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
props: {
|
props: {
|
||||||
@@ -259,6 +260,13 @@ export default {
|
|||||||
this.onBallClick(falg, val)
|
this.onBallClick(falg, val)
|
||||||
})
|
})
|
||||||
this.legendList = this.demo1.getGroups()
|
this.legendList = this.demo1.getGroups()
|
||||||
|
planeGrass.position.set(25, 1, 25)
|
||||||
|
this.demo1.scene.add(planeGrass)
|
||||||
|
function animate(time) {
|
||||||
|
requestAnimationFrame(animate)
|
||||||
|
planeGrass.update(time)
|
||||||
|
}
|
||||||
|
animate()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user