This commit is contained in:
2025-11-21 11:47:41 +08:00
parent 7f6e727b9c
commit eb611f1c8f
2 changed files with 283 additions and 4 deletions

View File

@@ -30,6 +30,29 @@ export function useParticleSystem() {
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(
(Math.random() - 0.5) * 0.05,
0.1 + Math.random() * 0.1,
0.03 + Math.random() * 0.03,
(Math.random() - 0.5) * 0.05,
)
@@ -101,20 +124,34 @@ export function useParticleSystem() {
const texture = createSmokeTexture()
const particles: Particle[] = []
let emissionCounter = 0
let startupTime = 0 // 启动时间计数器(秒)
const maxStartupTime = 3 // 3秒达到最大强度
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()
particlePos.x += (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)
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]
@@ -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 {
createSmokeTexture,
createFlameTexture,
createParticle,
updateParticle,
createEmitter,
createFlameEmitter,
}
}

View File

@@ -33,6 +33,14 @@ let outlinePass: any
let particleEmitter: any
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
@@ -105,7 +113,7 @@ function init() {
shadowMap: true,
shadowMapType: THREE.PCFSoftShadowMap,
toneMapping: THREE.ACESFilmicToneMapping,
toneMappingExposure: 1.2,
toneMappingExposure: 0.8,
})
// 创建控制器
@@ -219,6 +227,25 @@ function loadModel(particleUtils: any) {
child.userData.canAlert = true
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() {
animationId = requestAnimationFrame(animate)
controls.update()
@@ -242,6 +310,24 @@ function animate() {
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) {
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() {
showDialog.value = false
}
@@ -425,6 +558,13 @@ onUnmounted(() => {
if (particleEmitter) {
particleEmitter.stop()
}
// 停止所有火焰发射器
flameEmitters.forEach(({ emitter }) => {
emitter.stop()
})
flameEmitters.length = 0
burningMeshes.clear()
renderer.dispose()
composer.dispose()
})
@@ -519,6 +659,9 @@ onUnmounted(() => {
<button class="control-btn" @click="toggleParticles">
{{ isEmitting ? '关闭' : '开启' }}蒸汽效果
</button>
<button class="control-btn flame-btn" @click="toggleFlame">
{{ isFlaming ? '关闭' : '开启' }}火焰效果
</button>
</div>
<div class="control-section">
@@ -808,6 +951,15 @@ onUnmounted(() => {
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 {
background: rgba(0, 255, 255, 0.1);
color: #AAAAAA;