mt-lang 0.3.2 → 0.3.4
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.
- checksums.yaml +4 -4
- data/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/lsp/diagnostics.rb +1 -1
- data/lib/milk_tea/tooling/linter/imports_platform.rb +14 -13
- data/std/blackboard.mt +100 -0
- data/std/cmd.mt +132 -0
- data/std/color.mt +245 -0
- data/std/graph.mt +237 -0
- data/std/htn.mt +254 -0
- data/std/input.mt +132 -0
- data/std/noise.mt +201 -0
- data/std/pool.mt +82 -0
- data/std/signal.mt +66 -0
- data/std/steering.mt +132 -0
- data/std/timer.mt +81 -0
- data/std/tween.mt +374 -0
- data/std/utility.mt +208 -0
- metadata +14 -2
data/std/noise.mt
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
## Coherent noise generation — Perlin noise and fractal variants.
|
|
2
|
+
##
|
|
3
|
+
## Seed-based deterministic noise for procedural generation:
|
|
4
|
+
## terrain, textures, fog, animation, and more.
|
|
5
|
+
##
|
|
6
|
+
## import std.noise as ns
|
|
7
|
+
## var noise = ns.Noise.create(42)
|
|
8
|
+
## let h = noise.fbm2d(x * 0.01, y * 0.01)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
public struct Noise:
|
|
12
|
+
perm: array[ubyte, 512]
|
|
13
|
+
octaves: int
|
|
14
|
+
lacunarity: float
|
|
15
|
+
gain: float
|
|
16
|
+
frequency: float
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ── fade / interpolation ──
|
|
20
|
+
|
|
21
|
+
function fade(t: float) -> float:
|
|
22
|
+
return t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
function lerp(a: float, b: float, t: float) -> float:
|
|
26
|
+
return a + t * (b - a)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ── gradient functions ──
|
|
30
|
+
|
|
31
|
+
function grad2d(hash: int, x: float, y: float) -> float:
|
|
32
|
+
let h = hash & 3
|
|
33
|
+
let u = if h == 0 or h == 1: x else: y
|
|
34
|
+
let v = if h == 0 or h == 3: y else: x
|
|
35
|
+
return (if (h & 1) == 0: u else: -u) + (if (h & 2) == 0: v else: -v)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
function grad3d(hash: int, x: float, y: float, z: float) -> float:
|
|
39
|
+
let h = hash & 15
|
|
40
|
+
let u = if h < 8: x else: y
|
|
41
|
+
let v = if h < 4: y else: if h == 12 or h == 14: x else: z
|
|
42
|
+
let mu = if (h & 1) == 0: u else: -u
|
|
43
|
+
let mv = if (h & 2) == 0: v else: -v
|
|
44
|
+
return mu + mv
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ── internal noise functions ──
|
|
48
|
+
|
|
49
|
+
function perlin2d_impl(perm: array[ubyte, 512], x: float, y: float) -> float:
|
|
50
|
+
let ix = int<-(x)
|
|
51
|
+
let iy = int<-(y)
|
|
52
|
+
let X = ix & 255
|
|
53
|
+
let Y = iy & 255
|
|
54
|
+
let xf = x - float<-(ix - (ix & ~255))
|
|
55
|
+
let yf = y - float<-(iy - (iy & ~255))
|
|
56
|
+
|
|
57
|
+
let u = fade(if xf < 0.0: xf + 1.0 else: xf)
|
|
58
|
+
let v = fade(if yf < 0.0: yf + 1.0 else: yf)
|
|
59
|
+
|
|
60
|
+
let A = int<-(perm[X]) + Y
|
|
61
|
+
let B = int<-(perm[X + 1]) + Y
|
|
62
|
+
let aa = int<-(perm[A])
|
|
63
|
+
let ab = int<-(perm[A + 1])
|
|
64
|
+
let ba = int<-(perm[B])
|
|
65
|
+
let bb = int<-(perm[B + 1])
|
|
66
|
+
|
|
67
|
+
let x1 = lerp(grad2d(aa, xf, yf), grad2d(ba, xf - 1.0, yf), u)
|
|
68
|
+
let x2 = lerp(grad2d(ab, xf, yf - 1.0), grad2d(bb, xf - 1.0, yf - 1.0), u)
|
|
69
|
+
|
|
70
|
+
return lerp(x1, x2, v)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
function perlin3d_impl(perm: array[ubyte, 512], x: float, y: float, z: float) -> float:
|
|
74
|
+
let ix = int<-(x)
|
|
75
|
+
let iy = int<-(y)
|
|
76
|
+
let iz = int<-(z)
|
|
77
|
+
let X = ix & 255
|
|
78
|
+
let Y = iy & 255
|
|
79
|
+
let Z = iz & 255
|
|
80
|
+
let xf = x - float<-(ix - (ix & ~255))
|
|
81
|
+
let yf = y - float<-(iy - (iy & ~255))
|
|
82
|
+
let zf = z - float<-(iz - (iz & ~255))
|
|
83
|
+
|
|
84
|
+
let u = fade(if xf < 0.0: xf + 1.0 else: xf)
|
|
85
|
+
let v = fade(if yf < 0.0: yf + 1.0 else: yf)
|
|
86
|
+
let w = fade(if zf < 0.0: zf + 1.0 else: zf)
|
|
87
|
+
|
|
88
|
+
let A = int<-(perm[X]) + Y
|
|
89
|
+
let AA = int<-(perm[A]) + Z
|
|
90
|
+
let AB = int<-(perm[A + 1]) + Z
|
|
91
|
+
let B = int<-(perm[X + 1]) + Y
|
|
92
|
+
let BA = int<-(perm[B]) + Z
|
|
93
|
+
let BB = int<-(perm[B + 1]) + Z
|
|
94
|
+
|
|
95
|
+
let x1 = lerp(grad3d(int<-(perm[AA]), xf, yf, zf), grad3d(int<-(perm[BA]), xf - 1.0, yf, zf), u)
|
|
96
|
+
let x2 = lerp(grad3d(int<-(perm[AB]), xf, yf - 1.0, zf), grad3d(int<-(perm[BB]), xf - 1.0, yf - 1.0, zf), u)
|
|
97
|
+
let y1 = lerp(x1, x2, v)
|
|
98
|
+
|
|
99
|
+
let x3 = lerp(grad3d(int<-(perm[AA + 1]), xf, yf, zf - 1.0), grad3d(int<-(perm[BA + 1]), xf - 1.0, yf, zf - 1.0), u)
|
|
100
|
+
let x4 = lerp(grad3d(int<-(perm[AB + 1]), xf, yf - 1.0, zf - 1.0), grad3d(int<-(perm[BB + 1]), xf - 1.0, yf - 1.0, zf - 1.0), u)
|
|
101
|
+
let y2 = lerp(x3, x4, v)
|
|
102
|
+
|
|
103
|
+
return lerp(y1, y2, w)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# ── float floor for negative numbers ──
|
|
107
|
+
|
|
108
|
+
function floor_f(x: float) -> int:
|
|
109
|
+
let i = int<-(x)
|
|
110
|
+
if x < 0.0 and float<-(i) != x:
|
|
111
|
+
return i - 1
|
|
112
|
+
return i
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ── public API ──
|
|
116
|
+
|
|
117
|
+
extending Noise:
|
|
118
|
+
public static function create(seed: ulong) -> Noise:
|
|
119
|
+
var n = Noise(
|
|
120
|
+
perm = zero[array[ubyte, 512]],
|
|
121
|
+
octaves = 6,
|
|
122
|
+
lacunarity = 2.0,
|
|
123
|
+
gain = 0.5,
|
|
124
|
+
frequency = 1.0
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
var i: int = 0
|
|
128
|
+
while i < 256:
|
|
129
|
+
n.perm[i] = ubyte<-i
|
|
130
|
+
i += 1
|
|
131
|
+
|
|
132
|
+
var state: ulong = seed
|
|
133
|
+
var j: int = 255
|
|
134
|
+
while j > 0:
|
|
135
|
+
state = state * 6364136223846793005 + 1442695040888963407
|
|
136
|
+
let r = int<-(state % ulong<-(j + 1))
|
|
137
|
+
let tmp = n.perm[j]
|
|
138
|
+
n.perm[j] = n.perm[r]
|
|
139
|
+
n.perm[r] = tmp
|
|
140
|
+
j -= 1
|
|
141
|
+
|
|
142
|
+
i = 0
|
|
143
|
+
while i < 256:
|
|
144
|
+
n.perm[i + 256] = n.perm[i]
|
|
145
|
+
i += 1
|
|
146
|
+
|
|
147
|
+
return n
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
public function perlin2d(x: float, y: float) -> float:
|
|
151
|
+
return perlin2d_impl(this.perm, x, y)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
public function perlin3d(x: float, y: float, z: float) -> float:
|
|
155
|
+
return perlin3d_impl(this.perm, x, y, z)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
public function fbm2d(x: float, y: float) -> float:
|
|
159
|
+
var value: float = 0.0
|
|
160
|
+
var freq: float = this.frequency
|
|
161
|
+
var amp: float = 1.0
|
|
162
|
+
var max_val: float = 0.0
|
|
163
|
+
var i: int = 0
|
|
164
|
+
while i < this.octaves:
|
|
165
|
+
value = value + amp * perlin2d_impl(this.perm, x * freq, y * freq)
|
|
166
|
+
max_val = max_val + amp
|
|
167
|
+
amp = amp * this.gain
|
|
168
|
+
freq = freq * this.lacunarity
|
|
169
|
+
i += 1
|
|
170
|
+
return value / max_val
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
public function fbm3d(x: float, y: float, z: float) -> float:
|
|
174
|
+
var value: float = 0.0
|
|
175
|
+
var freq: float = this.frequency
|
|
176
|
+
var amp: float = 1.0
|
|
177
|
+
var max_val: float = 0.0
|
|
178
|
+
var i: int = 0
|
|
179
|
+
while i < this.octaves:
|
|
180
|
+
value = value + amp * perlin3d_impl(this.perm, x * freq, y * freq, z * freq)
|
|
181
|
+
max_val = max_val + amp
|
|
182
|
+
amp = amp * this.gain
|
|
183
|
+
freq = freq * this.lacunarity
|
|
184
|
+
i += 1
|
|
185
|
+
return value / max_val
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
public editable function set_octaves(n: int) -> void:
|
|
189
|
+
this.octaves = n
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
public editable function set_lacunarity(v: float) -> void:
|
|
193
|
+
this.lacunarity = v
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
public editable function set_gain(v: float) -> void:
|
|
197
|
+
this.gain = v
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
public editable function set_frequency(v: float) -> void:
|
|
201
|
+
this.frequency = v
|
data/std/pool.mt
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
## Object pool — fixed-capacity reusable object storage.
|
|
2
|
+
##
|
|
3
|
+
## Pre-allocate objects once, then acquire/release without
|
|
4
|
+
## allocation churn. O(1) acquire and release via free stack.
|
|
5
|
+
##
|
|
6
|
+
## import std.pool as pool
|
|
7
|
+
## var p = pool.Pool[Bullet].create(64)
|
|
8
|
+
## let id = p.acquire() else: /* pool exhausted */
|
|
9
|
+
## let bullet = p.get(id) # ptr to object
|
|
10
|
+
## p.release(id)
|
|
11
|
+
|
|
12
|
+
import std.vec as vec
|
|
13
|
+
import std.mem.heap as heap
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
public struct Pool[T]:
|
|
17
|
+
objects: ptr[T]
|
|
18
|
+
free_stack: vec.Vec[ptr_uint]
|
|
19
|
+
capacity: ptr_uint
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ── public API ──
|
|
23
|
+
|
|
24
|
+
extending Pool[T]:
|
|
25
|
+
public static function create(capacity: ptr_uint) -> Pool[T]:
|
|
26
|
+
var p = Pool[T](
|
|
27
|
+
objects = heap.must_alloc[T](capacity),
|
|
28
|
+
free_stack = vec.Vec[ptr_uint].with_capacity(capacity),
|
|
29
|
+
capacity = capacity
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Push all indices onto the free stack in reverse order so
|
|
33
|
+
# acquire() returns them in ascending order (0 first).
|
|
34
|
+
var i = capacity
|
|
35
|
+
while i > 0:
|
|
36
|
+
i -= 1
|
|
37
|
+
p.free_stack.push(i)
|
|
38
|
+
|
|
39
|
+
return p
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
public editable function acquire() -> Option[ptr_uint]:
|
|
43
|
+
let idx = this.free_stack.pop() else:
|
|
44
|
+
return Option[ptr_uint].none
|
|
45
|
+
return Option[ptr_uint].some(value = idx)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
public function get(index: ptr_uint) -> ptr[T]?:
|
|
49
|
+
if index >= this.capacity:
|
|
50
|
+
return null
|
|
51
|
+
unsafe:
|
|
52
|
+
return this.objects + index
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
public editable function release_object(index: ptr_uint) -> void:
|
|
56
|
+
if index >= this.capacity:
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
# Check for double-release by scanning free stack.
|
|
60
|
+
# Skip this check for simplicity — caller must not double-release.
|
|
61
|
+
this.free_stack.push(index)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
public function available() -> ptr_uint:
|
|
65
|
+
return this.free_stack.len()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
public function count() -> ptr_uint:
|
|
69
|
+
return this.capacity - this.free_stack.len()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
public editable function clear() -> void:
|
|
73
|
+
this.free_stack.clear()
|
|
74
|
+
var i = this.capacity
|
|
75
|
+
while i > 0:
|
|
76
|
+
i -= 1
|
|
77
|
+
this.free_stack.push(i)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
public editable function release() -> void:
|
|
81
|
+
this.free_stack.release()
|
|
82
|
+
heap.release(this.objects)
|
data/std/signal.mt
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
## Signal — fixed-capacity observer / publish-subscribe.
|
|
2
|
+
##
|
|
3
|
+
## Connect callbacks to signals and fire typed payloads.
|
|
4
|
+
## Up to 16 subscribers per signal, stack-allocated.
|
|
5
|
+
##
|
|
6
|
+
## import std.signal as sig
|
|
7
|
+
## var on_damage = sig.Signal[int].create()
|
|
8
|
+
## on_damage.connect(handle_damage)
|
|
9
|
+
## on_damage.fire(25)
|
|
10
|
+
|
|
11
|
+
public struct Signal[Payload]:
|
|
12
|
+
slots: array[fn(payload: Payload) -> void, 16]
|
|
13
|
+
count: ptr_uint
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ── public API ──
|
|
17
|
+
|
|
18
|
+
extending Signal[Payload]:
|
|
19
|
+
public static function create() -> Signal[Payload]:
|
|
20
|
+
var s = Signal[Payload](
|
|
21
|
+
slots = zero[array[fn(payload: Payload) -> void, 16]],
|
|
22
|
+
count = 0
|
|
23
|
+
)
|
|
24
|
+
return s
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
public editable function connect(callback: fn(payload: Payload) -> void) -> void:
|
|
28
|
+
if this.count >= 16:
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
var i: ptr_uint = 0
|
|
32
|
+
while i < this.count:
|
|
33
|
+
unsafe:
|
|
34
|
+
if this.slots[i] == callback:
|
|
35
|
+
return
|
|
36
|
+
i += 1
|
|
37
|
+
|
|
38
|
+
unsafe:
|
|
39
|
+
this.slots[this.count] = callback
|
|
40
|
+
this.count = this.count + 1
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
public editable function disconnect(callback: fn(payload: Payload) -> void) -> void:
|
|
44
|
+
var i: ptr_uint = 0
|
|
45
|
+
while i < this.count:
|
|
46
|
+
unsafe:
|
|
47
|
+
if this.slots[i] == callback:
|
|
48
|
+
# swap-remove with last
|
|
49
|
+
this.count = this.count - 1
|
|
50
|
+
if i < this.count:
|
|
51
|
+
this.slots[i] = this.slots[this.count]
|
|
52
|
+
return
|
|
53
|
+
i += 1
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
public function fire(payload: Payload) -> void:
|
|
57
|
+
var i: ptr_uint = 0
|
|
58
|
+
while i < this.count:
|
|
59
|
+
unsafe:
|
|
60
|
+
let cb = this.slots[i]
|
|
61
|
+
cb(payload)
|
|
62
|
+
i += 1
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
public function count() -> ptr_uint:
|
|
66
|
+
return this.count
|
data/std/steering.mt
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
## Steering behaviors — Craig Reynolds' autonomous motion.
|
|
2
|
+
##
|
|
3
|
+
## Pure-function steering forces for autonomous agents.
|
|
4
|
+
## Seek, flee, arrive, pursuit, evade, wander, and flocking
|
|
5
|
+
## (separation, alignment, cohesion). All return steering
|
|
6
|
+
## force vectors to be accumulated and applied by the caller.
|
|
7
|
+
##
|
|
8
|
+
## import std.steering as steer
|
|
9
|
+
## var force = steer.Steer.seek(pos, vel, target, max_speed)
|
|
10
|
+
## force = force + steer.Steer.separation(pos, neighbors, r, max_speed)
|
|
11
|
+
|
|
12
|
+
import std.linear_algebra as la
|
|
13
|
+
import std.math as math
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ── internal helpers ──
|
|
17
|
+
|
|
18
|
+
function set_magnitude(v: vec2, magnitude: float) -> vec2:
|
|
19
|
+
let len = v.length()
|
|
20
|
+
if len <= 0.0:
|
|
21
|
+
return v
|
|
22
|
+
return v * (magnitude / len)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
function limit_force(steering: vec2, max_force: float) -> vec2:
|
|
26
|
+
let mag = steering.length()
|
|
27
|
+
if mag > max_force:
|
|
28
|
+
return steering * (max_force / mag)
|
|
29
|
+
return steering
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ── public API ──
|
|
33
|
+
|
|
34
|
+
extending vec2:
|
|
35
|
+
public static function seek(position: vec2, velocity: vec2, target: vec2, max_speed: float) -> vec2:
|
|
36
|
+
let desired = target - position
|
|
37
|
+
let desired_vel = set_magnitude(desired, max_speed)
|
|
38
|
+
return desired_vel - velocity
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
public static function flee(position: vec2, velocity: vec2, threat: vec2, max_speed: float) -> vec2:
|
|
42
|
+
let desired = position - threat
|
|
43
|
+
let desired_vel = set_magnitude(desired, max_speed)
|
|
44
|
+
return desired_vel - velocity
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
public static function arrive(position: vec2, velocity: vec2, target: vec2, max_speed: float, slowing_distance: float) -> vec2:
|
|
48
|
+
let offset = target - position
|
|
49
|
+
let distance = offset.length()
|
|
50
|
+
if distance < 0.001:
|
|
51
|
+
return zero[vec2]
|
|
52
|
+
let speed = if distance < slowing_distance: max_speed * distance / slowing_distance else: max_speed
|
|
53
|
+
let desired_vel = offset * (speed / distance)
|
|
54
|
+
return desired_vel - velocity
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
public static function pursuit(position: vec2, velocity: vec2, target_pos: vec2, target_vel: vec2, max_speed: float) -> vec2:
|
|
58
|
+
let offset = target_pos - position
|
|
59
|
+
let distance = offset.length()
|
|
60
|
+
let my_speed = velocity.length()
|
|
61
|
+
var look_ahead: float = distance / (max_speed + 0.001)
|
|
62
|
+
if my_speed > 0.001:
|
|
63
|
+
look_ahead = distance / my_speed
|
|
64
|
+
let future = target_pos + target_vel * look_ahead
|
|
65
|
+
return vec2.seek(position, velocity, future, max_speed)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
public static function evade(position: vec2, velocity: vec2, threat_pos: vec2, threat_vel: vec2, max_speed: float) -> vec2:
|
|
69
|
+
let offset = threat_pos - position
|
|
70
|
+
let distance = offset.length()
|
|
71
|
+
let my_speed = velocity.length()
|
|
72
|
+
var look_ahead: float = distance / (max_speed + 0.001)
|
|
73
|
+
if my_speed > 0.001:
|
|
74
|
+
look_ahead = distance / my_speed
|
|
75
|
+
let future = threat_pos + threat_vel * look_ahead
|
|
76
|
+
return vec2.flee(position, velocity, future, max_speed)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
public static function wander(velocity: vec2, wander_angle: float, wander_distance: float, wander_radius: float) -> vec2:
|
|
80
|
+
if velocity.length() < 0.001:
|
|
81
|
+
return zero[vec2]
|
|
82
|
+
let forward = set_magnitude(velocity, 1.0)
|
|
83
|
+
let circle_center = forward * wander_distance
|
|
84
|
+
let wx = circle_center.x + wander_radius * float<-(math.cos(double<-(wander_angle)))
|
|
85
|
+
let wy = circle_center.y + wander_radius * float<-(math.sin(double<-(wander_angle)))
|
|
86
|
+
let displacement = vec2(x = wx, y = wy)
|
|
87
|
+
return displacement
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
public static function separation(position: vec2, neighbors: span[vec2], desired_separation: float) -> vec2:
|
|
91
|
+
var steer: vec2 = zero[vec2]
|
|
92
|
+
var count: ptr_uint = 0
|
|
93
|
+
for n in neighbors:
|
|
94
|
+
let d = position - n
|
|
95
|
+
let distance = d.length()
|
|
96
|
+
if distance > 0.0 and distance < desired_separation:
|
|
97
|
+
let repulsion = d * (1.0 / distance)
|
|
98
|
+
steer = steer + repulsion
|
|
99
|
+
count += 1
|
|
100
|
+
if count > 0:
|
|
101
|
+
steer = steer / float<-(count)
|
|
102
|
+
return steer
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
public static function alignment(velocity: vec2, neighbor_velocities: span[vec2]) -> vec2:
|
|
106
|
+
var sum: vec2 = zero[vec2]
|
|
107
|
+
var count: ptr_uint = 0
|
|
108
|
+
for nv in neighbor_velocities:
|
|
109
|
+
sum = sum + nv
|
|
110
|
+
count += 1
|
|
111
|
+
if count > 0:
|
|
112
|
+
let avg = sum / float<-(count)
|
|
113
|
+
return avg - velocity
|
|
114
|
+
return zero[vec2]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
public static function cohesion(position: vec2, neighbor_positions: span[vec2], radius: float) -> vec2:
|
|
118
|
+
var sum: vec2 = zero[vec2]
|
|
119
|
+
var count: ptr_uint = 0
|
|
120
|
+
for np in neighbor_positions:
|
|
121
|
+
let d = position - np
|
|
122
|
+
if d.length() < radius:
|
|
123
|
+
sum = sum + np
|
|
124
|
+
count += 1
|
|
125
|
+
if count > 0:
|
|
126
|
+
let center = sum / float<-(count)
|
|
127
|
+
return center - position
|
|
128
|
+
return zero[vec2]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
public static function limit(steering: vec2, max_force: float) -> vec2:
|
|
132
|
+
return limit_force(steering, max_force)
|
data/std/timer.mt
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
## Timer — countdown and repeating timers.
|
|
2
|
+
##
|
|
3
|
+
## Accumulate delta time and fire when duration is reached.
|
|
4
|
+
## Supports one-shot (auto-stop) and repeating (auto-reset).
|
|
5
|
+
##
|
|
6
|
+
## import std.timer as time
|
|
7
|
+
## var cooldown = time.Timer.create(0.5, false)
|
|
8
|
+
## var regen = time.Timer.create(1.0, true)
|
|
9
|
+
## ...
|
|
10
|
+
## if cooldown.step(dt): do_ability()
|
|
11
|
+
## if regen.step(dt): heal(1)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
public struct Timer:
|
|
15
|
+
duration: float
|
|
16
|
+
elapsed: float
|
|
17
|
+
active: bool
|
|
18
|
+
repeating: bool
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ── public API ──
|
|
22
|
+
|
|
23
|
+
extending Timer:
|
|
24
|
+
public static function create(duration: float, repeating: bool) -> Timer:
|
|
25
|
+
return Timer(
|
|
26
|
+
duration = duration,
|
|
27
|
+
elapsed = 0.0,
|
|
28
|
+
active = true,
|
|
29
|
+
repeating = repeating
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
public editable function step(dt: float) -> bool:
|
|
34
|
+
if not this.active:
|
|
35
|
+
return false
|
|
36
|
+
this.elapsed = this.elapsed + dt
|
|
37
|
+
if this.elapsed >= this.duration:
|
|
38
|
+
if this.repeating:
|
|
39
|
+
this.elapsed = this.elapsed - this.duration
|
|
40
|
+
else:
|
|
41
|
+
this.elapsed = this.duration
|
|
42
|
+
this.active = false
|
|
43
|
+
return true
|
|
44
|
+
return false
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
public function progress() -> float:
|
|
48
|
+
if this.duration <= 0.0:
|
|
49
|
+
return 1.0
|
|
50
|
+
let p = this.elapsed / this.duration
|
|
51
|
+
if p > 1.0: return 1.0
|
|
52
|
+
return p
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
public function time_left() -> float:
|
|
56
|
+
let t = this.duration - this.elapsed
|
|
57
|
+
if t < 0.0: return 0.0
|
|
58
|
+
return t
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
public editable function reset() -> void:
|
|
62
|
+
this.elapsed = 0.0
|
|
63
|
+
this.active = true
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
public editable function restart(duration: float) -> void:
|
|
67
|
+
this.duration = duration
|
|
68
|
+
this.elapsed = 0.0
|
|
69
|
+
this.active = true
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
public editable function stop() -> void:
|
|
73
|
+
this.active = false
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
public editable function pause() -> void:
|
|
77
|
+
this.active = false
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
public editable function resume() -> void:
|
|
81
|
+
this.active = true
|