@elicdavis/node-flow 0.1.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.
Files changed (68) hide show
  1. package/.github/workflows/deploy.yml +57 -0
  2. package/README.md +159 -0
  3. package/dev.tsconfig.json +18 -0
  4. package/docs/promotional-image.png +0 -0
  5. package/esbuild.ts +32 -0
  6. package/index.html +311 -0
  7. package/package.json +50 -0
  8. package/src/camera.ts +30 -0
  9. package/src/connection.ts +249 -0
  10. package/src/contextMenu.ts +367 -0
  11. package/src/draggable.ts +13 -0
  12. package/src/elements/base.ts +192 -0
  13. package/src/elements/container.ts +119 -0
  14. package/src/elements/interfaces.ts +50 -0
  15. package/src/elements/text.ts +86 -0
  16. package/src/graph.ts +535 -0
  17. package/src/graphSubsystem.ts +31 -0
  18. package/src/index.ts +28 -0
  19. package/src/input.ts +153 -0
  20. package/src/markdown/entry.ts +323 -0
  21. package/src/markdown/lexicalParser.ts +148 -0
  22. package/src/markdown/markdown.ts +14 -0
  23. package/src/markdown/syntaxParser.ts +344 -0
  24. package/src/markdown/token.ts +43 -0
  25. package/src/metadata.ts +3 -0
  26. package/src/node.ts +1130 -0
  27. package/src/nodes/factory.ts +87 -0
  28. package/src/nodes/publisher.ts +98 -0
  29. package/src/nodes/subsystem.ts +760 -0
  30. package/src/notes/note.ts +295 -0
  31. package/src/notes/subsystem.ts +262 -0
  32. package/src/organize.ts +181 -0
  33. package/src/pass/subsystem.ts +64 -0
  34. package/src/performance.ts +9 -0
  35. package/src/popup.ts +81 -0
  36. package/src/popups/form.ts +63 -0
  37. package/src/popups/string.ts +33 -0
  38. package/src/port.ts +223 -0
  39. package/src/quickMenu.ts +330 -0
  40. package/src/styles/box.ts +104 -0
  41. package/src/styles/canvasTextAlign.ts +8 -0
  42. package/src/styles/canvasTextBaseline.ts +9 -0
  43. package/src/styles/cursor.ts +18 -0
  44. package/src/styles/stroke.ts +39 -0
  45. package/src/styles/text.ts +90 -0
  46. package/src/styles/textBox.ts +82 -0
  47. package/src/theme.ts +78 -0
  48. package/src/types/box.ts +126 -0
  49. package/src/types/list.ts +41 -0
  50. package/src/types/pool.ts +66 -0
  51. package/src/types/text.ts +136 -0
  52. package/src/types/vector2.ts +34 -0
  53. package/src/utils/color.ts +136 -0
  54. package/src/utils/html.ts +21 -0
  55. package/src/utils/math.ts +8 -0
  56. package/src/utils/string.ts +98 -0
  57. package/src/widgets/button.ts +107 -0
  58. package/src/widgets/color.ts +151 -0
  59. package/src/widgets/factory.ts +46 -0
  60. package/src/widgets/image.ts +163 -0
  61. package/src/widgets/number.ts +153 -0
  62. package/src/widgets/slider.ts +216 -0
  63. package/src/widgets/string.ts +128 -0
  64. package/src/widgets/text.ts +92 -0
  65. package/src/widgets/toggle.ts +205 -0
  66. package/src/widgets/widget.ts +12 -0
  67. package/tsconfig.json +15 -0
  68. package/tsup.config.ts +10 -0
