优化碰撞识别,加入暖场帧数
This commit is contained in:
1 parent
b8652acce0
commit
fed1015849
21 files changed
+195
-23
No files matched your search
+63
-6
@@ -204,12 +204,47 @@ function syncEmitters() {
|
||||
emitterSync.sync(store, world, dotTex, trailTex, spineRuntime)
|
||||
}
|
||||
|
||||
/** 事件互相连接的粒子系统共享最长暖场帧数,保证子发射器也能从父事件产生。 */
|
||||
function warmupFramesBySystem() {
|
||||
const byId = new Map(store.systems.map((system) => [system.id, system]))
|
||||
const subEmitterIds = collectSubEmitterIds(store.systems)
|
||||
const neighbors = new Map(store.systems.map((system) => [system.id, new Set<number>()]))
|
||||
for (const system of store.systems) {
|
||||
if (system.config.eventEnabled === false) continue
|
||||
for (const rule of system.config.events || []) {
|
||||
if (!byId.has(rule.targetSystemId) || rule.targetSystemId === system.id) continue
|
||||
neighbors.get(system.id)!.add(rule.targetSystemId)
|
||||
neighbors.get(rule.targetSystemId)!.add(system.id)
|
||||
}
|
||||
}
|
||||
const result = new Map<number, number>()
|
||||
for (const system of store.systems) {
|
||||
if (result.has(system.id)) continue
|
||||
const component: number[] = []
|
||||
const pending = [system.id]
|
||||
while (pending.length) {
|
||||
const id = pending.pop()!
|
||||
if (component.includes(id)) continue
|
||||
component.push(id)
|
||||
pending.push(...neighbors.get(id)!)
|
||||
}
|
||||
const frames = Math.max(0, ...component.map((id) => {
|
||||
if (subEmitterIds.has(id)) return 0
|
||||
const config = byId.get(id)?.config
|
||||
if (config?.mode === 'stream' && config.streamBehavior === 'loop') return 0
|
||||
return Math.floor(Number(config?.warmupFrames) || 0)
|
||||
}))
|
||||
for (const id of component) result.set(id, frames)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步烘焙一个系统的整段帧缓存。
|
||||
* 与逐帧录制结果一致(同样的固定步长、同样的随机序列),因此不会改变预览内容,
|
||||
* 只是把"参数变动时就该生成好的烘焙结果"补齐。
|
||||
*/
|
||||
function bakeSystemFrames(system: ParticleSystem, emitter: ParticleEmitter, fps: number, totalFrames: number) {
|
||||
function bakeSystemFrames(system: ParticleSystem, emitter: ParticleEmitter, fps: number, totalFrames: number, warmupFrames: number) {
|
||||
const frameCount = Math.max(2, Math.floor(totalFrames) || 2)
|
||||
// 循环持续本来就是一次性烘焙,直接复用同一套逻辑。
|
||||
if (system.config.mode === 'stream' && system.config.streamBehavior === 'loop') {
|
||||
@@ -218,10 +253,12 @@ function bakeSystemFrames(system: ParticleSystem, emitter: ParticleEmitter, fps:
|
||||
}
|
||||
emitter.reset()
|
||||
const frameDuration = 1 / Math.max(1, fps)
|
||||
emitter.warmup(warmupFrames, frameDuration)
|
||||
const frames: ParticleState[][] = []
|
||||
for (let frame = 0; frame < frameCount; frame++) {
|
||||
// 与逐帧录制保持一致:第 0 帧保持空白,避免正式播放前提前发射。
|
||||
if (frame === 0) { emitter.apply([]); frames.push([]) } else frames.push(emitter.update(frameDuration))
|
||||
// 第 0 帧使用暖场后的状态;未设置暖场时仍保持从空白开始。
|
||||
if (frame === 0) frames.push(warmupFrames > 0 ? emitter.capture() : [])
|
||||
else frames.push(emitter.update(frameDuration))
|
||||
}
|
||||
system.frames = frames
|
||||
}
|
||||
@@ -233,6 +270,7 @@ function bakeSystemFrames(system: ParticleSystem, emitter: ParticleEmitter, fps:
|
||||
function ensureFramesBaked(fps: number) {
|
||||
const totalFrames = Math.max(2, Math.floor(store.timeline.totalFrames) || 2)
|
||||
const subEmitterIds = collectSubEmitterIds(store.systems)
|
||||
const warmupFrames = warmupFramesBySystem()
|
||||
// 烘焙预热会大量出生/死亡粒子,期间暂停跨系统事件,避免注入预热粒子。
|
||||
emitterSync.suspendEvents(true)
|
||||
try {
|
||||
@@ -242,7 +280,7 @@ function ensureFramesBaked(fps: number) {
|
||||
if ((system.frames?.length || 0) >= totalFrames) continue
|
||||
const emitter = emitterSync.emitters.get(system.id)
|
||||
if (!emitter) continue
|
||||
bakeSystemFrames(system as unknown as ParticleSystem, emitter, fps, totalFrames)
|
||||
bakeSystemFrames(system as unknown as ParticleSystem, emitter, fps, totalFrames, warmupFrames.get(system.id) || 0)
|
||||
}
|
||||
} finally {
|
||||
emitterSync.suspendEvents(false)
|
||||
@@ -398,6 +436,7 @@ function loop() {
|
||||
displayFrame = tl.frame
|
||||
collected.length = 0
|
||||
total = 0
|
||||
const warmedSystems = new Set<number>()
|
||||
if (!tl.recorded) {
|
||||
// 录制阶段:固定步长模拟并记录当前帧。
|
||||
emitterSync.suspendEvents(true)
|
||||
@@ -421,14 +460,32 @@ function loop() {
|
||||
} finally {
|
||||
emitterSync.suspendEvents(false)
|
||||
}
|
||||
if (tl.frame === 0) {
|
||||
const warmupFrames = warmupFramesBySystem()
|
||||
const targets = store.systems.flatMap((system) => {
|
||||
const emitter = emitterSync.emitters.get(system.id)
|
||||
const isLoopStream = system.config.mode === 'stream' && system.config.streamBehavior === 'loop'
|
||||
const frames = warmupFrames.get(system.id) || 0
|
||||
return emitter && !isLoopStream && frames > 0 ? [{ system, emitter, frames }] : []
|
||||
})
|
||||
const lastFrame = Math.max(0, ...targets.map(({ frames }) => frames))
|
||||
for (let warmupFrame = 0; warmupFrame < lastFrame; warmupFrame++) {
|
||||
for (const target of targets) {
|
||||
if (warmupFrame < target.frames) target.emitter.update(frameDuration)
|
||||
}
|
||||
}
|
||||
for (const { system } of targets) warmedSystems.add(system.id)
|
||||
}
|
||||
for (const [systemId, em] of emitterSync.emitters) {
|
||||
const system = store.systems.find((item) => item.id === systemId)
|
||||
const isLoopStream = system?.config.mode === 'stream' && system.config.streamBehavior === 'loop'
|
||||
const loopFrame = isLoopStream
|
||||
? system.frames?.[tl.frame]
|
||||
: undefined
|
||||
const forceEmptyInitialFrame = tl.frame === 0 && !isLoopStream
|
||||
const states = forceEmptyInitialFrame ? [] : (loopFrame || em.update(frameDuration))
|
||||
const forceEmptyInitialFrame = tl.frame === 0 && !isLoopStream && !warmedSystems.has(systemId)
|
||||
const states = forceEmptyInitialFrame
|
||||
? []
|
||||
: loopFrame || (warmedSystems.has(systemId) ? em.capture() : em.update(frameDuration))
|
||||
if (forceEmptyInitialFrame) em.apply([])
|
||||
else if (loopFrame) em.apply(loopFrame)
|
||||
if (system?.visible !== false) {
|
||||
|
||||
Reference in new issue
Block a user