@shopify/klint 0.0.97 → 0.2.0
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/bin/create-editor +423 -0
- package/bin/create-sandbox +232 -0
- package/bin/klint +146 -0
- package/dist/index.cjs +2369 -317
- package/dist/index.d.cts +847 -66
- package/dist/index.d.ts +847 -66
- package/dist/index.js +2362 -316
- package/package.json +11 -17
- package/dist/elements/index.cjs +0 -946
- package/dist/elements/index.d.cts +0 -658
- package/dist/elements/index.d.ts +0 -658
- package/dist/elements/index.js +0 -913
- package/dist/plugins/index.cjs +0 -1174
- package/dist/plugins/index.d.ts +0 -731
- package/dist/plugins/index.js +0 -1140
package/dist/index.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
1
8
|
// src/Klint.tsx
|
|
2
9
|
import React, { useRef, useEffect, useState, useCallback } from "react";
|
|
3
10
|
var DEFAULT_FPS = 60;
|
|
@@ -37,11 +44,18 @@ var CONFIG_PROPS = [
|
|
|
37
44
|
"__textWeight",
|
|
38
45
|
"__textStyle",
|
|
39
46
|
"__textSize",
|
|
47
|
+
"__textLeading",
|
|
40
48
|
"__textAlignment",
|
|
49
|
+
"__fillRule",
|
|
41
50
|
"__isPlaying"
|
|
42
51
|
];
|
|
43
|
-
function useAnimate(contextRef, draw, isVisible) {
|
|
52
|
+
function useAnimate(contextRef, draw, isVisible, enablePerformanceTracking = false) {
|
|
44
53
|
const animationFrameId = useRef(0);
|
|
54
|
+
const frameTimeHistoryRef = useRef([]);
|
|
55
|
+
const frameStartTimeRef = useRef(0);
|
|
56
|
+
const frameCountRef = useRef(0);
|
|
57
|
+
const lastFpsUpdateRef = useRef(0);
|
|
58
|
+
const droppedFramesRef = useRef(0);
|
|
45
59
|
const animate = useCallback(
|
|
46
60
|
(timestamp = 0) => {
|
|
47
61
|
if (!contextRef.current || !isVisible) return;
|
|
@@ -55,22 +69,55 @@ function useAnimate(contextRef, draw, isVisible) {
|
|
|
55
69
|
if (!context.__lastTargetTime) {
|
|
56
70
|
context.__lastTargetTime = now;
|
|
57
71
|
context.__lastRealTime = now;
|
|
72
|
+
frameStartTimeRef.current = now;
|
|
73
|
+
lastFpsUpdateRef.current = now;
|
|
58
74
|
}
|
|
59
75
|
const sinceLast = now - context.__lastTargetTime;
|
|
60
76
|
const epsilon = 5;
|
|
61
77
|
if (sinceLast >= target - epsilon) {
|
|
78
|
+
const frameStart = enablePerformanceTracking && context.__performance ? performance.now() : 0;
|
|
62
79
|
context.deltaTime = now - context.__lastRealTime;
|
|
63
80
|
draw(context);
|
|
64
81
|
if (context.time > 1e7) context.time = 0;
|
|
65
82
|
if (context.frame > 1e7) context.frame = 0;
|
|
66
|
-
context.time += context.deltaTime /
|
|
83
|
+
context.time += context.deltaTime / 1e3;
|
|
67
84
|
context.frame++;
|
|
68
85
|
context.__lastTargetTime = now;
|
|
69
86
|
context.__lastRealTime = now;
|
|
87
|
+
if (enablePerformanceTracking && context.__performance) {
|
|
88
|
+
const frameTime = performance.now() - frameStart;
|
|
89
|
+
const targetFrameTime = 1e3 / context.fps;
|
|
90
|
+
frameTimeHistoryRef.current.push(frameTime);
|
|
91
|
+
if (frameTimeHistoryRef.current.length > 60) {
|
|
92
|
+
frameTimeHistoryRef.current.shift();
|
|
93
|
+
}
|
|
94
|
+
const avgFrameTime = frameTimeHistoryRef.current.reduce((a, b) => a + b, 0) / frameTimeHistoryRef.current.length;
|
|
95
|
+
context.__performance.frameTime = frameTime;
|
|
96
|
+
context.__performance.averageFrameTime = avgFrameTime;
|
|
97
|
+
context.__performance.minFrameTime = Math.min(
|
|
98
|
+
...frameTimeHistoryRef.current
|
|
99
|
+
);
|
|
100
|
+
context.__performance.maxFrameTime = Math.max(
|
|
101
|
+
...frameTimeHistoryRef.current
|
|
102
|
+
);
|
|
103
|
+
if (frameTime > targetFrameTime * 1.1) {
|
|
104
|
+
droppedFramesRef.current++;
|
|
105
|
+
}
|
|
106
|
+
context.__performance.droppedFrames = droppedFramesRef.current;
|
|
107
|
+
frameCountRef.current++;
|
|
108
|
+
if (now - lastFpsUpdateRef.current >= 1e3) {
|
|
109
|
+
context.__performance.fps = frameCountRef.current;
|
|
110
|
+
frameCountRef.current = 0;
|
|
111
|
+
lastFpsUpdateRef.current = now;
|
|
112
|
+
if (typeof performance !== "undefined" && performance.memory) {
|
|
113
|
+
context.__performance.memoryUsage = performance.memory.usedJSHeapSize / 1048576;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
70
117
|
}
|
|
71
118
|
animationFrameId.current = requestAnimationFrame(animate);
|
|
72
119
|
},
|
|
73
|
-
[draw, isVisible, contextRef]
|
|
120
|
+
[draw, isVisible, contextRef, enablePerformanceTracking]
|
|
74
121
|
);
|
|
75
122
|
return {
|
|
76
123
|
animate,
|
|
@@ -83,7 +130,8 @@ function Klint({
|
|
|
83
130
|
draw,
|
|
84
131
|
options = {},
|
|
85
132
|
preload,
|
|
86
|
-
onVisible
|
|
133
|
+
onVisible,
|
|
134
|
+
enablePerformanceTracking = false
|
|
87
135
|
}) {
|
|
88
136
|
const canvasRef = useRef(null);
|
|
89
137
|
const containerRef = useRef(null);
|
|
@@ -93,13 +141,36 @@ function Klint({
|
|
|
93
141
|
null
|
|
94
142
|
);
|
|
95
143
|
const [isVisible, setIsVisible] = useState(true);
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
if (typeof import.meta !== "undefined" && import.meta.hot) {
|
|
146
|
+
import.meta.hot.dispose(() => {
|
|
147
|
+
console.log("[Klint] Component unmounting due to HMR");
|
|
148
|
+
if (contextRef.current) {
|
|
149
|
+
contextRef.current.__isPlaying = false;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (typeof module !== "undefined" && module.hot) {
|
|
154
|
+
module.hot.dispose(() => {
|
|
155
|
+
console.log("[Klint] Component unmounting due to Webpack HMR");
|
|
156
|
+
if (contextRef.current) {
|
|
157
|
+
contextRef.current.__isPlaying = false;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}, []);
|
|
96
162
|
const __options = {
|
|
97
163
|
...DEFAULT_OPTIONS,
|
|
98
164
|
...options
|
|
99
165
|
};
|
|
100
166
|
const [toStaticImage, setStaticImage] = useState(null);
|
|
101
167
|
const initContext = context?.initCoreContext;
|
|
102
|
-
const { animate, animationFrameId } = useAnimate(
|
|
168
|
+
const { animate, animationFrameId } = useAnimate(
|
|
169
|
+
contextRef,
|
|
170
|
+
draw,
|
|
171
|
+
isVisible,
|
|
172
|
+
enablePerformanceTracking
|
|
173
|
+
);
|
|
103
174
|
const updateCanvasSize = (shouldRedraw = false) => {
|
|
104
175
|
if (!containerRef.current || !contextRef.current || !canvasRef.current)
|
|
105
176
|
return;
|
|
@@ -129,6 +200,16 @@ function Klint({
|
|
|
129
200
|
const context2 = contextRef.current;
|
|
130
201
|
if (!context2) return;
|
|
131
202
|
context2.__dpr = dpr;
|
|
203
|
+
if (enablePerformanceTracking) {
|
|
204
|
+
context2.__performance = {
|
|
205
|
+
fps: 0,
|
|
206
|
+
frameTime: 0,
|
|
207
|
+
averageFrameTime: 0,
|
|
208
|
+
minFrameTime: Infinity,
|
|
209
|
+
maxFrameTime: 0,
|
|
210
|
+
droppedFrames: 0
|
|
211
|
+
};
|
|
212
|
+
}
|
|
132
213
|
if (__options.fps && __options.fps !== context2.fps) {
|
|
133
214
|
context2.fps = __options.fps;
|
|
134
215
|
}
|
|
@@ -591,7 +672,8 @@ var Color_default = Color;
|
|
|
591
672
|
|
|
592
673
|
// src/elements/Easing.tsx
|
|
593
674
|
var Easing = class {
|
|
594
|
-
constructor(
|
|
675
|
+
constructor() {
|
|
676
|
+
this.zeroOut = (val) => Object.is(val, -0) || Math.abs(val) < 1e-12 ? 0 : val;
|
|
595
677
|
this.normalize = (val) => {
|
|
596
678
|
return val * 0.5 + 0.5;
|
|
597
679
|
};
|
|
@@ -615,19 +697,19 @@ var Easing = class {
|
|
|
615
697
|
};
|
|
616
698
|
this.overshootIn = (val) => {
|
|
617
699
|
const k = 1.70158;
|
|
618
|
-
return val * val * (val * (k + 1) - k);
|
|
700
|
+
return this.zeroOut(val * val * (val * (k + 1) - k));
|
|
619
701
|
};
|
|
620
702
|
this.overshootOut = (val) => {
|
|
621
703
|
const m = val - 1;
|
|
622
704
|
const k = 1.70158;
|
|
623
|
-
return 1 + m * m * (m * (k + 1) + k);
|
|
705
|
+
return this.zeroOut(1 + m * m * (m * (k + 1) + k));
|
|
624
706
|
};
|
|
625
707
|
this.overshootInOut = (val) => {
|
|
626
708
|
const m = val - 1;
|
|
627
709
|
const t = val * 2;
|
|
628
710
|
const k = 1.70158 * 1.525;
|
|
629
|
-
if (val < 0.5) return val * t * (t * (k + 1) - k);
|
|
630
|
-
return 1 + 2 * m * m * (2 * m * (k + 1) + k);
|
|
711
|
+
if (val < 0.5) return this.zeroOut(val * t * (t * (k + 1) - k));
|
|
712
|
+
return this.zeroOut(1 + 2 * m * m * (2 * m * (k + 1) + k));
|
|
631
713
|
};
|
|
632
714
|
this.bounceOut = (val) => {
|
|
633
715
|
const r = 1 / 2.75;
|
|
@@ -680,48 +762,22 @@ var Easing = class {
|
|
|
680
762
|
this.log = () => {
|
|
681
763
|
console.log(this);
|
|
682
764
|
};
|
|
683
|
-
this.context = ctx;
|
|
684
765
|
}
|
|
685
766
|
};
|
|
686
767
|
var Easing_default = Easing;
|
|
687
768
|
|
|
688
|
-
// src/elements/State.tsx
|
|
689
|
-
var State = class {
|
|
690
|
-
constructor() {
|
|
691
|
-
this.store = /* @__PURE__ */ new Map();
|
|
692
|
-
}
|
|
693
|
-
set(key, value, callback) {
|
|
694
|
-
this.store.set(key, value);
|
|
695
|
-
callback?.(key, value);
|
|
696
|
-
}
|
|
697
|
-
get(key, callback) {
|
|
698
|
-
const value = this.store.get(key);
|
|
699
|
-
callback?.(key, value);
|
|
700
|
-
return value;
|
|
701
|
-
}
|
|
702
|
-
has(key) {
|
|
703
|
-
return this.store.has(key);
|
|
704
|
-
}
|
|
705
|
-
delete(key, callback) {
|
|
706
|
-
this.store.delete(key);
|
|
707
|
-
callback?.(key);
|
|
708
|
-
}
|
|
709
|
-
log() {
|
|
710
|
-
return this.store;
|
|
711
|
-
}
|
|
712
|
-
};
|
|
713
|
-
var State_default = State;
|
|
714
|
-
|
|
715
769
|
// src/elements/Vector.tsx
|
|
716
770
|
var Vector = class _Vector {
|
|
717
771
|
/**
|
|
718
772
|
* Creates a new Vector
|
|
719
773
|
* @param x - X-coordinate (default: 0)
|
|
720
774
|
* @param y - Y-coordinate (default: 0)
|
|
775
|
+
* @param z - Z-coordinate (default: 0)
|
|
721
776
|
*/
|
|
722
|
-
constructor(x = 0, y = 0) {
|
|
777
|
+
constructor(x = 0, y = 0, z = 0) {
|
|
723
778
|
this.x = x;
|
|
724
779
|
this.y = y;
|
|
780
|
+
this.z = z;
|
|
725
781
|
}
|
|
726
782
|
/**
|
|
727
783
|
* Adds another vector to this vector
|
|
@@ -731,6 +787,7 @@ var Vector = class _Vector {
|
|
|
731
787
|
add(v) {
|
|
732
788
|
this.x += v.x;
|
|
733
789
|
this.y += v.y;
|
|
790
|
+
this.z += v.z;
|
|
734
791
|
return this;
|
|
735
792
|
}
|
|
736
793
|
/**
|
|
@@ -741,6 +798,7 @@ var Vector = class _Vector {
|
|
|
741
798
|
sub(v) {
|
|
742
799
|
this.x -= v.x;
|
|
743
800
|
this.y -= v.y;
|
|
801
|
+
this.z -= v.z;
|
|
744
802
|
return this;
|
|
745
803
|
}
|
|
746
804
|
/**
|
|
@@ -751,6 +809,7 @@ var Vector = class _Vector {
|
|
|
751
809
|
mult(n) {
|
|
752
810
|
this.x *= n;
|
|
753
811
|
this.y *= n;
|
|
812
|
+
this.z *= n;
|
|
754
813
|
return this;
|
|
755
814
|
}
|
|
756
815
|
/**
|
|
@@ -761,11 +820,11 @@ var Vector = class _Vector {
|
|
|
761
820
|
div(n) {
|
|
762
821
|
this.x /= n;
|
|
763
822
|
this.y /= n;
|
|
823
|
+
this.z /= n;
|
|
764
824
|
return this;
|
|
765
825
|
}
|
|
766
|
-
// to do : project, perp, slerp
|
|
767
826
|
/**
|
|
768
|
-
* Rotates this vector by an angle
|
|
827
|
+
* Rotates this vector by an angle (around z-axis for 2D rotation)
|
|
769
828
|
* @param angle - Angle in radians
|
|
770
829
|
* @returns This vector after rotation
|
|
771
830
|
*/
|
|
@@ -783,7 +842,7 @@ var Vector = class _Vector {
|
|
|
783
842
|
* @returns The magnitude of the vector
|
|
784
843
|
*/
|
|
785
844
|
mag() {
|
|
786
|
-
return Math.sqrt(this.x * this.x + this.y * this.y);
|
|
845
|
+
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
|
|
787
846
|
}
|
|
788
847
|
/**
|
|
789
848
|
* Alias for mag() - calculates the length of this vector
|
|
@@ -798,7 +857,7 @@ var Vector = class _Vector {
|
|
|
798
857
|
* @returns The dot product
|
|
799
858
|
*/
|
|
800
859
|
dot(v) {
|
|
801
|
-
return this.x * v.x + this.y * v.y;
|
|
860
|
+
return this.x * v.x + this.y * v.y + this.z * v.z;
|
|
802
861
|
}
|
|
803
862
|
/**
|
|
804
863
|
* Calculates the distance between this vector and another vector
|
|
@@ -806,21 +865,21 @@ var Vector = class _Vector {
|
|
|
806
865
|
* @returns The distance between the vectors
|
|
807
866
|
*/
|
|
808
867
|
dist(v) {
|
|
809
|
-
return Math.hypot(this.x - v.x, this.y - v.y);
|
|
868
|
+
return Math.hypot(this.x - v.x, this.y - v.y, this.z - v.z);
|
|
810
869
|
}
|
|
811
870
|
/**
|
|
812
|
-
* Calculates the angle of this vector
|
|
871
|
+
* Calculates the angle of this vector (in 2D, ignoring z)
|
|
813
872
|
* @returns The angle in radians
|
|
814
873
|
*/
|
|
815
874
|
angle() {
|
|
816
|
-
return Math.atan2(
|
|
875
|
+
return Math.atan2(this.y, this.x);
|
|
817
876
|
}
|
|
818
877
|
/**
|
|
819
878
|
* Creates a copy of this vector
|
|
820
879
|
* @returns A new Vector with the same coordinates
|
|
821
880
|
*/
|
|
822
881
|
copy() {
|
|
823
|
-
return new _Vector(this.x, this.y);
|
|
882
|
+
return new _Vector(this.x, this.y, this.z);
|
|
824
883
|
}
|
|
825
884
|
/**
|
|
826
885
|
* Normalizes this vector (sets its magnitude to 1)
|
|
@@ -834,107 +893,128 @@ var Vector = class _Vector {
|
|
|
834
893
|
* Sets the coordinates of this vector
|
|
835
894
|
* @param x - New X-coordinate
|
|
836
895
|
* @param y - New Y-coordinate
|
|
896
|
+
* @param z - New Z-coordinate (default: 0)
|
|
837
897
|
* @returns This vector after setting coordinates
|
|
838
898
|
*/
|
|
839
|
-
set(x, y) {
|
|
899
|
+
set(x, y, z = 0) {
|
|
840
900
|
this.x = x;
|
|
841
901
|
this.y = y;
|
|
902
|
+
this.z = z;
|
|
842
903
|
return this;
|
|
843
904
|
}
|
|
844
905
|
/**
|
|
845
|
-
*
|
|
846
|
-
* @param
|
|
847
|
-
* @
|
|
848
|
-
* @param r - The radius (distance from center)
|
|
849
|
-
* @returns A new Vector at the calculated position
|
|
906
|
+
* Calculates the cross product of this vector with another vector
|
|
907
|
+
* @param v - The other vector
|
|
908
|
+
* @returns A new Vector representing the cross product
|
|
850
909
|
*/
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
var Vector_default = Vector;
|
|
858
|
-
|
|
859
|
-
// src/elements/Time.tsx
|
|
860
|
-
var Time = class {
|
|
861
|
-
constructor(ctx) {
|
|
862
|
-
this.timelines = /* @__PURE__ */ new Map();
|
|
863
|
-
this.currentTimeline = "default";
|
|
864
|
-
this.DEFAULT_DURATION = 8 * 60;
|
|
865
|
-
this.staggers = [];
|
|
866
|
-
this.context = ctx;
|
|
867
|
-
this.timelines.set("default", {
|
|
868
|
-
progress: 0,
|
|
869
|
-
duration: this.DEFAULT_DURATION
|
|
870
|
-
});
|
|
910
|
+
cross(v) {
|
|
911
|
+
return new _Vector(
|
|
912
|
+
this.y * v.z - this.z * v.y,
|
|
913
|
+
this.z * v.x - this.x * v.z,
|
|
914
|
+
this.x * v.y - this.y * v.x
|
|
915
|
+
);
|
|
871
916
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
917
|
+
/**
|
|
918
|
+
* Calculates this vector's position relative to another vector
|
|
919
|
+
* @param v - The reference vector
|
|
920
|
+
* @returns This vector after making it relative to v
|
|
921
|
+
*/
|
|
922
|
+
relativeTo(v) {
|
|
923
|
+
this.x -= v.x;
|
|
924
|
+
this.y -= v.y;
|
|
925
|
+
this.z -= v.z;
|
|
877
926
|
return this;
|
|
878
927
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
928
|
+
/**
|
|
929
|
+
* Makes this vector point towards a target vector
|
|
930
|
+
* @param target - The target vector to look at
|
|
931
|
+
* @returns This vector after pointing towards target
|
|
932
|
+
*/
|
|
933
|
+
lookAt(target) {
|
|
934
|
+
const direction = new _Vector(
|
|
935
|
+
target.x - this.x,
|
|
936
|
+
target.y - this.y,
|
|
937
|
+
target.z - this.z
|
|
938
|
+
);
|
|
939
|
+
direction.normalize();
|
|
940
|
+
this.x = direction.x;
|
|
941
|
+
this.y = direction.y;
|
|
942
|
+
this.z = direction.z;
|
|
886
943
|
return this;
|
|
887
944
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
945
|
+
/**
|
|
946
|
+
* Converts this vector from world space to screen space
|
|
947
|
+
* @param width - Screen width
|
|
948
|
+
* @param height - Screen height
|
|
949
|
+
* @returns This vector after screen space conversion
|
|
950
|
+
*/
|
|
951
|
+
toScreen(width, height) {
|
|
952
|
+
this.x = (this.x + 1) * width / 2;
|
|
953
|
+
this.y = (1 - this.y) * height / 2;
|
|
891
954
|
return this;
|
|
892
955
|
}
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
956
|
+
/**
|
|
957
|
+
* Spherical linear interpolation between this vector and another vector
|
|
958
|
+
* @param v - The target vector
|
|
959
|
+
* @param amt - Interpolation amount (0-1)
|
|
960
|
+
* @returns This vector after interpolation
|
|
961
|
+
*/
|
|
962
|
+
slerp(v, amt) {
|
|
963
|
+
if (amt === 0) {
|
|
964
|
+
return this;
|
|
965
|
+
}
|
|
966
|
+
if (amt === 1) {
|
|
967
|
+
return this.set(v.x, v.y, v.z);
|
|
968
|
+
}
|
|
969
|
+
const selfMag = this.mag();
|
|
970
|
+
const vMag = v.mag();
|
|
971
|
+
const magmag = selfMag * vMag;
|
|
972
|
+
if (magmag === 0) {
|
|
973
|
+
this.mult(1 - amt).add(new _Vector(v.x * amt, v.y * amt, v.z * amt));
|
|
974
|
+
return this;
|
|
975
|
+
}
|
|
976
|
+
const axis = this.cross(v);
|
|
977
|
+
const axisMag = axis.mag();
|
|
978
|
+
const theta = Math.atan2(axisMag, this.dot(v));
|
|
979
|
+
if (axisMag > 0) {
|
|
980
|
+
axis.x /= axisMag;
|
|
981
|
+
axis.y /= axisMag;
|
|
982
|
+
axis.z /= axisMag;
|
|
983
|
+
} else if (theta < Math.PI * 0.5) {
|
|
984
|
+
this.mult(1 - amt).add(new _Vector(v.x * amt, v.y * amt, v.z * amt));
|
|
985
|
+
return this;
|
|
986
|
+
} else {
|
|
987
|
+
if (this.z === 0 && v.z === 0) {
|
|
988
|
+
axis.set(0, 0, 1);
|
|
989
|
+
} else if (this.x !== 0) {
|
|
990
|
+
axis.set(this.y, -this.x, 0).normalize();
|
|
915
991
|
} else {
|
|
916
|
-
|
|
992
|
+
axis.set(1, 0, 0);
|
|
917
993
|
}
|
|
918
994
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
Math.min(1, to),
|
|
927
|
-
0,
|
|
928
|
-
1
|
|
929
|
-
);
|
|
930
|
-
callback(localProgress);
|
|
995
|
+
const ey = axis.cross(this);
|
|
996
|
+
const lerpedMagFactor = 1 - amt + amt * vMag / selfMag;
|
|
997
|
+
const cosMultiplier = lerpedMagFactor * Math.cos(amt * theta);
|
|
998
|
+
const sinMultiplier = lerpedMagFactor * Math.sin(amt * theta);
|
|
999
|
+
this.x = this.x * cosMultiplier + ey.x * sinMultiplier;
|
|
1000
|
+
this.y = this.y * cosMultiplier + ey.y * sinMultiplier;
|
|
1001
|
+
this.z = this.z * cosMultiplier + ey.z * sinMultiplier;
|
|
931
1002
|
return this;
|
|
932
1003
|
}
|
|
933
|
-
|
|
934
|
-
|
|
1004
|
+
/**
|
|
1005
|
+
* Creates a new vector at a specified angle and distance from a center point
|
|
1006
|
+
* @param center - The center point vector
|
|
1007
|
+
* @param a - The angle in radians
|
|
1008
|
+
* @param r - The radius (distance from center)
|
|
1009
|
+
* @returns A new Vector at the calculated position
|
|
1010
|
+
*/
|
|
1011
|
+
static fromAngle(center, a, r) {
|
|
1012
|
+
const x = Math.cos(a) * r + center.x;
|
|
1013
|
+
const y = Math.sin(a) * r + center.y;
|
|
1014
|
+
return new _Vector(x, y, center.z);
|
|
935
1015
|
}
|
|
936
1016
|
};
|
|
937
|
-
var
|
|
1017
|
+
var Vector_default = Vector;
|
|
938
1018
|
|
|
939
1019
|
// src/elements/Text.tsx
|
|
940
1020
|
var Text = class {
|
|
@@ -1143,138 +1223,1512 @@ var Thing = class {
|
|
|
1143
1223
|
};
|
|
1144
1224
|
var Thing_default = Thing;
|
|
1145
1225
|
|
|
1146
|
-
// src/
|
|
1147
|
-
var
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
if (
|
|
1172
|
-
|
|
1173
|
-
|
|
1226
|
+
// src/elements/Grid.tsx
|
|
1227
|
+
var Grid = class {
|
|
1228
|
+
/**
|
|
1229
|
+
* Creates a new Grid instance
|
|
1230
|
+
* @param ctx - The Klint context
|
|
1231
|
+
*/
|
|
1232
|
+
constructor(ctx) {
|
|
1233
|
+
this.context = ctx;
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Create a rectangular grid of points
|
|
1237
|
+
* @param x - X position of the grid
|
|
1238
|
+
* @param y - Y position of the grid
|
|
1239
|
+
* @param width - Width of the grid
|
|
1240
|
+
* @param height - Height of the grid
|
|
1241
|
+
* @param countX - Number of points horizontally
|
|
1242
|
+
* @param countY - Number of points vertically
|
|
1243
|
+
* @param options - Grid options
|
|
1244
|
+
* @returns Array of grid points
|
|
1245
|
+
*/
|
|
1246
|
+
rect(x, y, width, height, countX, countY, options) {
|
|
1247
|
+
const origin = options?.origin || "corner";
|
|
1248
|
+
const points = [];
|
|
1249
|
+
let startX = x;
|
|
1250
|
+
let startY = y;
|
|
1251
|
+
if (origin === "center") {
|
|
1252
|
+
startX = x - width / 2;
|
|
1253
|
+
startY = y - height / 2;
|
|
1174
1254
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1255
|
+
const spacingX = countX > 1 ? width / (countX - 1) : 0;
|
|
1256
|
+
const spacingY = countY > 1 ? height / (countY - 1) : 0;
|
|
1257
|
+
for (let j = 0; j < countY; j++) {
|
|
1258
|
+
for (let i = 0; i < countX; i++) {
|
|
1259
|
+
const pointX = startX + i * spacingX;
|
|
1260
|
+
const pointY = startY + j * spacingY;
|
|
1261
|
+
const id = j * countX + i;
|
|
1262
|
+
points.push({
|
|
1263
|
+
x: pointX,
|
|
1264
|
+
y: pointY,
|
|
1265
|
+
i,
|
|
1266
|
+
j,
|
|
1267
|
+
id
|
|
1268
|
+
});
|
|
1182
1269
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
context.Color = ctx.Color;
|
|
1226
|
-
context.createVector = (x = 0, y = 0) => new Vector_default(x, y);
|
|
1227
|
-
context.Easing = ctx.Easing;
|
|
1228
|
-
context.Text = ctx.Text;
|
|
1229
|
-
Object.entries(KlintFunctions).forEach(([name, fn]) => {
|
|
1230
|
-
context[name] = fn(context);
|
|
1270
|
+
}
|
|
1271
|
+
return points;
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Create a radial grid of points
|
|
1275
|
+
* @param x - Center X position
|
|
1276
|
+
* @param y - Center Y position
|
|
1277
|
+
* @param radius - Maximum radius
|
|
1278
|
+
* @param count - Number of points per ring
|
|
1279
|
+
* @param ringCount - Number of rings
|
|
1280
|
+
* @param ringSpace - Space between rings
|
|
1281
|
+
* @param options - Grid options
|
|
1282
|
+
* @returns Array of grid points
|
|
1283
|
+
*/
|
|
1284
|
+
radial(x, y, radius, count, ringCount, ringSpace, options) {
|
|
1285
|
+
const perStepCount = options?.perStepCount || 0;
|
|
1286
|
+
const points = [];
|
|
1287
|
+
let id = 0;
|
|
1288
|
+
for (let ring = 0; ring < ringCount; ring++) {
|
|
1289
|
+
const ringRadius = ring * ringSpace;
|
|
1290
|
+
if (ringRadius > radius) break;
|
|
1291
|
+
const ringPointCount = count + perStepCount * ring;
|
|
1292
|
+
for (let i = 0; i < ringPointCount; i++) {
|
|
1293
|
+
const angle = Math.PI * 2 * i / ringPointCount;
|
|
1294
|
+
const pointX = x + Math.cos(angle) * ringRadius;
|
|
1295
|
+
const pointY = y + Math.sin(angle) * ringRadius;
|
|
1296
|
+
points.push({
|
|
1297
|
+
x: pointX,
|
|
1298
|
+
y: pointY,
|
|
1299
|
+
i,
|
|
1300
|
+
j: ring,
|
|
1301
|
+
id: id++
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
if (ringCount > 0 && points.length === 0) {
|
|
1306
|
+
points.push({
|
|
1307
|
+
x,
|
|
1308
|
+
y,
|
|
1309
|
+
i: 0,
|
|
1310
|
+
j: 0,
|
|
1311
|
+
id: 0
|
|
1231
1312
|
});
|
|
1232
1313
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1314
|
+
return points;
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Create a hexagonal grid of points
|
|
1318
|
+
* @param x - X position of the grid
|
|
1319
|
+
* @param y - Y position of the grid
|
|
1320
|
+
* @param width - Width of the grid area
|
|
1321
|
+
* @param height - Height of the grid area
|
|
1322
|
+
* @param size - Size of each hexagon
|
|
1323
|
+
* @param options - Grid options
|
|
1324
|
+
* @returns Array of grid points
|
|
1325
|
+
*/
|
|
1326
|
+
hex(x, y, width, height, size, options) {
|
|
1327
|
+
const origin = options?.origin || "corner";
|
|
1328
|
+
const pointy = options?.pointy !== false;
|
|
1329
|
+
const points = [];
|
|
1330
|
+
let startX = x;
|
|
1331
|
+
let startY = y;
|
|
1332
|
+
if (origin === "center") {
|
|
1333
|
+
startX = x - width / 2;
|
|
1334
|
+
startY = y - height / 2;
|
|
1335
|
+
}
|
|
1336
|
+
const hexWidth = pointy ? Math.sqrt(3) * size : 2 * size;
|
|
1337
|
+
const hexHeight = pointy ? 2 * size : Math.sqrt(3) * size;
|
|
1338
|
+
const cols = Math.ceil(width / hexWidth) + 1;
|
|
1339
|
+
const rows = Math.ceil(height / (hexHeight * 0.75)) + 1;
|
|
1340
|
+
let id = 0;
|
|
1341
|
+
for (let j = 0; j < rows; j++) {
|
|
1342
|
+
for (let i = 0; i < cols; i++) {
|
|
1343
|
+
let pointX;
|
|
1344
|
+
let pointY;
|
|
1345
|
+
if (pointy) {
|
|
1346
|
+
pointX = startX + i * hexWidth;
|
|
1347
|
+
pointY = startY + j * hexHeight * 0.75;
|
|
1348
|
+
if (j % 2 === 1) {
|
|
1349
|
+
pointX += hexWidth / 2;
|
|
1350
|
+
}
|
|
1351
|
+
} else {
|
|
1352
|
+
pointX = startX + i * hexWidth * 0.75;
|
|
1353
|
+
pointY = startY + j * hexHeight;
|
|
1354
|
+
if (i % 2 === 1) {
|
|
1355
|
+
pointY += hexHeight / 2;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
if (pointX <= startX + width && pointY <= startY + height) {
|
|
1359
|
+
points.push({
|
|
1360
|
+
x: pointX,
|
|
1361
|
+
y: pointY,
|
|
1362
|
+
i,
|
|
1363
|
+
j,
|
|
1364
|
+
id: id++
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1237
1367
|
}
|
|
1238
1368
|
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1369
|
+
return points;
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Create a triangular grid of points
|
|
1373
|
+
* @param x - X position of the grid
|
|
1374
|
+
* @param y - Y position of the grid
|
|
1375
|
+
* @param width - Width of the grid
|
|
1376
|
+
* @param height - Height of the grid
|
|
1377
|
+
* @param size - Size of each triangle
|
|
1378
|
+
* @param options - Grid options
|
|
1379
|
+
* @returns Array of grid points
|
|
1380
|
+
*/
|
|
1381
|
+
triangle(x, y, width, height, size, options) {
|
|
1382
|
+
const origin = options?.origin || "corner";
|
|
1383
|
+
const points = [];
|
|
1384
|
+
let startX = x;
|
|
1385
|
+
let startY = y;
|
|
1386
|
+
if (origin === "center") {
|
|
1387
|
+
startX = x - width / 2;
|
|
1388
|
+
startY = y - height / 2;
|
|
1241
1389
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1390
|
+
const triHeight = Math.sqrt(3) / 2 * size;
|
|
1391
|
+
const cols = Math.ceil(width / (size / 2)) + 1;
|
|
1392
|
+
const rows = Math.ceil(height / triHeight) + 1;
|
|
1393
|
+
let id = 0;
|
|
1394
|
+
for (let j = 0; j < rows; j++) {
|
|
1395
|
+
for (let i = 0; i < cols; i++) {
|
|
1396
|
+
const pointX = startX + i * (size / 2);
|
|
1397
|
+
const pointY = startY + j * triHeight;
|
|
1398
|
+
if (pointX <= startX + width && pointY <= startY + height) {
|
|
1399
|
+
points.push({
|
|
1400
|
+
x: pointX,
|
|
1401
|
+
y: pointY,
|
|
1402
|
+
i,
|
|
1403
|
+
j,
|
|
1404
|
+
id: id++
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1248
1408
|
}
|
|
1249
|
-
|
|
1250
|
-
return context;
|
|
1251
|
-
},
|
|
1252
|
-
getOffscreen: (ctx) => (id) => {
|
|
1253
|
-
const offscreen = ctx.__offscreens.get(id);
|
|
1254
|
-
if (!offscreen)
|
|
1255
|
-
throw new Error(`No offscreen context found with id: ${id}`);
|
|
1256
|
-
return offscreen;
|
|
1409
|
+
return points;
|
|
1257
1410
|
}
|
|
1258
1411
|
};
|
|
1259
|
-
var
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1412
|
+
var Grid_default = Grid;
|
|
1413
|
+
|
|
1414
|
+
// src/elements/Strip.tsx
|
|
1415
|
+
var Strip = class {
|
|
1416
|
+
/**
|
|
1417
|
+
* Creates a new Strip instance
|
|
1418
|
+
* @param ctx - The Klint context
|
|
1419
|
+
*/
|
|
1420
|
+
constructor(ctx) {
|
|
1421
|
+
this.context = ctx;
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Create a strip of triangles from points
|
|
1425
|
+
* Points are connected in a zigzag pattern:
|
|
1426
|
+
* 0 - 2 - 4 ...
|
|
1427
|
+
* | / | / |
|
|
1428
|
+
* 1 - 3 - 5 ...
|
|
1429
|
+
*
|
|
1430
|
+
* @param points - Array of points (must be even number for complete triangles)
|
|
1431
|
+
* @param draw - Optional callback to customize each triangle's appearance
|
|
1432
|
+
*/
|
|
1433
|
+
triangles(points, draw) {
|
|
1434
|
+
if (points.length < 3) return;
|
|
1435
|
+
const numTriangles = Math.floor((points.length - 2) * 2);
|
|
1436
|
+
for (let i = 0; i < numTriangles; i++) {
|
|
1437
|
+
const baseIndex = Math.floor(i / 2) * 2;
|
|
1438
|
+
const isEven = i % 2 === 0;
|
|
1439
|
+
let p1;
|
|
1440
|
+
let p2;
|
|
1441
|
+
let p3;
|
|
1442
|
+
if (isEven) {
|
|
1443
|
+
p1 = points[baseIndex];
|
|
1444
|
+
p2 = points[baseIndex + 1];
|
|
1445
|
+
p3 = points[baseIndex + 2];
|
|
1446
|
+
} else {
|
|
1447
|
+
p1 = points[baseIndex + 1];
|
|
1448
|
+
p2 = points[baseIndex + 3];
|
|
1449
|
+
p3 = points[baseIndex + 2];
|
|
1450
|
+
}
|
|
1451
|
+
if (!p1 || !p2 || !p3) continue;
|
|
1452
|
+
const center = {
|
|
1453
|
+
x: (p1.x + p2.x + p3.x) / 3,
|
|
1454
|
+
y: (p1.y + p2.y + p3.y) / 3
|
|
1455
|
+
};
|
|
1456
|
+
const triangle = {
|
|
1457
|
+
id: i,
|
|
1458
|
+
center,
|
|
1459
|
+
points: [p1, p2, p3]
|
|
1460
|
+
};
|
|
1461
|
+
let fillColor;
|
|
1462
|
+
if (draw) {
|
|
1463
|
+
const result = draw(triangle);
|
|
1464
|
+
if (typeof result === "string") {
|
|
1465
|
+
fillColor = result;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
this.context.beginPath();
|
|
1469
|
+
this.context.moveTo(p1.x, p1.y);
|
|
1470
|
+
this.context.lineTo(p2.x, p2.y);
|
|
1471
|
+
this.context.lineTo(p3.x, p3.y);
|
|
1472
|
+
this.context.closePath();
|
|
1473
|
+
if (fillColor) {
|
|
1474
|
+
const prevFill = this.context.fillStyle;
|
|
1475
|
+
this.context.fillStyle = fillColor;
|
|
1476
|
+
this.context.fill();
|
|
1477
|
+
this.context.fillStyle = prevFill;
|
|
1478
|
+
} else if (this.context.checkTransparency("fill")) {
|
|
1479
|
+
this.context.fill();
|
|
1480
|
+
}
|
|
1481
|
+
if (this.context.checkTransparency("stroke")) {
|
|
1482
|
+
this.context.stroke();
|
|
1483
|
+
}
|
|
1272
1484
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Create a strip of quads from points
|
|
1488
|
+
* Points are connected in a grid pattern:
|
|
1489
|
+
* 0 - 2 - 4 ...
|
|
1490
|
+
* | | |
|
|
1491
|
+
* 1 - 3 - 5 ...
|
|
1492
|
+
*
|
|
1493
|
+
* @param points - Array of points (must be even number for complete quads)
|
|
1494
|
+
* @param draw - Optional callback to customize each quad's appearance
|
|
1495
|
+
*/
|
|
1496
|
+
quads(points, draw) {
|
|
1497
|
+
if (points.length < 4) return;
|
|
1498
|
+
const numQuads = Math.floor((points.length - 2) / 2);
|
|
1499
|
+
for (let i = 0; i < numQuads; i++) {
|
|
1500
|
+
const baseIndex = i * 2;
|
|
1501
|
+
const p1 = points[baseIndex];
|
|
1502
|
+
const p2 = points[baseIndex + 1];
|
|
1503
|
+
const p3 = points[baseIndex + 3];
|
|
1504
|
+
const p4 = points[baseIndex + 2];
|
|
1505
|
+
if (!p1 || !p2 || !p3 || !p4) continue;
|
|
1506
|
+
const center = {
|
|
1507
|
+
x: (p1.x + p2.x + p3.x + p4.x) / 4,
|
|
1508
|
+
y: (p1.y + p2.y + p3.y + p4.y) / 4
|
|
1509
|
+
};
|
|
1510
|
+
const quad = {
|
|
1511
|
+
id: i,
|
|
1512
|
+
center,
|
|
1513
|
+
points: [p1, p2, p3, p4]
|
|
1514
|
+
};
|
|
1515
|
+
let fillColor;
|
|
1516
|
+
if (draw) {
|
|
1517
|
+
const result = draw(quad);
|
|
1518
|
+
if (typeof result === "string") {
|
|
1519
|
+
fillColor = result;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
this.context.beginPath();
|
|
1523
|
+
this.context.moveTo(p1.x, p1.y);
|
|
1524
|
+
this.context.lineTo(p2.x, p2.y);
|
|
1525
|
+
this.context.lineTo(p3.x, p3.y);
|
|
1526
|
+
this.context.lineTo(p4.x, p4.y);
|
|
1527
|
+
this.context.closePath();
|
|
1528
|
+
if (fillColor) {
|
|
1529
|
+
const prevFill = this.context.fillStyle;
|
|
1530
|
+
this.context.fillStyle = fillColor;
|
|
1531
|
+
this.context.fill();
|
|
1532
|
+
this.context.fillStyle = prevFill;
|
|
1533
|
+
} else if (this.context.checkTransparency("fill")) {
|
|
1534
|
+
this.context.fill();
|
|
1535
|
+
}
|
|
1536
|
+
if (this.context.checkTransparency("stroke")) {
|
|
1537
|
+
this.context.stroke();
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Create a single hull shape from points
|
|
1543
|
+
* Points are connected following the winding order:
|
|
1544
|
+
* 0 - 2 - 4 - ... n-1
|
|
1545
|
+
* | |
|
|
1546
|
+
* 1 - 3 - 5 - ... n
|
|
1547
|
+
*
|
|
1548
|
+
* Final order: 0 - 2 - 4 ... n-1, n, ... 5 - 3 - 1
|
|
1549
|
+
*
|
|
1550
|
+
* @param points - Array of points
|
|
1551
|
+
* @param draw - Optional callback to add elements along the hull
|
|
1552
|
+
*/
|
|
1553
|
+
hull(points, draw) {
|
|
1554
|
+
if (points.length < 2) return;
|
|
1555
|
+
const hullPath = [];
|
|
1556
|
+
for (let i = 0; i < points.length; i += 2) {
|
|
1557
|
+
hullPath.push(points[i]);
|
|
1558
|
+
}
|
|
1559
|
+
for (let i = points.length - 1 - points.length % 2; i >= 1; i -= 2) {
|
|
1560
|
+
hullPath.push(points[i]);
|
|
1561
|
+
}
|
|
1562
|
+
this.context.beginPath();
|
|
1563
|
+
this.context.moveTo(hullPath[0].x, hullPath[0].y);
|
|
1564
|
+
for (let i = 1; i < hullPath.length; i++) {
|
|
1565
|
+
this.context.lineTo(hullPath[i].x, hullPath[i].y);
|
|
1566
|
+
}
|
|
1567
|
+
this.context.closePath();
|
|
1568
|
+
if (this.context.checkTransparency("fill")) {
|
|
1569
|
+
this.context.fill();
|
|
1570
|
+
}
|
|
1571
|
+
if (this.context.checkTransparency("stroke")) {
|
|
1572
|
+
this.context.stroke();
|
|
1573
|
+
}
|
|
1574
|
+
if (draw) {
|
|
1575
|
+
const numPairs = Math.floor(points.length / 2);
|
|
1576
|
+
for (let i = 0; i < numPairs; i++) {
|
|
1577
|
+
const topPoint = points[i * 2];
|
|
1578
|
+
const bottomPoint = points[i * 2 + 1];
|
|
1579
|
+
if (topPoint && bottomPoint) {
|
|
1580
|
+
const center = {
|
|
1581
|
+
x: (topPoint.x + bottomPoint.x) / 2,
|
|
1582
|
+
y: (topPoint.y + bottomPoint.y) / 2
|
|
1583
|
+
};
|
|
1584
|
+
const hull = {
|
|
1585
|
+
id: i,
|
|
1586
|
+
center
|
|
1587
|
+
};
|
|
1588
|
+
draw(hull);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
/**
|
|
1594
|
+
* Create a ribbon/tape effect from points
|
|
1595
|
+
* Similar to hull but with configurable width
|
|
1596
|
+
*
|
|
1597
|
+
* @param points - Array of center points
|
|
1598
|
+
* @param width - Width of the ribbon
|
|
1599
|
+
* @param draw - Optional callback
|
|
1600
|
+
*/
|
|
1601
|
+
ribbon(points, width, draw) {
|
|
1602
|
+
if (points.length < 2) return;
|
|
1603
|
+
const offsetPoints = [];
|
|
1604
|
+
for (let i = 0; i < points.length; i++) {
|
|
1605
|
+
const curr = points[i];
|
|
1606
|
+
const prev = points[i - 1] || curr;
|
|
1607
|
+
const next = points[i + 1] || curr;
|
|
1608
|
+
const dx = next.x - prev.x;
|
|
1609
|
+
const dy = next.y - prev.y;
|
|
1610
|
+
const len = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
1611
|
+
const perpX = -dy / len * (width / 2);
|
|
1612
|
+
const perpY = dx / len * (width / 2);
|
|
1613
|
+
offsetPoints.push({
|
|
1614
|
+
top: { x: curr.x + perpX, y: curr.y + perpY },
|
|
1615
|
+
bottom: { x: curr.x - perpX, y: curr.y - perpY }
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
this.context.beginPath();
|
|
1619
|
+
this.context.moveTo(offsetPoints[0].top.x, offsetPoints[0].top.y);
|
|
1620
|
+
for (let i = 1; i < offsetPoints.length; i++) {
|
|
1621
|
+
this.context.lineTo(offsetPoints[i].top.x, offsetPoints[i].top.y);
|
|
1622
|
+
}
|
|
1623
|
+
for (let i = offsetPoints.length - 1; i >= 0; i--) {
|
|
1624
|
+
this.context.lineTo(offsetPoints[i].bottom.x, offsetPoints[i].bottom.y);
|
|
1625
|
+
}
|
|
1626
|
+
this.context.closePath();
|
|
1627
|
+
if (this.context.checkTransparency("fill")) {
|
|
1628
|
+
this.context.fill();
|
|
1629
|
+
}
|
|
1630
|
+
if (this.context.checkTransparency("stroke")) {
|
|
1631
|
+
this.context.stroke();
|
|
1632
|
+
}
|
|
1633
|
+
if (draw) {
|
|
1634
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
1635
|
+
const center = {
|
|
1636
|
+
x: (points[i].x + points[i + 1].x) / 2,
|
|
1637
|
+
y: (points[i].y + points[i + 1].y) / 2
|
|
1638
|
+
};
|
|
1639
|
+
const result = draw({ id: i, center });
|
|
1640
|
+
if (typeof result === "string") {
|
|
1641
|
+
this.context.beginPath();
|
|
1642
|
+
this.context.moveTo(offsetPoints[i].top.x, offsetPoints[i].top.y);
|
|
1643
|
+
this.context.lineTo(offsetPoints[i + 1].top.x, offsetPoints[i + 1].top.y);
|
|
1644
|
+
this.context.lineTo(offsetPoints[i + 1].bottom.x, offsetPoints[i + 1].bottom.y);
|
|
1645
|
+
this.context.lineTo(offsetPoints[i].bottom.x, offsetPoints[i].bottom.y);
|
|
1646
|
+
this.context.closePath();
|
|
1647
|
+
const prevFill = this.context.fillStyle;
|
|
1648
|
+
this.context.fillStyle = result;
|
|
1649
|
+
this.context.fill();
|
|
1650
|
+
this.context.fillStyle = prevFill;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
};
|
|
1656
|
+
var Strip_default = Strip;
|
|
1657
|
+
|
|
1658
|
+
// src/elements/Noise.tsx
|
|
1659
|
+
var Noise = class {
|
|
1660
|
+
/**
|
|
1661
|
+
* Creates a new Noise instance
|
|
1662
|
+
* @param ctx - The Klint context
|
|
1663
|
+
*/
|
|
1664
|
+
constructor(ctx) {
|
|
1665
|
+
this.perm = [];
|
|
1666
|
+
this.permMod12 = [];
|
|
1667
|
+
this.grad3 = [
|
|
1668
|
+
[1, 1, 0],
|
|
1669
|
+
[-1, 1, 0],
|
|
1670
|
+
[1, -1, 0],
|
|
1671
|
+
[-1, -1, 0],
|
|
1672
|
+
[1, 0, 1],
|
|
1673
|
+
[-1, 0, 1],
|
|
1674
|
+
[1, 0, -1],
|
|
1675
|
+
[-1, 0, -1],
|
|
1676
|
+
[0, 1, 1],
|
|
1677
|
+
[0, -1, 1],
|
|
1678
|
+
[0, 1, -1],
|
|
1679
|
+
[0, -1, -1]
|
|
1680
|
+
];
|
|
1681
|
+
this.grad4 = [
|
|
1682
|
+
[0, 1, 1, 1],
|
|
1683
|
+
[0, 1, 1, -1],
|
|
1684
|
+
[0, 1, -1, 1],
|
|
1685
|
+
[0, 1, -1, -1],
|
|
1686
|
+
[0, -1, 1, 1],
|
|
1687
|
+
[0, -1, 1, -1],
|
|
1688
|
+
[0, -1, -1, 1],
|
|
1689
|
+
[0, -1, -1, -1],
|
|
1690
|
+
[1, 0, 1, 1],
|
|
1691
|
+
[1, 0, 1, -1],
|
|
1692
|
+
[1, 0, -1, 1],
|
|
1693
|
+
[1, 0, -1, -1],
|
|
1694
|
+
[-1, 0, 1, 1],
|
|
1695
|
+
[-1, 0, 1, -1],
|
|
1696
|
+
[-1, 0, -1, 1],
|
|
1697
|
+
[-1, 0, -1, -1],
|
|
1698
|
+
[1, 1, 0, 1],
|
|
1699
|
+
[1, 1, 0, -1],
|
|
1700
|
+
[1, -1, 0, 1],
|
|
1701
|
+
[1, -1, 0, -1],
|
|
1702
|
+
[-1, 1, 0, 1],
|
|
1703
|
+
[-1, 1, 0, -1],
|
|
1704
|
+
[-1, -1, 0, 1],
|
|
1705
|
+
[-1, -1, 0, -1],
|
|
1706
|
+
[1, 1, 1, 0],
|
|
1707
|
+
[1, 1, -1, 0],
|
|
1708
|
+
[1, -1, 1, 0],
|
|
1709
|
+
[1, -1, -1, 0],
|
|
1710
|
+
[-1, 1, 1, 0],
|
|
1711
|
+
[-1, 1, -1, 0],
|
|
1712
|
+
[-1, -1, 1, 0],
|
|
1713
|
+
[-1, -1, -1, 0]
|
|
1714
|
+
];
|
|
1715
|
+
this.currentSeed = Math.random();
|
|
1716
|
+
this.F2 = 0.5 * (Math.sqrt(3) - 1);
|
|
1717
|
+
this.G2 = (3 - Math.sqrt(3)) / 6;
|
|
1718
|
+
this.F3 = 1 / 3;
|
|
1719
|
+
this.G3 = 1 / 6;
|
|
1720
|
+
this.F4 = (Math.sqrt(5) - 1) / 4;
|
|
1721
|
+
this.G4 = (5 - Math.sqrt(5)) / 20;
|
|
1722
|
+
this.context = ctx;
|
|
1723
|
+
this.buildPermutationTable();
|
|
1724
|
+
}
|
|
1725
|
+
/**
|
|
1726
|
+
* Build permutation table for noise generation
|
|
1727
|
+
*/
|
|
1728
|
+
buildPermutationTable() {
|
|
1729
|
+
const p = [];
|
|
1730
|
+
for (let i = 0; i < 256; i++) {
|
|
1731
|
+
p[i] = i;
|
|
1732
|
+
}
|
|
1733
|
+
let n = 256;
|
|
1734
|
+
while (n > 0) {
|
|
1735
|
+
const index = Math.floor(this.random() * n--);
|
|
1736
|
+
const temp = p[n];
|
|
1737
|
+
p[n] = p[index];
|
|
1738
|
+
p[index] = temp;
|
|
1739
|
+
}
|
|
1740
|
+
this.perm = [];
|
|
1741
|
+
this.permMod12 = [];
|
|
1742
|
+
for (let i = 0; i < 512; i++) {
|
|
1743
|
+
this.perm[i] = p[i & 255];
|
|
1744
|
+
this.permMod12[i] = this.perm[i] % 12;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
/**
|
|
1748
|
+
* Seeded random number generator
|
|
1749
|
+
*/
|
|
1750
|
+
random() {
|
|
1751
|
+
const x = Math.sin(this.currentSeed++) * 1e4;
|
|
1752
|
+
return x - Math.floor(x);
|
|
1753
|
+
}
|
|
1754
|
+
/**
|
|
1755
|
+
* Set seed for noise generation
|
|
1756
|
+
* @param seed - Seed value for reproducible noise
|
|
1757
|
+
*/
|
|
1758
|
+
seed(seed) {
|
|
1759
|
+
this.currentSeed = seed !== void 0 ? seed : Math.random() * 1e4;
|
|
1760
|
+
this.buildPermutationTable();
|
|
1761
|
+
}
|
|
1762
|
+
/**
|
|
1763
|
+
* Fade function for Perlin noise
|
|
1764
|
+
*/
|
|
1765
|
+
fade(t) {
|
|
1766
|
+
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
1767
|
+
}
|
|
1768
|
+
/**
|
|
1769
|
+
* Linear interpolation
|
|
1770
|
+
*/
|
|
1771
|
+
lerp(t, a, b) {
|
|
1772
|
+
return a + t * (b - a);
|
|
1773
|
+
}
|
|
1774
|
+
perlin(x, y, z, w) {
|
|
1775
|
+
if (y === void 0) {
|
|
1776
|
+
const xi = Math.floor(x) & 255;
|
|
1777
|
+
const xf = x - Math.floor(x);
|
|
1778
|
+
const u = this.fade(xf);
|
|
1779
|
+
const a = this.perm[xi];
|
|
1780
|
+
const b = this.perm[xi + 1];
|
|
1781
|
+
const grad1 = (hash, x2) => (hash & 1) === 0 ? x2 : -x2;
|
|
1782
|
+
return this.lerp(u, grad1(a, xf), grad1(b, xf - 1));
|
|
1783
|
+
} else if (z === void 0) {
|
|
1784
|
+
const xi = Math.floor(x) & 255;
|
|
1785
|
+
const yi = Math.floor(y) & 255;
|
|
1786
|
+
const xf = x - Math.floor(x);
|
|
1787
|
+
const yf = y - Math.floor(y);
|
|
1788
|
+
const u = this.fade(xf);
|
|
1789
|
+
const v = this.fade(yf);
|
|
1790
|
+
const aa = this.perm[this.perm[xi] + yi];
|
|
1791
|
+
const ab = this.perm[this.perm[xi] + yi + 1];
|
|
1792
|
+
const ba = this.perm[this.perm[xi + 1] + yi];
|
|
1793
|
+
const bb = this.perm[this.perm[xi + 1] + yi + 1];
|
|
1794
|
+
const grad2 = (hash, x3, y2) => {
|
|
1795
|
+
const h = hash & 3;
|
|
1796
|
+
const u2 = h < 2 ? x3 : y2;
|
|
1797
|
+
const v2 = h < 2 ? y2 : x3;
|
|
1798
|
+
return ((h & 1) === 0 ? u2 : -u2) + ((h & 2) === 0 ? v2 : -v2);
|
|
1799
|
+
};
|
|
1800
|
+
const x1 = this.lerp(u, grad2(aa, xf, yf), grad2(ba, xf - 1, yf));
|
|
1801
|
+
const x2 = this.lerp(u, grad2(ab, xf, yf - 1), grad2(bb, xf - 1, yf - 1));
|
|
1802
|
+
return this.lerp(v, x1, x2);
|
|
1803
|
+
} else if (w === void 0) {
|
|
1804
|
+
const xi = Math.floor(x) & 255;
|
|
1805
|
+
const yi = Math.floor(y) & 255;
|
|
1806
|
+
const zi = Math.floor(z) & 255;
|
|
1807
|
+
const xf = x - Math.floor(x);
|
|
1808
|
+
const yf = y - Math.floor(y);
|
|
1809
|
+
const zf = z - Math.floor(z);
|
|
1810
|
+
const u = this.fade(xf);
|
|
1811
|
+
const v = this.fade(yf);
|
|
1812
|
+
const w2 = this.fade(zf);
|
|
1813
|
+
const aaa = this.perm[this.perm[this.perm[xi] + yi] + zi];
|
|
1814
|
+
const aba = this.perm[this.perm[this.perm[xi] + yi + 1] + zi];
|
|
1815
|
+
const aab = this.perm[this.perm[this.perm[xi] + yi] + zi + 1];
|
|
1816
|
+
const abb = this.perm[this.perm[this.perm[xi] + yi + 1] + zi + 1];
|
|
1817
|
+
const baa = this.perm[this.perm[this.perm[xi + 1] + yi] + zi];
|
|
1818
|
+
const bba = this.perm[this.perm[this.perm[xi + 1] + yi + 1] + zi];
|
|
1819
|
+
const bab = this.perm[this.perm[this.perm[xi + 1] + yi] + zi + 1];
|
|
1820
|
+
const bbb = this.perm[this.perm[this.perm[xi + 1] + yi + 1] + zi + 1];
|
|
1821
|
+
const grad3 = (hash, x2, y2, z2) => {
|
|
1822
|
+
const h = hash & 15;
|
|
1823
|
+
const u2 = h < 8 ? x2 : y2;
|
|
1824
|
+
const v2 = h < 4 ? y2 : h === 12 || h === 14 ? x2 : z2;
|
|
1825
|
+
return ((h & 1) === 0 ? u2 : -u2) + ((h & 2) === 0 ? v2 : -v2);
|
|
1826
|
+
};
|
|
1827
|
+
const x1 = this.lerp(
|
|
1828
|
+
w2,
|
|
1829
|
+
this.lerp(
|
|
1830
|
+
v,
|
|
1831
|
+
this.lerp(u, grad3(aaa, xf, yf, zf), grad3(baa, xf - 1, yf, zf)),
|
|
1832
|
+
this.lerp(u, grad3(aba, xf, yf - 1, zf), grad3(bba, xf - 1, yf - 1, zf))
|
|
1833
|
+
),
|
|
1834
|
+
this.lerp(
|
|
1835
|
+
v,
|
|
1836
|
+
this.lerp(u, grad3(aab, xf, yf, zf - 1), grad3(bab, xf - 1, yf, zf - 1)),
|
|
1837
|
+
this.lerp(u, grad3(abb, xf, yf - 1, zf - 1), grad3(bbb, xf - 1, yf - 1, zf - 1))
|
|
1838
|
+
)
|
|
1839
|
+
);
|
|
1840
|
+
return x1;
|
|
1841
|
+
} else {
|
|
1842
|
+
return this.perlin(x, y, z) * 0.5 + this.perlin(x + w, y + w, z + w) * 0.5;
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
simplex(x, y, z, w) {
|
|
1846
|
+
if (y === void 0) {
|
|
1847
|
+
return this.perlin(x);
|
|
1848
|
+
} else if (z === void 0) {
|
|
1849
|
+
let n0 = 0, n1 = 0, n2 = 0;
|
|
1850
|
+
const s = (x + y) * this.F2;
|
|
1851
|
+
const i = Math.floor(x + s);
|
|
1852
|
+
const j = Math.floor(y + s);
|
|
1853
|
+
const t = (i + j) * this.G2;
|
|
1854
|
+
const X0 = i - t;
|
|
1855
|
+
const Y0 = j - t;
|
|
1856
|
+
const x0 = x - X0;
|
|
1857
|
+
const y0 = y - Y0;
|
|
1858
|
+
let i1, j1;
|
|
1859
|
+
if (x0 > y0) {
|
|
1860
|
+
i1 = 1;
|
|
1861
|
+
j1 = 0;
|
|
1862
|
+
} else {
|
|
1863
|
+
i1 = 0;
|
|
1864
|
+
j1 = 1;
|
|
1865
|
+
}
|
|
1866
|
+
const x1 = x0 - i1 + this.G2;
|
|
1867
|
+
const y1 = y0 - j1 + this.G2;
|
|
1868
|
+
const x2 = x0 - 1 + 2 * this.G2;
|
|
1869
|
+
const y2 = y0 - 1 + 2 * this.G2;
|
|
1870
|
+
const ii = i & 255;
|
|
1871
|
+
const jj = j & 255;
|
|
1872
|
+
const gi0 = this.permMod12[ii + this.perm[jj]];
|
|
1873
|
+
const gi1 = this.permMod12[ii + i1 + this.perm[jj + j1]];
|
|
1874
|
+
const gi2 = this.permMod12[ii + 1 + this.perm[jj + 1]];
|
|
1875
|
+
let t0 = 0.5 - x0 * x0 - y0 * y0;
|
|
1876
|
+
if (t0 < 0) {
|
|
1877
|
+
n0 = 0;
|
|
1878
|
+
} else {
|
|
1879
|
+
t0 *= t0;
|
|
1880
|
+
n0 = t0 * t0 * this.dot2(this.grad3[gi0], x0, y0);
|
|
1881
|
+
}
|
|
1882
|
+
let t1 = 0.5 - x1 * x1 - y1 * y1;
|
|
1883
|
+
if (t1 < 0) {
|
|
1884
|
+
n1 = 0;
|
|
1885
|
+
} else {
|
|
1886
|
+
t1 *= t1;
|
|
1887
|
+
n1 = t1 * t1 * this.dot2(this.grad3[gi1], x1, y1);
|
|
1888
|
+
}
|
|
1889
|
+
let t2 = 0.5 - x2 * x2 - y2 * y2;
|
|
1890
|
+
if (t2 < 0) {
|
|
1891
|
+
n2 = 0;
|
|
1892
|
+
} else {
|
|
1893
|
+
t2 *= t2;
|
|
1894
|
+
n2 = t2 * t2 * this.dot2(this.grad3[gi2], x2, y2);
|
|
1895
|
+
}
|
|
1896
|
+
return 70 * (n0 + n1 + n2);
|
|
1897
|
+
} else if (w === void 0) {
|
|
1898
|
+
let n0 = 0, n1 = 0, n2 = 0, n3 = 0;
|
|
1899
|
+
const s = (x + y + z) * this.F3;
|
|
1900
|
+
const i = Math.floor(x + s);
|
|
1901
|
+
const j = Math.floor(y + s);
|
|
1902
|
+
const k = Math.floor(z + s);
|
|
1903
|
+
const t = (i + j + k) * this.G3;
|
|
1904
|
+
const X0 = i - t;
|
|
1905
|
+
const Y0 = j - t;
|
|
1906
|
+
const Z0 = k - t;
|
|
1907
|
+
const x0 = x - X0;
|
|
1908
|
+
const y0 = y - Y0;
|
|
1909
|
+
const z0 = z - Z0;
|
|
1910
|
+
let i1, j1, k1;
|
|
1911
|
+
let i2, j2, k2;
|
|
1912
|
+
if (x0 >= y0) {
|
|
1913
|
+
if (y0 >= z0) {
|
|
1914
|
+
i1 = 1;
|
|
1915
|
+
j1 = 0;
|
|
1916
|
+
k1 = 0;
|
|
1917
|
+
i2 = 1;
|
|
1918
|
+
j2 = 1;
|
|
1919
|
+
k2 = 0;
|
|
1920
|
+
} else if (x0 >= z0) {
|
|
1921
|
+
i1 = 1;
|
|
1922
|
+
j1 = 0;
|
|
1923
|
+
k1 = 0;
|
|
1924
|
+
i2 = 1;
|
|
1925
|
+
j2 = 0;
|
|
1926
|
+
k2 = 1;
|
|
1927
|
+
} else {
|
|
1928
|
+
i1 = 0;
|
|
1929
|
+
j1 = 0;
|
|
1930
|
+
k1 = 1;
|
|
1931
|
+
i2 = 1;
|
|
1932
|
+
j2 = 0;
|
|
1933
|
+
k2 = 1;
|
|
1934
|
+
}
|
|
1935
|
+
} else {
|
|
1936
|
+
if (y0 < z0) {
|
|
1937
|
+
i1 = 0;
|
|
1938
|
+
j1 = 0;
|
|
1939
|
+
k1 = 1;
|
|
1940
|
+
i2 = 0;
|
|
1941
|
+
j2 = 1;
|
|
1942
|
+
k2 = 1;
|
|
1943
|
+
} else if (x0 < z0) {
|
|
1944
|
+
i1 = 0;
|
|
1945
|
+
j1 = 1;
|
|
1946
|
+
k1 = 0;
|
|
1947
|
+
i2 = 0;
|
|
1948
|
+
j2 = 1;
|
|
1949
|
+
k2 = 1;
|
|
1950
|
+
} else {
|
|
1951
|
+
i1 = 0;
|
|
1952
|
+
j1 = 1;
|
|
1953
|
+
k1 = 0;
|
|
1954
|
+
i2 = 1;
|
|
1955
|
+
j2 = 1;
|
|
1956
|
+
k2 = 0;
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
const x1 = x0 - i1 + this.G3;
|
|
1960
|
+
const y1 = y0 - j1 + this.G3;
|
|
1961
|
+
const z1 = z0 - k1 + this.G3;
|
|
1962
|
+
const x2 = x0 - i2 + 2 * this.G3;
|
|
1963
|
+
const y2 = y0 - j2 + 2 * this.G3;
|
|
1964
|
+
const z2 = z0 - k2 + 2 * this.G3;
|
|
1965
|
+
const x3 = x0 - 1 + 3 * this.G3;
|
|
1966
|
+
const y3 = y0 - 1 + 3 * this.G3;
|
|
1967
|
+
const z3 = z0 - 1 + 3 * this.G3;
|
|
1968
|
+
const ii = i & 255;
|
|
1969
|
+
const jj = j & 255;
|
|
1970
|
+
const kk = k & 255;
|
|
1971
|
+
const gi0 = this.permMod12[ii + this.perm[jj + this.perm[kk]]];
|
|
1972
|
+
const gi1 = this.permMod12[ii + i1 + this.perm[jj + j1 + this.perm[kk + k1]]];
|
|
1973
|
+
const gi2 = this.permMod12[ii + i2 + this.perm[jj + j2 + this.perm[kk + k2]]];
|
|
1974
|
+
const gi3 = this.permMod12[ii + 1 + this.perm[jj + 1 + this.perm[kk + 1]]];
|
|
1975
|
+
let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
|
|
1976
|
+
if (t0 < 0) {
|
|
1977
|
+
n0 = 0;
|
|
1978
|
+
} else {
|
|
1979
|
+
t0 *= t0;
|
|
1980
|
+
n0 = t0 * t0 * this.dot3(this.grad3[gi0], x0, y0, z0);
|
|
1981
|
+
}
|
|
1982
|
+
let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
|
|
1983
|
+
if (t1 < 0) {
|
|
1984
|
+
n1 = 0;
|
|
1985
|
+
} else {
|
|
1986
|
+
t1 *= t1;
|
|
1987
|
+
n1 = t1 * t1 * this.dot3(this.grad3[gi1], x1, y1, z1);
|
|
1988
|
+
}
|
|
1989
|
+
let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
|
|
1990
|
+
if (t2 < 0) {
|
|
1991
|
+
n2 = 0;
|
|
1992
|
+
} else {
|
|
1993
|
+
t2 *= t2;
|
|
1994
|
+
n2 = t2 * t2 * this.dot3(this.grad3[gi2], x2, y2, z2);
|
|
1995
|
+
}
|
|
1996
|
+
let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
|
|
1997
|
+
if (t3 < 0) {
|
|
1998
|
+
n3 = 0;
|
|
1999
|
+
} else {
|
|
2000
|
+
t3 *= t3;
|
|
2001
|
+
n3 = t3 * t3 * this.dot3(this.grad3[gi3], x3, y3, z3);
|
|
2002
|
+
}
|
|
2003
|
+
return 32 * (n0 + n1 + n2 + n3);
|
|
2004
|
+
} else {
|
|
2005
|
+
return this.simplex(x, y, z) * 0.5 + this.simplex(x + w, y + w, z + w) * 0.5;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Dot product for 2D
|
|
2010
|
+
*/
|
|
2011
|
+
dot2(g, x, y) {
|
|
2012
|
+
return g[0] * x + g[1] * y;
|
|
2013
|
+
}
|
|
2014
|
+
/**
|
|
2015
|
+
* Dot product for 3D
|
|
2016
|
+
*/
|
|
2017
|
+
dot3(g, x, y, z) {
|
|
2018
|
+
return g[0] * x + g[1] * y + g[2] * z;
|
|
2019
|
+
}
|
|
2020
|
+
hash(x, y, z, w) {
|
|
2021
|
+
let n = 0;
|
|
2022
|
+
if (y === void 0) {
|
|
2023
|
+
n = Math.sin(x * 12.9898 + this.currentSeed) * 43758.5453;
|
|
2024
|
+
} else if (z === void 0) {
|
|
2025
|
+
n = Math.sin(x * 12.9898 + y * 78.233 + this.currentSeed) * 43758.5453;
|
|
2026
|
+
} else if (w === void 0) {
|
|
2027
|
+
n = Math.sin(x * 12.9898 + y * 78.233 + z * 37.719 + this.currentSeed) * 43758.5453;
|
|
2028
|
+
} else {
|
|
2029
|
+
n = Math.sin(x * 12.9898 + y * 78.233 + z * 37.719 + w * 59.1337 + this.currentSeed) * 43758.5453;
|
|
2030
|
+
}
|
|
2031
|
+
return n - Math.floor(n);
|
|
2032
|
+
}
|
|
2033
|
+
/**
|
|
2034
|
+
* Fractal Brownian Motion (fBm) noise
|
|
2035
|
+
* Supports options object for amplitude/frequency/lacunarity/gain/octaves.
|
|
2036
|
+
*/
|
|
2037
|
+
fbm(x, y, z, options) {
|
|
2038
|
+
let yVal = void 0;
|
|
2039
|
+
let zVal = void 0;
|
|
2040
|
+
let opts = typeof y === "object" ? y : typeof z === "object" ? z : {};
|
|
2041
|
+
if (typeof y === "number") yVal = y;
|
|
2042
|
+
if (typeof z === "number") zVal = z;
|
|
2043
|
+
if (options) opts = { ...opts, ...options };
|
|
2044
|
+
const octaves = opts.octaves ?? 4;
|
|
2045
|
+
if (octaves <= 0) return 0;
|
|
2046
|
+
let amplitude = opts.amplitude ?? 1;
|
|
2047
|
+
let frequency = opts.frequency ?? 1;
|
|
2048
|
+
const lacunarity = opts.lacunarity ?? 2;
|
|
2049
|
+
const gain = opts.gain ?? 0.5;
|
|
2050
|
+
let value = 0;
|
|
2051
|
+
let maxValue = 0;
|
|
2052
|
+
for (let i = 0; i < octaves; i++) {
|
|
2053
|
+
if (yVal === void 0) {
|
|
2054
|
+
value += amplitude * this.perlin(x * frequency);
|
|
2055
|
+
} else if (zVal === void 0) {
|
|
2056
|
+
value += amplitude * this.perlin(x * frequency, yVal * frequency);
|
|
2057
|
+
} else {
|
|
2058
|
+
value += amplitude * this.perlin(x * frequency, yVal * frequency, zVal * frequency);
|
|
2059
|
+
}
|
|
2060
|
+
maxValue += amplitude;
|
|
2061
|
+
amplitude *= gain;
|
|
2062
|
+
frequency *= lacunarity;
|
|
2063
|
+
}
|
|
2064
|
+
return maxValue === 0 ? 0 : value / maxValue;
|
|
2065
|
+
}
|
|
2066
|
+
/**
|
|
2067
|
+
* Turbulence noise (absolute value of noise)
|
|
2068
|
+
* Supports options object for octaves.
|
|
2069
|
+
*/
|
|
2070
|
+
turbulence(x, y, z, options) {
|
|
2071
|
+
let yVal = void 0;
|
|
2072
|
+
let zVal = void 0;
|
|
2073
|
+
let opts = typeof y === "object" ? y : typeof z === "object" ? z : {};
|
|
2074
|
+
if (typeof y === "number") yVal = y;
|
|
2075
|
+
if (typeof z === "number") zVal = z;
|
|
2076
|
+
if (options) opts = { ...opts, ...options };
|
|
2077
|
+
const octaves = opts.octaves ?? 4;
|
|
2078
|
+
if (octaves <= 0) return 0;
|
|
2079
|
+
let value = 0;
|
|
2080
|
+
let amplitude = 1;
|
|
2081
|
+
let frequency = 1;
|
|
2082
|
+
let maxValue = 0;
|
|
2083
|
+
for (let i = 0; i < octaves; i++) {
|
|
2084
|
+
let noise = 0;
|
|
2085
|
+
if (yVal === void 0) {
|
|
2086
|
+
noise = this.perlin(x * frequency);
|
|
2087
|
+
} else if (zVal === void 0) {
|
|
2088
|
+
noise = this.perlin(x * frequency, yVal * frequency);
|
|
2089
|
+
} else {
|
|
2090
|
+
noise = this.perlin(x * frequency, yVal * frequency, zVal * frequency);
|
|
2091
|
+
}
|
|
2092
|
+
value += amplitude * Math.abs(noise);
|
|
2093
|
+
maxValue += amplitude;
|
|
2094
|
+
amplitude *= 0.5;
|
|
2095
|
+
frequency *= 2;
|
|
2096
|
+
}
|
|
2097
|
+
return maxValue === 0 ? 0 : value / maxValue;
|
|
2098
|
+
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Ridged multifractal noise (simple implementation)
|
|
2101
|
+
*/
|
|
2102
|
+
ridge(x, y, options) {
|
|
2103
|
+
let yVal = void 0;
|
|
2104
|
+
let opts = typeof y === "object" ? y : {};
|
|
2105
|
+
if (typeof y === "number") yVal = y;
|
|
2106
|
+
if (options) opts = { ...opts, ...options };
|
|
2107
|
+
const octaves = opts.octaves ?? 4;
|
|
2108
|
+
if (octaves <= 0) return 0;
|
|
2109
|
+
let amplitude = opts.amplitude ?? 1;
|
|
2110
|
+
let frequency = opts.frequency ?? 1;
|
|
2111
|
+
const lacunarity = opts.lacunarity ?? 2;
|
|
2112
|
+
const gain = opts.gain ?? 0.5;
|
|
2113
|
+
let value = 0;
|
|
2114
|
+
let weight = 1;
|
|
2115
|
+
let maxValue = 0;
|
|
2116
|
+
for (let i = 0; i < octaves; i++) {
|
|
2117
|
+
const n = yVal === void 0 ? this.perlin(x * frequency) : this.perlin(x * frequency, yVal * frequency);
|
|
2118
|
+
let signal = 1 - Math.abs(n);
|
|
2119
|
+
signal *= signal;
|
|
2120
|
+
signal *= weight;
|
|
2121
|
+
weight = signal * 2;
|
|
2122
|
+
weight = Math.min(Math.max(weight, 0), 1);
|
|
2123
|
+
value += signal * amplitude;
|
|
2124
|
+
maxValue += amplitude;
|
|
2125
|
+
amplitude *= gain;
|
|
2126
|
+
frequency *= lacunarity;
|
|
2127
|
+
}
|
|
2128
|
+
return maxValue === 0 ? 0 : value / maxValue;
|
|
2129
|
+
}
|
|
2130
|
+
/**
|
|
2131
|
+
* Cellular / Worley noise (2D simple implementation)
|
|
2132
|
+
*/
|
|
2133
|
+
cellular(x, y, options) {
|
|
2134
|
+
let yVal = void 0;
|
|
2135
|
+
let opts = typeof y === "object" ? y : {};
|
|
2136
|
+
if (typeof y === "number") yVal = y;
|
|
2137
|
+
if (options) opts = { ...opts, ...options };
|
|
2138
|
+
if (yVal === void 0) yVal = 0;
|
|
2139
|
+
const xi = Math.floor(x);
|
|
2140
|
+
const yi = Math.floor(yVal);
|
|
2141
|
+
const distance = opts.distance ?? "euclidean";
|
|
2142
|
+
let minDist = Infinity;
|
|
2143
|
+
for (let j = -1; j <= 1; j++) {
|
|
2144
|
+
for (let i = -1; i <= 1; i++) {
|
|
2145
|
+
const fx = i + this.hash(xi + i, yi + j);
|
|
2146
|
+
const fy = j + this.hash(yi + j, xi + i);
|
|
2147
|
+
const dx = fx + xi - x;
|
|
2148
|
+
const dy = fy + yi - yVal;
|
|
2149
|
+
const dist = distance === "manhattan" ? Math.abs(dx) + Math.abs(dy) : Math.sqrt(dx * dx + dy * dy);
|
|
2150
|
+
if (dist < minDist) minDist = dist;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
const maxDist = Math.SQRT2;
|
|
2154
|
+
const normalized = 1 - Math.min(minDist / maxDist, 1);
|
|
2155
|
+
return normalized;
|
|
2156
|
+
}
|
|
2157
|
+
};
|
|
2158
|
+
var Noise_default = Noise;
|
|
2159
|
+
|
|
2160
|
+
// src/elements/Hotspot.tsx
|
|
2161
|
+
var Hotspot = class {
|
|
2162
|
+
/**
|
|
2163
|
+
* Creates a new Hotspot instance
|
|
2164
|
+
* @param ctx - The Klint context
|
|
2165
|
+
*/
|
|
2166
|
+
constructor(ctx) {
|
|
2167
|
+
this.context = ctx;
|
|
2168
|
+
}
|
|
2169
|
+
/**
|
|
2170
|
+
* Check if point is inside a circle
|
|
2171
|
+
* @param point - Point to check (usually mouse position)
|
|
2172
|
+
* @param x - Circle center X
|
|
2173
|
+
* @param y - Circle center Y
|
|
2174
|
+
* @param radius - Circle radius
|
|
2175
|
+
* @returns True if point is inside the circle
|
|
2176
|
+
*/
|
|
2177
|
+
circle(point, x, y, radius) {
|
|
2178
|
+
const dx = point.x - x;
|
|
2179
|
+
const dy = point.y - y;
|
|
2180
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
2181
|
+
return distance <= radius;
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Check if point is inside a rectangle
|
|
2185
|
+
* @param point - Point to check (usually mouse position)
|
|
2186
|
+
* @param x - Rectangle X position
|
|
2187
|
+
* @param y - Rectangle Y position
|
|
2188
|
+
* @param width - Rectangle width
|
|
2189
|
+
* @param height - Rectangle height
|
|
2190
|
+
* @returns True if point is inside the rectangle
|
|
2191
|
+
*/
|
|
2192
|
+
rect(point, x, y, width, height) {
|
|
2193
|
+
const origin = this.context.__rectangleOrigin || "corner";
|
|
2194
|
+
let left, top;
|
|
2195
|
+
if (origin === "center") {
|
|
2196
|
+
left = x - width / 2;
|
|
2197
|
+
top = y - height / 2;
|
|
2198
|
+
} else {
|
|
2199
|
+
left = x;
|
|
2200
|
+
top = y;
|
|
2201
|
+
}
|
|
2202
|
+
const right = left + width;
|
|
2203
|
+
const bottom = top + height;
|
|
2204
|
+
return point.x >= left && point.x <= right && point.y >= top && point.y <= bottom;
|
|
2205
|
+
}
|
|
2206
|
+
/**
|
|
2207
|
+
* Check if point is inside an ellipse
|
|
2208
|
+
* @param point - Point to check
|
|
2209
|
+
* @param x - Ellipse center X
|
|
2210
|
+
* @param y - Ellipse center Y
|
|
2211
|
+
* @param radiusX - Horizontal radius
|
|
2212
|
+
* @param radiusY - Vertical radius
|
|
2213
|
+
* @param rotation - Rotation angle in radians
|
|
2214
|
+
* @returns True if point is inside the ellipse
|
|
2215
|
+
*/
|
|
2216
|
+
ellipse(point, x, y, radiusX, radiusY, rotation = 0) {
|
|
2217
|
+
const cos = Math.cos(-rotation);
|
|
2218
|
+
const sin = Math.sin(-rotation);
|
|
2219
|
+
const dx = point.x - x;
|
|
2220
|
+
const dy = point.y - y;
|
|
2221
|
+
const localX = dx * cos - dy * sin;
|
|
2222
|
+
const localY = dx * sin + dy * cos;
|
|
2223
|
+
return localX * localX / (radiusX * radiusX) + localY * localY / (radiusY * radiusY) <= 1;
|
|
2224
|
+
}
|
|
2225
|
+
/**
|
|
2226
|
+
* Check if point is inside a polygon
|
|
2227
|
+
* @param point - Point to check
|
|
2228
|
+
* @param vertices - Array of polygon vertices
|
|
2229
|
+
* @returns True if point is inside the polygon
|
|
2230
|
+
*/
|
|
2231
|
+
polygon(point, vertices) {
|
|
2232
|
+
let inside = false;
|
|
2233
|
+
const n = vertices.length;
|
|
2234
|
+
for (let i = 0, j = n - 1; i < n; j = i++) {
|
|
2235
|
+
const xi = vertices[i].x, yi = vertices[i].y;
|
|
2236
|
+
const xj = vertices[j].x, yj = vertices[j].y;
|
|
2237
|
+
const intersect = yi > point.y !== yj > point.y && point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi;
|
|
2238
|
+
if (intersect) inside = !inside;
|
|
2239
|
+
}
|
|
2240
|
+
return inside;
|
|
2241
|
+
}
|
|
2242
|
+
/**
|
|
2243
|
+
* Check if point is inside a Path2D
|
|
2244
|
+
* @param point - Point to check
|
|
2245
|
+
* @param path - Path2D object
|
|
2246
|
+
* @returns True if point is inside the path
|
|
2247
|
+
*/
|
|
2248
|
+
path(point, path) {
|
|
2249
|
+
return this.context.isPointInPath(path, point.x, point.y);
|
|
2250
|
+
}
|
|
2251
|
+
};
|
|
2252
|
+
var Hotspot_default = Hotspot;
|
|
2253
|
+
|
|
2254
|
+
// src/elements/Performance.tsx
|
|
2255
|
+
var Performance = class {
|
|
2256
|
+
constructor(ctx) {
|
|
2257
|
+
this.textMetricsCache = /* @__PURE__ */ new Map();
|
|
2258
|
+
this.frameTimeHistory = [];
|
|
2259
|
+
this.MAX_HISTORY = 60;
|
|
2260
|
+
this.context = ctx;
|
|
2261
|
+
}
|
|
2262
|
+
/**
|
|
2263
|
+
* Batch multiple canvas operations together for better performance
|
|
2264
|
+
*/
|
|
2265
|
+
batchDraw(drawFn) {
|
|
2266
|
+
this.context.save();
|
|
2267
|
+
this.context.beginPath();
|
|
2268
|
+
try {
|
|
2269
|
+
drawFn();
|
|
2270
|
+
} finally {
|
|
2271
|
+
this.context.restore();
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
/**
|
|
2275
|
+
* Optimize drawing by using offscreen canvas for static elements
|
|
2276
|
+
*/
|
|
2277
|
+
useOffscreenCache(id, width, height, renderFn) {
|
|
2278
|
+
let offscreen = this.context.__offscreens.get(id);
|
|
2279
|
+
if (!offscreen || offscreen instanceof HTMLImageElement) {
|
|
2280
|
+
offscreen = this.context.createOffscreen(id, width, height);
|
|
2281
|
+
if (offscreen && !(offscreen instanceof HTMLImageElement)) {
|
|
2282
|
+
renderFn(offscreen);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
if (offscreen) {
|
|
2286
|
+
if (offscreen instanceof HTMLImageElement) {
|
|
2287
|
+
this.context.image(offscreen, 0, 0);
|
|
2288
|
+
} else {
|
|
2289
|
+
this.context.image(offscreen.canvas, 0, 0);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
/**
|
|
2294
|
+
* Throttle expensive operations to run less frequently
|
|
2295
|
+
*/
|
|
2296
|
+
throttleFrame(interval, fn) {
|
|
2297
|
+
const cacheKey = `__throttle_${fn.toString().slice(0, 50)}`;
|
|
2298
|
+
const cached = this.context[cacheKey];
|
|
2299
|
+
if (!cached || this.context.frame % interval === 0) {
|
|
2300
|
+
const result = fn();
|
|
2301
|
+
this.context[cacheKey] = { value: result, frame: this.context.frame };
|
|
2302
|
+
return result;
|
|
2303
|
+
}
|
|
2304
|
+
return cached.value;
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Detect potential memory leaks by tracking object creation
|
|
2308
|
+
*/
|
|
2309
|
+
useLeakDetection() {
|
|
2310
|
+
const objectCounts = /* @__PURE__ */ new Map();
|
|
2311
|
+
let lastCheckFrame = 0;
|
|
2312
|
+
return {
|
|
2313
|
+
track: (type) => {
|
|
2314
|
+
const count = objectCounts.get(type) || 0;
|
|
2315
|
+
objectCounts.set(type, count + 1);
|
|
2316
|
+
},
|
|
2317
|
+
check: () => {
|
|
2318
|
+
if (this.context.frame - lastCheckFrame < 60) return;
|
|
2319
|
+
lastCheckFrame = this.context.frame;
|
|
2320
|
+
const warnings = [];
|
|
2321
|
+
objectCounts.forEach((count, type) => {
|
|
2322
|
+
if (count > 1e4) {
|
|
2323
|
+
warnings.push(
|
|
2324
|
+
`Potential memory leak: ${type} has ${count} instances`
|
|
2325
|
+
);
|
|
2326
|
+
}
|
|
2327
|
+
});
|
|
2328
|
+
if (warnings.length > 0) {
|
|
2329
|
+
console.warn("[Klint] Memory leak warnings:", warnings);
|
|
2330
|
+
}
|
|
2331
|
+
objectCounts.clear();
|
|
2332
|
+
}
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* Optimize text rendering by caching text measurements
|
|
2337
|
+
*/
|
|
2338
|
+
getCachedTextMetrics(text, font) {
|
|
2339
|
+
const cacheKey = `${font}:${text}`;
|
|
2340
|
+
if (!this.textMetricsCache.has(cacheKey)) {
|
|
2341
|
+
this.context.save();
|
|
2342
|
+
this.context.font = font;
|
|
2343
|
+
const metrics = this.context.measureText(text);
|
|
2344
|
+
this.context.restore();
|
|
2345
|
+
this.textMetricsCache.set(cacheKey, metrics);
|
|
2346
|
+
if (this.textMetricsCache.size > 1e3) {
|
|
2347
|
+
const firstKey = this.textMetricsCache.keys().next().value;
|
|
2348
|
+
if (firstKey) this.textMetricsCache.delete(firstKey);
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
return this.textMetricsCache.get(cacheKey);
|
|
2352
|
+
}
|
|
2353
|
+
/**
|
|
2354
|
+
* Clear all performance caches
|
|
2355
|
+
*/
|
|
2356
|
+
clearCaches() {
|
|
2357
|
+
this.textMetricsCache.clear();
|
|
2358
|
+
}
|
|
2359
|
+
/**
|
|
2360
|
+
* Get current performance metrics
|
|
2361
|
+
*/
|
|
2362
|
+
getMetrics() {
|
|
2363
|
+
return this.context.__performance || null;
|
|
2364
|
+
}
|
|
2365
|
+
/**
|
|
2366
|
+
* Render performance widget on canvas
|
|
2367
|
+
*/
|
|
2368
|
+
render(options = {}) {
|
|
2369
|
+
const metrics = this.getMetrics();
|
|
2370
|
+
if (!metrics) return;
|
|
2371
|
+
const {
|
|
2372
|
+
x = 10,
|
|
2373
|
+
y = 10,
|
|
2374
|
+
width = 200,
|
|
2375
|
+
height = 120,
|
|
2376
|
+
backgroundColor = "rgba(0, 0, 0, 0.8)",
|
|
2377
|
+
textColor = "#ffffff",
|
|
2378
|
+
accentColor = "#4ecdc4",
|
|
2379
|
+
showGraph = true,
|
|
2380
|
+
graphHeight = 40,
|
|
2381
|
+
fontSize = 12,
|
|
2382
|
+
showMemory = true
|
|
2383
|
+
} = options;
|
|
2384
|
+
if (showGraph) {
|
|
2385
|
+
this.frameTimeHistory.push(metrics.frameTime);
|
|
2386
|
+
if (this.frameTimeHistory.length > this.MAX_HISTORY) {
|
|
2387
|
+
this.frameTimeHistory.shift();
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
this.context.save();
|
|
2391
|
+
this.context.fillStyle = backgroundColor;
|
|
2392
|
+
this.context.fillRect(x, y, width, height);
|
|
2393
|
+
this.context.strokeStyle = accentColor;
|
|
2394
|
+
this.context.lineWidth = 1;
|
|
2395
|
+
this.context.strokeRect(x, y, width, height);
|
|
2396
|
+
this.context.fillStyle = textColor;
|
|
2397
|
+
this.context.font = `${fontSize}px monospace`;
|
|
2398
|
+
this.context.textAlign = "left";
|
|
2399
|
+
this.context.textBaseline = "top";
|
|
2400
|
+
let currentY = y + 8;
|
|
2401
|
+
const fpsColor = metrics.fps >= 55 ? "#4ecdc4" : metrics.fps >= 30 ? "#ffd93d" : "#ff6b6b";
|
|
2402
|
+
this.context.fillStyle = fpsColor;
|
|
2403
|
+
this.context.fillText(`FPS: ${metrics.fps}`, x + 8, currentY);
|
|
2404
|
+
currentY += fontSize + 4;
|
|
2405
|
+
this.context.fillStyle = textColor;
|
|
2406
|
+
this.context.fillText(`Frame: ${metrics.frameTime.toFixed(2)}ms`, x + 8, currentY);
|
|
2407
|
+
currentY += fontSize + 4;
|
|
2408
|
+
this.context.fillText(`Avg: ${metrics.averageFrameTime.toFixed(2)}ms`, x + 8, currentY);
|
|
2409
|
+
currentY += fontSize + 4;
|
|
2410
|
+
this.context.fillText(
|
|
2411
|
+
`Min: ${metrics.minFrameTime.toFixed(2)}ms / Max: ${metrics.maxFrameTime.toFixed(2)}ms`,
|
|
2412
|
+
x + 8,
|
|
2413
|
+
currentY
|
|
2414
|
+
);
|
|
2415
|
+
currentY += fontSize + 4;
|
|
2416
|
+
if (metrics.droppedFrames > 0) {
|
|
2417
|
+
this.context.fillStyle = "#ff6b6b";
|
|
2418
|
+
this.context.fillText(`Dropped: ${metrics.droppedFrames}`, x + 8, currentY);
|
|
2419
|
+
currentY += fontSize + 4;
|
|
2420
|
+
}
|
|
2421
|
+
if (showMemory && metrics.memoryUsage !== void 0) {
|
|
2422
|
+
this.context.fillStyle = textColor;
|
|
2423
|
+
this.context.fillText(`Memory: ${metrics.memoryUsage.toFixed(2)}MB`, x + 8, currentY);
|
|
2424
|
+
currentY += fontSize + 4;
|
|
2425
|
+
}
|
|
2426
|
+
if (showGraph && this.frameTimeHistory.length > 1) {
|
|
2427
|
+
const graphX = x + 8;
|
|
2428
|
+
const graphY = currentY + 4;
|
|
2429
|
+
const graphWidth = width - 16;
|
|
2430
|
+
const targetFrameTime = 1e3 / this.context.fps;
|
|
2431
|
+
this.context.fillStyle = "rgba(255, 255, 255, 0.1)";
|
|
2432
|
+
this.context.fillRect(graphX, graphY, graphWidth, graphHeight);
|
|
2433
|
+
const targetY = graphY + graphHeight - targetFrameTime / (targetFrameTime * 2) * graphHeight;
|
|
2434
|
+
this.context.strokeStyle = "rgba(255, 255, 255, 0.3)";
|
|
2435
|
+
this.context.lineWidth = 1;
|
|
2436
|
+
this.context.beginPath();
|
|
2437
|
+
this.context.moveTo(graphX, targetY);
|
|
2438
|
+
this.context.lineTo(graphX + graphWidth, targetY);
|
|
2439
|
+
this.context.stroke();
|
|
2440
|
+
this.context.strokeStyle = accentColor;
|
|
2441
|
+
this.context.lineWidth = 2;
|
|
2442
|
+
this.context.beginPath();
|
|
2443
|
+
const maxFrameTime = Math.max(...this.frameTimeHistory, targetFrameTime * 2);
|
|
2444
|
+
const stepX = graphWidth / (this.frameTimeHistory.length - 1);
|
|
2445
|
+
this.frameTimeHistory.forEach((frameTime, index) => {
|
|
2446
|
+
const normalizedTime = Math.min(frameTime / maxFrameTime, 1);
|
|
2447
|
+
const pointY = graphY + graphHeight - normalizedTime * graphHeight;
|
|
2448
|
+
const pointX = graphX + index * stepX;
|
|
2449
|
+
if (index === 0) {
|
|
2450
|
+
this.context.moveTo(pointX, pointY);
|
|
2451
|
+
} else {
|
|
2452
|
+
this.context.lineTo(pointX, pointY);
|
|
2453
|
+
}
|
|
2454
|
+
});
|
|
2455
|
+
this.context.stroke();
|
|
2456
|
+
}
|
|
2457
|
+
this.context.restore();
|
|
2458
|
+
}
|
|
2459
|
+
/**
|
|
2460
|
+
* Alias for render() - shows performance widget
|
|
2461
|
+
*/
|
|
2462
|
+
show(options) {
|
|
2463
|
+
this.render(options);
|
|
2464
|
+
}
|
|
2465
|
+
};
|
|
2466
|
+
var Performance_default = Performance;
|
|
2467
|
+
|
|
2468
|
+
// src/elements/SSR.tsx
|
|
2469
|
+
var SSR = class {
|
|
2470
|
+
constructor(ctx) {
|
|
2471
|
+
this.context = ctx || null;
|
|
2472
|
+
}
|
|
2473
|
+
/**
|
|
2474
|
+
* Render a Klint sketch to a static image (base64 data URL)
|
|
2475
|
+
*/
|
|
2476
|
+
async renderToImage(draw, options) {
|
|
2477
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
2478
|
+
return this.renderInBrowser(draw, options);
|
|
2479
|
+
}
|
|
2480
|
+
throw new Error(
|
|
2481
|
+
"Server-side rendering requires 'canvas' package. Install it with: npm install canvas\nAlternatively, use SSR.generateImageUrl() to generate images via an API endpoint."
|
|
2482
|
+
);
|
|
2483
|
+
}
|
|
2484
|
+
/**
|
|
2485
|
+
* Generate a static image URL for a Klint sketch
|
|
2486
|
+
*/
|
|
2487
|
+
async generateImageUrl(draw, options) {
|
|
2488
|
+
if (typeof window === "undefined") {
|
|
2489
|
+
throw new Error(
|
|
2490
|
+
"generateImageUrl() requires a browser environment. For server-side rendering, use renderToImage() with the 'canvas' package."
|
|
2491
|
+
);
|
|
2492
|
+
}
|
|
2493
|
+
return this.renderInBrowser(draw, options);
|
|
2494
|
+
}
|
|
2495
|
+
/**
|
|
2496
|
+
* Check if Klint can run in the current environment
|
|
2497
|
+
*/
|
|
2498
|
+
canRender() {
|
|
2499
|
+
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
2500
|
+
return true;
|
|
2501
|
+
}
|
|
2502
|
+
try {
|
|
2503
|
+
__require("canvas");
|
|
2504
|
+
return true;
|
|
2505
|
+
} catch {
|
|
2506
|
+
return false;
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
/**
|
|
2510
|
+
* Render in browser environment (client-side)
|
|
2511
|
+
*/
|
|
2512
|
+
async renderInBrowser(draw, options) {
|
|
2513
|
+
return new Promise((resolve, reject) => {
|
|
2514
|
+
try {
|
|
2515
|
+
const canvas = document.createElement("canvas");
|
|
2516
|
+
const dpr = options.dpr || (typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1);
|
|
2517
|
+
canvas.width = options.width * dpr;
|
|
2518
|
+
canvas.height = options.height * dpr;
|
|
2519
|
+
const ctx = canvas.getContext("2d");
|
|
2520
|
+
if (!ctx) {
|
|
2521
|
+
reject(new Error("Failed to get canvas context"));
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
const klintContext = this.createMinimalContext(
|
|
2525
|
+
ctx,
|
|
2526
|
+
canvas,
|
|
2527
|
+
options.width,
|
|
2528
|
+
options.height,
|
|
2529
|
+
dpr
|
|
2530
|
+
);
|
|
2531
|
+
draw(klintContext);
|
|
2532
|
+
const mimeType = options.format === "jpeg" ? "image/jpeg" : options.format === "webp" ? "image/webp" : "image/png";
|
|
2533
|
+
const quality = options.quality !== void 0 ? options.quality : 0.85;
|
|
2534
|
+
const dataUrl = canvas.toDataURL(mimeType, quality);
|
|
2535
|
+
resolve(dataUrl);
|
|
2536
|
+
} catch (error) {
|
|
2537
|
+
reject(error);
|
|
2538
|
+
}
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
/**
|
|
2542
|
+
* Create a minimal KlintContext for server rendering
|
|
2543
|
+
* This provides basic functionality without full Klint features
|
|
2544
|
+
*/
|
|
2545
|
+
createMinimalContext(ctx, canvas, width, height, dpr) {
|
|
2546
|
+
return {
|
|
2547
|
+
canvas,
|
|
2548
|
+
width: width * dpr,
|
|
2549
|
+
height: height * dpr,
|
|
2550
|
+
// Basic canvas methods
|
|
2551
|
+
fillRect: ctx.fillRect.bind(ctx),
|
|
2552
|
+
strokeRect: ctx.strokeRect.bind(ctx),
|
|
2553
|
+
clearRect: ctx.clearRect.bind(ctx),
|
|
2554
|
+
beginPath: ctx.beginPath.bind(ctx),
|
|
2555
|
+
moveTo: ctx.moveTo.bind(ctx),
|
|
2556
|
+
lineTo: ctx.lineTo.bind(ctx),
|
|
2557
|
+
arc: ctx.arc.bind(ctx),
|
|
2558
|
+
fill: ctx.fill.bind(ctx),
|
|
2559
|
+
stroke: ctx.stroke.bind(ctx),
|
|
2560
|
+
save: ctx.save.bind(ctx),
|
|
2561
|
+
restore: ctx.restore.bind(ctx),
|
|
2562
|
+
translate: ctx.translate.bind(ctx),
|
|
2563
|
+
rotate: ctx.rotate.bind(ctx),
|
|
2564
|
+
scale: ctx.scale.bind(ctx),
|
|
2565
|
+
// Basic properties
|
|
2566
|
+
fillStyle: ctx.fillStyle,
|
|
2567
|
+
strokeStyle: ctx.strokeStyle,
|
|
2568
|
+
lineWidth: ctx.lineWidth,
|
|
2569
|
+
// Minimal Klint-like API
|
|
2570
|
+
background: (color) => {
|
|
2571
|
+
ctx.fillStyle = color || "#000";
|
|
2572
|
+
ctx.fillRect(0, 0, width * dpr, height * dpr);
|
|
2573
|
+
},
|
|
2574
|
+
fillColor: (color) => {
|
|
2575
|
+
ctx.fillStyle = color;
|
|
2576
|
+
},
|
|
2577
|
+
strokeColor: (color) => {
|
|
2578
|
+
ctx.strokeStyle = color;
|
|
2579
|
+
},
|
|
2580
|
+
circle: (x, y, radius) => {
|
|
2581
|
+
ctx.beginPath();
|
|
2582
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
2583
|
+
ctx.fill();
|
|
2584
|
+
},
|
|
2585
|
+
rectangle: (x, y, w, h) => {
|
|
2586
|
+
ctx.fillRect(x, y, w, h || w);
|
|
2587
|
+
},
|
|
2588
|
+
// Time properties (static for server rendering)
|
|
2589
|
+
frame: 0,
|
|
2590
|
+
time: 0,
|
|
2591
|
+
deltaTime: 0,
|
|
2592
|
+
fps: 60
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
};
|
|
2596
|
+
var SSR_default = SSR;
|
|
2597
|
+
|
|
2598
|
+
// src/KlintFunctions.tsx
|
|
2599
|
+
var KlintCoreFunctions = {
|
|
2600
|
+
saveCanvas: (ctx) => () => {
|
|
2601
|
+
const link = document.createElement("a");
|
|
2602
|
+
link.download = "canvas.png";
|
|
2603
|
+
link.href = ctx.canvas.toDataURL();
|
|
2604
|
+
link.click();
|
|
2605
|
+
},
|
|
2606
|
+
fullscreen: (ctx) => () => {
|
|
2607
|
+
ctx.canvas.requestFullscreen?.();
|
|
2608
|
+
},
|
|
2609
|
+
play: (ctx) => () => {
|
|
2610
|
+
if (!ctx.__isPlaying) ctx.__isPlaying = true;
|
|
2611
|
+
},
|
|
2612
|
+
pause: (ctx) => () => {
|
|
2613
|
+
if (ctx.__isPlaying) ctx.__isPlaying = false;
|
|
2614
|
+
},
|
|
2615
|
+
// to do
|
|
2616
|
+
redraw: () => () => {
|
|
2617
|
+
},
|
|
2618
|
+
extend: (ctx) => (name, data, enforceReplace = false) => {
|
|
2619
|
+
if (name in ctx && !enforceReplace) return;
|
|
2620
|
+
ctx[name] = data;
|
|
2621
|
+
},
|
|
2622
|
+
passImage: () => (element) => {
|
|
2623
|
+
if (!element.complete) {
|
|
2624
|
+
console.warn("Image passed to passImage() is not fully loaded");
|
|
2625
|
+
return null;
|
|
2626
|
+
}
|
|
2627
|
+
return element;
|
|
2628
|
+
},
|
|
2629
|
+
passImages: () => (elements) => {
|
|
2630
|
+
return elements.map((element) => {
|
|
2631
|
+
if (!element.complete) {
|
|
2632
|
+
console.warn("Image passed to passImages() is not fully loaded");
|
|
2633
|
+
return null;
|
|
2634
|
+
}
|
|
2635
|
+
return element;
|
|
2636
|
+
});
|
|
2637
|
+
},
|
|
2638
|
+
saveConfig: (ctx) => (from) => {
|
|
2639
|
+
return Object.fromEntries(
|
|
2640
|
+
CONFIG_PROPS.map((key) => [
|
|
2641
|
+
key,
|
|
2642
|
+
from?.[key] ?? ctx[key]
|
|
2643
|
+
])
|
|
2644
|
+
);
|
|
2645
|
+
},
|
|
2646
|
+
restoreConfig: (ctx) => (config) => {
|
|
2647
|
+
Object.assign(ctx, config);
|
|
2648
|
+
},
|
|
2649
|
+
describe: (ctx) => (description) => {
|
|
2650
|
+
ctx.__description = description;
|
|
2651
|
+
},
|
|
2652
|
+
createOffscreen: (ctx) => (id, width, height, options, callback) => {
|
|
2653
|
+
const offscreen = document.createElement("canvas");
|
|
2654
|
+
offscreen.width = width * ctx.__dpr;
|
|
2655
|
+
offscreen.height = height * ctx.__dpr;
|
|
2656
|
+
const context = offscreen.getContext("2d", {
|
|
2657
|
+
alpha: options?.alpha ?? true,
|
|
2658
|
+
willReadFrequently: options?.willreadfrequently ?? false
|
|
2659
|
+
});
|
|
2660
|
+
if (!context) throw new Error("Failed to create offscreen context");
|
|
2661
|
+
context.__dpr = ctx.__dpr;
|
|
2662
|
+
context.width = width * ctx.__dpr;
|
|
2663
|
+
context.height = height * ctx.__dpr;
|
|
2664
|
+
context.__isMainContext = false;
|
|
2665
|
+
context.__imageOrigin = "corner";
|
|
2666
|
+
context.__rectangleOrigin = "corner";
|
|
2667
|
+
context.__canvasOrigin = "corner";
|
|
2668
|
+
context.__textFont = "sans-serif";
|
|
2669
|
+
context.__textWeight = "normal";
|
|
2670
|
+
context.__textStyle = "normal";
|
|
2671
|
+
context.__textSize = 120;
|
|
2672
|
+
context.__textLeading = void 0;
|
|
2673
|
+
context.__textAlignment = {
|
|
2674
|
+
horizontal: "left",
|
|
2675
|
+
vertical: "top"
|
|
2676
|
+
};
|
|
2677
|
+
context.__fillRule = "nonzero";
|
|
2678
|
+
if (!options?.ignoreFunctions) {
|
|
2679
|
+
context.Color = ctx.Color;
|
|
2680
|
+
context.createVector = (x = 0, y = 0) => new Vector_default(x, y);
|
|
2681
|
+
context.Easing = ctx.Easing;
|
|
2682
|
+
context.Text = ctx.Text;
|
|
2683
|
+
Object.entries(KlintFunctions).forEach(([name, fn]) => {
|
|
2684
|
+
context[name] = fn(context);
|
|
2685
|
+
});
|
|
2686
|
+
}
|
|
2687
|
+
if (options?.origin) {
|
|
2688
|
+
context.__canvasOrigin = options.origin;
|
|
2689
|
+
if (options.origin === "center") {
|
|
2690
|
+
context.translate(context.width * 0.5, context.height * 0.5);
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
if (callback) {
|
|
2694
|
+
callback(context);
|
|
2695
|
+
}
|
|
2696
|
+
if (options?.static === "true") {
|
|
2697
|
+
const base64 = offscreen.toDataURL();
|
|
2698
|
+
const img = new Image();
|
|
2699
|
+
img.src = base64;
|
|
2700
|
+
ctx.__offscreens.set(id, img);
|
|
2701
|
+
return img;
|
|
2702
|
+
}
|
|
2703
|
+
ctx.__offscreens.set(id, context);
|
|
2704
|
+
return context;
|
|
2705
|
+
},
|
|
2706
|
+
getOffscreen: (ctx) => (id) => {
|
|
2707
|
+
const offscreen = ctx.__offscreens.get(id);
|
|
2708
|
+
if (!offscreen)
|
|
2709
|
+
throw new Error(`No offscreen context found with id: ${id}`);
|
|
2710
|
+
return offscreen;
|
|
2711
|
+
}
|
|
2712
|
+
};
|
|
2713
|
+
var KlintFunctions = {
|
|
2714
|
+
extend: (ctx) => (name, data, enforceReplace = false) => {
|
|
2715
|
+
if (name in ctx && !enforceReplace) return;
|
|
2716
|
+
ctx[name] = data;
|
|
2717
|
+
},
|
|
2718
|
+
background: (ctx) => (color) => {
|
|
2719
|
+
ctx.resetTransform();
|
|
2720
|
+
ctx.push();
|
|
2721
|
+
if (color && color !== "transparent") {
|
|
2722
|
+
ctx.fillStyle = color;
|
|
2723
|
+
ctx.fillRect(0, 0, ctx.width, ctx.height);
|
|
2724
|
+
} else {
|
|
2725
|
+
ctx.clearRect(0, 0, ctx.width, ctx.height);
|
|
2726
|
+
}
|
|
2727
|
+
ctx.pop();
|
|
2728
|
+
if (ctx.__canvasOrigin === "center")
|
|
2729
|
+
ctx.translate(ctx.width * 0.5, ctx.height * 0.5);
|
|
2730
|
+
},
|
|
2731
|
+
reset: (ctx) => () => {
|
|
1278
2732
|
ctx.clearRect(0, 0, ctx.width, ctx.height);
|
|
1279
2733
|
ctx.resetTransform();
|
|
1280
2734
|
},
|
|
@@ -1322,9 +2776,12 @@ var KlintFunctions = {
|
|
|
1322
2776
|
return true;
|
|
1323
2777
|
},
|
|
1324
2778
|
drawIfVisible: (ctx) => () => {
|
|
1325
|
-
if (ctx.checkTransparency("fill")) ctx.fill();
|
|
2779
|
+
if (ctx.checkTransparency("fill")) ctx.fill(ctx.__fillRule || "nonzero");
|
|
1326
2780
|
if (ctx.checkTransparency("stroke")) ctx.stroke();
|
|
1327
2781
|
},
|
|
2782
|
+
fillRule: (ctx) => (rule) => {
|
|
2783
|
+
ctx.__fillRule = rule;
|
|
2784
|
+
},
|
|
1328
2785
|
line: (ctx) => (x1, y1, x2, y2) => {
|
|
1329
2786
|
if (!ctx.checkTransparency("stroke")) return;
|
|
1330
2787
|
ctx.beginPath();
|
|
@@ -1386,17 +2843,29 @@ var KlintFunctions = {
|
|
|
1386
2843
|
ctx.__currentContours = [];
|
|
1387
2844
|
},
|
|
1388
2845
|
beginContour: (ctx) => () => {
|
|
1389
|
-
if (!ctx.__startedShape) return;
|
|
1390
|
-
if (ctx.__startedContour && ctx.__currentContour?.length) {
|
|
1391
|
-
ctx.__currentContours?.push([...ctx.__currentContour]);
|
|
1392
|
-
}
|
|
2846
|
+
if (!ctx.__startedShape || ctx.__startedContour) return;
|
|
1393
2847
|
ctx.__startedContour = true;
|
|
1394
2848
|
ctx.__currentContour = [];
|
|
1395
2849
|
},
|
|
1396
2850
|
vertex: (ctx) => (x, y) => {
|
|
1397
2851
|
if (!ctx.__startedShape) return;
|
|
1398
2852
|
const points = ctx.__startedContour ? ctx.__currentContour : ctx.__currentShape;
|
|
1399
|
-
points?.push(
|
|
2853
|
+
points?.push({ type: "line", x, y });
|
|
2854
|
+
},
|
|
2855
|
+
bezierVertex: (ctx) => (cp1x, cp1y, cp2x, cp2y, x, y) => {
|
|
2856
|
+
if (!ctx.__startedShape) return;
|
|
2857
|
+
const points = ctx.__startedContour ? ctx.__currentContour : ctx.__currentShape;
|
|
2858
|
+
points?.push({ type: "bezier", cp1x, cp1y, cp2x, cp2y, x, y });
|
|
2859
|
+
},
|
|
2860
|
+
quadraticVertex: (ctx) => (cpx, cpy, x, y) => {
|
|
2861
|
+
if (!ctx.__startedShape) return;
|
|
2862
|
+
const points = ctx.__startedContour ? ctx.__currentContour : ctx.__currentShape;
|
|
2863
|
+
points?.push({ type: "quadratic", cpx, cpy, x, y });
|
|
2864
|
+
},
|
|
2865
|
+
arcVertex: (ctx) => (x1, y1, x2, y2, radius) => {
|
|
2866
|
+
if (!ctx.__startedShape) return;
|
|
2867
|
+
const points = ctx.__startedContour ? ctx.__currentContour : ctx.__currentShape;
|
|
2868
|
+
points?.push({ type: "arc", x1, y1, x2, y2, radius });
|
|
1400
2869
|
},
|
|
1401
2870
|
endContour: (ctx) => (forceRevert = true) => {
|
|
1402
2871
|
if (!ctx.__startedContour || !ctx.__currentContour?.length) return;
|
|
@@ -1414,17 +2883,38 @@ var KlintFunctions = {
|
|
|
1414
2883
|
const points = ctx.__currentShape;
|
|
1415
2884
|
if (!points?.length) return;
|
|
1416
2885
|
const drawPath = (points2, close2 = false) => {
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
const
|
|
1424
|
-
|
|
1425
|
-
|
|
2886
|
+
if (points2.length === 0) return;
|
|
2887
|
+
const firstPoint = points2[0];
|
|
2888
|
+
const startX = firstPoint.type === "line" ? firstPoint.x : firstPoint.type === "bezier" ? firstPoint.x : firstPoint.type === "quadratic" ? firstPoint.x : firstPoint.x2;
|
|
2889
|
+
const startY = firstPoint.type === "line" ? firstPoint.y : firstPoint.type === "bezier" ? firstPoint.y : firstPoint.type === "quadratic" ? firstPoint.y : firstPoint.y2;
|
|
2890
|
+
ctx.moveTo(startX, startY);
|
|
2891
|
+
for (let i = 0; i < points2.length; i++) {
|
|
2892
|
+
const point = points2[i];
|
|
2893
|
+
switch (point.type) {
|
|
2894
|
+
case "line":
|
|
2895
|
+
if (i > 0) ctx.lineTo(point.x, point.y);
|
|
2896
|
+
break;
|
|
2897
|
+
case "bezier":
|
|
2898
|
+
ctx.bezierCurveTo(
|
|
2899
|
+
point.cp1x,
|
|
2900
|
+
point.cp1y,
|
|
2901
|
+
point.cp2x,
|
|
2902
|
+
point.cp2y,
|
|
2903
|
+
point.x,
|
|
2904
|
+
point.y
|
|
2905
|
+
);
|
|
2906
|
+
break;
|
|
2907
|
+
case "quadratic":
|
|
2908
|
+
ctx.quadraticCurveTo(point.cpx, point.cpy, point.x, point.y);
|
|
2909
|
+
break;
|
|
2910
|
+
case "arc":
|
|
2911
|
+
ctx.arcTo(point.x1, point.y1, point.x2, point.y2, point.radius);
|
|
2912
|
+
break;
|
|
1426
2913
|
}
|
|
1427
2914
|
}
|
|
2915
|
+
if (close2 && points2.length > 1) {
|
|
2916
|
+
ctx.closePath();
|
|
2917
|
+
}
|
|
1428
2918
|
};
|
|
1429
2919
|
ctx.beginPath();
|
|
1430
2920
|
drawPath(points, close);
|
|
@@ -1448,6 +2938,9 @@ var KlintFunctions = {
|
|
|
1448
2938
|
addColorStop: () => (gradient, offset = 0, color = "#000") => {
|
|
1449
2939
|
return gradient.addColorStop(offset, color);
|
|
1450
2940
|
},
|
|
2941
|
+
PI: () => Math.PI,
|
|
2942
|
+
TWO_PI: () => Math.PI * 2,
|
|
2943
|
+
TAU: () => Math.PI * 2,
|
|
1451
2944
|
constrain: () => (val, floor, ceil) => {
|
|
1452
2945
|
return Math.max(floor, Math.min(val, ceil));
|
|
1453
2946
|
},
|
|
@@ -1485,6 +2978,14 @@ var KlintFunctions = {
|
|
|
1485
2978
|
const t = (n - A) / (B - A);
|
|
1486
2979
|
return ctx.lerp(C, D, t, bounded);
|
|
1487
2980
|
},
|
|
2981
|
+
bezierLerp: () => (a, b, c, d, t) => {
|
|
2982
|
+
const u = 1 - t;
|
|
2983
|
+
return u * u * u * a + 3 * u * u * t * b + 3 * u * t * t * c + t * t * t * d;
|
|
2984
|
+
},
|
|
2985
|
+
bezierTangent: () => (a, b, c, d, t) => {
|
|
2986
|
+
const u = 1 - t;
|
|
2987
|
+
return 3 * d * t * t - 3 * c * t * t + 6 * c * u * t - 6 * b * u * t + 3 * b * u * u - 3 * a * u * u;
|
|
2988
|
+
},
|
|
1488
2989
|
textFont: (ctx) => (font) => {
|
|
1489
2990
|
ctx.__textFont = font;
|
|
1490
2991
|
},
|
|
@@ -1520,7 +3021,8 @@ var KlintFunctions = {
|
|
|
1520
3021
|
ctx.__textAlignment.vertical = vertical ?? ctx.__textAlignment.vertical;
|
|
1521
3022
|
},
|
|
1522
3023
|
textLeading: (ctx) => (spacing) => {
|
|
1523
|
-
ctx.
|
|
3024
|
+
ctx.__textLeading = spacing;
|
|
3025
|
+
return ctx.__textLeading;
|
|
1524
3026
|
},
|
|
1525
3027
|
computeFont: (ctx) => () => {
|
|
1526
3028
|
ctx.computeTextStyle();
|
|
@@ -1539,10 +3041,110 @@ var KlintFunctions = {
|
|
|
1539
3041
|
if (ctx.textBaseline !== ctx.__textAlignment.vertical) {
|
|
1540
3042
|
ctx.textBaseline = ctx.__textAlignment.vertical;
|
|
1541
3043
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
ctx.
|
|
3044
|
+
const textString = String(text);
|
|
3045
|
+
if (textString.includes("\n")) {
|
|
3046
|
+
const lines = textString.split("\n");
|
|
3047
|
+
const firstLineMetrics = ctx.measureText(lines[0] || "M");
|
|
3048
|
+
const defaultLineHeight = firstLineMetrics.actualBoundingBoxAscent + firstLineMetrics.actualBoundingBoxDescent;
|
|
3049
|
+
const lineHeight = ctx.__textLeading ?? ctx.__textSize * 1.2;
|
|
3050
|
+
const totalHeight = lines.length * lineHeight - (lineHeight - defaultLineHeight);
|
|
3051
|
+
let startY = y;
|
|
3052
|
+
if (ctx.__textAlignment.vertical === "middle") {
|
|
3053
|
+
startY = y - totalHeight / 2 + defaultLineHeight / 2;
|
|
3054
|
+
} else if (ctx.__textAlignment.vertical === "bottom") {
|
|
3055
|
+
startY = y - totalHeight + defaultLineHeight;
|
|
3056
|
+
} else if (ctx.__textAlignment.vertical === "top") {
|
|
3057
|
+
startY = y + defaultLineHeight / 2;
|
|
3058
|
+
}
|
|
3059
|
+
lines.forEach((line, index) => {
|
|
3060
|
+
const lineY = startY + index * lineHeight;
|
|
3061
|
+
if (ctx.checkTransparency("fill"))
|
|
3062
|
+
ctx.fillText(line, x, lineY, maxWidth);
|
|
3063
|
+
if (ctx.checkTransparency("stroke"))
|
|
3064
|
+
ctx.strokeText(line, x, lineY, maxWidth);
|
|
3065
|
+
});
|
|
3066
|
+
} else {
|
|
3067
|
+
if (ctx.checkTransparency("fill"))
|
|
3068
|
+
ctx.fillText(textString, x, y, maxWidth);
|
|
3069
|
+
if (ctx.checkTransparency("stroke"))
|
|
3070
|
+
ctx.strokeText(textString, x, y, maxWidth);
|
|
3071
|
+
}
|
|
3072
|
+
},
|
|
3073
|
+
paragraph: (ctx) => (text, x, y, width, options) => {
|
|
3074
|
+
if (text === void 0) return;
|
|
3075
|
+
ctx.computeFont();
|
|
3076
|
+
const textString = String(text);
|
|
3077
|
+
const justification = options?.justification || "left";
|
|
3078
|
+
const overflow = options?.overflow || 0;
|
|
3079
|
+
const breakMode = options?.break || "words";
|
|
3080
|
+
const originalAlign = ctx.textAlign;
|
|
3081
|
+
const originalBaseline = ctx.textBaseline;
|
|
3082
|
+
if (justification === "center") {
|
|
3083
|
+
ctx.textAlign = "center";
|
|
3084
|
+
} else if (justification === "right") {
|
|
3085
|
+
ctx.textAlign = "right";
|
|
3086
|
+
} else {
|
|
3087
|
+
ctx.textAlign = "left";
|
|
3088
|
+
}
|
|
3089
|
+
ctx.textBaseline = "top";
|
|
3090
|
+
const tokens = breakMode === "letters" ? textString.split("") : textString.split(/\s+/);
|
|
3091
|
+
const lines = [];
|
|
3092
|
+
let currentLine = "";
|
|
3093
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
3094
|
+
const token = tokens[i];
|
|
3095
|
+
const testLine = currentLine ? breakMode === "letters" ? currentLine + token : currentLine + " " + token : token;
|
|
3096
|
+
const metrics = ctx.measureText(testLine);
|
|
3097
|
+
if (metrics.width > width && currentLine !== "") {
|
|
3098
|
+
lines.push(currentLine);
|
|
3099
|
+
currentLine = token;
|
|
3100
|
+
} else {
|
|
3101
|
+
currentLine = testLine;
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
if (currentLine) {
|
|
3105
|
+
lines.push(currentLine);
|
|
3106
|
+
}
|
|
3107
|
+
const lineHeight = ctx.__textLeading ?? ctx.__textSize * 1.2;
|
|
3108
|
+
let linesToDraw = lines;
|
|
3109
|
+
if (overflow > 0 && lines.length * lineHeight > overflow) {
|
|
3110
|
+
const maxLines = Math.floor(overflow / lineHeight);
|
|
3111
|
+
linesToDraw = lines.slice(0, maxLines);
|
|
3112
|
+
}
|
|
3113
|
+
linesToDraw.forEach((line, index) => {
|
|
3114
|
+
const lineY = y + index * lineHeight;
|
|
3115
|
+
let lineX = x;
|
|
3116
|
+
if (justification === "center") {
|
|
3117
|
+
lineX = x + width / 2;
|
|
3118
|
+
} else if (justification === "right") {
|
|
3119
|
+
lineX = x + width;
|
|
3120
|
+
} else if (justification === "justified" && index < linesToDraw.length - 1) {
|
|
3121
|
+
const words = line.split(/\s+/);
|
|
3122
|
+
if (words.length > 1) {
|
|
3123
|
+
const textWidth = ctx.measureText(words.join("")).width;
|
|
3124
|
+
const totalSpaceWidth = width - textWidth;
|
|
3125
|
+
const spaceWidth = totalSpaceWidth / (words.length - 1);
|
|
3126
|
+
let currentX = x;
|
|
3127
|
+
words.forEach((word, wordIndex) => {
|
|
3128
|
+
if (ctx.checkTransparency("fill")) {
|
|
3129
|
+
ctx.fillText(word, currentX, lineY);
|
|
3130
|
+
}
|
|
3131
|
+
if (ctx.checkTransparency("stroke")) {
|
|
3132
|
+
ctx.strokeText(word, currentX, lineY);
|
|
3133
|
+
}
|
|
3134
|
+
currentX += ctx.measureText(word).width + spaceWidth;
|
|
3135
|
+
});
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
if (ctx.checkTransparency("fill")) {
|
|
3140
|
+
ctx.fillText(line, lineX, lineY);
|
|
3141
|
+
}
|
|
3142
|
+
if (ctx.checkTransparency("stroke")) {
|
|
3143
|
+
ctx.strokeText(line, lineX, lineY);
|
|
3144
|
+
}
|
|
3145
|
+
});
|
|
3146
|
+
ctx.textAlign = originalAlign;
|
|
3147
|
+
ctx.textBaseline = originalBaseline;
|
|
1546
3148
|
},
|
|
1547
3149
|
// DO NOT use putImageData for images you can draw : https://www.measurethat.net/Benchmarks/Show/9510/0/putimagedata-vs-drawimage
|
|
1548
3150
|
image: (ctx) => (image, x, y, arg3, arg4, arg5, arg6, arg7, arg8) => {
|
|
@@ -1592,11 +3194,8 @@ var KlintFunctions = {
|
|
|
1592
3194
|
return ctx.getImageData(0, 0, ctx.width, ctx.height);
|
|
1593
3195
|
},
|
|
1594
3196
|
updatePixels: (ctx) => (pixels) => {
|
|
1595
|
-
const
|
|
1596
|
-
|
|
1597
|
-
ctx.width,
|
|
1598
|
-
ctx.height
|
|
1599
|
-
);
|
|
3197
|
+
const pixelArray = pixels instanceof Uint8ClampedArray ? new Uint8ClampedArray(pixels) : new Uint8ClampedArray(pixels);
|
|
3198
|
+
const imageData = new ImageData(pixelArray, ctx.width, ctx.height);
|
|
1600
3199
|
ctx.putImageData(imageData, 0, 0);
|
|
1601
3200
|
},
|
|
1602
3201
|
readPixels: (ctx) => (x, y, w = 1, h = 1) => {
|
|
@@ -1612,7 +3211,7 @@ var KlintFunctions = {
|
|
|
1612
3211
|
ctx.globalAlpha = ctx.constrain(value, 0, 1);
|
|
1613
3212
|
},
|
|
1614
3213
|
blend: (ctx) => (blend) => {
|
|
1615
|
-
ctx.globalCompositeOperation = blend;
|
|
3214
|
+
ctx.globalCompositeOperation = blend === "default" ? "source-over" : blend;
|
|
1616
3215
|
},
|
|
1617
3216
|
setCanvasOrigin: (ctx) => (type) => {
|
|
1618
3217
|
ctx.__canvasOrigin = type;
|
|
@@ -1650,6 +3249,68 @@ var KlintFunctions = {
|
|
|
1650
3249
|
if (ctx.__canvasOrigin === "center") {
|
|
1651
3250
|
ctx.translate(ctx.width * 0.5, ctx.height * 0.5);
|
|
1652
3251
|
}
|
|
3252
|
+
},
|
|
3253
|
+
clipTo: (ctx) => (callback, fillRule) => {
|
|
3254
|
+
const originalFill = ctx.fill;
|
|
3255
|
+
const originalStroke = ctx.stroke;
|
|
3256
|
+
const originalDrawIfVisible = ctx.drawIfVisible;
|
|
3257
|
+
const originalBeginPath = ctx.beginPath;
|
|
3258
|
+
ctx.fill = () => {
|
|
3259
|
+
};
|
|
3260
|
+
ctx.stroke = () => {
|
|
3261
|
+
};
|
|
3262
|
+
ctx.drawIfVisible = () => {
|
|
3263
|
+
};
|
|
3264
|
+
let allowBeginPath = true;
|
|
3265
|
+
ctx.beginPath = () => {
|
|
3266
|
+
if (allowBeginPath) {
|
|
3267
|
+
originalBeginPath.call(ctx);
|
|
3268
|
+
allowBeginPath = false;
|
|
3269
|
+
}
|
|
3270
|
+
};
|
|
3271
|
+
ctx.beginPath();
|
|
3272
|
+
callback(ctx);
|
|
3273
|
+
ctx.clip(fillRule || ctx.__fillRule || "nonzero");
|
|
3274
|
+
ctx.beginPath = originalBeginPath;
|
|
3275
|
+
ctx.drawIfVisible = originalDrawIfVisible;
|
|
3276
|
+
ctx.fill = originalFill;
|
|
3277
|
+
ctx.stroke = originalStroke;
|
|
3278
|
+
},
|
|
3279
|
+
canIuseFilter: (ctx) => () => {
|
|
3280
|
+
return ctx.filter !== void 0 && ctx.filter !== null;
|
|
3281
|
+
},
|
|
3282
|
+
blur: (ctx) => (radius) => {
|
|
3283
|
+
if (ctx.filter === void 0 || ctx.filter === null) return;
|
|
3284
|
+
ctx.filter = `blur(${radius}px)`;
|
|
3285
|
+
},
|
|
3286
|
+
SVGfilter: (ctx) => (url) => {
|
|
3287
|
+
if (ctx.filter === void 0 || ctx.filter === null) return;
|
|
3288
|
+
if (!url || !url.startsWith("url(")) return;
|
|
3289
|
+
ctx.filter = url;
|
|
3290
|
+
},
|
|
3291
|
+
dropShadow: (ctx) => (offsetX, offsetY, blurRadius, color) => {
|
|
3292
|
+
if (ctx.filter === void 0 || ctx.filter === null) return;
|
|
3293
|
+
ctx.filter = `drop-shadow(${offsetX}px ${offsetY}px ${blurRadius}px ${color})`;
|
|
3294
|
+
},
|
|
3295
|
+
grayscale: (ctx) => (amount) => {
|
|
3296
|
+
if (ctx.filter === void 0 || ctx.filter === null) return;
|
|
3297
|
+
const value = ctx.constrain(amount, 0, 1);
|
|
3298
|
+
ctx.filter = `grayscale(${value})`;
|
|
3299
|
+
},
|
|
3300
|
+
hue: (ctx) => (angle) => {
|
|
3301
|
+
if (ctx.filter === void 0 || ctx.filter === null) return;
|
|
3302
|
+
const degrees = angle * 180 / Math.PI;
|
|
3303
|
+
ctx.filter = `hue-rotate(${degrees}deg)`;
|
|
3304
|
+
},
|
|
3305
|
+
invert: (ctx) => (amount) => {
|
|
3306
|
+
if (ctx.filter === void 0 || ctx.filter === null) return;
|
|
3307
|
+
const value = ctx.constrain(amount, 0, 1);
|
|
3308
|
+
ctx.filter = `invert(${value})`;
|
|
3309
|
+
},
|
|
3310
|
+
filterOpacity: (ctx) => (value) => {
|
|
3311
|
+
if (ctx.filter === void 0 || ctx.filter === null) return;
|
|
3312
|
+
const amount = ctx.constrain(value, 0, 1);
|
|
3313
|
+
ctx.filter = `opacity(${amount})`;
|
|
1653
3314
|
}
|
|
1654
3315
|
};
|
|
1655
3316
|
|
|
@@ -1687,13 +3348,50 @@ var DEFAULT_GESTURE_STATE = {
|
|
|
1687
3348
|
lastX: 0,
|
|
1688
3349
|
lastY: 0
|
|
1689
3350
|
};
|
|
3351
|
+
var DEFAULT_KEYBOARD_STATE = {
|
|
3352
|
+
pressedKeys: /* @__PURE__ */ new Set(),
|
|
3353
|
+
modifiers: {
|
|
3354
|
+
alt: false,
|
|
3355
|
+
shift: false,
|
|
3356
|
+
ctrl: false,
|
|
3357
|
+
meta: false
|
|
3358
|
+
},
|
|
3359
|
+
lastKey: null,
|
|
3360
|
+
lastKeyTime: 0
|
|
3361
|
+
};
|
|
1690
3362
|
function useKlint() {
|
|
1691
3363
|
const contextRef = useRef2(null);
|
|
1692
3364
|
const mouseRef = useRef2(null);
|
|
1693
3365
|
const scrollRef = useRef2(null);
|
|
1694
3366
|
const gestureRef = useRef2(null);
|
|
3367
|
+
const keyboardRef = useRef2(null);
|
|
1695
3368
|
const useDev = () => {
|
|
1696
|
-
|
|
3369
|
+
useEffect2(() => {
|
|
3370
|
+
if (process.env.NODE_ENV === "development") {
|
|
3371
|
+
if (typeof import.meta !== "undefined" && import.meta.hot) {
|
|
3372
|
+
console.log("[Klint] hot updated - clearing non-context state");
|
|
3373
|
+
if (contextRef.current) {
|
|
3374
|
+
const ctx = contextRef.current;
|
|
3375
|
+
ctx.frame = 0;
|
|
3376
|
+
ctx.time = 0;
|
|
3377
|
+
ctx.__offscreens?.clear();
|
|
3378
|
+
ctx.__startedShape = false;
|
|
3379
|
+
ctx.__currentShape = null;
|
|
3380
|
+
ctx.__startedContour = false;
|
|
3381
|
+
ctx.__currentContours = null;
|
|
3382
|
+
ctx.__currentContour = null;
|
|
3383
|
+
ctx.__isReadyToDraw = true;
|
|
3384
|
+
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
}, []);
|
|
3389
|
+
useEffect2(() => {
|
|
3390
|
+
if (process.env.NODE_ENV === "development" && contextRef.current) {
|
|
3391
|
+
console.log("[Klint] hot updated - clearing context state");
|
|
3392
|
+
contextRef.current.__isReadyToDraw = true;
|
|
3393
|
+
}
|
|
3394
|
+
});
|
|
1697
3395
|
};
|
|
1698
3396
|
const KlintImage = () => {
|
|
1699
3397
|
const imagesRef = useRef2(/* @__PURE__ */ new Map());
|
|
@@ -1767,6 +3465,8 @@ function useKlint() {
|
|
|
1767
3465
|
if (!contextRef.current?.canvas) return;
|
|
1768
3466
|
const canvas = contextRef.current.canvas;
|
|
1769
3467
|
const ctx = contextRef.current;
|
|
3468
|
+
const controller = new AbortController();
|
|
3469
|
+
const { signal } = controller;
|
|
1770
3470
|
const updateMousePosition = (e) => {
|
|
1771
3471
|
const rect = canvas.getBoundingClientRect();
|
|
1772
3472
|
const dpr = window.devicePixelRatio || 1;
|
|
@@ -1805,20 +3505,13 @@ function useKlint() {
|
|
|
1805
3505
|
const handleClick = (e) => {
|
|
1806
3506
|
if (clickCallbackRef.current) clickCallbackRef.current(ctx, e);
|
|
1807
3507
|
};
|
|
1808
|
-
canvas.addEventListener("mousemove", updateMousePosition);
|
|
1809
|
-
canvas.addEventListener("mousedown", handleMouseDown);
|
|
1810
|
-
canvas.addEventListener("mouseup", handleMouseUp);
|
|
1811
|
-
canvas.addEventListener("mouseenter", handleMouseEnter);
|
|
1812
|
-
canvas.addEventListener("mouseleave", handleMouseLeave);
|
|
1813
|
-
canvas.addEventListener("click", handleClick);
|
|
1814
|
-
return () =>
|
|
1815
|
-
canvas.removeEventListener("mousemove", updateMousePosition);
|
|
1816
|
-
canvas.removeEventListener("mousedown", handleMouseDown);
|
|
1817
|
-
canvas.removeEventListener("mouseup", handleMouseUp);
|
|
1818
|
-
canvas.removeEventListener("mouseenter", handleMouseEnter);
|
|
1819
|
-
canvas.removeEventListener("mouseleave", handleMouseLeave);
|
|
1820
|
-
canvas.removeEventListener("click", handleClick);
|
|
1821
|
-
};
|
|
3508
|
+
canvas.addEventListener("mousemove", updateMousePosition, { signal });
|
|
3509
|
+
canvas.addEventListener("mousedown", handleMouseDown, { signal });
|
|
3510
|
+
canvas.addEventListener("mouseup", handleMouseUp, { signal });
|
|
3511
|
+
canvas.addEventListener("mouseenter", handleMouseEnter, { signal });
|
|
3512
|
+
canvas.addEventListener("mouseleave", handleMouseLeave, { signal });
|
|
3513
|
+
canvas.addEventListener("click", handleClick, { signal });
|
|
3514
|
+
return () => controller.abort();
|
|
1822
3515
|
});
|
|
1823
3516
|
return {
|
|
1824
3517
|
mouse: mouseRef.current,
|
|
@@ -1838,6 +3531,8 @@ function useKlint() {
|
|
|
1838
3531
|
if (!contextRef.current?.canvas) return;
|
|
1839
3532
|
const canvas = contextRef.current.canvas;
|
|
1840
3533
|
const ctx = contextRef.current;
|
|
3534
|
+
const controller = new AbortController();
|
|
3535
|
+
const { signal } = controller;
|
|
1841
3536
|
const handleScroll = (e) => {
|
|
1842
3537
|
e.preventDefault();
|
|
1843
3538
|
if (!scrollRef.current) return;
|
|
@@ -1850,8 +3545,8 @@ function useKlint() {
|
|
|
1850
3545
|
scrollCallbackRef.current(ctx, scrollRef.current, e);
|
|
1851
3546
|
}
|
|
1852
3547
|
};
|
|
1853
|
-
canvas.addEventListener("wheel", handleScroll);
|
|
1854
|
-
return () =>
|
|
3548
|
+
canvas.addEventListener("wheel", handleScroll, { signal });
|
|
3549
|
+
return () => controller.abort();
|
|
1855
3550
|
});
|
|
1856
3551
|
return {
|
|
1857
3552
|
scroll: scrollRef.current,
|
|
@@ -1997,18 +3692,19 @@ function useKlint() {
|
|
|
1997
3692
|
touchEndCallbackRef.current(ctx, e, gestureRef.current);
|
|
1998
3693
|
}
|
|
1999
3694
|
};
|
|
3695
|
+
const controller = new AbortController();
|
|
3696
|
+
const { signal } = controller;
|
|
2000
3697
|
canvas.addEventListener("touchstart", handleTouchStart, {
|
|
2001
|
-
passive: false
|
|
3698
|
+
passive: false,
|
|
3699
|
+
signal
|
|
2002
3700
|
});
|
|
2003
|
-
canvas.addEventListener("touchmove", handleTouchMove, {
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
canvas.removeEventListener("touchcancel", handleTouchCancel);
|
|
2011
|
-
};
|
|
3701
|
+
canvas.addEventListener("touchmove", handleTouchMove, {
|
|
3702
|
+
passive: false,
|
|
3703
|
+
signal
|
|
3704
|
+
});
|
|
3705
|
+
canvas.addEventListener("touchend", handleTouchEnd, { signal });
|
|
3706
|
+
canvas.addEventListener("touchcancel", handleTouchCancel, { signal });
|
|
3707
|
+
return () => controller.abort();
|
|
2012
3708
|
}, []);
|
|
2013
3709
|
return {
|
|
2014
3710
|
gesture: gestureRef.current,
|
|
@@ -2021,6 +3717,315 @@ function useKlint() {
|
|
|
2021
3717
|
onTouchEnd: (callback) => touchEndCallbackRef.current = callback
|
|
2022
3718
|
};
|
|
2023
3719
|
};
|
|
3720
|
+
const KlintKeyboard = () => {
|
|
3721
|
+
if (!keyboardRef.current) {
|
|
3722
|
+
keyboardRef.current = { ...DEFAULT_KEYBOARD_STATE };
|
|
3723
|
+
}
|
|
3724
|
+
const keyPressedCallbackRef = useRef2(/* @__PURE__ */ new Map());
|
|
3725
|
+
const keyReleasedCallbackRef = useRef2(/* @__PURE__ */ new Map());
|
|
3726
|
+
const keyComboCallbackRef = useRef2(/* @__PURE__ */ new Map());
|
|
3727
|
+
useEffect2(() => {
|
|
3728
|
+
if (!contextRef.current) return;
|
|
3729
|
+
const ctx = contextRef.current;
|
|
3730
|
+
const controller = new AbortController();
|
|
3731
|
+
const { signal } = controller;
|
|
3732
|
+
const updateModifiers = (e) => {
|
|
3733
|
+
if (!keyboardRef.current) return;
|
|
3734
|
+
keyboardRef.current.modifiers.alt = e.altKey;
|
|
3735
|
+
keyboardRef.current.modifiers.shift = e.shiftKey;
|
|
3736
|
+
keyboardRef.current.modifiers.ctrl = e.ctrlKey;
|
|
3737
|
+
keyboardRef.current.modifiers.meta = e.metaKey;
|
|
3738
|
+
};
|
|
3739
|
+
const normalizeKey2 = (key) => {
|
|
3740
|
+
const keyMap = {
|
|
3741
|
+
" ": "Space",
|
|
3742
|
+
Control: "Ctrl",
|
|
3743
|
+
Escape: "Esc"
|
|
3744
|
+
};
|
|
3745
|
+
return keyMap[key] || key;
|
|
3746
|
+
};
|
|
3747
|
+
const handleKeyDown = (e) => {
|
|
3748
|
+
if (!keyboardRef.current) return;
|
|
3749
|
+
const normalizedKey = normalizeKey2(e.key);
|
|
3750
|
+
keyboardRef.current.pressedKeys.add(normalizedKey);
|
|
3751
|
+
keyboardRef.current.lastKey = normalizedKey;
|
|
3752
|
+
keyboardRef.current.lastKeyTime = performance.now();
|
|
3753
|
+
updateModifiers(e);
|
|
3754
|
+
const keyCallback = keyPressedCallbackRef.current.get(normalizedKey);
|
|
3755
|
+
if (keyCallback) {
|
|
3756
|
+
keyCallback(ctx, e);
|
|
3757
|
+
}
|
|
3758
|
+
const pressedKeysArray = Array.from(
|
|
3759
|
+
keyboardRef.current.pressedKeys
|
|
3760
|
+
).sort();
|
|
3761
|
+
const comboKey = pressedKeysArray.join("+");
|
|
3762
|
+
const comboCallback = keyComboCallbackRef.current.get(comboKey);
|
|
3763
|
+
if (comboCallback) {
|
|
3764
|
+
comboCallback(ctx, e);
|
|
3765
|
+
}
|
|
3766
|
+
};
|
|
3767
|
+
const handleKeyUp = (e) => {
|
|
3768
|
+
if (!keyboardRef.current) return;
|
|
3769
|
+
const normalizedKey = normalizeKey2(e.key);
|
|
3770
|
+
keyboardRef.current.pressedKeys.delete(normalizedKey);
|
|
3771
|
+
updateModifiers(e);
|
|
3772
|
+
const keyCallback = keyReleasedCallbackRef.current.get(normalizedKey);
|
|
3773
|
+
if (keyCallback) {
|
|
3774
|
+
keyCallback(ctx, e);
|
|
3775
|
+
}
|
|
3776
|
+
};
|
|
3777
|
+
window.addEventListener("keydown", handleKeyDown, { signal });
|
|
3778
|
+
window.addEventListener("keyup", handleKeyUp, { signal });
|
|
3779
|
+
return () => controller.abort();
|
|
3780
|
+
}, []);
|
|
3781
|
+
const createComboKey = (keys) => {
|
|
3782
|
+
return keys.map(normalizeKey).sort().join("+");
|
|
3783
|
+
};
|
|
3784
|
+
const normalizeKey = (key) => {
|
|
3785
|
+
const keyMap = {
|
|
3786
|
+
" ": "Space",
|
|
3787
|
+
Control: "Ctrl",
|
|
3788
|
+
Escape: "Esc"
|
|
3789
|
+
};
|
|
3790
|
+
return keyMap[key] || key;
|
|
3791
|
+
};
|
|
3792
|
+
return {
|
|
3793
|
+
keyboard: keyboardRef.current,
|
|
3794
|
+
// Register callback for single key press
|
|
3795
|
+
keyPressed: (key, callback) => {
|
|
3796
|
+
keyPressedCallbackRef.current.set(normalizeKey(key), callback);
|
|
3797
|
+
},
|
|
3798
|
+
// Register callback for single key release
|
|
3799
|
+
keyReleased: (key, callback) => {
|
|
3800
|
+
keyReleasedCallbackRef.current.set(normalizeKey(key), callback);
|
|
3801
|
+
},
|
|
3802
|
+
// Register callback for key combination (e.g., ['Alt', 'Shift'])
|
|
3803
|
+
keyCombo: (keys, callback) => {
|
|
3804
|
+
const comboKey = createComboKey(keys);
|
|
3805
|
+
keyComboCallbackRef.current.set(comboKey, callback);
|
|
3806
|
+
},
|
|
3807
|
+
// Utility functions
|
|
3808
|
+
isPressed: (key) => {
|
|
3809
|
+
return keyboardRef.current?.pressedKeys.has(normalizeKey(key)) || false;
|
|
3810
|
+
},
|
|
3811
|
+
arePressed: (keys) => {
|
|
3812
|
+
if (!keyboardRef.current) return false;
|
|
3813
|
+
return keys.every(
|
|
3814
|
+
(key) => keyboardRef.current.pressedKeys.has(normalizeKey(key))
|
|
3815
|
+
);
|
|
3816
|
+
},
|
|
3817
|
+
// Clear all callbacks
|
|
3818
|
+
clearCallbacks: () => {
|
|
3819
|
+
keyPressedCallbackRef.current.clear();
|
|
3820
|
+
keyReleasedCallbackRef.current.clear();
|
|
3821
|
+
keyComboCallbackRef.current.clear();
|
|
3822
|
+
}
|
|
3823
|
+
};
|
|
3824
|
+
};
|
|
3825
|
+
const KlintTimeline = () => {
|
|
3826
|
+
const timelinesRef = useRef2(/* @__PURE__ */ new Map());
|
|
3827
|
+
const callbacksRef = useRef2({ start: [], end: [], loop: [] });
|
|
3828
|
+
const Timeline = {
|
|
3829
|
+
create: (setup, options = {}) => {
|
|
3830
|
+
const tracks = /* @__PURE__ */ new Map();
|
|
3831
|
+
const parents = /* @__PURE__ */ new Set();
|
|
3832
|
+
let currentProgress = 0;
|
|
3833
|
+
let hasStarted = false;
|
|
3834
|
+
let hasEnded = false;
|
|
3835
|
+
const defaultEasing = options.defaultEasing || ((t) => t);
|
|
3836
|
+
const defaultLoop = options.defaultLoop || 0;
|
|
3837
|
+
const executeCallback = (callback, errorMsg = "Callback error") => {
|
|
3838
|
+
try {
|
|
3839
|
+
callback();
|
|
3840
|
+
} catch (e) {
|
|
3841
|
+
console.warn(`${errorMsg}:`, e);
|
|
3842
|
+
}
|
|
3843
|
+
};
|
|
3844
|
+
function createKeyframes() {
|
|
3845
|
+
const segments = [];
|
|
3846
|
+
let currentPos = 0;
|
|
3847
|
+
let loopCount = defaultLoop;
|
|
3848
|
+
let parentTrack = void 0;
|
|
3849
|
+
const parseEasingCallback = (easing, callback) => {
|
|
3850
|
+
if (typeof easing === "function" && typeof callback === "undefined") {
|
|
3851
|
+
if (easing.length === 0) {
|
|
3852
|
+
return {
|
|
3853
|
+
easing: defaultEasing,
|
|
3854
|
+
callback: easing
|
|
3855
|
+
};
|
|
3856
|
+
}
|
|
3857
|
+
return {
|
|
3858
|
+
easing,
|
|
3859
|
+
callback: void 0
|
|
3860
|
+
};
|
|
3861
|
+
}
|
|
3862
|
+
return {
|
|
3863
|
+
easing: easing || defaultEasing,
|
|
3864
|
+
callback
|
|
3865
|
+
};
|
|
3866
|
+
};
|
|
3867
|
+
const builder = {
|
|
3868
|
+
start(value, delay = 0, callback) {
|
|
3869
|
+
segments.push({ pos: delay, value, callback, type: "start" });
|
|
3870
|
+
currentPos = delay;
|
|
3871
|
+
return builder;
|
|
3872
|
+
},
|
|
3873
|
+
at(progress, value, easing, callback) {
|
|
3874
|
+
const { easing: finalEasing, callback: finalCallback } = parseEasingCallback(easing, callback);
|
|
3875
|
+
segments.push({
|
|
3876
|
+
pos: progress,
|
|
3877
|
+
value,
|
|
3878
|
+
easing: finalEasing,
|
|
3879
|
+
callback: finalCallback,
|
|
3880
|
+
type: "tween"
|
|
3881
|
+
});
|
|
3882
|
+
currentPos = progress;
|
|
3883
|
+
return builder;
|
|
3884
|
+
},
|
|
3885
|
+
then(value, duration, easing, callback) {
|
|
3886
|
+
const { easing: finalEasing, callback: finalCallback } = parseEasingCallback(easing, callback);
|
|
3887
|
+
const nextPos = currentPos + duration;
|
|
3888
|
+
segments.push({
|
|
3889
|
+
pos: nextPos,
|
|
3890
|
+
value,
|
|
3891
|
+
easing: finalEasing,
|
|
3892
|
+
callback: finalCallback,
|
|
3893
|
+
type: "tween"
|
|
3894
|
+
});
|
|
3895
|
+
currentPos = nextPos;
|
|
3896
|
+
return builder;
|
|
3897
|
+
},
|
|
3898
|
+
loop(count = Infinity) {
|
|
3899
|
+
loopCount = count;
|
|
3900
|
+
return builder;
|
|
3901
|
+
},
|
|
3902
|
+
_compile: () => {
|
|
3903
|
+
segments.sort((a, b) => a.pos - b.pos);
|
|
3904
|
+
return { segments, loopCount, parentTrack };
|
|
3905
|
+
}
|
|
3906
|
+
};
|
|
3907
|
+
return builder;
|
|
3908
|
+
}
|
|
3909
|
+
function interpolateTrack(compiled, progress) {
|
|
3910
|
+
const { segments } = compiled;
|
|
3911
|
+
if (!segments.length) return 0;
|
|
3912
|
+
progress = Math.max(0, Math.min(1, progress));
|
|
3913
|
+
const valueSegments = segments.filter(
|
|
3914
|
+
(seg) => seg.type !== "callback"
|
|
3915
|
+
);
|
|
3916
|
+
if (!valueSegments.length) return 0;
|
|
3917
|
+
let prevSeg = valueSegments[0];
|
|
3918
|
+
for (let i = 1; i < valueSegments.length; i++) {
|
|
3919
|
+
const seg = valueSegments[i];
|
|
3920
|
+
if (progress <= seg.pos) {
|
|
3921
|
+
if (seg.type === "tween" && prevSeg.value !== void 0 && seg.value !== void 0) {
|
|
3922
|
+
const t = (progress - prevSeg.pos) / (seg.pos - prevSeg.pos);
|
|
3923
|
+
const easedT = seg.easing ? seg.easing(t) : t;
|
|
3924
|
+
return prevSeg.value + (seg.value - prevSeg.value) * easedT;
|
|
3925
|
+
}
|
|
3926
|
+
return seg.value || 0;
|
|
3927
|
+
}
|
|
3928
|
+
if (seg.value !== void 0) {
|
|
3929
|
+
prevSeg = seg;
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
return prevSeg.value || 0;
|
|
3933
|
+
}
|
|
3934
|
+
const timeline = {
|
|
3935
|
+
track: (keyframesOrFn) => {
|
|
3936
|
+
const compiled = typeof keyframesOrFn === "function" ? timeline.keyframes(keyframesOrFn) : keyframesOrFn;
|
|
3937
|
+
const track = {
|
|
3938
|
+
...compiled,
|
|
3939
|
+
currentValue: 0,
|
|
3940
|
+
getValue: (progress) => interpolateTrack(compiled, progress),
|
|
3941
|
+
get current() {
|
|
3942
|
+
return this.currentValue;
|
|
3943
|
+
},
|
|
3944
|
+
value: function() {
|
|
3945
|
+
return this.currentValue;
|
|
3946
|
+
}
|
|
3947
|
+
};
|
|
3948
|
+
tracks.set(track, compiled);
|
|
3949
|
+
return track;
|
|
3950
|
+
},
|
|
3951
|
+
keyframes: (fn) => {
|
|
3952
|
+
const kf = createKeyframes();
|
|
3953
|
+
fn(kf);
|
|
3954
|
+
return kf._compile();
|
|
3955
|
+
},
|
|
3956
|
+
stagger: (count, offset, keyframesFn) => {
|
|
3957
|
+
const compiled = timeline.keyframes(keyframesFn);
|
|
3958
|
+
return Array.from({ length: count }, (_, i) => {
|
|
3959
|
+
const track = {
|
|
3960
|
+
...compiled,
|
|
3961
|
+
currentValue: 0,
|
|
3962
|
+
staggerDelay: i * offset,
|
|
3963
|
+
getValue: (progress) => {
|
|
3964
|
+
const staggeredProgress = Math.max(0, progress - i * offset);
|
|
3965
|
+
const normalizedProgress = Math.min(
|
|
3966
|
+
1,
|
|
3967
|
+
staggeredProgress / (1 - i * offset)
|
|
3968
|
+
);
|
|
3969
|
+
return normalizedProgress > 0 ? interpolateTrack(compiled, normalizedProgress) : 0;
|
|
3970
|
+
},
|
|
3971
|
+
get current() {
|
|
3972
|
+
return this.currentValue;
|
|
3973
|
+
},
|
|
3974
|
+
value: function() {
|
|
3975
|
+
return this.currentValue;
|
|
3976
|
+
}
|
|
3977
|
+
};
|
|
3978
|
+
tracks.set(track, compiled);
|
|
3979
|
+
return track;
|
|
3980
|
+
});
|
|
3981
|
+
},
|
|
3982
|
+
update: (progress) => {
|
|
3983
|
+
const prevProgress = currentProgress;
|
|
3984
|
+
currentProgress = progress;
|
|
3985
|
+
if (!hasStarted && progress > 0) {
|
|
3986
|
+
hasStarted = true;
|
|
3987
|
+
callbacksRef.current.start.forEach(
|
|
3988
|
+
(cb) => executeCallback(cb, "Start callback error")
|
|
3989
|
+
);
|
|
3990
|
+
}
|
|
3991
|
+
if (!hasEnded && progress >= 1) {
|
|
3992
|
+
hasEnded = true;
|
|
3993
|
+
callbacksRef.current.end.forEach(
|
|
3994
|
+
(cb) => executeCallback(cb, "End callback error")
|
|
3995
|
+
);
|
|
3996
|
+
}
|
|
3997
|
+
if (progress < prevProgress) {
|
|
3998
|
+
if (progress === 0) {
|
|
3999
|
+
hasStarted = false;
|
|
4000
|
+
hasEnded = false;
|
|
4001
|
+
} else if (progress < 1) {
|
|
4002
|
+
hasEnded = false;
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
for (const [track, compiled] of tracks) {
|
|
4006
|
+
track.currentValue = track.getValue(progress);
|
|
4007
|
+
if (compiled.segments) {
|
|
4008
|
+
compiled.segments.forEach((seg) => {
|
|
4009
|
+
if (seg.callback && seg.pos <= progress && seg.pos > prevProgress) {
|
|
4010
|
+
executeCallback(seg.callback);
|
|
4011
|
+
}
|
|
4012
|
+
});
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
},
|
|
4016
|
+
progress: () => currentProgress
|
|
4017
|
+
};
|
|
4018
|
+
const result = setup(timeline);
|
|
4019
|
+
return { ...result, update: timeline.update };
|
|
4020
|
+
}
|
|
4021
|
+
};
|
|
4022
|
+
return {
|
|
4023
|
+
Timeline,
|
|
4024
|
+
onStart: (fn) => callbacksRef.current.start.push(fn),
|
|
4025
|
+
onEnd: (fn) => callbacksRef.current.end.push(fn),
|
|
4026
|
+
onLoop: (fn) => callbacksRef.current.loop.push(fn)
|
|
4027
|
+
};
|
|
4028
|
+
};
|
|
2024
4029
|
const KlintWindow = () => {
|
|
2025
4030
|
const resizeCallbackRef = useRef2(
|
|
2026
4031
|
null
|
|
@@ -2031,6 +4036,8 @@ function useKlint() {
|
|
|
2031
4036
|
useEffect2(() => {
|
|
2032
4037
|
if (!contextRef.current) return;
|
|
2033
4038
|
const ctx = contextRef.current;
|
|
4039
|
+
const controller = new AbortController();
|
|
4040
|
+
const { signal } = controller;
|
|
2034
4041
|
const handleResize = () => {
|
|
2035
4042
|
if (resizeCallbackRef.current) resizeCallbackRef.current(ctx);
|
|
2036
4043
|
};
|
|
@@ -2046,19 +4053,13 @@ function useKlint() {
|
|
|
2046
4053
|
visibilityChangeCallbackRef.current(ctx, isVisible);
|
|
2047
4054
|
}
|
|
2048
4055
|
};
|
|
2049
|
-
window.addEventListener("resize", handleResize);
|
|
2050
|
-
window.addEventListener("blur", handleBlur);
|
|
2051
|
-
window.addEventListener("focus", handleFocus);
|
|
2052
|
-
document.addEventListener("visibilitychange", handleVisibilityChange
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
window.removeEventListener("focus", handleFocus);
|
|
2057
|
-
document.removeEventListener(
|
|
2058
|
-
"visibilitychange",
|
|
2059
|
-
handleVisibilityChange
|
|
2060
|
-
);
|
|
2061
|
-
};
|
|
4056
|
+
window.addEventListener("resize", handleResize, { signal });
|
|
4057
|
+
window.addEventListener("blur", handleBlur, { signal });
|
|
4058
|
+
window.addEventListener("focus", handleFocus, { signal });
|
|
4059
|
+
document.addEventListener("visibilitychange", handleVisibilityChange, {
|
|
4060
|
+
signal
|
|
4061
|
+
});
|
|
4062
|
+
return () => controller.abort();
|
|
2062
4063
|
}, []);
|
|
2063
4064
|
return {
|
|
2064
4065
|
onResize: (callback) => resizeCallbackRef.current = callback,
|
|
@@ -2081,20 +4082,27 @@ function useKlint() {
|
|
|
2081
4082
|
context.__textWeight = "normal";
|
|
2082
4083
|
context.__textStyle = "normal";
|
|
2083
4084
|
context.__textSize = 72;
|
|
4085
|
+
context.__textLeading = void 0;
|
|
2084
4086
|
context.__textAlignment = {
|
|
2085
4087
|
horizontal: "left",
|
|
2086
4088
|
vertical: "top"
|
|
2087
4089
|
};
|
|
4090
|
+
context.__fillRule = "nonzero";
|
|
2088
4091
|
context.__offscreens = /* @__PURE__ */ new Map();
|
|
2089
4092
|
context.__isPlaying = true;
|
|
2090
4093
|
context.__currentContext = context;
|
|
2091
4094
|
context.Color = new Color_default();
|
|
2092
4095
|
context.createVector = (x = 0, y = 0) => new Vector_default(x, y);
|
|
2093
|
-
context.
|
|
2094
|
-
context.
|
|
2095
|
-
context.Time = new Time_default(context);
|
|
4096
|
+
context.Vector = new Vector_default();
|
|
4097
|
+
context.Easing = new Easing_default();
|
|
2096
4098
|
context.Text = new Text_default(context);
|
|
2097
4099
|
context.Thing = new Thing_default(context);
|
|
4100
|
+
context.Grid = new Grid_default(context);
|
|
4101
|
+
context.Strip = new Strip_default(context);
|
|
4102
|
+
context.Noise = new Noise_default(context);
|
|
4103
|
+
context.Hotspot = new Hotspot_default(context);
|
|
4104
|
+
context.Performance = new Performance_default(context);
|
|
4105
|
+
context.SSR = new SSR_default(context);
|
|
2098
4106
|
Object.entries(KlintCoreFunctions).forEach(([name, fn]) => {
|
|
2099
4107
|
context[name] = fn(context);
|
|
2100
4108
|
});
|
|
@@ -2125,6 +4133,30 @@ function useKlint() {
|
|
|
2125
4133
|
contextRef.current.__isPlaying = !contextRef.current.__isPlaying;
|
|
2126
4134
|
}
|
|
2127
4135
|
}, []);
|
|
4136
|
+
const KlintPerformance = () => {
|
|
4137
|
+
const metricsRef = useRef2(null);
|
|
4138
|
+
useEffect2(() => {
|
|
4139
|
+
if (!contextRef.current?.__performance) return;
|
|
4140
|
+
const updateMetrics = () => {
|
|
4141
|
+
if (contextRef.current?.__performance) {
|
|
4142
|
+
metricsRef.current = { ...contextRef.current.__performance };
|
|
4143
|
+
}
|
|
4144
|
+
};
|
|
4145
|
+
const interval = setInterval(updateMetrics, 100);
|
|
4146
|
+
return () => clearInterval(interval);
|
|
4147
|
+
}, []);
|
|
4148
|
+
return {
|
|
4149
|
+
metrics: metricsRef.current,
|
|
4150
|
+
getMetrics: () => contextRef.current?.__performance || null,
|
|
4151
|
+
reset: () => {
|
|
4152
|
+
if (contextRef.current?.__performance) {
|
|
4153
|
+
contextRef.current.__performance.droppedFrames = 0;
|
|
4154
|
+
contextRef.current.__performance.minFrameTime = Infinity;
|
|
4155
|
+
contextRef.current.__performance.maxFrameTime = 0;
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
};
|
|
4159
|
+
};
|
|
2128
4160
|
return {
|
|
2129
4161
|
context: {
|
|
2130
4162
|
context: contextRef.current,
|
|
@@ -2133,8 +4165,11 @@ function useKlint() {
|
|
|
2133
4165
|
KlintMouse,
|
|
2134
4166
|
KlintScroll,
|
|
2135
4167
|
KlintGesture,
|
|
4168
|
+
KlintKeyboard,
|
|
2136
4169
|
KlintWindow,
|
|
2137
4170
|
KlintImage,
|
|
4171
|
+
KlintTimeline,
|
|
4172
|
+
KlintPerformance,
|
|
2138
4173
|
togglePlay,
|
|
2139
4174
|
useDev
|
|
2140
4175
|
};
|
|
@@ -2180,10 +4215,21 @@ var useStorage = (initialProps = {}) => {
|
|
|
2180
4215
|
};
|
|
2181
4216
|
export {
|
|
2182
4217
|
CONFIG_PROPS,
|
|
4218
|
+
Color_default as Color,
|
|
2183
4219
|
EPSILON,
|
|
4220
|
+
Easing_default as Easing,
|
|
4221
|
+
Grid_default as Grid,
|
|
4222
|
+
Hotspot_default as Hotspot,
|
|
2184
4223
|
Klint,
|
|
2185
4224
|
KlintCoreFunctions,
|
|
2186
4225
|
KlintFunctions,
|
|
4226
|
+
Noise_default as Noise,
|
|
4227
|
+
Performance_default as Performance,
|
|
4228
|
+
SSR_default as SSR,
|
|
4229
|
+
Strip_default as Strip,
|
|
4230
|
+
Text_default as Text,
|
|
4231
|
+
Thing_default as Thing,
|
|
4232
|
+
Vector_default as Vector,
|
|
2187
4233
|
useKlint,
|
|
2188
4234
|
useProps,
|
|
2189
4235
|
useStorage
|