demos
This commit is contained in:
@@ -30,6 +30,29 @@ export function useParticleSystem() {
|
|||||||
return texture
|
return texture
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建火焰纹理
|
||||||
|
*/
|
||||||
|
function createFlameTexture() {
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
canvas.width = 64
|
||||||
|
canvas.height = 64
|
||||||
|
const ctx = canvas.getContext('2d')!
|
||||||
|
|
||||||
|
const gradient = ctx.createRadialGradient(32, 32, 0, 32, 32, 32)
|
||||||
|
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)')
|
||||||
|
gradient.addColorStop(0.3, 'rgba(255, 200, 0, 0.8)')
|
||||||
|
gradient.addColorStop(0.6, 'rgba(255, 100, 0, 0.4)')
|
||||||
|
gradient.addColorStop(1, 'rgba(255, 0, 0, 0)')
|
||||||
|
|
||||||
|
ctx.fillStyle = gradient
|
||||||
|
ctx.fillRect(0, 0, 64, 64)
|
||||||
|
|
||||||
|
const texture = new THREE.Texture(canvas)
|
||||||
|
texture.needsUpdate = true
|
||||||
|
return texture
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建粒子
|
* 创建粒子
|
||||||
*/
|
*/
|
||||||
@@ -60,7 +83,7 @@ export function useParticleSystem() {
|
|||||||
|
|
||||||
const velocity = options.velocity || new THREE.Vector3(
|
const velocity = options.velocity || new THREE.Vector3(
|
||||||
(Math.random() - 0.5) * 0.05,
|
(Math.random() - 0.5) * 0.05,
|
||||||
0.1 + Math.random() * 0.1,
|
0.03 + Math.random() * 0.03,
|
||||||
(Math.random() - 0.5) * 0.05,
|
(Math.random() - 0.5) * 0.05,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -101,20 +124,34 @@ export function useParticleSystem() {
|
|||||||
const texture = createSmokeTexture()
|
const texture = createSmokeTexture()
|
||||||
const particles: Particle[] = []
|
const particles: Particle[] = []
|
||||||
let emissionCounter = 0
|
let emissionCounter = 0
|
||||||
|
let startupTime = 0 // 启动时间计数器(秒)
|
||||||
|
const maxStartupTime = 3 // 3秒达到最大强度
|
||||||
|
|
||||||
function emit() {
|
function emit() {
|
||||||
for (let i = 0; i < emissionRate; i++) {
|
// 根据启动时间计算当前发射强度(0 到 1)
|
||||||
|
const intensity = Math.min(startupTime / maxStartupTime, 1)
|
||||||
|
const currentRate = Math.floor(emissionRate * intensity)
|
||||||
|
|
||||||
|
for (let i = 0; i < currentRate; i++) {
|
||||||
const particlePos = position.clone()
|
const particlePos = position.clone()
|
||||||
particlePos.x += (Math.random() - 0.5) * 0.3
|
particlePos.x += (Math.random() - 0.5) * 0.3
|
||||||
particlePos.z += (Math.random() - 0.5) * 0.3
|
particlePos.z += (Math.random() - 0.5) * 0.3
|
||||||
|
|
||||||
const particle = createParticle(texture, particlePos)
|
// 粒子大小也随启动时间渐变
|
||||||
|
const particle = createParticle(texture, particlePos, {
|
||||||
|
size: 0.3 * intensity + 0.2, // 从 0.2 渐变到 0.5
|
||||||
|
})
|
||||||
scene.add(particle.sprite)
|
scene.add(particle.sprite)
|
||||||
particles.push(particle)
|
particles.push(particle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
|
// 更新启动时间(每帧约 1/60 秒)
|
||||||
|
if (startupTime < maxStartupTime) {
|
||||||
|
startupTime += 1 / 60
|
||||||
|
}
|
||||||
|
|
||||||
// 更新现有粒子
|
// 更新现有粒子
|
||||||
for (let i = particles.length - 1; i >= 0; i--) {
|
for (let i = particles.length - 1; i >= 0; i--) {
|
||||||
const particle = particles[i]
|
const particle = particles[i]
|
||||||
@@ -153,10 +190,100 @@ export function useParticleSystem() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建火焰发射器
|
||||||
|
*/
|
||||||
|
function createFlameEmitter(
|
||||||
|
scene: THREE.Scene,
|
||||||
|
position: THREE.Vector3,
|
||||||
|
emissionRate = 8,
|
||||||
|
) {
|
||||||
|
const texture = createFlameTexture()
|
||||||
|
const particles: Particle[] = []
|
||||||
|
let emissionCounter = 0
|
||||||
|
let startupTime = 0 // 启动时间计数器(秒)
|
||||||
|
const maxStartupTime = 3 // 3秒达到最大强度
|
||||||
|
|
||||||
|
function emit() {
|
||||||
|
// 根据启动时间计算当前发射强度(0 到 1)
|
||||||
|
const intensity = Math.min(startupTime / maxStartupTime, 1)
|
||||||
|
const currentRate = Math.floor(emissionRate * intensity)
|
||||||
|
|
||||||
|
for (let i = 0; i < currentRate; i++) {
|
||||||
|
const particlePos = position.clone()
|
||||||
|
particlePos.x += (Math.random() - 0.5) * 0.2 * intensity // 扩散范围也渐变
|
||||||
|
particlePos.z += (Math.random() - 0.5) * 0.2 * intensity
|
||||||
|
|
||||||
|
// 火焰颜色随机(黄色到橙红色)
|
||||||
|
const colorVariation = Math.random()
|
||||||
|
const color = colorVariation > 0.5 ? 0xFFAA00 : 0xFF4400
|
||||||
|
|
||||||
|
const particle = createParticle(texture, particlePos, {
|
||||||
|
color,
|
||||||
|
opacity: 0.6 + 0.2 * intensity, // 透明度从 0.6 到 0.8
|
||||||
|
size: 0.15 + 0.15 * intensity, // 大小从 0.15 渐变到 0.3
|
||||||
|
velocity: new THREE.Vector3(
|
||||||
|
(Math.random() - 0.5) * 0.03,
|
||||||
|
(0.05 + Math.random() * 0.05) * intensity, // 速度也渐变,降低到原来的1/3
|
||||||
|
(Math.random() - 0.5) * 0.03,
|
||||||
|
),
|
||||||
|
maxAge: 50,
|
||||||
|
})
|
||||||
|
scene.add(particle.sprite)
|
||||||
|
particles.push(particle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
// 更新启动时间(每帧约 1/60 秒)
|
||||||
|
if (startupTime < maxStartupTime) {
|
||||||
|
startupTime += 1 / 60
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新现有粒子
|
||||||
|
for (let i = particles.length - 1; i >= 0; i--) {
|
||||||
|
const particle = particles[i]
|
||||||
|
if (!particle)
|
||||||
|
continue
|
||||||
|
|
||||||
|
const shouldRemove = updateParticle(particle)
|
||||||
|
|
||||||
|
if (shouldRemove) {
|
||||||
|
scene.remove(particle.sprite)
|
||||||
|
particle.sprite.material.dispose()
|
||||||
|
particles.splice(i, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 控制发射频率
|
||||||
|
emissionCounter++
|
||||||
|
if (emissionCounter >= 1) {
|
||||||
|
emit()
|
||||||
|
emissionCounter = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
particles.forEach((particle) => {
|
||||||
|
scene.remove(particle.sprite)
|
||||||
|
particle.sprite.material.dispose()
|
||||||
|
})
|
||||||
|
particles.length = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
update,
|
||||||
|
stop,
|
||||||
|
particles,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createSmokeTexture,
|
createSmokeTexture,
|
||||||
|
createFlameTexture,
|
||||||
createParticle,
|
createParticle,
|
||||||
updateParticle,
|
updateParticle,
|
||||||
createEmitter,
|
createEmitter,
|
||||||
|
createFlameEmitter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,14 @@ let outlinePass: any
|
|||||||
let particleEmitter: any
|
let particleEmitter: any
|
||||||
const isEmitting = ref(false)
|
const isEmitting = ref(false)
|
||||||
|
|
||||||
|
// 火焰发射器
|
||||||
|
let flameEmitter: any
|
||||||
|
const isFlaming = ref(false)
|
||||||
|
const flameEmitters: any[] = [] // 所有火焰发射器数组
|
||||||
|
const burningMeshes = new Set<THREE.Mesh>() // 已着火的网格集合
|
||||||
|
let spreadTimer = 0 // 蔓延计时器
|
||||||
|
const spreadInterval = 2 // 每2秒蔓延一次
|
||||||
|
|
||||||
// 地板流动效果
|
// 地板流动效果
|
||||||
let flowingGround: any
|
let flowingGround: any
|
||||||
|
|
||||||
@@ -105,7 +113,7 @@ function init() {
|
|||||||
shadowMap: true,
|
shadowMap: true,
|
||||||
shadowMapType: THREE.PCFSoftShadowMap,
|
shadowMapType: THREE.PCFSoftShadowMap,
|
||||||
toneMapping: THREE.ACESFilmicToneMapping,
|
toneMapping: THREE.ACESFilmicToneMapping,
|
||||||
toneMappingExposure: 1.2,
|
toneMappingExposure: 0.8,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 创建控制器
|
// 创建控制器
|
||||||
@@ -219,6 +227,25 @@ function loadModel(particleUtils: any) {
|
|||||||
child.userData.canAlert = true
|
child.userData.canAlert = true
|
||||||
child.userData.status = 'normal'
|
child.userData.status = 'normal'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 找到 pingt01_0019_pCylinder10001 添加火焰效果
|
||||||
|
if (child.name === 'pingt01_0019_pCylinder10001') {
|
||||||
|
const box = new THREE.Box3().setFromObject(child)
|
||||||
|
const center = box.getCenter(new THREE.Vector3())
|
||||||
|
const topPosition = new THREE.Vector3(center.x, box.max.y, center.z)
|
||||||
|
|
||||||
|
flameEmitter = particleUtils.createFlameEmitter(scene, topPosition, 8)
|
||||||
|
flameEmitters.push({ emitter: flameEmitter, mesh: child })
|
||||||
|
burningMeshes.add(child)
|
||||||
|
isFlaming.value = true // 默认开启火焰效果
|
||||||
|
|
||||||
|
// 给着火物体添加发光效果
|
||||||
|
if (child.material) {
|
||||||
|
const material = child.material as THREE.MeshStandardMaterial
|
||||||
|
material.emissive = new THREE.Color(0xFF4400)
|
||||||
|
material.emissiveIntensity = 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -226,6 +253,47 @@ function loadModel(particleUtils: any) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 火焰蔓延函数
|
||||||
|
function spreadFire() {
|
||||||
|
if (!model)
|
||||||
|
return
|
||||||
|
|
||||||
|
const particleUtils = useParticleSystem()
|
||||||
|
const spreadDistance = 3 // 蔓延距离阈值
|
||||||
|
|
||||||
|
// 遍历所有已着火的物体
|
||||||
|
burningMeshes.forEach((burningMesh) => {
|
||||||
|
const burningBox = new THREE.Box3().setFromObject(burningMesh)
|
||||||
|
const burningCenter = burningBox.getCenter(new THREE.Vector3())
|
||||||
|
|
||||||
|
// 遍历模型中的所有网格
|
||||||
|
model.traverse((child) => {
|
||||||
|
if (child instanceof THREE.Mesh && !burningMeshes.has(child)) {
|
||||||
|
// 计算与着火物体的距离
|
||||||
|
const childBox = new THREE.Box3().setFromObject(child)
|
||||||
|
const childCenter = childBox.getCenter(new THREE.Vector3())
|
||||||
|
const distance = burningCenter.distanceTo(childCenter)
|
||||||
|
|
||||||
|
// 如果距离小于阈值,点燃该物体
|
||||||
|
if (distance < spreadDistance) {
|
||||||
|
const topPosition = new THREE.Vector3(childCenter.x, childBox.max.y, childCenter.z)
|
||||||
|
const newFlameEmitter = particleUtils.createFlameEmitter(scene, topPosition, 6)
|
||||||
|
|
||||||
|
flameEmitters.push({ emitter: newFlameEmitter, mesh: child })
|
||||||
|
burningMeshes.add(child)
|
||||||
|
|
||||||
|
// 给新着火物体添加发光效果
|
||||||
|
if (child.material) {
|
||||||
|
const material = child.material as THREE.MeshStandardMaterial
|
||||||
|
material.emissive = new THREE.Color(0xFF4400)
|
||||||
|
material.emissiveIntensity = 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function animate() {
|
function animate() {
|
||||||
animationId = requestAnimationFrame(animate)
|
animationId = requestAnimationFrame(animate)
|
||||||
controls.update()
|
controls.update()
|
||||||
@@ -242,6 +310,24 @@ function animate() {
|
|||||||
particleEmitter.update()
|
particleEmitter.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新火焰粒子系统
|
||||||
|
if (isFlaming.value) {
|
||||||
|
// 更新所有火焰发射器
|
||||||
|
flameEmitters.forEach(({ emitter }) => {
|
||||||
|
emitter.update()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 火焰蔓延逻辑
|
||||||
|
if (model) {
|
||||||
|
spreadTimer += 1 / 60 // 每帧增加约1/60秒
|
||||||
|
|
||||||
|
if (spreadTimer >= spreadInterval) {
|
||||||
|
spreadTimer = 0
|
||||||
|
spreadFire()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 更新告警闪烁效果
|
// 更新告警闪烁效果
|
||||||
if (isAlerting.value && alertMesh) {
|
if (isAlerting.value && alertMesh) {
|
||||||
alertTime += 0.05
|
alertTime += 0.05
|
||||||
@@ -400,6 +486,53 @@ function toggleParticles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleFlame() {
|
||||||
|
isFlaming.value = !isFlaming.value
|
||||||
|
|
||||||
|
if (!isFlaming.value) {
|
||||||
|
// 停止所有火焰发射器
|
||||||
|
flameEmitters.forEach(({ emitter, mesh }) => {
|
||||||
|
emitter.stop()
|
||||||
|
|
||||||
|
// 移除发光效果
|
||||||
|
if (mesh.material) {
|
||||||
|
const material = mesh.material as THREE.MeshStandardMaterial
|
||||||
|
material.emissive = new THREE.Color(0x000000)
|
||||||
|
material.emissiveIntensity = 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 清空数组和集合
|
||||||
|
flameEmitters.length = 0
|
||||||
|
burningMeshes.clear()
|
||||||
|
spreadTimer = 0
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 重新点燃初始物体
|
||||||
|
if (model) {
|
||||||
|
const particleUtils = useParticleSystem()
|
||||||
|
model.traverse((child) => {
|
||||||
|
if (child instanceof THREE.Mesh && child.name === 'pingt01_0019_pCylinder10001') {
|
||||||
|
const box = new THREE.Box3().setFromObject(child)
|
||||||
|
const center = box.getCenter(new THREE.Vector3())
|
||||||
|
const topPosition = new THREE.Vector3(center.x, box.max.y, center.z)
|
||||||
|
|
||||||
|
const newFlameEmitter = particleUtils.createFlameEmitter(scene, topPosition, 8)
|
||||||
|
flameEmitters.push({ emitter: newFlameEmitter, mesh: child })
|
||||||
|
burningMeshes.add(child)
|
||||||
|
|
||||||
|
// 添加发光效果
|
||||||
|
if (child.material) {
|
||||||
|
const material = child.material as THREE.MeshStandardMaterial
|
||||||
|
material.emissive = new THREE.Color(0xFF4400)
|
||||||
|
material.emissiveIntensity = 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function closeDialog() {
|
function closeDialog() {
|
||||||
showDialog.value = false
|
showDialog.value = false
|
||||||
}
|
}
|
||||||
@@ -425,6 +558,13 @@ onUnmounted(() => {
|
|||||||
if (particleEmitter) {
|
if (particleEmitter) {
|
||||||
particleEmitter.stop()
|
particleEmitter.stop()
|
||||||
}
|
}
|
||||||
|
// 停止所有火焰发射器
|
||||||
|
flameEmitters.forEach(({ emitter }) => {
|
||||||
|
emitter.stop()
|
||||||
|
})
|
||||||
|
flameEmitters.length = 0
|
||||||
|
burningMeshes.clear()
|
||||||
|
|
||||||
renderer.dispose()
|
renderer.dispose()
|
||||||
composer.dispose()
|
composer.dispose()
|
||||||
})
|
})
|
||||||
@@ -519,6 +659,9 @@ onUnmounted(() => {
|
|||||||
<button class="control-btn" @click="toggleParticles">
|
<button class="control-btn" @click="toggleParticles">
|
||||||
{{ isEmitting ? '关闭' : '开启' }}蒸汽效果
|
{{ isEmitting ? '关闭' : '开启' }}蒸汽效果
|
||||||
</button>
|
</button>
|
||||||
|
<button class="control-btn flame-btn" @click="toggleFlame">
|
||||||
|
{{ isFlaming ? '关闭' : '开启' }}火焰效果
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-section">
|
<div class="control-section">
|
||||||
@@ -808,6 +951,15 @@ onUnmounted(() => {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flame-btn {
|
||||||
|
background: linear-gradient(135deg, #FF6600 0%, #FF0000 100%);
|
||||||
|
box-shadow: 0 2px 8px rgba(255, 100, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flame-btn:hover {
|
||||||
|
box-shadow: 0 4px 12px rgba(255, 100, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
.info-item {
|
.info-item {
|
||||||
background: rgba(0, 255, 255, 0.1);
|
background: rgba(0, 255, 255, 0.1);
|
||||||
color: #AAAAAA;
|
color: #AAAAAA;
|
||||||
|
|||||||
Reference in New Issue
Block a user