|
| 1 | +# Particle System PRD |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +### Context & Goals |
| 6 | + |
| 7 | +- Add GPU-accelerated particle effects (muzzle flash, smoke, sparks). |
| 8 | +- Author effects via ECS `ParticleComponent` plus presets. |
| 9 | +- Support bursts and continuous emitters with lifetime curves. |
| 10 | + |
| 11 | +### Current Pain Points |
| 12 | + |
| 13 | +- No standardized particle effects pipeline. |
| 14 | +- Visual feedback missing for actions and impacts. |
| 15 | +- Manual effect coding increases coupling and regressions. |
| 16 | + |
| 17 | +## Proposed Solution |
| 18 | + |
| 19 | +### High‑level Summary |
| 20 | + |
| 21 | +- Introduce `ParticleSystem` updating emitter state and GPU buffers. |
| 22 | +- Define `ParticleComponent` schema (Zod) with emission and material params. |
| 23 | +- Implement instanced/points-based renderer with curves (size, color over time). |
| 24 | +- Provide presets and editor-friendly parameters. |
| 25 | + |
| 26 | +### Architecture & Directory Structure |
| 27 | + |
| 28 | +``` |
| 29 | +/src/core/ |
| 30 | + ├── systems/ |
| 31 | + │ └── ParticleSystem.ts |
| 32 | + ├── lib/particles/ |
| 33 | + │ ├── curves.ts |
| 34 | + │ ├── gpuBuffers.ts |
| 35 | + │ └── materials.ts |
| 36 | + └── components/particles/ |
| 37 | + ├── ParticleEmitter.tsx |
| 38 | + └── ParticleComponent.schema.ts |
| 39 | +``` |
| 40 | + |
| 41 | +## Implementation Plan |
| 42 | + |
| 43 | +1. Phase 1: Core (1 day) |
| 44 | + |
| 45 | + 1. Component schema + emitter lifecycle |
| 46 | + 2. CPU sim baseline; instanced rendering |
| 47 | + 3. Curves and color/size over lifetime |
| 48 | + |
| 49 | +2. Phase 2: GPU Path (1 day) |
| 50 | + |
| 51 | + 1. GPU buffer management |
| 52 | + 2. Materials and sorting strategies |
| 53 | + 3. Perf instrumentation |
| 54 | + |
| 55 | +3. Phase 3: Presets & Events (0.5 day) |
| 56 | + 1. Common presets (smoke/sparks) |
| 57 | + 2. Trigger via events (e.g., collision) |
| 58 | + |
| 59 | +## File and Directory Structures |
| 60 | + |
| 61 | +``` |
| 62 | +/docs/implementation/ |
| 63 | + └── 4-10-particle-system-prd.md |
| 64 | +``` |
| 65 | + |
| 66 | +## Technical Details |
| 67 | + |
| 68 | +```ts |
| 69 | +export interface IParticleComponent { |
| 70 | + maxParticles: number; |
| 71 | + emissionRate?: number; // particles/sec |
| 72 | + burst?: { count: number; interval: number }; |
| 73 | + lifetime: [number, number]; // min,max |
| 74 | + startColor?: [number, number, number, number]; |
| 75 | + endColor?: [number, number, number, number]; |
| 76 | + startSize?: number; |
| 77 | + endSize?: number; |
| 78 | + worldSpace?: boolean; |
| 79 | +} |
| 80 | + |
| 81 | +export interface IParticleSystemApi { |
| 82 | + triggerBurst(entityId: number, count?: number): void; |
| 83 | +} |
| 84 | +``` |
| 85 | + |
| 86 | +### Editor & Component Integration |
| 87 | + |
| 88 | +```ts |
| 89 | +// 1) Add a new KnownComponentType |
| 90 | +// src/core/lib/ecs/IComponent.ts |
| 91 | +export const KnownComponentTypes = { |
| 92 | + // ...existing, |
| 93 | + PARTICLES: 'Particles', |
| 94 | +} as const; |
| 95 | + |
| 96 | +// 2) Schema & data type |
| 97 | +// src/core/components/particles/ParticleComponent.schema.ts |
| 98 | +import { z } from 'zod'; |
| 99 | +export const ParticleComponentSchema = z.object({ |
| 100 | + enabled: z.boolean().default(true), |
| 101 | + maxParticles: z.number().int().positive().default(1024), |
| 102 | + emissionRate: z.number().nonnegative().default(0), |
| 103 | + burst: z.object({ count: z.number().int().positive(), interval: z.number().positive() }).optional(), |
| 104 | + lifetime: z.tuple([z.number().positive(), z.number().positive()]).default([0.5, 1.5]), |
| 105 | + startColor: z.tuple([z.number(), z.number(), z.number(), z.number()]).default([1,1,1,1]), |
| 106 | + endColor: z.tuple([z.number(), z.number(), z.number(), z.number()]).default([1,1,1,0]), |
| 107 | + startSize: z.number().positive().default(0.1), |
| 108 | + endSize: z.number().positive().default(0.01), |
| 109 | + worldSpace: z.boolean().default(true), |
| 110 | +}); |
| 111 | +export type ParticleData = z.infer<typeof ParticleComponentSchema>; |
| 112 | + |
| 113 | +// 3) Inspector adapter |
| 114 | +// src/editor/components/inspector/adapters/ParticleAdapter.tsx |
| 115 | +export const ParticleAdapter: React.FC<{ |
| 116 | + particleComponent: IComponent<ParticleData> | null; |
| 117 | + updateComponent: (type: string, data: unknown) => boolean; |
| 118 | + removeComponent: (type: string) => boolean; |
| 119 | +}> = ({ particleComponent, updateComponent, removeComponent }) => { |
| 120 | + // form controls for emission, lifetime, colors, sizes |
| 121 | + return null; |
| 122 | +}; |
| 123 | + |
| 124 | +// 4) Add menu entry + defaults |
| 125 | +// src/editor/components/menus/AddComponentMenu.tsx |
| 126 | +COMPONENT_DEFINITIONS.push({ |
| 127 | + id: KnownComponentTypes.PARTICLES, |
| 128 | + name: 'Particles', |
| 129 | + description: 'GPU-accelerated particle emitter', |
| 130 | + icon: /* choose icon */, category: 'VFX', |
| 131 | +}); |
| 132 | +// handle defaultData |
| 133 | +case KnownComponentTypes.PARTICLES: |
| 134 | + defaultData = { enabled: true, maxParticles: 1024, emissionRate: 50, lifetime: [0.5,1.5] }; |
| 135 | + break; |
| 136 | + |
| 137 | +// 5) Visualization (optional) |
| 138 | +// src/editor/components/panels/ViewportPanel/components/ParticleVisualization.tsx |
| 139 | +// draw emitter gizmos, bounds, trajectories when selected |
| 140 | + |
| 141 | +// 6) Runtime registration |
| 142 | +// src/core/systems/ParticleSystem.ts |
| 143 | +registerSystem({ id: 'core.particles', order: 70, update: (dt) => particleSystem.update(dt) }); |
| 144 | +``` |
| 145 | + |
| 146 | +## Usage Examples |
| 147 | + |
| 148 | +```ts |
| 149 | +// Create an emitter and trigger a burst |
| 150 | +particles.triggerBurst(entityId, 30); |
| 151 | +``` |
| 152 | + |
| 153 | +## Testing Strategy |
| 154 | + |
| 155 | +- Unit: curve evaluation, lifetime pool reuse, schema validation. |
| 156 | +- Integration: emitter updates in step loop, rendering stability under load. |
| 157 | + |
| 158 | +## Edge Cases |
| 159 | + |
| 160 | +| Edge Case | Remediation | |
| 161 | +| ------------------- | --------------------------------------------------- | |
| 162 | +| Excess particles | Cap by `maxParticles` with LRU replacement | |
| 163 | +| Transparent sorting | Use approximate back-to-front or disable depthWrite | |
| 164 | + |
| 165 | +## Sequence Diagram |
| 166 | + |
| 167 | +```mermaid |
| 168 | +sequenceDiagram |
| 169 | + participant ECS |
| 170 | + participant ParticleSystem |
| 171 | + participant Renderer |
| 172 | + ECS->>ParticleSystem: update emitters |
| 173 | + ParticleSystem->>Renderer: upload buffers |
| 174 | + Renderer-->>ParticleSystem: frame rendered |
| 175 | +``` |
| 176 | + |
| 177 | +## Risks & Mitigations |
| 178 | + |
| 179 | +| Risk | Mitigation | |
| 180 | +| ------------ | ------------------------------------------ | |
| 181 | +| GPU overdraw | Limit size, soft particle options, pooling | |
| 182 | +| GC pressure | Reuse buffers; object pools | |
| 183 | + |
| 184 | +## Timeline |
| 185 | + |
| 186 | +- Total: ~2.5 days (Core 1, GPU 1, Presets 0.5) |
| 187 | + |
| 188 | +## Acceptance Criteria |
| 189 | + |
| 190 | +- Emitters spawn particles with curves and pooling. |
| 191 | +- GPU path sustains target FPS for 5k particles. |
| 192 | +- Event-triggered effects work (e.g., collisions). |
| 193 | + |
| 194 | +## Conclusion |
| 195 | + |
| 196 | +Adds a scalable particle pipeline with ECS-first authoring and GPU efficiency. |
| 197 | + |
| 198 | +## Assumptions & Dependencies |
| 199 | + |
| 200 | +- Three.js renderer; existing event bus; Zod available. |
0 commit comments