sonolus.py 0.3.1__py3-none-any.whl → 0.3.3__py3-none-any.whl
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.
Potentially problematic release.
This version of sonolus.py might be problematic. Click here for more details.
- sonolus/backend/finalize.py +16 -4
- sonolus/backend/node.py +13 -5
- sonolus/backend/optimize/allocate.py +41 -4
- sonolus/backend/optimize/flow.py +24 -7
- sonolus/backend/optimize/optimize.py +2 -9
- sonolus/backend/utils.py +6 -1
- sonolus/backend/visitor.py +72 -23
- sonolus/build/cli.py +6 -1
- sonolus/build/engine.py +1 -1
- sonolus/script/archetype.py +52 -24
- sonolus/script/array.py +20 -8
- sonolus/script/array_like.py +30 -3
- sonolus/script/containers.py +27 -7
- sonolus/script/debug.py +66 -8
- sonolus/script/globals.py +17 -0
- sonolus/script/internal/builtin_impls.py +12 -8
- sonolus/script/internal/context.py +55 -1
- sonolus/script/internal/range.py +25 -2
- sonolus/script/internal/simulation_context.py +131 -0
- sonolus/script/internal/tuple_impl.py +18 -11
- sonolus/script/interval.py +60 -2
- sonolus/script/iterator.py +3 -2
- sonolus/script/num.py +11 -2
- sonolus/script/options.py +24 -1
- sonolus/script/quad.py +41 -3
- sonolus/script/record.py +24 -3
- sonolus/script/runtime.py +411 -0
- sonolus/script/stream.py +133 -16
- sonolus/script/transform.py +291 -2
- sonolus/script/values.py +9 -3
- sonolus/script/vec.py +14 -2
- {sonolus_py-0.3.1.dist-info → sonolus_py-0.3.3.dist-info}/METADATA +1 -1
- {sonolus_py-0.3.1.dist-info → sonolus_py-0.3.3.dist-info}/RECORD +36 -35
- {sonolus_py-0.3.1.dist-info → sonolus_py-0.3.3.dist-info}/WHEEL +0 -0
- {sonolus_py-0.3.1.dist-info → sonolus_py-0.3.3.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.3.1.dist-info → sonolus_py-0.3.3.dist-info}/licenses/LICENSE +0 -0
sonolus/script/stream.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from math import inf
|
|
3
4
|
from typing import cast, dataclass_transform
|
|
4
5
|
|
|
5
|
-
from sonolus.backend.ir import IRExpr, IRInstr, IRPureInstr
|
|
6
|
+
from sonolus.backend.ir import IRConst, IRExpr, IRInstr, IRPureInstr
|
|
6
7
|
from sonolus.backend.mode import Mode
|
|
7
8
|
from sonolus.backend.ops import Op
|
|
8
9
|
from sonolus.script.internal.context import ctx
|
|
@@ -14,6 +15,7 @@ from sonolus.script.internal.value import BackingValue, Value
|
|
|
14
15
|
from sonolus.script.iterator import SonolusIterator
|
|
15
16
|
from sonolus.script.num import Num
|
|
16
17
|
from sonolus.script.record import Record
|
|
18
|
+
from sonolus.script.runtime import prev_time, time
|
|
17
19
|
from sonolus.script.values import sizeof
|
|
18
20
|
|
|
19
21
|
|
|
@@ -174,10 +176,11 @@ class _SparseStreamBacking(BackingValue):
|
|
|
174
176
|
)
|
|
175
177
|
|
|
176
178
|
|
|
177
|
-
class Stream[T](Record
|
|
179
|
+
class Stream[T](Record):
|
|
178
180
|
"""Represents a stream.
|
|
179
181
|
|
|
180
|
-
Most users should use `@stream
|
|
182
|
+
Most users should use [`@streams`][sonolus.script.stream.streams] to declare streams and stream groups rather than
|
|
183
|
+
using this class directly.
|
|
181
184
|
|
|
182
185
|
If used directly, it is important that streams do not overlap. No other streams should have an offset in
|
|
183
186
|
`range(self.offset, self.offset + max(1, sizeof(self.element_type())))`, or they will overlap and interfere
|
|
@@ -248,6 +251,12 @@ class Stream[T](Record, BackingValue):
|
|
|
248
251
|
_check_can_read_stream()
|
|
249
252
|
return _stream_get_next_key(self.offset, key)
|
|
250
253
|
|
|
254
|
+
def next_key_or_default(self, key: int | float, default: int | float) -> int:
|
|
255
|
+
"""Get the next key, or the default value if there is no next key."""
|
|
256
|
+
_check_can_read_stream()
|
|
257
|
+
next_key = self.next_key(key)
|
|
258
|
+
return next_key if next_key > key else default
|
|
259
|
+
|
|
251
260
|
def previous_key(self, key: int | float) -> int:
|
|
252
261
|
"""Get the previous key, or the key unchanged if it is the first key or the stream is empty.
|
|
253
262
|
|
|
@@ -256,6 +265,12 @@ class Stream[T](Record, BackingValue):
|
|
|
256
265
|
_check_can_read_stream()
|
|
257
266
|
return _stream_get_previous_key(self.offset, key)
|
|
258
267
|
|
|
268
|
+
def previous_key_or_default(self, key: int | float, default: int | float) -> int:
|
|
269
|
+
"""Get the previous key, or the default value if there is no previous key."""
|
|
270
|
+
_check_can_read_stream()
|
|
271
|
+
previous_key = self.previous_key(key)
|
|
272
|
+
return previous_key if previous_key < key else default
|
|
273
|
+
|
|
259
274
|
def has_next_key(self, key: int | float) -> bool:
|
|
260
275
|
"""Check if there is a next key after the given key in the stream."""
|
|
261
276
|
_check_can_read_stream()
|
|
@@ -302,6 +317,14 @@ class Stream[T](Record, BackingValue):
|
|
|
302
317
|
_check_can_read_stream()
|
|
303
318
|
return self[self.next_key_inclusive(key)]
|
|
304
319
|
|
|
320
|
+
def get_previous_inclusive(self, key: int | float) -> T:
|
|
321
|
+
"""Get the value corresponding to the previous key, or the value at the given key if it is in the stream.
|
|
322
|
+
|
|
323
|
+
Equivalent to `self[self.previous_key_inclusive(key)]`.
|
|
324
|
+
"""
|
|
325
|
+
_check_can_read_stream()
|
|
326
|
+
return self[self.previous_key_inclusive(key)]
|
|
327
|
+
|
|
305
328
|
def iter_items_from(self, start: int | float, /) -> SonolusIterator[tuple[int | float, T]]:
|
|
306
329
|
"""Iterate over the items in the stream in ascending order starting from the given key.
|
|
307
330
|
|
|
@@ -317,6 +340,22 @@ class Stream[T](Record, BackingValue):
|
|
|
317
340
|
_check_can_read_stream()
|
|
318
341
|
return _StreamAscIterator(self, self.next_key_inclusive(start))
|
|
319
342
|
|
|
343
|
+
def iter_items_since_previous_frame(self) -> SonolusIterator[tuple[int | float, T]]:
|
|
344
|
+
"""Iterate over the items in the stream since the last frame.
|
|
345
|
+
|
|
346
|
+
This is a convenience method that iterates over the items in the stream occurring after the time of the
|
|
347
|
+
previous frame and up to and including the current time.
|
|
348
|
+
|
|
349
|
+
Usage:
|
|
350
|
+
```python
|
|
351
|
+
stream = ...
|
|
352
|
+
for key, value in stream.iter_items_since_previous_frame():
|
|
353
|
+
do_something(key, value)
|
|
354
|
+
```
|
|
355
|
+
"""
|
|
356
|
+
_check_can_read_stream()
|
|
357
|
+
return _StreamBoundedAscIterator(self, self.next_key(prev_time()), time())
|
|
358
|
+
|
|
320
359
|
def iter_items_from_desc(self, start: int | float, /) -> SonolusIterator[tuple[int | float, T]]:
|
|
321
360
|
"""Iterate over the items in the stream in descending order starting from the given key.
|
|
322
361
|
|
|
@@ -347,6 +386,22 @@ class Stream[T](Record, BackingValue):
|
|
|
347
386
|
_check_can_read_stream()
|
|
348
387
|
return _StreamAscKeyIterator(self, self.next_key_inclusive(start))
|
|
349
388
|
|
|
389
|
+
def iter_keys_since_previous_frame(self) -> SonolusIterator[int | float]:
|
|
390
|
+
"""Iterate over the keys in the stream since the last frame.
|
|
391
|
+
|
|
392
|
+
This is a convenience method that iterates over the keys in the stream occurring after the time of the
|
|
393
|
+
previous frame and up to and including the current time.
|
|
394
|
+
|
|
395
|
+
Usage:
|
|
396
|
+
```python
|
|
397
|
+
stream = ...
|
|
398
|
+
for key in stream.iter_keys_since_previous_frame():
|
|
399
|
+
do_something(key)
|
|
400
|
+
```
|
|
401
|
+
"""
|
|
402
|
+
_check_can_read_stream()
|
|
403
|
+
return _StreamBoundedAscKeyIterator(self, self.next_key(prev_time()), time())
|
|
404
|
+
|
|
350
405
|
def iter_keys_from_desc(self, start: int | float, /) -> SonolusIterator[int | float]:
|
|
351
406
|
"""Iterate over the keys in the stream in descending order starting from the given key.
|
|
352
407
|
|
|
@@ -377,6 +432,22 @@ class Stream[T](Record, BackingValue):
|
|
|
377
432
|
_check_can_read_stream()
|
|
378
433
|
return _StreamAscValueIterator(self, self.next_key_inclusive(start))
|
|
379
434
|
|
|
435
|
+
def iter_values_since_previous_frame(self) -> SonolusIterator[T]:
|
|
436
|
+
"""Iterate over the values in the stream since the last frame.
|
|
437
|
+
|
|
438
|
+
This is a convenience method that iterates over the values in the stream occurring after the time of the
|
|
439
|
+
previous frame and up to and including the current time.
|
|
440
|
+
|
|
441
|
+
Usage:
|
|
442
|
+
```python
|
|
443
|
+
stream = ...
|
|
444
|
+
for value in stream.iter_values_since_previous_frame():
|
|
445
|
+
do_something(value)
|
|
446
|
+
```
|
|
447
|
+
"""
|
|
448
|
+
_check_can_read_stream()
|
|
449
|
+
return _StreamBoundedAscValueIterator(self, self.next_key(prev_time()), time())
|
|
450
|
+
|
|
380
451
|
def iter_values_from_desc(self, start: int | float, /) -> SonolusIterator[T]:
|
|
381
452
|
"""Iterate over the values in the stream in descending order starting from the given key.
|
|
382
453
|
|
|
@@ -396,7 +467,8 @@ class Stream[T](Record, BackingValue):
|
|
|
396
467
|
class StreamGroup[T, Size](Record):
|
|
397
468
|
"""Represents a group of streams.
|
|
398
469
|
|
|
399
|
-
Most users should use `@stream
|
|
470
|
+
Most users should use [`@streams`][sonolus.script.stream.streams] to declare stream groups rather than using this
|
|
471
|
+
class directly.
|
|
400
472
|
|
|
401
473
|
Usage:
|
|
402
474
|
Declaring a stream group:
|
|
@@ -444,13 +516,28 @@ class _StreamAscIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
|
444
516
|
current_key: int | float
|
|
445
517
|
|
|
446
518
|
def has_next(self) -> bool:
|
|
447
|
-
return self.
|
|
519
|
+
return self.current_key in self.stream
|
|
520
|
+
|
|
521
|
+
def get(self) -> tuple[int | float, T]:
|
|
522
|
+
return self.current_key, self.stream[self.current_key]
|
|
523
|
+
|
|
524
|
+
def advance(self):
|
|
525
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
class _StreamBoundedAscIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
529
|
+
stream: Stream[T]
|
|
530
|
+
current_key: int | float
|
|
531
|
+
end_key: int | float
|
|
532
|
+
|
|
533
|
+
def has_next(self) -> bool:
|
|
534
|
+
return self.current_key in self.stream and self.current_key <= self.end_key
|
|
448
535
|
|
|
449
536
|
def get(self) -> tuple[int | float, T]:
|
|
450
537
|
return self.current_key, self.stream[self.current_key]
|
|
451
538
|
|
|
452
539
|
def advance(self):
|
|
453
|
-
self.current_key = self.stream.
|
|
540
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
454
541
|
|
|
455
542
|
|
|
456
543
|
class _StreamDescIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
@@ -458,13 +545,13 @@ class _StreamDescIterator[T](Record, SonolusIterator[tuple[int | float, T]]):
|
|
|
458
545
|
current_key: int | float
|
|
459
546
|
|
|
460
547
|
def has_next(self) -> bool:
|
|
461
|
-
return self.
|
|
548
|
+
return self.current_key in self.stream
|
|
462
549
|
|
|
463
550
|
def get(self) -> tuple[int | float, T]:
|
|
464
551
|
return self.current_key, self.stream[self.current_key]
|
|
465
552
|
|
|
466
553
|
def advance(self):
|
|
467
|
-
self.current_key = self.stream.
|
|
554
|
+
self.current_key = self.stream.previous_key_or_default(self.current_key, -inf)
|
|
468
555
|
|
|
469
556
|
|
|
470
557
|
class _StreamAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
@@ -472,13 +559,28 @@ class _StreamAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
|
472
559
|
current_key: int | float
|
|
473
560
|
|
|
474
561
|
def has_next(self) -> bool:
|
|
475
|
-
return self.
|
|
562
|
+
return self.current_key in self.stream
|
|
563
|
+
|
|
564
|
+
def get(self) -> int | float:
|
|
565
|
+
return self.current_key
|
|
566
|
+
|
|
567
|
+
def advance(self):
|
|
568
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
class _StreamBoundedAscKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
572
|
+
stream: Stream[T]
|
|
573
|
+
current_key: int | float
|
|
574
|
+
end_key: int | float
|
|
575
|
+
|
|
576
|
+
def has_next(self) -> bool:
|
|
577
|
+
return self.current_key in self.stream and self.current_key <= self.end_key
|
|
476
578
|
|
|
477
579
|
def get(self) -> int | float:
|
|
478
580
|
return self.current_key
|
|
479
581
|
|
|
480
582
|
def advance(self):
|
|
481
|
-
self.current_key = self.stream.
|
|
583
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
482
584
|
|
|
483
585
|
|
|
484
586
|
class _StreamDescKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
@@ -486,13 +588,13 @@ class _StreamDescKeyIterator[T](Record, SonolusIterator[int | float]):
|
|
|
486
588
|
current_key: int | float
|
|
487
589
|
|
|
488
590
|
def has_next(self) -> bool:
|
|
489
|
-
return self.
|
|
591
|
+
return self.current_key in self.stream
|
|
490
592
|
|
|
491
593
|
def get(self) -> int | float:
|
|
492
594
|
return self.current_key
|
|
493
595
|
|
|
494
596
|
def advance(self):
|
|
495
|
-
self.current_key = self.stream.
|
|
597
|
+
self.current_key = self.stream.previous_key_or_default(self.current_key, -inf)
|
|
496
598
|
|
|
497
599
|
|
|
498
600
|
class _StreamAscValueIterator[T](Record, SonolusIterator[T]):
|
|
@@ -500,13 +602,28 @@ class _StreamAscValueIterator[T](Record, SonolusIterator[T]):
|
|
|
500
602
|
current_key: int | float
|
|
501
603
|
|
|
502
604
|
def has_next(self) -> bool:
|
|
503
|
-
return self.
|
|
605
|
+
return self.current_key in self.stream
|
|
606
|
+
|
|
607
|
+
def get(self) -> T:
|
|
608
|
+
return self.stream[self.current_key]
|
|
609
|
+
|
|
610
|
+
def advance(self):
|
|
611
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
class _StreamBoundedAscValueIterator[T](Record, SonolusIterator[T]):
|
|
615
|
+
stream: Stream[T]
|
|
616
|
+
current_key: int | float
|
|
617
|
+
end_key: int | float
|
|
618
|
+
|
|
619
|
+
def has_next(self) -> bool:
|
|
620
|
+
return self.current_key in self.stream and self.current_key <= self.end_key
|
|
504
621
|
|
|
505
622
|
def get(self) -> T:
|
|
506
623
|
return self.stream[self.current_key]
|
|
507
624
|
|
|
508
625
|
def advance(self):
|
|
509
|
-
self.current_key = self.stream.
|
|
626
|
+
self.current_key = self.stream.next_key_or_default(self.current_key, inf)
|
|
510
627
|
|
|
511
628
|
|
|
512
629
|
class _StreamDescValueIterator[T](Record, SonolusIterator[T]):
|
|
@@ -514,13 +631,13 @@ class _StreamDescValueIterator[T](Record, SonolusIterator[T]):
|
|
|
514
631
|
current_key: int | float
|
|
515
632
|
|
|
516
633
|
def has_next(self) -> bool:
|
|
517
|
-
return self.
|
|
634
|
+
return self.current_key in self.stream
|
|
518
635
|
|
|
519
636
|
def get(self) -> T:
|
|
520
637
|
return self.stream[self.current_key]
|
|
521
638
|
|
|
522
639
|
def advance(self):
|
|
523
|
-
self.current_key = self.stream.
|
|
640
|
+
self.current_key = self.stream.previous_key_or_default(self.current_key, -inf)
|
|
524
641
|
|
|
525
642
|
|
|
526
643
|
@native_function(Op.StreamGetNextKey)
|
sonolus/script/transform.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from math import cos, sin
|
|
2
2
|
from typing import Self
|
|
3
3
|
|
|
4
|
+
from sonolus.script.interval import lerp, remap
|
|
4
5
|
from sonolus.script.quad import Quad, QuadLike
|
|
5
6
|
from sonolus.script.record import Record
|
|
6
7
|
from sonolus.script.vec import Vec2
|
|
@@ -136,7 +137,7 @@ class Transform2d(Record):
|
|
|
136
137
|
"""Rotate about the origin and return a new transform.
|
|
137
138
|
|
|
138
139
|
Args:
|
|
139
|
-
angle: The angle of rotation in radians.
|
|
140
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
140
141
|
|
|
141
142
|
Returns:
|
|
142
143
|
A new transform after rotation.
|
|
@@ -159,7 +160,7 @@ class Transform2d(Record):
|
|
|
159
160
|
"""Rotate about the pivot and return a new transform.
|
|
160
161
|
|
|
161
162
|
Args:
|
|
162
|
-
angle: The angle of rotation in radians.
|
|
163
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
163
164
|
pivot: The pivot point for rotation.
|
|
164
165
|
|
|
165
166
|
Returns:
|
|
@@ -396,3 +397,291 @@ class Transform2d(Record):
|
|
|
396
397
|
tl=self.transform_vec(quad.tl),
|
|
397
398
|
tr=self.transform_vec(quad.tr),
|
|
398
399
|
)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
class InvertibleTransform2d(Record):
|
|
403
|
+
"""A transformation matrix for 2D points that can be inverted.
|
|
404
|
+
|
|
405
|
+
Usage:
|
|
406
|
+
```python
|
|
407
|
+
InvertibleTransform2d.new()
|
|
408
|
+
```
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
forward: Transform2d
|
|
412
|
+
inverse: Transform2d
|
|
413
|
+
|
|
414
|
+
@classmethod
|
|
415
|
+
def new(cls) -> Self:
|
|
416
|
+
"""Create a new identity transform.
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
A new identity transform.
|
|
420
|
+
"""
|
|
421
|
+
return cls(
|
|
422
|
+
forward=Transform2d.new(),
|
|
423
|
+
inverse=Transform2d.new(),
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
def translate(self, translation: Vec2, /) -> Self:
|
|
427
|
+
"""Translate along the x and y axes and return a new transform.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
translation: The translation vector.
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
A new invertible transform after translation.
|
|
434
|
+
"""
|
|
435
|
+
return InvertibleTransform2d(
|
|
436
|
+
forward=self.forward.translate(translation),
|
|
437
|
+
inverse=Transform2d.new().translate(-translation).compose(self.inverse),
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
def scale(self, factor: Vec2, /) -> Self:
|
|
441
|
+
"""Scale about the origin and return a new transform.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
factor: The scale factor vector.
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
A new invertible transform after scaling.
|
|
448
|
+
"""
|
|
449
|
+
return InvertibleTransform2d(
|
|
450
|
+
forward=self.forward.scale(factor),
|
|
451
|
+
inverse=Transform2d.new().scale(Vec2.one() / factor).compose(self.inverse),
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
def scale_about(self, factor: Vec2, /, pivot: Vec2) -> Self:
|
|
455
|
+
"""Scale about the pivot and return a new transform.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
factor: The scale factor vector.
|
|
459
|
+
pivot: The pivot point for scaling.
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
A new invertible transform after scaling.
|
|
463
|
+
"""
|
|
464
|
+
return InvertibleTransform2d(
|
|
465
|
+
forward=self.forward.scale_about(factor, pivot),
|
|
466
|
+
inverse=Transform2d.new().scale_about(Vec2.one() / factor, pivot).compose(self.inverse),
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
def rotate(self, angle: float, /) -> Self:
|
|
470
|
+
"""Rotate about the origin and return a new transform.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
A new invertible transform after rotation.
|
|
477
|
+
"""
|
|
478
|
+
return InvertibleTransform2d(
|
|
479
|
+
forward=self.forward.rotate(angle),
|
|
480
|
+
inverse=Transform2d.new().rotate(-angle).compose(self.inverse),
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
def rotate_about(self, angle: float, /, pivot: Vec2) -> Self:
|
|
484
|
+
"""Rotate about the pivot and return a new transform.
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
angle: The angle of rotation in radians. Positive angles rotate counterclockwise.
|
|
488
|
+
pivot: The pivot point for rotation.
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
A new invertible transform after rotation.
|
|
492
|
+
"""
|
|
493
|
+
return InvertibleTransform2d(
|
|
494
|
+
forward=self.forward.rotate_about(angle, pivot),
|
|
495
|
+
inverse=Transform2d.new().rotate_about(-angle, pivot).compose(self.inverse),
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
def shear_x(self, m: float, /) -> Self:
|
|
499
|
+
"""Shear along the x-axis and return a new transform.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
m: The shear factor along the x-axis.
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
A new invertible transform after shearing.
|
|
506
|
+
"""
|
|
507
|
+
return InvertibleTransform2d(
|
|
508
|
+
forward=self.forward.shear_x(m),
|
|
509
|
+
inverse=Transform2d.new().shear_x(-m).compose(self.inverse),
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
def shear_y(self, m: float, /) -> Self:
|
|
513
|
+
"""Shear along the y-axis and return a new transform.
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
m: The shear factor along the y-axis.
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
A new invertible transform after shearing.
|
|
520
|
+
"""
|
|
521
|
+
return InvertibleTransform2d(
|
|
522
|
+
forward=self.forward.shear_y(m),
|
|
523
|
+
inverse=Transform2d.new().shear_y(-m).compose(self.inverse),
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
def simple_perspective_x(self, x: float, /) -> Self:
|
|
527
|
+
"""Apply perspective along the x-axis with vanishing point at the given x coordinate and return a new transform.
|
|
528
|
+
|
|
529
|
+
Args:
|
|
530
|
+
x: The x coordinate of the vanishing point.
|
|
531
|
+
|
|
532
|
+
Returns:
|
|
533
|
+
A new invertible transform after applying perspective.
|
|
534
|
+
"""
|
|
535
|
+
return InvertibleTransform2d(
|
|
536
|
+
forward=self.forward.simple_perspective_x(x),
|
|
537
|
+
inverse=Transform2d.new().simple_perspective_x(-x).compose(self.inverse),
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
def simple_perspective_y(self, y: float, /) -> Self:
|
|
541
|
+
"""Apply perspective along the y-axis with vanishing point at the given y coordinate and return a new transform.
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
y: The y coordinate of the vanishing point.
|
|
545
|
+
|
|
546
|
+
Returns:
|
|
547
|
+
A new invertible transform after applying perspective.
|
|
548
|
+
"""
|
|
549
|
+
return InvertibleTransform2d(
|
|
550
|
+
forward=self.forward.simple_perspective_y(y),
|
|
551
|
+
inverse=Transform2d.new().simple_perspective_y(-y).compose(self.inverse),
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
def perspective_x(self, foreground_x: float, vanishing_point: Vec2, /) -> Self:
|
|
555
|
+
"""Apply a perspective transformation along the x-axis and return a new transform.
|
|
556
|
+
|
|
557
|
+
Args:
|
|
558
|
+
foreground_x: The foreground x-coordinate.
|
|
559
|
+
vanishing_point: The vanishing point vector.
|
|
560
|
+
|
|
561
|
+
Returns:
|
|
562
|
+
A new invertible transform after applying perspective.
|
|
563
|
+
"""
|
|
564
|
+
return InvertibleTransform2d(
|
|
565
|
+
forward=self.forward.perspective_x(foreground_x, vanishing_point),
|
|
566
|
+
inverse=Transform2d.new().inverse_perspective_x(foreground_x, vanishing_point).compose(self.inverse),
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
def perspective_y(self, foreground_y: float, vanishing_point: Vec2, /) -> Self:
|
|
570
|
+
"""Apply a perspective transformation along the y-axis and return a new transform.
|
|
571
|
+
|
|
572
|
+
Args:
|
|
573
|
+
foreground_y: The foreground y-coordinate.
|
|
574
|
+
vanishing_point: The vanishing point vector.
|
|
575
|
+
|
|
576
|
+
Returns:
|
|
577
|
+
A new invertible transform after applying perspective.
|
|
578
|
+
"""
|
|
579
|
+
return InvertibleTransform2d(
|
|
580
|
+
forward=self.forward.perspective_y(foreground_y, vanishing_point),
|
|
581
|
+
inverse=Transform2d.new().inverse_perspective_y(foreground_y, vanishing_point).compose(self.inverse),
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
def normalize(self) -> Self:
|
|
585
|
+
"""Normalize the transform to have a 1 in the bottom right corner and return a new transform.
|
|
586
|
+
|
|
587
|
+
This may fail in some special cases involving perspective transformations where the bottom right corner is 0.
|
|
588
|
+
|
|
589
|
+
Returns:
|
|
590
|
+
A new normalized invertible transform.
|
|
591
|
+
"""
|
|
592
|
+
return InvertibleTransform2d(
|
|
593
|
+
forward=self.forward.normalize(),
|
|
594
|
+
inverse=self.inverse.normalize(),
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
def compose(self, other: Self, /) -> Self:
|
|
598
|
+
"""Compose with another invertible transform which is applied after this transform and return a new transform.
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
other: The other invertible transform to compose with.
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
A new invertible transform resulting from the composition.
|
|
605
|
+
"""
|
|
606
|
+
return InvertibleTransform2d(
|
|
607
|
+
forward=self.forward.compose(other.forward),
|
|
608
|
+
inverse=other.inverse.compose(self.inverse),
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
def compose_before(self, other: Self, /) -> Self:
|
|
612
|
+
"""Compose with another invertible transform which is applied before this transform and return a new transform.
|
|
613
|
+
|
|
614
|
+
Args:
|
|
615
|
+
other: The other invertible transform to compose with.
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
A new invertible transform resulting from the composition.
|
|
619
|
+
"""
|
|
620
|
+
return other.compose(self)
|
|
621
|
+
|
|
622
|
+
def transform_vec(self, v: Vec2) -> Vec2:
|
|
623
|
+
"""Transform a [`Vec2`][sonolus.script.vec.Vec2] and return a new [`Vec2`][sonolus.script.vec.Vec2].
|
|
624
|
+
|
|
625
|
+
Args:
|
|
626
|
+
v: The vector to transform.
|
|
627
|
+
|
|
628
|
+
Returns:
|
|
629
|
+
A new transformed vector.
|
|
630
|
+
"""
|
|
631
|
+
return self.forward.transform_vec(v)
|
|
632
|
+
|
|
633
|
+
def inverse_transform_vec(self, v: Vec2) -> Vec2:
|
|
634
|
+
"""Inverse transform a [`Vec2`][sonolus.script.vec.Vec2] and return a new [`Vec2`][sonolus.script.vec.Vec2].
|
|
635
|
+
|
|
636
|
+
Args:
|
|
637
|
+
v: The vector to inverse transform.
|
|
638
|
+
|
|
639
|
+
Returns:
|
|
640
|
+
A new inverse transformed vector.
|
|
641
|
+
"""
|
|
642
|
+
return self.inverse.transform_vec(v)
|
|
643
|
+
|
|
644
|
+
def transform_quad(self, quad: QuadLike) -> Quad:
|
|
645
|
+
"""Transform a [`Quad`][sonolus.script.quad.Quad] and return a new [`Quad`][sonolus.script.quad.Quad].
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
quad: The quad to transform.
|
|
649
|
+
|
|
650
|
+
Returns:
|
|
651
|
+
A new transformed quad.
|
|
652
|
+
"""
|
|
653
|
+
return self.forward.transform_quad(quad)
|
|
654
|
+
|
|
655
|
+
def inverse_transform_quad(self, quad: QuadLike) -> Quad:
|
|
656
|
+
"""Inverse transform a [`Quad`][sonolus.script.quad.Quad] and return a new [`Quad`][sonolus.script.quad.Quad].
|
|
657
|
+
|
|
658
|
+
Args:
|
|
659
|
+
quad: The quad to inverse transform.
|
|
660
|
+
|
|
661
|
+
Returns:
|
|
662
|
+
A new inverse transformed quad.
|
|
663
|
+
"""
|
|
664
|
+
return self.inverse.transform_quad(quad)
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def perspective_approach(
|
|
668
|
+
distance_ratio: float,
|
|
669
|
+
progress: float,
|
|
670
|
+
) -> float:
|
|
671
|
+
"""Calculate the perspective correct approach curve given the initial distance, target distance, and progress.
|
|
672
|
+
|
|
673
|
+
For typical engines with stage tilt, distance_ratio is the displayed width of a lane at the judge line divided
|
|
674
|
+
by the displayed width of a lane at note spawn. For flat stages, this will be 1.0, and this function would simply
|
|
675
|
+
return progress unchanged.
|
|
676
|
+
|
|
677
|
+
Args:
|
|
678
|
+
distance_ratio: The ratio of the distance at note spawn to the distance at the judge line.
|
|
679
|
+
progress: The progress value, where 0 corresponds to note spawn and 1 corresponds to the judge line.
|
|
680
|
+
|
|
681
|
+
Returns:
|
|
682
|
+
The perspective-corrected progress value.
|
|
683
|
+
"""
|
|
684
|
+
d_0 = distance_ratio
|
|
685
|
+
d_1 = 1.0
|
|
686
|
+
d = max(lerp(d_0, d_1, progress), 1e-6) # Avoid a zero or negative distance
|
|
687
|
+
return remap(1 / d_0, 1 / d_1, 0, 1, 1 / d)
|
sonolus/script/values.py
CHANGED
|
@@ -19,13 +19,19 @@ def alloc[T](type_: type[T]) -> T:
|
|
|
19
19
|
|
|
20
20
|
@meta_fn
|
|
21
21
|
def zeros[T](type_: type[T]) -> T:
|
|
22
|
-
"""Make a new instance of the given type initialized with zeros.
|
|
22
|
+
"""Make a new instance of the given type initialized with zeros.
|
|
23
|
+
|
|
24
|
+
Generally works the same as the unary `+` operator on record and array types.
|
|
25
|
+
"""
|
|
23
26
|
return validate_concrete_type(type_)._zero_()
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
@meta_fn
|
|
27
30
|
def copy[T](value: T) -> T:
|
|
28
|
-
"""Make a deep copy of the given value.
|
|
31
|
+
"""Make a deep copy of the given value.
|
|
32
|
+
|
|
33
|
+
Generally works the same as the unary `+` operator on records and arrays.
|
|
34
|
+
"""
|
|
29
35
|
value = validate_value(value)
|
|
30
36
|
if ctx():
|
|
31
37
|
return value._copy_()
|
|
@@ -34,7 +40,7 @@ def copy[T](value: T) -> T:
|
|
|
34
40
|
|
|
35
41
|
|
|
36
42
|
def swap[T](a: T, b: T):
|
|
37
|
-
"""Swap the values of the two
|
|
43
|
+
"""Swap the values of the two provided mutable values."""
|
|
38
44
|
temp = copy(a)
|
|
39
45
|
a @= b
|
|
40
46
|
b @= temp
|
sonolus/script/vec.py
CHANGED
|
@@ -73,6 +73,18 @@ class Vec2(Record):
|
|
|
73
73
|
"""
|
|
74
74
|
return cls(x=1, y=0)
|
|
75
75
|
|
|
76
|
+
@classmethod
|
|
77
|
+
def unit(cls, angle: Num) -> Self:
|
|
78
|
+
"""Return a unit vector (magnitude 1) at a given angle in radians.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
angle: The angle in radians.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
A new unit vector at the specified angle.
|
|
85
|
+
"""
|
|
86
|
+
return Vec2(x=cos(angle), y=sin(angle))
|
|
87
|
+
|
|
76
88
|
@property
|
|
77
89
|
def magnitude(self) -> Num:
|
|
78
90
|
"""Calculate the magnitude (length) of the vector.
|
|
@@ -106,7 +118,7 @@ class Vec2(Record):
|
|
|
106
118
|
"""Rotate the vector by a given angle in radians and return a new vector.
|
|
107
119
|
|
|
108
120
|
Args:
|
|
109
|
-
angle: The angle to rotate the vector by, in radians.
|
|
121
|
+
angle: The angle to rotate the vector by, in radians. Positive angles rotate counterclockwise.
|
|
110
122
|
|
|
111
123
|
Returns:
|
|
112
124
|
A new vector rotated by the given angle.
|
|
@@ -120,7 +132,7 @@ class Vec2(Record):
|
|
|
120
132
|
"""Rotate the vector about a pivot by a given angle in radians and return a new vector.
|
|
121
133
|
|
|
122
134
|
Args:
|
|
123
|
-
angle: The angle to rotate the vector by, in radians.
|
|
135
|
+
angle: The angle to rotate the vector by, in radians. Positive angles rotate counterclockwise.
|
|
124
136
|
pivot: The pivot point to rotate about.
|
|
125
137
|
|
|
126
138
|
Returns:
|