feat(effects): вода, облако яда, луч света; пульт и окна демонстрации
- Поле: вода (сплошная заливка по штриху, превью кистью), туман/огонь/дождь без изменений логики. - Действия: облако яда (частицы, круглая текстура, звук oblako-yada.mp3, длительность как у трека), луч света и заморозка со звуками из public/. - Пульт: инструменты воды и яда, синхрон SFX, тесты панели и ластика. - Окно управления: дочернее от окна просмотра (Z-order). - Типы эффектов, effectsStore prune, hit-test ластика. Made-with: Cursor
This commit is contained in:
@@ -20,6 +20,20 @@ void test('pickEraseTargetId: fire/rain по штриху как туман', ()
|
||||
assert.equal(id, 'f1');
|
||||
});
|
||||
|
||||
void test('pickEraseTargetId: вода по штриху как туман', () => {
|
||||
const water: EffectInstance = {
|
||||
...base,
|
||||
id: 'w1',
|
||||
type: 'water',
|
||||
points: [{ x: 0.4, y: 0.55, tMs: 0 }],
|
||||
radiusN: 0.06,
|
||||
opacity: 0.5,
|
||||
lifetimeMs: null,
|
||||
};
|
||||
const id = pickEraseTargetId([water], { x: 0.41, y: 0.55 }, 0.05);
|
||||
assert.equal(id, 'w1');
|
||||
});
|
||||
|
||||
void test('minDistSqEffectToPoint: молния — расстояние до отрезка', () => {
|
||||
const bolt: EffectInstance = {
|
||||
...base,
|
||||
@@ -37,6 +51,23 @@ void test('minDistSqEffectToPoint: молния — расстояние до о
|
||||
assert.equal(end, 0);
|
||||
});
|
||||
|
||||
void test('minDistSqEffectToPoint: луч света — как у молнии, отрезок', () => {
|
||||
const beam: EffectInstance = {
|
||||
...base,
|
||||
id: 'S1',
|
||||
type: 'sunbeam',
|
||||
start: { x: 0.5, y: 0 },
|
||||
end: { x: 0.5, y: 0.8 },
|
||||
widthN: 0.04,
|
||||
intensity: 1,
|
||||
lifetimeMs: 220,
|
||||
};
|
||||
const onBeam = minDistSqEffectToPoint(beam, { x: 0.5, y: 0.4 });
|
||||
assert.equal(onBeam, 0);
|
||||
const aside = minDistSqEffectToPoint(beam, { x: 0.52, y: 0.4 });
|
||||
assert.ok(aside > 0 && aside < 0.01);
|
||||
});
|
||||
|
||||
void test('pickEraseTargetId: scorch с учётом inst.radiusN', () => {
|
||||
const sc: EffectInstance = {
|
||||
...base,
|
||||
|
||||
@@ -30,7 +30,8 @@ export function minDistSqEffectToPoint(inst: EffectInstance, p: { x: number; y:
|
||||
switch (inst.type) {
|
||||
case 'fog':
|
||||
case 'fire':
|
||||
case 'rain': {
|
||||
case 'rain':
|
||||
case 'water': {
|
||||
let best = Number.POSITIVE_INFINITY;
|
||||
for (const q of inst.points) {
|
||||
const dx = q.x - p.x;
|
||||
@@ -40,6 +41,7 @@ export function minDistSqEffectToPoint(inst: EffectInstance, p: { x: number; y:
|
||||
return best;
|
||||
}
|
||||
case 'lightning':
|
||||
case 'sunbeam':
|
||||
return distSqPointToSegment(p.x, p.y, inst.start.x, inst.start.y, inst.end.x, inst.end.y);
|
||||
case 'freeze': {
|
||||
const dx = inst.at.x - p.x;
|
||||
@@ -47,7 +49,8 @@ export function minDistSqEffectToPoint(inst: EffectInstance, p: { x: number; y:
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
case 'scorch':
|
||||
case 'ice': {
|
||||
case 'ice':
|
||||
case 'poisonCloud': {
|
||||
const dx = inst.at.x - p.x;
|
||||
const dy = inst.at.y - p.y;
|
||||
return dx * dx + dy * dy;
|
||||
@@ -58,7 +61,7 @@ export function minDistSqEffectToPoint(inst: EffectInstance, p: { x: number; y:
|
||||
}
|
||||
|
||||
function eraseHitThresholdSq(inst: EffectInstance, toolRadiusN: number): number {
|
||||
if (inst.type === 'scorch' || inst.type === 'ice') {
|
||||
if (inst.type === 'scorch' || inst.type === 'ice' || inst.type === 'poisonCloud') {
|
||||
const r = toolRadiusN + inst.radiusN;
|
||||
return r * r;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
export type EffectToolType = 'fog' | 'fire' | 'rain' | 'lightning' | 'freeze' | 'eraser';
|
||||
export type EffectToolType =
|
||||
| 'fog'
|
||||
| 'fire'
|
||||
| 'rain'
|
||||
| 'water'
|
||||
| 'lightning'
|
||||
| 'sunbeam'
|
||||
| 'poisonCloud'
|
||||
| 'freeze'
|
||||
| 'eraser';
|
||||
|
||||
export type EffectInstanceType = 'fog' | 'fire' | 'rain' | 'lightning' | 'freeze' | 'scorch' | 'ice';
|
||||
export type EffectInstanceType =
|
||||
| 'fog'
|
||||
| 'fire'
|
||||
| 'rain'
|
||||
| 'water'
|
||||
| 'lightning'
|
||||
| 'sunbeam'
|
||||
| 'poisonCloud'
|
||||
| 'freeze'
|
||||
| 'scorch'
|
||||
| 'ice';
|
||||
|
||||
/** Нормализованные координаты (0..1) относительно области предпросмотра/презентации. */
|
||||
export type NPoint = { x: number; y: number; tMs: number; pressure?: number };
|
||||
@@ -38,6 +57,14 @@ export type RainInstance = EffectInstanceBase & {
|
||||
lifetimeMs: number | null;
|
||||
};
|
||||
|
||||
export type WaterInstance = EffectInstanceBase & {
|
||||
type: 'water';
|
||||
points: NPoint[];
|
||||
radiusN: number;
|
||||
opacity: number;
|
||||
lifetimeMs: number | null;
|
||||
};
|
||||
|
||||
export type LightningInstance = EffectInstanceBase & {
|
||||
type: 'lightning';
|
||||
start: { x: number; y: number };
|
||||
@@ -47,6 +74,25 @@ export type LightningInstance = EffectInstanceBase & {
|
||||
lifetimeMs: number;
|
||||
};
|
||||
|
||||
/** Прямой луч сверху к точке удара (как у молнии геометрия штриха, другой визуал). */
|
||||
export type SunbeamInstance = EffectInstanceBase & {
|
||||
type: 'sunbeam';
|
||||
start: { x: number; y: number };
|
||||
end: { x: number; y: number };
|
||||
widthN: number;
|
||||
intensity: number;
|
||||
lifetimeMs: number;
|
||||
};
|
||||
|
||||
/** «Облако яда» — ядерный гриб в точке удара. */
|
||||
export type PoisonCloudInstance = EffectInstanceBase & {
|
||||
type: 'poisonCloud';
|
||||
at: { x: number; y: number };
|
||||
radiusN: number;
|
||||
intensity: number;
|
||||
lifetimeMs: number;
|
||||
};
|
||||
|
||||
export type FreezeInstance = EffectInstanceBase & {
|
||||
type: 'freeze';
|
||||
at: { x: number; y: number };
|
||||
@@ -69,14 +115,18 @@ export type IceInstance = EffectInstanceBase & {
|
||||
at: { x: number; y: number };
|
||||
radiusN: number;
|
||||
opacity: number;
|
||||
lifetimeMs: number;
|
||||
/** `null` — пятно не истекает по времени (снимается только «очистить» или ластик). */
|
||||
lifetimeMs: number | null;
|
||||
};
|
||||
|
||||
export type EffectInstance =
|
||||
| FogInstance
|
||||
| FireInstance
|
||||
| RainInstance
|
||||
| WaterInstance
|
||||
| LightningInstance
|
||||
| SunbeamInstance
|
||||
| PoisonCloudInstance
|
||||
| FreezeInstance
|
||||
| ScorchInstance
|
||||
| IceInstance;
|
||||
|
||||
Reference in New Issue
Block a user