sonolus.py 0.1.3__py3-none-any.whl → 0.1.5__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.

Files changed (90) hide show
  1. sonolus/backend/blocks.py +756 -756
  2. sonolus/backend/excepthook.py +37 -37
  3. sonolus/backend/finalize.py +77 -69
  4. sonolus/backend/interpret.py +7 -7
  5. sonolus/backend/ir.py +29 -3
  6. sonolus/backend/mode.py +24 -24
  7. sonolus/backend/node.py +40 -40
  8. sonolus/backend/ops.py +197 -197
  9. sonolus/backend/optimize/__init__.py +0 -0
  10. sonolus/backend/optimize/allocate.py +126 -0
  11. sonolus/backend/optimize/constant_evaluation.py +374 -0
  12. sonolus/backend/optimize/copy_coalesce.py +85 -0
  13. sonolus/backend/optimize/dead_code.py +185 -0
  14. sonolus/backend/optimize/dominance.py +96 -0
  15. sonolus/backend/{flow.py → optimize/flow.py} +122 -92
  16. sonolus/backend/optimize/inlining.py +137 -0
  17. sonolus/backend/optimize/liveness.py +177 -0
  18. sonolus/backend/optimize/optimize.py +44 -0
  19. sonolus/backend/optimize/passes.py +52 -0
  20. sonolus/backend/optimize/simplify.py +191 -0
  21. sonolus/backend/optimize/ssa.py +200 -0
  22. sonolus/backend/place.py +17 -25
  23. sonolus/backend/utils.py +58 -48
  24. sonolus/backend/visitor.py +1151 -882
  25. sonolus/build/cli.py +7 -1
  26. sonolus/build/compile.py +88 -90
  27. sonolus/build/engine.py +10 -5
  28. sonolus/build/level.py +24 -23
  29. sonolus/build/node.py +43 -43
  30. sonolus/script/archetype.py +438 -139
  31. sonolus/script/array.py +27 -10
  32. sonolus/script/array_like.py +297 -0
  33. sonolus/script/bucket.py +253 -191
  34. sonolus/script/containers.py +257 -51
  35. sonolus/script/debug.py +26 -10
  36. sonolus/script/easing.py +365 -0
  37. sonolus/script/effect.py +191 -131
  38. sonolus/script/engine.py +71 -4
  39. sonolus/script/globals.py +303 -269
  40. sonolus/script/instruction.py +205 -151
  41. sonolus/script/internal/__init__.py +5 -5
  42. sonolus/script/internal/builtin_impls.py +255 -144
  43. sonolus/script/{callbacks.py → internal/callbacks.py} +127 -127
  44. sonolus/script/internal/constant.py +139 -0
  45. sonolus/script/internal/context.py +26 -9
  46. sonolus/script/internal/descriptor.py +17 -17
  47. sonolus/script/internal/dict_impl.py +65 -0
  48. sonolus/script/internal/generic.py +6 -9
  49. sonolus/script/internal/impl.py +38 -13
  50. sonolus/script/internal/introspection.py +17 -14
  51. sonolus/script/internal/math_impls.py +121 -0
  52. sonolus/script/internal/native.py +40 -38
  53. sonolus/script/internal/random.py +67 -0
  54. sonolus/script/internal/range.py +81 -0
  55. sonolus/script/internal/transient.py +51 -0
  56. sonolus/script/internal/tuple_impl.py +113 -0
  57. sonolus/script/internal/value.py +3 -3
  58. sonolus/script/interval.py +338 -112
  59. sonolus/script/iterator.py +167 -214
  60. sonolus/script/level.py +24 -0
  61. sonolus/script/num.py +80 -48
  62. sonolus/script/options.py +257 -191
  63. sonolus/script/particle.py +190 -157
  64. sonolus/script/pointer.py +30 -30
  65. sonolus/script/print.py +102 -81
  66. sonolus/script/project.py +8 -0
  67. sonolus/script/quad.py +263 -0
  68. sonolus/script/record.py +47 -16
  69. sonolus/script/runtime.py +52 -1
  70. sonolus/script/sprite.py +418 -333
  71. sonolus/script/text.py +409 -407
  72. sonolus/script/timing.py +114 -42
  73. sonolus/script/transform.py +332 -48
  74. sonolus/script/ui.py +216 -160
  75. sonolus/script/values.py +6 -13
  76. sonolus/script/vec.py +196 -78
  77. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
  78. sonolus_py-0.1.5.dist-info/RECORD +89 -0
  79. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +1 -1
  80. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +21 -21
  81. sonolus/backend/allocate.py +0 -51
  82. sonolus/backend/optimize.py +0 -9
  83. sonolus/backend/passes.py +0 -6
  84. sonolus/backend/simplify.py +0 -30
  85. sonolus/script/comptime.py +0 -160
  86. sonolus/script/graphics.py +0 -150
  87. sonolus/script/math.py +0 -92
  88. sonolus/script/range.py +0 -58
  89. sonolus_py-0.1.3.dist-info/RECORD +0 -75
  90. {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
sonolus/script/timing.py CHANGED
@@ -1,42 +1,114 @@
1
- from sonolus.backend.ops import Op
2
- from sonolus.script.internal.native import native_function
3
-
4
-
5
- @native_function(Op.BeatToBPM)
6
- def beat_to_bpm(beat: float) -> float:
7
- raise NotImplementedError
8
-
9
-
10
- @native_function(Op.BeatToTime)
11
- def beat_to_time(beat: float) -> float:
12
- raise NotImplementedError
13
-
14
-
15
- @native_function(Op.BeatToStartingBeat)
16
- def beat_to_starting_beat(beat: float) -> float:
17
- raise NotImplementedError
18
-
19
-
20
- @native_function(Op.BeatToStartingTime)
21
- def beat_to_starting_time(beat: float) -> float:
22
- raise NotImplementedError
23
-
24
-
25
- @native_function(Op.TimeToScaledTime)
26
- def time_to_scaled_time(time: float) -> float:
27
- raise NotImplementedError
28
-
29
-
30
- @native_function(Op.TimeToStartingScaledTime)
31
- def time_to_starting_scaled_time(time: float) -> float:
32
- raise NotImplementedError
33
-
34
-
35
- @native_function(Op.TimeToStartingTime)
36
- def time_to_starting_time(time: float) -> float:
37
- raise NotImplementedError
38
-
39
-
40
- @native_function(Op.TimeToTimeScale)
41
- def time_to_timescale(time: float) -> float:
42
- raise NotImplementedError
1
+ from sonolus.backend.ops import Op
2
+ from sonolus.script.internal.native import native_function
3
+
4
+
5
+ @native_function(Op.BeatToBPM)
6
+ def beat_to_bpm(beat: float) -> float:
7
+ """Get the bpm at the given beat.
8
+
9
+ Args:
10
+ beat: The beat to get the bpm at.
11
+
12
+ Returns:
13
+ The bpm at the given beat.
14
+ """
15
+ raise NotImplementedError
16
+
17
+
18
+ @native_function(Op.BeatToTime)
19
+ def beat_to_time(beat: float) -> float:
20
+ """Get the time at the given beat.
21
+
22
+ Args:
23
+ beat: The beat to get the time at.
24
+
25
+ Returns:
26
+ The time at the given beat.
27
+ """
28
+ raise NotImplementedError
29
+
30
+
31
+ @native_function(Op.BeatToStartingBeat)
32
+ def beat_to_starting_beat(beat: float) -> float:
33
+ """Get the starting beat of the bpm section at the given beat.
34
+
35
+ I.e. the beat of the bpm change at or immediately before the given beat.
36
+
37
+ Args:
38
+ beat: The beat to get the starting beat of the bpm section at.
39
+
40
+ Returns:
41
+ The starting beat of the bpm section at the given beat.
42
+ """
43
+ raise NotImplementedError
44
+
45
+
46
+ @native_function(Op.BeatToStartingTime)
47
+ def beat_to_starting_time(beat: float) -> float:
48
+ """Get the starting time of the bpm section at the given beat.
49
+
50
+ I.e. the time of the bpm change at or immediately before the given beat.
51
+
52
+ Args:
53
+ beat: The beat to get the starting time of the bpm section at.
54
+
55
+ Returns:
56
+ The starting time of the bpm section at the given beat.
57
+ """
58
+ raise NotImplementedError
59
+
60
+
61
+ @native_function(Op.TimeToScaledTime)
62
+ def time_to_scaled_time(time: float) -> float:
63
+ """Get the scaled (timescale adjusted) time at the given time.
64
+
65
+ Args:
66
+ time: The time to get the scaled time at.
67
+
68
+ Returns:
69
+ The scaled (timescale adjusted) time at the given time.
70
+ """
71
+ raise NotImplementedError
72
+
73
+
74
+ @native_function(Op.TimeToStartingScaledTime)
75
+ def time_to_starting_scaled_time(time: float) -> float:
76
+ """Get the starting scaled (timescale adjusted) time at the given time.
77
+
78
+ I.e. the scaled time of the timescale change at or immediately before the given time.
79
+
80
+ Args:
81
+ time: The time to get the starting scaled time at.
82
+
83
+ Returns:
84
+ The starting scaled time at the given time.
85
+ """
86
+ raise NotImplementedError
87
+
88
+
89
+ @native_function(Op.TimeToStartingTime)
90
+ def time_to_starting_time(time: float) -> float:
91
+ """Get the starting time of the timescale section at the given time.
92
+
93
+ I.e. the time of the timescale change at or immediately before the given time.
94
+
95
+ Args:
96
+ time: The time to get the starting time of the timescale section at.
97
+
98
+ Returns:
99
+ The starting time of the timescale section at the given time.
100
+ """
101
+ raise NotImplementedError
102
+
103
+
104
+ @native_function(Op.TimeToTimeScale)
105
+ def time_to_timescale(time: float) -> float:
106
+ """Get the timescale at the given time.
107
+
108
+ Args:
109
+ time: The time to get the timescale at.
110
+
111
+ Returns:
112
+ The timescale at the given time.
113
+ """
114
+ raise NotImplementedError
@@ -1,13 +1,20 @@
1
- # fmt: off
1
+ from math import cos, sin
2
2
  from typing import Self
3
3
 
4
- from sonolus.script.graphics import Quad, QuadLike
5
- from sonolus.script.math import cos, sin
4
+ from sonolus.script.quad import Quad, QuadLike
6
5
  from sonolus.script.record import Record
7
6
  from sonolus.script.vec import Vec2
8
7
 
9
8
 
10
9
  class Transform2d(Record):
10
+ """A transformation matrix for 2D points.
11
+
12
+ Usage:
13
+ ```
14
+ Transform2d.new()
15
+ ```
16
+ """
17
+
11
18
  a00: float
12
19
  a01: float
13
20
  a02: float
@@ -16,96 +23,373 @@ class Transform2d(Record):
16
23
  a12: float
17
24
  a20: float
18
25
  a21: float
26
+ a22: float
19
27
 
20
28
  @classmethod
21
29
  def new(cls) -> Self:
30
+ """Create a new identity transform.
31
+
32
+ Returns:
33
+ A new identity transform.
34
+ """
22
35
  return cls(
23
- 1, 0, 0,
24
- 0, 1, 0,
25
- 0, 0,
36
+ 1,
37
+ 0,
38
+ 0,
39
+ 0,
40
+ 1,
41
+ 0,
42
+ 0,
43
+ 0,
44
+ 1,
26
45
  )
27
46
 
28
- def _compose(self,
29
- b00: float, b01: float, b02: float,
30
- b10: float, b11: float, b12: float,
31
- b20: float, b21: float,
47
+ def _compose(
48
+ self,
49
+ b00: float,
50
+ b01: float,
51
+ b02: float,
52
+ b10: float,
53
+ b11: float,
54
+ b12: float,
55
+ b20: float,
56
+ b21: float,
57
+ b22: float,
32
58
  ) -> Self:
33
- """Multiply the matrix with another matrix on the left."""
59
+ # Multiply by b on the left (b @ a)
34
60
  a00 = self.a00 * b00 + self.a10 * b01 + self.a20 * b02
35
61
  a01 = self.a01 * b00 + self.a11 * b01 + self.a21 * b02
36
- a02 = self.a02 * b00 + self.a12 * b01 + b02
62
+ a02 = self.a02 * b00 + self.a12 * b01 + self.a22 * b02
37
63
  a10 = self.a00 * b10 + self.a10 * b11 + self.a20 * b12
38
64
  a11 = self.a01 * b10 + self.a11 * b11 + self.a21 * b12
39
- a12 = self.a02 * b10 + self.a12 * b11 + b12
40
- a20 = self.a00 * b20 + self.a10 * b21 + b20
41
- a21 = self.a01 * b20 + self.a11 * b21 + b21
42
- a22 = self.a02 * b20 + self.a12 * b21 + 1
65
+ a12 = self.a02 * b10 + self.a12 * b11 + self.a22 * b12
66
+ a20 = self.a00 * b20 + self.a10 * b21 + self.a20 * b22
67
+ a21 = self.a01 * b20 + self.a11 * b21 + self.a21 * b22
68
+ a22 = self.a02 * b20 + self.a12 * b21 + self.a22 * b22
43
69
  return Transform2d(
44
- a00 / a22, a01 / a22, a02 / a22,
45
- a10 / a22, a11 / a22, a12 / a22,
46
- a20 / a22, a21 / a22,
70
+ a00,
71
+ a01,
72
+ a02,
73
+ a10,
74
+ a11,
75
+ a12,
76
+ a20,
77
+ a21,
78
+ a22,
47
79
  )
48
80
 
49
81
  def translate(self, translation: Vec2, /) -> Self:
82
+ """Translate along the x and y axes and return a new transform.
83
+
84
+ Args:
85
+ translation: The translation vector.
86
+
87
+ Returns:
88
+ A new transform after translation.
89
+ """
50
90
  return self._compose(
51
- 1, 0, translation.x,
52
- 0, 1, translation.y,
53
- 0, 0,
91
+ 1,
92
+ 0,
93
+ translation.x,
94
+ 0,
95
+ 1,
96
+ translation.y,
97
+ 0,
98
+ 0,
99
+ 1,
54
100
  )
55
101
 
56
102
  def scale(self, factor: Vec2, /) -> Self:
103
+ """Scale about the origin and return a new transform.
104
+
105
+ Args:
106
+ factor: The scale factor vector.
107
+
108
+ Returns:
109
+ A new transform after scaling.
110
+ """
57
111
  return self._compose(
58
- factor.x, 0, 0,
59
- 0, factor.y, 0,
60
- 0, 0,
112
+ factor.x,
113
+ 0,
114
+ 0,
115
+ 0,
116
+ factor.y,
117
+ 0,
118
+ 0,
119
+ 0,
120
+ 1,
61
121
  )
62
122
 
63
- def rotate(self, angle: float) -> Self:
123
+ def scale_about(self, factor: Vec2, /, pivot: Vec2) -> Self:
124
+ """Scale about the pivot and return a new transform.
125
+
126
+ Args:
127
+ factor: The scale factor vector.
128
+ pivot: The pivot point for scaling.
129
+
130
+ Returns:
131
+ A new transform after scaling.
132
+ """
133
+ return self.translate(-pivot).scale(factor).translate(pivot)
134
+
135
+ def rotate(self, angle: float, /) -> Self:
136
+ """Rotate about the origin and return a new transform.
137
+
138
+ Args:
139
+ angle: The angle of rotation in radians.
140
+
141
+ Returns:
142
+ A new transform after rotation.
143
+ """
64
144
  c = cos(angle)
65
145
  s = sin(angle)
66
146
  return self._compose(
67
- c, -s, 0,
68
- s, c, 0,
69
- 0, 0,
147
+ c,
148
+ -s,
149
+ 0,
150
+ s,
151
+ c,
152
+ 0,
153
+ 0,
154
+ 0,
155
+ 1,
156
+ )
157
+
158
+ def rotate_about(self, angle: float, /, pivot: Vec2) -> Self:
159
+ """Rotate about the pivot and return a new transform.
160
+
161
+ Args:
162
+ angle: The angle of rotation in radians.
163
+ pivot: The pivot point for rotation.
164
+
165
+ Returns:
166
+ A new transform after rotation.
167
+ """
168
+ return self.translate(-pivot).rotate(angle).translate(pivot)
169
+
170
+ def shear_x(self, m: float, /) -> Self:
171
+ """Shear along the x-axis and return a new transform.
172
+
173
+ Args:
174
+ m: The shear factor along the x-axis.
175
+
176
+ Returns:
177
+ A new transform after shearing.
178
+ """
179
+ return self._compose(
180
+ 1,
181
+ m,
182
+ 0,
183
+ 0,
184
+ 1,
185
+ 0,
186
+ 0,
187
+ 0,
188
+ 1,
70
189
  )
71
190
 
72
- def shear_x(self, m: float) -> Self:
73
- """Shear along the x-axis."""
191
+ def shear_y(self, m: float, /) -> Self:
192
+ """Shear along the y-axis and return a new transform.
193
+
194
+ Args:
195
+ m: The shear factor along the y-axis.
196
+
197
+ Returns:
198
+ A new transform after shearing.
199
+ """
74
200
  return self._compose(
75
- 1, m, 0,
76
- 0, 1, 0,
77
- 0, 0,
201
+ 1,
202
+ 0,
203
+ 0,
204
+ m,
205
+ 1,
206
+ 0,
207
+ 0,
208
+ 0,
209
+ 1,
78
210
  )
79
211
 
80
- def shear_y(self, m: float) -> Self:
81
- """Shear along the y-axis."""
212
+ def simple_perspective_x(self, x: float, /) -> Self:
213
+ """Apply perspective along the x-axis with vanishing point at the given x coordinate and return a new transform.
214
+
215
+ Args:
216
+ x: The x coordinate of the vanishing point.
217
+
218
+ Returns:
219
+ A new transform after applying perspective.
220
+ """
82
221
  return self._compose(
83
- 1, 0, 0,
84
- m, 1, 0,
85
- 0, 0,
222
+ 1,
223
+ 0,
224
+ 0,
225
+ 0,
226
+ 1,
227
+ 0,
228
+ 1 / x,
229
+ 0,
230
+ 1,
86
231
  )
87
232
 
88
- def perspective_vanish_y(self, y: float) -> Self:
233
+ def simple_perspective_y(self, y: float, /) -> Self:
234
+ """Apply perspective along the y-axis with vanishing point at the given y coordinate and return a new transform.
235
+
236
+ Args:
237
+ y: The y coordinate of the vanishing point.
238
+
239
+ Returns:
240
+ A new transform after applying perspective.
241
+ """
89
242
  return self._compose(
90
- 1, 0, 0,
91
- 0, 1, 0,
92
- 0, 1 / y,
243
+ 1,
244
+ 0,
245
+ 0,
246
+ 0,
247
+ 1,
248
+ 0,
249
+ 0,
250
+ 1 / y,
251
+ 1,
252
+ )
253
+
254
+ def perspective_x(self, foreground_x: float, vanishing_point: Vec2, /) -> Self:
255
+ """Apply a perspective transformation along the x-axis and return a new transform.
256
+
257
+ Args:
258
+ foreground_x: The foreground x-coordinate.
259
+ vanishing_point: The vanishing point vector.
260
+
261
+ Returns:
262
+ A new transform after applying perspective.
263
+ """
264
+ return (
265
+ self.simple_perspective_x(vanishing_point.x - foreground_x)
266
+ .shear_y(vanishing_point.y / (vanishing_point.x - foreground_x))
267
+ .translate(Vec2(foreground_x, 0))
268
+ )
269
+
270
+ def perspective_y(self, foreground_y: float, vanishing_point: Vec2, /) -> Self:
271
+ """Apply a perspective transformation along the y-axis and return a new transform.
272
+
273
+ Args:
274
+ foreground_y: The foreground y-coordinate.
275
+ vanishing_point: The vanishing point vector.
276
+
277
+ Returns:
278
+ A new transform after applying perspective.
279
+ """
280
+ return (
281
+ self.simple_perspective_y(vanishing_point.y - foreground_y)
282
+ .shear_x(vanishing_point.x / (vanishing_point.y - foreground_y))
283
+ .translate(Vec2(0, foreground_y))
284
+ )
285
+
286
+ def inverse_perspective_x(self, foreground_x: float, vanishing_point: Vec2, /) -> Self:
287
+ """Apply the inverse of a perspective transformation along the x-axis and return a new transform.
288
+
289
+ Args:
290
+ foreground_x: The foreground x-coordinate.
291
+ vanishing_point: The vanishing point vector.
292
+
293
+ Returns:
294
+ A new transform after applying the inverse perspective.
295
+ """
296
+ return (
297
+ self.translate(Vec2(-foreground_x, 0))
298
+ .shear_y(-vanishing_point.y / (vanishing_point.x - foreground_x))
299
+ .simple_perspective_x(-vanishing_point.x + foreground_x)
300
+ )
301
+
302
+ def inverse_perspective_y(self, foreground_y: float, vanishing_point: Vec2, /) -> Self:
303
+ """Apply the inverse of a perspective transformation along the y-axis and return a new transform.
304
+
305
+ Args:
306
+ foreground_y: The foreground y-coordinate.
307
+ vanishing_point: The vanishing point vector.
308
+
309
+ Returns:
310
+ A new transform after applying the inverse perspective.
311
+ """
312
+ return (
313
+ self.translate(Vec2(0, -foreground_y))
314
+ .shear_x(-vanishing_point.x / (vanishing_point.y - foreground_y))
315
+ .simple_perspective_y(-vanishing_point.y + foreground_y)
93
316
  )
94
317
 
95
- def compose(self, other: Self) -> Self:
318
+ def normalize(self) -> Self:
319
+ """Normalize the transform to have a 1 in the bottom right corner and return a new transform.
320
+
321
+ This may fail in some special cases involving perspective transformations where the bottom right corner is 0.
322
+
323
+ Returns:
324
+ A new normalized transform.
325
+ """
326
+ return Transform2d(
327
+ self.a00 / self.a22,
328
+ self.a01 / self.a22,
329
+ self.a02 / self.a22,
330
+ self.a10 / self.a22,
331
+ self.a11 / self.a22,
332
+ self.a12 / self.a22,
333
+ self.a20 / self.a22,
334
+ self.a21 / self.a22,
335
+ 1,
336
+ )
337
+
338
+ def compose(self, other: Self, /) -> Self:
339
+ """Compose with another transform which is applied after this transform and return a new transform.
340
+
341
+ Args:
342
+ other: The other transform to compose with.
343
+
344
+ Returns:
345
+ A new transform resulting from the composition.
346
+ """
96
347
  return self._compose(
97
- other.a00, other.a01, other.a02,
98
- other.a10, other.a11, other.a12,
99
- other.a20, other.a21,
348
+ other.a00,
349
+ other.a01,
350
+ other.a02,
351
+ other.a10,
352
+ other.a11,
353
+ other.a12,
354
+ other.a20,
355
+ other.a21,
356
+ other.a22,
100
357
  )
101
358
 
359
+ def compose_before(self, other: Self, /) -> Self:
360
+ """Compose with another transform which is applied before this transform and return a new transform.
361
+
362
+ Args:
363
+ other: The other transform to compose with.
364
+
365
+ Returns:
366
+ A new transform resulting from the composition.
367
+ """
368
+ return other.compose(self)
369
+
102
370
  def transform_vec(self, v: Vec2) -> Vec2:
371
+ """Transform a Vec2 and return a new Vec2.
372
+
373
+ Args:
374
+ v: The vector to transform.
375
+
376
+ Returns:
377
+ A new transformed vector.
378
+ """
103
379
  x = self.a00 * v.x + self.a01 * v.y + self.a02
104
380
  y = self.a10 * v.x + self.a11 * v.y + self.a12
105
- w = self.a20 * v.x + self.a21 * v.y + 1
381
+ w = self.a20 * v.x + self.a21 * v.y + self.a22
106
382
  return Vec2(x / w, y / w)
107
383
 
108
384
  def transform_quad(self, quad: QuadLike) -> Quad:
385
+ """Transform a Quad and return a new Quad.
386
+
387
+ Args:
388
+ quad: The quad to transform.
389
+
390
+ Returns:
391
+ A new transformed quad.
392
+ """
109
393
  return Quad(
110
394
  bl=self.transform_vec(quad.bl),
111
395
  br=self.transform_vec(quad.br),