demos
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user