canvasengine 2.0.0-beta.52 → 2.0.0-beta.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/DisplayObject.d.ts.map +1 -1
- package/dist/components/Sprite.d.ts +11 -4
- package/dist/components/Sprite.d.ts.map +1 -1
- package/dist/components/Viewport.d.ts +1 -0
- package/dist/components/Viewport.d.ts.map +1 -1
- package/dist/components/types/DisplayObject.d.ts +12 -0
- package/dist/components/types/DisplayObject.d.ts.map +1 -1
- package/dist/directives/FogVisibility.d.ts +47 -0
- package/dist/directives/FogVisibility.d.ts.map +1 -0
- package/dist/directives/index.d.ts +1 -0
- package/dist/directives/index.d.ts.map +1 -1
- package/dist/index.global.js +3 -3
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1222 -1055
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DisplayObject.ts +27 -0
- package/src/components/Sprite.ts +87 -18
- package/src/components/Viewport.ts +4 -0
- package/src/components/types/DisplayObject.ts +13 -1
- package/src/directives/FogVisibility.ts +273 -0
- package/src/directives/index.ts +2 -1
package/package.json
CHANGED
|
@@ -320,6 +320,33 @@ export function DisplayObject(extendClass) {
|
|
|
320
320
|
props.maskOf.componentInstance.mask = this as any;
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
|
+
if (props.shadowCaster !== undefined) {
|
|
324
|
+
const shadowCasterValue = (props.shadowCaster as any)?.value ?? props.shadowCaster;
|
|
325
|
+
if (
|
|
326
|
+
shadowCasterValue &&
|
|
327
|
+
typeof shadowCasterValue === "object" &&
|
|
328
|
+
!Array.isArray(shadowCasterValue)
|
|
329
|
+
) {
|
|
330
|
+
const current = ((this as any).shadowCaster ?? {}) as Record<string, unknown>;
|
|
331
|
+
(this as any).shadowCaster = { ...current, ...shadowCasterValue };
|
|
332
|
+
} else {
|
|
333
|
+
(this as any).shadowCaster = shadowCasterValue;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (props.footprintCaster !== undefined) {
|
|
337
|
+
const footprintCasterValue =
|
|
338
|
+
(props.footprintCaster as any)?.value ?? props.footprintCaster;
|
|
339
|
+
if (
|
|
340
|
+
footprintCasterValue &&
|
|
341
|
+
typeof footprintCasterValue === "object" &&
|
|
342
|
+
!Array.isArray(footprintCasterValue)
|
|
343
|
+
) {
|
|
344
|
+
const current = ((this as any).footprintCaster ?? {}) as Record<string, unknown>;
|
|
345
|
+
(this as any).footprintCaster = { ...current, ...footprintCasterValue };
|
|
346
|
+
} else {
|
|
347
|
+
(this as any).footprintCaster = footprintCasterValue;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
323
350
|
if (props.blendMode) this.blendMode = props.blendMode;
|
|
324
351
|
if (props.filterArea) this.filterArea = props.filterArea;
|
|
325
352
|
const currentFilters = this.filters || [];
|
package/src/components/Sprite.ts
CHANGED
|
@@ -58,13 +58,21 @@ type AnimationDataFrames = {
|
|
|
58
58
|
data: TextureOptionsMerging;
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
+
export type HitboxAnchorMode = "top-left" | "center" | "foot";
|
|
62
|
+
|
|
63
|
+
export type Hitbox = {
|
|
64
|
+
w: number;
|
|
65
|
+
h: number;
|
|
66
|
+
anchorMode?: HitboxAnchorMode;
|
|
67
|
+
};
|
|
68
|
+
|
|
61
69
|
export enum StandardAnimation {
|
|
62
70
|
Stand = "stand",
|
|
63
71
|
Walk = "walk",
|
|
64
72
|
}
|
|
65
73
|
|
|
66
74
|
export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
67
|
-
public hitbox:
|
|
75
|
+
public hitbox: Hitbox | null = null;
|
|
68
76
|
public applyTransform: (
|
|
69
77
|
frame: FrameOptionsMerging,
|
|
70
78
|
data: TextureOptionsMerging,
|
|
@@ -442,7 +450,9 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
442
450
|
this.play(this.sheetCurrentAnimation, [this.sheetParams]);
|
|
443
451
|
}
|
|
444
452
|
|
|
445
|
-
if (props.hitbox
|
|
453
|
+
if (props.hitbox !== undefined) {
|
|
454
|
+
this.hitbox = this.normalizeHitbox(props.hitbox);
|
|
455
|
+
}
|
|
446
456
|
|
|
447
457
|
if (props.scaleMode) this.baseTexture.scaleMode = props.scaleMode;
|
|
448
458
|
else if (props.image && this.fullProps.rectangle === undefined) {
|
|
@@ -470,6 +480,10 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
470
480
|
});
|
|
471
481
|
}
|
|
472
482
|
}
|
|
483
|
+
|
|
484
|
+
if (this.hitbox && !this.spritesheet) {
|
|
485
|
+
this.applyHitboxAnchor(this.texture.width, this.texture.height);
|
|
486
|
+
}
|
|
473
487
|
}
|
|
474
488
|
|
|
475
489
|
async onDestroy(parent: Element, afterDestroy: () => void): Promise<void> {
|
|
@@ -669,27 +683,12 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
669
683
|
}
|
|
670
684
|
|
|
671
685
|
const realSize = getVal<"spriteRealSize">("spriteRealSize");
|
|
672
|
-
const heightOfSprite =
|
|
673
|
-
typeof realSize == "number" ? realSize : realSize?.height;
|
|
674
|
-
const widthOfSprite =
|
|
675
|
-
typeof realSize == "number" ? realSize : realSize?.width;
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
const applyAnchorBySize = () => {
|
|
679
|
-
if (heightOfSprite && this.hitbox) {
|
|
680
|
-
const { spriteWidth, spriteHeight } = data;
|
|
681
|
-
const w = (spriteWidth - this.hitbox.w) / 2 / spriteWidth;
|
|
682
|
-
const gap = (spriteHeight - heightOfSprite) / 2;
|
|
683
|
-
const h = (spriteHeight - this.hitbox.h - gap) / spriteHeight;
|
|
684
|
-
this.anchor.set(w, h);
|
|
685
|
-
}
|
|
686
|
-
};
|
|
687
686
|
|
|
688
687
|
if (frame.sound) {
|
|
689
688
|
//RpgSound.get(frame.sound).play()
|
|
690
689
|
}
|
|
691
690
|
|
|
692
|
-
|
|
691
|
+
this.applyHitboxAnchor(data.spriteWidth, data.spriteHeight, realSize);
|
|
693
692
|
|
|
694
693
|
applyTransform("anchor");
|
|
695
694
|
applyTransform("scale");
|
|
@@ -717,6 +716,75 @@ export class CanvasSprite extends DisplayObject(PixiSprite) {
|
|
|
717
716
|
this.frameIndex++;
|
|
718
717
|
}
|
|
719
718
|
}
|
|
719
|
+
|
|
720
|
+
private applyHitboxAnchor(
|
|
721
|
+
spriteWidth: number,
|
|
722
|
+
spriteHeight: number,
|
|
723
|
+
realSize?: TextureOptionsMerging["spriteRealSize"]
|
|
724
|
+
) {
|
|
725
|
+
if (!this.hitbox || !spriteWidth || !spriteHeight) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const heightOfSprite =
|
|
730
|
+
typeof realSize === "number" ? realSize : realSize?.height;
|
|
731
|
+
const resolvedHeight = heightOfSprite ?? spriteHeight;
|
|
732
|
+
const gap = Math.max(0, (spriteHeight - resolvedHeight) / 2);
|
|
733
|
+
const hitboxTopLeftX = this.clamp(
|
|
734
|
+
(spriteWidth - this.hitbox.w) / 2 / spriteWidth
|
|
735
|
+
);
|
|
736
|
+
const hitboxTopLeftY = this.clamp(
|
|
737
|
+
(spriteHeight - this.hitbox.h - gap) / spriteHeight
|
|
738
|
+
);
|
|
739
|
+
const hitboxCenterX = this.clamp(
|
|
740
|
+
hitboxTopLeftX + this.hitbox.w / 2 / spriteWidth
|
|
741
|
+
);
|
|
742
|
+
const hitboxCenterY = this.clamp(
|
|
743
|
+
hitboxTopLeftY + this.hitbox.h / 2 / spriteHeight
|
|
744
|
+
);
|
|
745
|
+
const footY = this.clamp((spriteHeight - gap) / spriteHeight);
|
|
746
|
+
|
|
747
|
+
let anchorX = hitboxTopLeftX;
|
|
748
|
+
let anchorY = hitboxTopLeftY;
|
|
749
|
+
|
|
750
|
+
switch (this.hitbox.anchorMode ?? "top-left") {
|
|
751
|
+
case "center":
|
|
752
|
+
anchorX = hitboxCenterX;
|
|
753
|
+
anchorY = hitboxCenterY;
|
|
754
|
+
break;
|
|
755
|
+
case "foot":
|
|
756
|
+
anchorX = hitboxCenterX;
|
|
757
|
+
anchorY = footY;
|
|
758
|
+
break;
|
|
759
|
+
case "top-left":
|
|
760
|
+
default:
|
|
761
|
+
break;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
this.anchor.set(anchorX, anchorY);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
private normalizeHitbox(hitbox: unknown): Hitbox | null {
|
|
768
|
+
const resolvedHitbox = (hitbox as any)?.value ?? hitbox;
|
|
769
|
+
if (!resolvedHitbox || typeof resolvedHitbox !== "object") {
|
|
770
|
+
return null;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const { w, h, anchorMode } = resolvedHitbox as Partial<Hitbox>;
|
|
774
|
+
if (typeof w !== "number" || typeof h !== "number") {
|
|
775
|
+
return null;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return {
|
|
779
|
+
w,
|
|
780
|
+
h,
|
|
781
|
+
anchorMode,
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
private clamp(value: number) {
|
|
786
|
+
return Math.min(1, Math.max(0, value));
|
|
787
|
+
}
|
|
720
788
|
}
|
|
721
789
|
|
|
722
790
|
export interface CanvasSprite extends PixiSprite {
|
|
@@ -733,6 +801,7 @@ export interface SpriteProps extends DisplayObjectProps {
|
|
|
733
801
|
params?: any;
|
|
734
802
|
onFinish?: () => void;
|
|
735
803
|
};
|
|
804
|
+
hitbox?: Hitbox;
|
|
736
805
|
scaleMode?: number;
|
|
737
806
|
image?: string;
|
|
738
807
|
rectangle?: {
|
|
@@ -34,6 +34,7 @@ export interface ViewportProps extends Props {
|
|
|
34
34
|
screenHeight?: number;
|
|
35
35
|
worldWidth?: number;
|
|
36
36
|
worldHeight?: number;
|
|
37
|
+
sortableChildren?: boolean;
|
|
37
38
|
clamp?: boolean | {
|
|
38
39
|
left?: number;
|
|
39
40
|
right?: number;
|
|
@@ -146,6 +147,9 @@ export class CanvasViewport extends DisplayObject(Container) {
|
|
|
146
147
|
if (props.worldHeight !== undefined) {
|
|
147
148
|
this.viewport.worldHeight = props.worldHeight
|
|
148
149
|
}
|
|
150
|
+
if (props.sortableChildren !== undefined) {
|
|
151
|
+
this.viewport.sortableChildren = props.sortableChildren
|
|
152
|
+
}
|
|
149
153
|
if (props.drag) {
|
|
150
154
|
this.viewport.drag(props.drag)
|
|
151
155
|
}
|
|
@@ -4,6 +4,7 @@ import { DragProps } from "../../directives/Drag";
|
|
|
4
4
|
import { ViewportFollowProps } from "../../directives/ViewportFollow";
|
|
5
5
|
import { ShakeProps } from "../../directives/Shake";
|
|
6
6
|
import { FlashProps } from "../../directives/Flash";
|
|
7
|
+
import { FogVisibilityProps } from "../../directives/FogVisibility";
|
|
7
8
|
|
|
8
9
|
export type FlexDirection = 'row' | 'column' | 'row-reverse' | 'column-reverse';
|
|
9
10
|
export type JustifyContent = 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around';
|
|
@@ -64,12 +65,23 @@ export interface DisplayObjectProps {
|
|
|
64
65
|
filters?: any[];
|
|
65
66
|
blendMode?: SignalOrPrimitive<PIXI.BLEND_MODES>;
|
|
66
67
|
blur?: SignalOrPrimitive<number>;
|
|
68
|
+
/**
|
|
69
|
+
* Optional metadata used by presets (for example `SpriteShadows`)
|
|
70
|
+
* to mark this display object as a shadow caster.
|
|
71
|
+
*/
|
|
72
|
+
shadowCaster?: any;
|
|
73
|
+
/**
|
|
74
|
+
* Optional metadata used by presets (for example `Footprints`)
|
|
75
|
+
* to mark this display object as a footprint caster.
|
|
76
|
+
*/
|
|
77
|
+
footprintCaster?: any;
|
|
67
78
|
|
|
68
79
|
// Directives
|
|
69
80
|
drag?: DragProps;
|
|
70
81
|
viewportFollow?: ViewportFollowProps;
|
|
71
82
|
shake?: ShakeProps;
|
|
72
83
|
flash?: FlashProps;
|
|
84
|
+
fogVisibility?: FogVisibilityProps;
|
|
73
85
|
|
|
74
86
|
// Events
|
|
75
87
|
click?: PIXI.FederatedEventHandler;
|
|
@@ -103,4 +115,4 @@ export interface DisplayObjectProps {
|
|
|
103
115
|
touchstart?: PIXI.FederatedEventHandler;
|
|
104
116
|
wheel?: PIXI.FederatedEventHandler<PIXI.FederatedWheelEvent>;
|
|
105
117
|
tabindex?: SignalOrPrimitive<number>;
|
|
106
|
-
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { isSignal } from "@signe/reactive";
|
|
2
|
+
import { Container } from "pixi.js";
|
|
3
|
+
import { Subscription } from "rxjs";
|
|
4
|
+
import { SignalOrPrimitive } from "../components/types";
|
|
5
|
+
import { Directive, registerDirective } from "../engine/directive";
|
|
6
|
+
import { Element } from "../engine/reactive";
|
|
7
|
+
|
|
8
|
+
export type FogVisibilityState = "visible" | "explored" | "unknown";
|
|
9
|
+
export type FogVisibilityMode = "visible" | "explored";
|
|
10
|
+
export type FogVisibilityHideAs = "visible" | "alpha";
|
|
11
|
+
|
|
12
|
+
export type FogVisibilityController = {
|
|
13
|
+
version?: () => number;
|
|
14
|
+
clarityAt?: (x: number, y: number) => number;
|
|
15
|
+
isVisibleAt?: (x: number, y: number, threshold?: number) => boolean;
|
|
16
|
+
isExploredAt?: (x: number, y: number) => boolean;
|
|
17
|
+
stateAt?: (x: number, y: number, clearThreshold?: number) => FogVisibilityState | string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type FogVisibilityPoint = {
|
|
21
|
+
x: SignalOrPrimitive<number>;
|
|
22
|
+
y: SignalOrPrimitive<number>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type FogVisibilityProps = {
|
|
26
|
+
controller?: SignalOrPrimitive<FogVisibilityController | null | undefined>;
|
|
27
|
+
mode?: SignalOrPrimitive<FogVisibilityMode>;
|
|
28
|
+
threshold?: SignalOrPrimitive<number>;
|
|
29
|
+
point?: SignalOrPrimitive<FogVisibilityPoint>;
|
|
30
|
+
hideAs?: SignalOrPrimitive<FogVisibilityHideAs>;
|
|
31
|
+
hiddenAlpha?: SignalOrPrimitive<number>;
|
|
32
|
+
sampleHz?: SignalOrPrimitive<number>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type NormalizedFogVisibilityProps = {
|
|
36
|
+
controller: FogVisibilityController | null;
|
|
37
|
+
mode: FogVisibilityMode;
|
|
38
|
+
threshold: number;
|
|
39
|
+
hideAs: FogVisibilityHideAs;
|
|
40
|
+
hiddenAlpha: number;
|
|
41
|
+
sampleHz: number;
|
|
42
|
+
point?: FogVisibilityPoint;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const clamp = (value: number, min: number, max: number) =>
|
|
46
|
+
Math.max(min, Math.min(max, value));
|
|
47
|
+
|
|
48
|
+
export class FogVisibility extends Directive {
|
|
49
|
+
private elementRef: Element<Container> | null = null;
|
|
50
|
+
private tickSubscription: Subscription | null = null;
|
|
51
|
+
private sampleAccumulatorMs = 0;
|
|
52
|
+
private managesAlpha = false;
|
|
53
|
+
|
|
54
|
+
onInit(element: Element<Container>) {
|
|
55
|
+
this.elementRef = element;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onMount(element: Element<Container>) {
|
|
59
|
+
this.elementRef = element;
|
|
60
|
+
this.evaluateVisibility();
|
|
61
|
+
this.bindTick();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
onUpdate() {
|
|
65
|
+
this.sampleAccumulatorMs = 0;
|
|
66
|
+
this.evaluateVisibility();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onDestroy() {
|
|
70
|
+
const instance = this.elementRef?.componentInstance as any;
|
|
71
|
+
if (instance) {
|
|
72
|
+
const baseVisible = this.readBaseVisible();
|
|
73
|
+
instance.visible = baseVisible;
|
|
74
|
+
if (this.managesAlpha) {
|
|
75
|
+
instance.alpha = this.readBaseAlpha();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.tickSubscription?.unsubscribe();
|
|
80
|
+
this.tickSubscription = null;
|
|
81
|
+
this.sampleAccumulatorMs = 0;
|
|
82
|
+
this.managesAlpha = false;
|
|
83
|
+
this.elementRef = null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private bindTick() {
|
|
87
|
+
this.tickSubscription?.unsubscribe();
|
|
88
|
+
this.tickSubscription = null;
|
|
89
|
+
|
|
90
|
+
const tick = this.elementRef?.props?.context?.tick;
|
|
91
|
+
if (!tick?.observable) return;
|
|
92
|
+
|
|
93
|
+
this.tickSubscription = tick.observable.subscribe((payload: any) => {
|
|
94
|
+
const tickValue = payload?.value ?? payload;
|
|
95
|
+
const deltaTime = Number(tickValue?.deltaTime);
|
|
96
|
+
const options = this.readOptions();
|
|
97
|
+
const intervalMs = 1000 / options.sampleHz;
|
|
98
|
+
|
|
99
|
+
this.sampleAccumulatorMs += Number.isFinite(deltaTime) ? deltaTime : intervalMs;
|
|
100
|
+
if (this.sampleAccumulatorMs < intervalMs) return;
|
|
101
|
+
|
|
102
|
+
this.sampleAccumulatorMs = 0;
|
|
103
|
+
this.evaluateVisibility(options);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private resolveSignalValue<T>(value: SignalOrPrimitive<T> | undefined, fallback: T): T {
|
|
108
|
+
if (value === undefined || value === null) return fallback;
|
|
109
|
+
if (isSignal(value as any)) {
|
|
110
|
+
const signalValue = (value as any)();
|
|
111
|
+
return signalValue === undefined || signalValue === null ? fallback : signalValue;
|
|
112
|
+
}
|
|
113
|
+
return value as T;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private readOptions(): NormalizedFogVisibilityProps {
|
|
117
|
+
const fogVisibility = this.elementRef?.props?.fogVisibility;
|
|
118
|
+
const raw = (fogVisibility as any)?.value ?? fogVisibility ?? {};
|
|
119
|
+
|
|
120
|
+
const controller = this.resolveSignalValue(
|
|
121
|
+
raw.controller as SignalOrPrimitive<FogVisibilityController | null | undefined>,
|
|
122
|
+
null
|
|
123
|
+
);
|
|
124
|
+
const mode = this.resolveSignalValue(raw.mode as SignalOrPrimitive<FogVisibilityMode>, "visible");
|
|
125
|
+
const threshold = clamp(
|
|
126
|
+
Number(this.resolveSignalValue(raw.threshold as SignalOrPrimitive<number>, 0.65)),
|
|
127
|
+
0,
|
|
128
|
+
1
|
|
129
|
+
);
|
|
130
|
+
const hideAs = this.resolveSignalValue(
|
|
131
|
+
raw.hideAs as SignalOrPrimitive<FogVisibilityHideAs>,
|
|
132
|
+
"visible"
|
|
133
|
+
);
|
|
134
|
+
const hiddenAlpha = clamp(
|
|
135
|
+
Number(this.resolveSignalValue(raw.hiddenAlpha as SignalOrPrimitive<number>, 0)),
|
|
136
|
+
0,
|
|
137
|
+
1
|
|
138
|
+
);
|
|
139
|
+
const sampleHz = clamp(
|
|
140
|
+
Number(this.resolveSignalValue(raw.sampleHz as SignalOrPrimitive<number>, 30)),
|
|
141
|
+
1,
|
|
142
|
+
240
|
|
143
|
+
);
|
|
144
|
+
const point = raw.point
|
|
145
|
+
? this.resolveSignalValue(raw.point as SignalOrPrimitive<FogVisibilityPoint>, undefined as any)
|
|
146
|
+
: undefined;
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
controller: controller && typeof controller === "object" ? controller : null,
|
|
150
|
+
mode,
|
|
151
|
+
threshold,
|
|
152
|
+
hideAs,
|
|
153
|
+
hiddenAlpha,
|
|
154
|
+
sampleHz,
|
|
155
|
+
point,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private readBaseVisible() {
|
|
160
|
+
const visibleSource = (this.elementRef?.propObservables as any)?.visible ?? this.elementRef?.props?.visible;
|
|
161
|
+
return !!this.resolveSignalValue(visibleSource as SignalOrPrimitive<boolean>, true);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private readBaseAlpha() {
|
|
165
|
+
const alphaSource = (this.elementRef?.propObservables as any)?.alpha ?? this.elementRef?.props?.alpha;
|
|
166
|
+
const value = Number(this.resolveSignalValue(alphaSource as SignalOrPrimitive<number>, 1));
|
|
167
|
+
if (!Number.isFinite(value)) return 1;
|
|
168
|
+
return clamp(value, 0, 1);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private samplePoint(options: NormalizedFogVisibilityProps) {
|
|
172
|
+
if (options.point && typeof options.point === "object") {
|
|
173
|
+
const x = Number(this.resolveSignalValue(options.point.x, NaN));
|
|
174
|
+
const y = Number(this.resolveSignalValue(options.point.y, NaN));
|
|
175
|
+
if (Number.isFinite(x) && Number.isFinite(y)) {
|
|
176
|
+
return { x, y };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const instance = this.elementRef?.componentInstance as any;
|
|
181
|
+
return {
|
|
182
|
+
x: Number(instance?.x ?? 0),
|
|
183
|
+
y: Number(instance?.y ?? 0),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private resolveVisibleState(
|
|
188
|
+
controller: FogVisibilityController,
|
|
189
|
+
x: number,
|
|
190
|
+
y: number,
|
|
191
|
+
threshold: number
|
|
192
|
+
) {
|
|
193
|
+
if (typeof controller.isVisibleAt === "function") {
|
|
194
|
+
return !!controller.isVisibleAt(x, y, threshold);
|
|
195
|
+
}
|
|
196
|
+
if (typeof controller.clarityAt === "function") {
|
|
197
|
+
const clarity = Number(controller.clarityAt(x, y));
|
|
198
|
+
if (!Number.isFinite(clarity)) return true;
|
|
199
|
+
return clarity >= threshold;
|
|
200
|
+
}
|
|
201
|
+
if (typeof controller.stateAt === "function") {
|
|
202
|
+
const state = controller.stateAt(x, y, threshold);
|
|
203
|
+
return state === "visible";
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private resolveExploredState(
|
|
209
|
+
controller: FogVisibilityController,
|
|
210
|
+
x: number,
|
|
211
|
+
y: number,
|
|
212
|
+
threshold: number
|
|
213
|
+
) {
|
|
214
|
+
if (typeof controller.isExploredAt === "function") {
|
|
215
|
+
return !!controller.isExploredAt(x, y);
|
|
216
|
+
}
|
|
217
|
+
if (typeof controller.stateAt === "function") {
|
|
218
|
+
const state = controller.stateAt(x, y, threshold);
|
|
219
|
+
return state !== "unknown";
|
|
220
|
+
}
|
|
221
|
+
return this.resolveVisibleState(controller, x, y, threshold);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private evaluateVisibility(explicitOptions?: NormalizedFogVisibilityProps) {
|
|
225
|
+
const element = this.elementRef;
|
|
226
|
+
const instance = element?.componentInstance as any;
|
|
227
|
+
if (!instance) return;
|
|
228
|
+
|
|
229
|
+
const options = explicitOptions ?? this.readOptions();
|
|
230
|
+
const baseVisible = this.readBaseVisible();
|
|
231
|
+
const baseAlpha = this.readBaseAlpha();
|
|
232
|
+
|
|
233
|
+
if (!options.controller) {
|
|
234
|
+
instance.visible = baseVisible;
|
|
235
|
+
if (this.managesAlpha) {
|
|
236
|
+
instance.alpha = baseAlpha;
|
|
237
|
+
this.managesAlpha = false;
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Link directive reactivity to FogOfWar controller internal revision.
|
|
243
|
+
if (typeof options.controller.version === "function") {
|
|
244
|
+
options.controller.version();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const { x, y } = this.samplePoint(options);
|
|
248
|
+
let fogAllowsVisibility = true;
|
|
249
|
+
|
|
250
|
+
if (options.mode === "explored") {
|
|
251
|
+
fogAllowsVisibility = this.resolveExploredState(options.controller, x, y, options.threshold);
|
|
252
|
+
} else {
|
|
253
|
+
fogAllowsVisibility = this.resolveVisibleState(options.controller, x, y, options.threshold);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (options.hideAs === "alpha") {
|
|
257
|
+
instance.visible = baseVisible;
|
|
258
|
+
instance.alpha = fogAllowsVisibility
|
|
259
|
+
? baseAlpha
|
|
260
|
+
: Math.min(baseAlpha, options.hiddenAlpha);
|
|
261
|
+
this.managesAlpha = true;
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
instance.visible = baseVisible && fogAllowsVisibility;
|
|
266
|
+
if (this.managesAlpha) {
|
|
267
|
+
instance.alpha = baseAlpha;
|
|
268
|
+
this.managesAlpha = false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
registerDirective("fogVisibility", FogVisibility);
|
package/src/directives/index.ts
CHANGED