package/src/theme.ts ADDED
@@ -0,0 +1,78 @@
1
+ export const Theme = {
2
+ FontFamily: "Courier New",
3
+ Graph: {
4
+ BackgroundColor: "#07212a",
5
+ },
6
+ Node: {
7
+ FontColor: "#afb9bb",
8
+ BackgroundColor: "#0c2b35",
9
+ Title: {
10
+ Color: "#154050",
11
+ FontColor: "#afb9bb",
12
+ Padding: 5
13
+ },
14
+ BorderRadius: 15,
15
+ Border: {
16
+ Idle: "#1c1c1c",
17
+ MouseOver: "#8e8e8e",
18
+ Grabbed: "white",
19
+ Selected: "#6e6e6e"
20
+ },
21
+ Port: {
22
+ FontColor: "#afb9bb"
23
+ }
24
+ },
25
+ BoxSelect: {
26
+ Color: "white",
27
+ Size: 1,
28
+ Radius: 2,
29
+ LineDashLength: 5,
30
+ },
31
+ Widget: {
32
+ FontColor: "#afb9bb",
33
+ BackgroundColor: "#0f313c",
34
+ Border: {
35
+ Color: "#154050",
36
+ Size: 2,
37
+ Radius: 2
38
+ },
39
+ Hover: {
40
+ BackgroundColor: "#195366"
41
+ },
42
+ Slider: {
43
+ FillColor: "#07212A",
44
+ },
45
+ Button: {
46
+ Click: {
47
+ BackgroundColor: "#1f637a"
48
+ },
49
+ }
50
+ },
51
+ ContextMenu: {
52
+ BackgroundColor: "#07212A",
53
+ HighlightColor: "#205A6D",
54
+ FontColor: "#afb9bb",
55
+ },
56
+ Note: {
57
+ FontColor: "#afb9bb",
58
+ FontSize: 16,
59
+ EntrySpacing: 20,
60
+ LineSpacing: 5,
61
+ DotSize: 3,
62
+ HeaderLineWidth: 2,
63
+ H1: {
64
+ FontSize: 32,
65
+ },
66
+ H2: {
67
+ FontSize: 16,
68
+ },
69
+ H3: {
70
+ FontSize: 16,
71
+ },
72
+ CodeBlock: {
73
+ BackgroundColor: "#0c2b35",
74
+ Padding: 16,
75
+ BorderRadius: 4,
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,126 @@
1
+ import { VectorPool } from "./pool";
2
+ import { AddVector2, CopyVector2, SubVector2, Vector2, Zero } from "./vector2";
3
+
4
+ export interface Box {
5
+ Position: Vector2
6
+ Size: Vector2
7
+ }
8
+
9
+ export function CopyBox(dst: Box, src: Box): void {
10
+ dst.Position.x = src.Position.x;
11
+ dst.Position.y = src.Position.y;
12
+ dst.Size.x = src.Size.x;
13
+ dst.Size.y = src.Size.y;
14
+ }
15
+
16
+ function normalizeMinMax(min: Vector2, max: Vector2) {
17
+ if (min.x > max.x) {
18
+ let temp = max.x;
19
+ max.x = min.x;
20
+ min.x = temp;
21
+ }
22
+
23
+ if (min.y > max.y) {
24
+ let temp = max.y;
25
+ max.y = min.y;
26
+ min.y = temp;
27
+ }
28
+ }
29
+
30
+ export function BoxIntersection(a: Box, b: Box): boolean {
31
+ let intersects = false;
32
+
33
+ VectorPool.run(() => {
34
+ const aMin = VectorPool.get();
35
+ const aMax = VectorPool.get();
36
+ CopyVector2(aMin, a.Position);
37
+ AddVector2(aMax, aMin, a.Size);
38
+ normalizeMinMax(aMin, aMax);
39
+
40
+ const bMin = VectorPool.get();
41
+ const bMax = VectorPool.get();
42
+ CopyVector2(bMin, b.Position);
43
+ AddVector2(bMax, bMin, b.Size);
44
+ normalizeMinMax(bMin, bMax);
45
+
46
+ intersects = (aMin.x <= bMax.x) && (aMax.x >= bMin.x) &&
47
+ (aMin.y <= bMax.y) && (aMax.y >= bMin.y);
48
+ })
49
+
50
+ return intersects;
51
+ }
52
+
53
+ // TODO: This is probably incorrect. Needs more testing
54
+ // export function ExpandBox(boxToExpand: Box, box: Box): void {
55
+ // const newPos: Vector2 = Zero();
56
+ // const newSize: Vector2 = Zero();
57
+
58
+ // CopyVector2(newPos, boxToExpand.Position);
59
+ // CopyVector2(newSize, boxToExpand.Size);
60
+
61
+
62
+ // const positionOffset: Vector2 = Zero();
63
+
64
+ // // Get Position Adjustment
65
+ // SubVector2(positionOffset, box.Position, boxToExpand.Position);
66
+
67
+ // // We only care about adjustments that make us bigger.
68
+ // positionOffset.x = Math.min(positionOffset.x, 0);
69
+ // positionOffset.y = Math.min(positionOffset.y, 0);
70
+
71
+ // // Adjust our position
72
+ // AddVector2(boxToExpand.Position, boxToExpand.Position, positionOffset);
73
+
74
+ // // Increase our size to take into account new position
75
+ // SubVector2(newSize, newSize, positionOffset);
76
+
77
+ // const sizeIncrease: Vector2 = Zero();
78
+
79
+ // const curEndPos: Vector2 = {
80
+ // x: boxToExpand.Position.x + boxToExpand.Size.x,
81
+ // y: boxToExpand.Position.y + boxToExpand.Size.y,
82
+ // };
83
+
84
+ // const boxEndPos: Vector2 = {
85
+ // x: box.Position.x + box.Size.x,
86
+ // y: box.Position.y + box.Size.y,
87
+ // };
88
+
89
+ // SubVector2(sizeIncrease, boxEndPos, curEndPos);
90
+
91
+ // // We only care about increases to our size
92
+ // sizeIncrease.x = Math.max(sizeIncrease.x, 0);
93
+ // sizeIncrease.y = Math.max(sizeIncrease.y, 0);
94
+
95
+ // // Increase our size to take into account new position
96
+ // SubVector2(newSize, newSize, sizeIncrease)
97
+
98
+ // // Finally, Set result
99
+ // CopyVector2(boxToExpand.Position, newPos)
100
+ // CopyVector2(boxToExpand.Size, newSize)
101
+ // }
102
+
103
+ export function BoxCenter(box: Box, out: Vector2): Vector2 {
104
+ out.x = box.Position.x + (box.Size.x / 2);
105
+ out.y = box.Position.y + (box.Size.y / 2);
106
+ return out
107
+ }
108
+
109
+ export function InBox(box: Box, position: Vector2): boolean {
110
+ const min = box.Position
111
+ if (position.x < min.x) {
112
+ return false
113
+ }
114
+ if (position.y < min.y) {
115
+ return false
116
+ }
117
+
118
+ if (position.x > min.x + box.Size.x) {
119
+ return false
120
+ }
121
+ if (position.y > min.y + box.Size.y) {
122
+ return false
123
+ }
124
+
125
+ return true
126
+ }
@@ -0,0 +1,41 @@
1
+ export class List<Type> {
2
+
3
+ #arr: Array<Type>;
4
+
5
+ #count: number;
6
+
7
+ constructor() {
8
+ this.#arr = new Array<Type>();
9
+ this.#count = 0;
10
+ }
11
+
12
+ public Count(): number {
13
+ return this.#count;
14
+ }
15
+
16
+ public Clear(): void {
17
+ this.#count = 0;
18
+ }
19
+
20
+ public At(index: number): Type {
21
+ return this.#arr[index];
22
+ }
23
+
24
+ public Push(value: Type): void {
25
+ if (this.#arr.length === this.#count) {
26
+ this.#arr.push(value);
27
+ } else {
28
+ this.#arr[this.#count] = value;
29
+ }
30
+ this.#count++;
31
+ }
32
+
33
+ public ToArray(): Array<Type> {
34
+ let arr = new Array<Type>(this.Count());
35
+ for(let i = 0; i < this.#count; i ++) {
36
+ arr[i] = this.#arr[i]
37
+ }
38
+ return arr;
39
+ }
40
+
41
+ }
@@ -0,0 +1,66 @@
1
+ import { Vector2 } from "./vector2";
2
+
3
+
4
+
5
+ export class Pool<Type> {
6
+
7
+ #arr: Array<Type>;
8
+
9
+ #count: number;
10
+
11
+ #builder: () => Type;
12
+
13
+ #reset: (v: Type) => void;
14
+
15
+ #runningDepth: number;
16
+
17
+ constructor(builder: () => Type, reset: (v: Type) => void) {
18
+ this.#arr = new Array<Type>();
19
+ this.#count = 0;
20
+ this.#builder = builder;
21
+ this.#reset = reset;
22
+ this.#runningDepth = 0;
23
+ }
24
+
25
+ public runIf(condition: boolean, fn: () => void) {
26
+ if (condition) {
27
+ this.run(fn);
28
+ }
29
+ }
30
+
31
+ #running(): boolean {
32
+ return this.#runningDepth > 0;
33
+ }
34
+
35
+ public run(fn: () => void) {
36
+ const start = this.#count;
37
+ this.#runningDepth += 1;
38
+ fn();
39
+ this.#runningDepth -= 1;
40
+ this.#count = start;
41
+ // console.log(this.#arr.length);
42
+ }
43
+
44
+ get(): Type {
45
+ if(!this.#running()) {
46
+ throw new Error("can't use pool outside of running context");
47
+ }
48
+
49
+ let value: Type;
50
+ if (this.#arr.length === this.#count) {
51
+ value = this.#builder();
52
+ this.#arr.push(this.#builder());
53
+ } else {
54
+ value = this.#arr[this.#count];
55
+ this.#reset(value);
56
+ }
57
+ this.#count++;
58
+ return value;
59
+ }
60
+
61
+ }
62
+
63
+ export const VectorPool = new Pool<Vector2>(
64
+ () => ({ x: 0, y: 0 }),
65
+ (v) => { v.x = 0; v.y = 0; }
66
+ )
@@ -0,0 +1,136 @@
1
+ import { FontWeight, TextStyle, TextStyleConfig } from "../styles/text";
2
+ import { splitString, splitStringIntoLines } from "../utils/string";
3
+ import { CopyVector2, ScaleVector, Vector2, Zero } from "./vector2";
4
+
5
+ export class Text {
6
+
7
+ #measured: boolean
8
+
9
+ #size: Vector2;
10
+
11
+ #style: TextStyle;
12
+
13
+ #value: string
14
+
15
+ constructor(value: string, style?: TextStyleConfig) {
16
+ this.#value = value;
17
+ this.#measured = false;
18
+ this.#size = Zero();
19
+ this.#style = new TextStyle(style);
20
+
21
+ if (!document.fonts.check(`16px "${this.#style.getFont()}"`)) {
22
+ document.fonts.addEventListener("loadingdone", (event) => {
23
+ this.#measured = false;
24
+ });
25
+ }
26
+ }
27
+
28
+ set(newValue: string): void {
29
+ this.#value = newValue;
30
+ this.#measured = false;
31
+ }
32
+
33
+ get(): string {
34
+ return this.#value;
35
+ }
36
+
37
+ breakIntoLines(ctx: CanvasRenderingContext2D, maxWidth: number): Array<Text> {
38
+ const results = new Array<Text>();
39
+
40
+ this.#style.setupStyle(ctx, 1);
41
+ const entries = splitStringIntoLines(ctx, this.#value, maxWidth);
42
+ if (entries.length === 1) {
43
+ return [this];
44
+ }
45
+ for (let i = 0; i < entries.length; i++) {
46
+ const text = new Text(entries[i])
47
+ text.#style = this.#style;
48
+ results.push(text)
49
+ }
50
+
51
+ return results;
52
+ }
53
+
54
+ split(char: string): Array<Text> {
55
+ const entries = this.#value.split(char)
56
+ const results = new Array<Text>();
57
+ for (let i = 0; i < entries.length; i++) {
58
+ const text = new Text(entries[i])
59
+ text.#style = this.#style;
60
+ results.push(text)
61
+ }
62
+ return results;
63
+ }
64
+
65
+ splitAtIndex(index: number): Array<Text> {
66
+ const results = [
67
+ new Text(this.#value.substring(0, index)),
68
+ new Text(this.#value.substring(index, 0)),
69
+ ];
70
+
71
+
72
+ for (let i = 0; i < results.length; i++) {
73
+ results[i].#style = this.#style;
74
+ }
75
+ return results;
76
+ }
77
+
78
+ splitAtWidth(ctx: CanvasRenderingContext2D, maxWidth: number): Array<Text> {
79
+ this.#style.setupStyle(ctx, 1);
80
+ const entries = splitString(ctx, this.#value, maxWidth);
81
+ if (entries.length === 1) {
82
+ return [this];
83
+ }
84
+
85
+ const results = new Array<Text>();
86
+ for (let i = 0; i < entries.length; i++) {
87
+ const text = new Text(entries[i])
88
+ text.#style = this.#style;
89
+ results.push(text)
90
+ }
91
+ return results;
92
+ }
93
+
94
+ #measure(ctx: CanvasRenderingContext2D): void {
95
+ if (this.#measured) {
96
+ return;
97
+ }
98
+
99
+ this.#style.setupStyle(ctx, 1);
100
+ const measurements = ctx.measureText(this.#value);
101
+ this.#size.x = measurements.width;
102
+ this.#size.y = measurements.actualBoundingBoxAscent + measurements.actualBoundingBoxDescent;
103
+ this.#measured = true;
104
+ }
105
+
106
+ size(ctx: CanvasRenderingContext2D, scale: number, out: Vector2): void {
107
+ this.#measure(ctx);
108
+ CopyVector2(out, this.#size);
109
+ ScaleVector(out, scale);
110
+ }
111
+
112
+ setColor(color: string): void {
113
+ this.#style.setColor(color);
114
+ }
115
+
116
+ getColor(): string {
117
+ return this.#style.getColor();
118
+ }
119
+
120
+ setSize(size: number): void {
121
+ this.#style.setSize(size);
122
+ }
123
+
124
+ setWeight(weight: FontWeight): void {
125
+ this.#style.setWeight(weight);
126
+ }
127
+
128
+ render(ctx: CanvasRenderingContext2D, scale: number, position: Vector2): void {
129
+ this.#style.setupStyle(ctx, scale)
130
+ ctx.fillText(this.#value, position.x, position.y);
131
+ }
132
+
133
+ style(): TextStyle {
134
+ return this.#style;
135
+ }
136
+ }
@@ -0,0 +1,34 @@
1
+ export interface Vector2 {
2
+ x: number
3
+ y: number
4
+ }
5
+
6
+ export function CopyVector2(dst: Vector2, src: Vector2): void {
7
+ dst.x = src.x;
8
+ dst.y = src.y;
9
+ }
10
+
11
+ export function SubVector2(dst: Vector2, a: Vector2, b: Vector2): void {
12
+ dst.x = a.x - b.x;
13
+ dst.y = a.y - b.y;
14
+ }
15
+
16
+ export function AddVector2(dst: Vector2, a: Vector2, b: Vector2): void {
17
+ dst.x = a.x + b.x;
18
+ dst.y = a.y + b.y;
19
+ }
20
+
21
+ export function ScaleVector(dst: Vector2, scale: number): void {
22
+ dst.x *= scale;
23
+ dst.y *= scale;
24
+ }
25
+
26
+ export function Zero(): Vector2 {
27
+ return { x: 0, y: 0 };
28
+ }
29
+
30
+ export function Distance(a: Vector2, b: Vector2): number {
31
+ const xd = b.x - a.x
32
+ const yd = b.y - a.y
33
+ return Math.sqrt((xd*xd)+(yd*yd))
34
+ }
@@ -0,0 +1,136 @@
1
+ import { Clamp01 } from './math';
2
+
3
+ export interface Color {
4
+ r: number, // 0 - 1
5
+ g: number, // 0 - 1
6
+ b: number, // 0 - 1
7
+ }
8
+
9
+ export interface HSV {
10
+ h: number, // angle in degrees
11
+ s: number, // 0 - 1
12
+ v: number, // 0 - 1
13
+ }
14
+
15
+ function componentToHex(c: number) {
16
+ var hex = c.toString(16);
17
+ return hex.length == 1 ? "0" + hex : hex;
18
+ }
19
+
20
+ export function RgbToHex(c: Color): string {
21
+ return "#" + componentToHex(Math.round(c.r * 255)) + componentToHex(Math.round(c.g * 255)) + componentToHex(Math.round(c.b * 255));
22
+ }
23
+
24
+ export function HexToColor(hex: string): Color | null {
25
+ var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
26
+ return result ? {
27
+ r: parseInt(result[1], 16) / 255,
28
+ g: parseInt(result[2], 16) / 255,
29
+ b: parseInt(result[3], 16) / 255
30
+ } : null;
31
+ }
32
+
33
+
34
+ export function HSV2RGB(hsv: HSV, out: Color): void {
35
+ if (hsv.s <= 0.0) { // < is bogus, just shuts up warnings
36
+ out.r = hsv.v;
37
+ out.g = hsv.v;
38
+ out.b = hsv.v;
39
+ return;
40
+ }
41
+ let hh = hsv.h;
42
+ if (hh >= 360.0) {
43
+ hh = 0.0;
44
+ }
45
+ hh /= 60.0;
46
+
47
+ const i = Math.round(hh);
48
+ const ff = hh - i;
49
+ const p = hsv.v * (1.0 - hsv.s);
50
+ const q = hsv.v * (1.0 - (hsv.s * ff));
51
+ const t = hsv.v * (1.0 - (hsv.s * (1.0 - ff)));
52
+
53
+ switch (i) {
54
+ case 0:
55
+ out.r = hsv.v;
56
+ out.g = t;
57
+ out.b = p;
58
+ break;
59
+ case 1:
60
+ out.r = q;
61
+ out.g = hsv.v;
62
+ out.b = p;
63
+ break;
64
+ case 2:
65
+ out.r = p;
66
+ out.g = hsv.v;
67
+ out.b = t;
68
+ break;
69
+
70
+ case 3:
71
+ out.r = p;
72
+ out.g = q;
73
+ out.b = hsv.v;
74
+ break;
75
+ case 4:
76
+ out.r = t;
77
+ out.g = p;
78
+ out.b = hsv.v;
79
+ break;
80
+ // case 5:
81
+ default:
82
+ out.r = hsv.v;
83
+ out.g = p;
84
+ out.b = q;
85
+ break;
86
+ }
87
+
88
+ out.r = Clamp01(out.r)
89
+ out.g = Clamp01(out.g)
90
+ out.b = Clamp01(out.b)
91
+ }
92
+
93
+ export function RGB2HSV(rgb: Color, out: HSV): void {
94
+ out.h = 0;
95
+ out.s = 0;
96
+ out.v = 0;
97
+ let min: number = 0;
98
+ let max: number = 0;
99
+ let delta: number = 0;
100
+
101
+ min = rgb.r < rgb.g ? rgb.r : rgb.g;
102
+ min = min < rgb.b ? min : rgb.b;
103
+
104
+ max = rgb.r > rgb.g ? rgb.r : rgb.g;
105
+ max = max > rgb.b ? max : rgb.b;
106
+
107
+ out.v = max; // v
108
+ delta = max - min;
109
+ if (delta < 0.00001) {
110
+ out.s = 0;
111
+ out.h = 0; // undefined, maybe nan?
112
+ return;
113
+ }
114
+ if (max > 0.0) { // NOTE: if Max is == 0, this divide would cause a crash
115
+ out.s = (delta / max); // s
116
+ } else {
117
+ // if max is 0, then r = g = b = 0
118
+ // s = 0, h is undefined
119
+ out.s = 0.0;
120
+ out.h = NaN; // its now undefined
121
+ return;
122
+ }
123
+ if (rgb.r >= max) // > is bogus, just keeps compilor happy
124
+ out.h = (rgb.g - rgb.b) / delta; // between yellow & magenta
125
+ else
126
+ if (rgb.g >= max)
127
+ out.h = 2.0 + (rgb.b - rgb.r) / delta; // between cyan & yellow
128
+ else
129
+ out.h = 4.0 + (rgb.r - rgb.g) / delta; // between magenta & cyan
130
+
131
+ out.h *= 60.0; // degrees
132
+
133
+ if (out.h < 0.0)
134
+ out.h += 360.0;
135
+
136
+ }
@@ -0,0 +1,21 @@
1
+ export function HtmlFromString(htmlString: string): Element {
2
+ var div = document.createElement('div');
3
+ div.innerHTML = htmlString;
4
+ const child = div.firstElementChild;
5
+ if (child === null || child === undefined) {
6
+ throw new Error("unable to create html from string")
7
+ }
8
+ return child;
9
+ }
10
+
11
+ interface CSS {
12
+ [name: string]: any
13
+ }
14
+
15
+ export function CssToString(css: CSS): string {
16
+ let cssString = ""
17
+ for (let entry in css) {
18
+ cssString += entry + ":" + css[entry] + ";";
19
+ }
20
+ return cssString;
21
+ }
@@ -0,0 +1,8 @@
1
+ export function Clamp(v: number, min: number, max: number): number {
2
+ return Math.min(Math.max(v, min), max)
3
+ }
4
+
5
+ export function Clamp01(v: number): number {
6
+ return Clamp(v, 0, 1);
7
+ }
8
+