pxt-common-packages 12.3.1 → 12.3.3

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 (41) hide show
  1. package/built/common-sim.d.ts +4 -0
  2. package/built/common-sim.js +275 -0
  3. package/libs/azureiot/built/debug/binary.js +461 -461
  4. package/libs/browser-events/browserEvents.ts +9 -9
  5. package/libs/color/built/debug/binary.js +8 -8
  6. package/libs/color-sensor/built/debug/binary.js +8 -8
  7. package/libs/controller/built/debug/binary.js +9969 -8494
  8. package/libs/controller---none/built/debug/binary.js +9948 -8473
  9. package/libs/datalogger/built/debug/binary.js +63 -63
  10. package/libs/edge-connector/built/debug/binary.js +8 -8
  11. package/libs/esp32/built/debug/binary.js +462 -462
  12. package/libs/game/_locales/game-strings.json +4 -0
  13. package/libs/game/built/debug/binary.js +9843 -8368
  14. package/libs/game/docs/reference/sprites/on-overlap.md +32 -109
  15. package/libs/game/docs/reference/sprites/sprite/say.md +43 -18
  16. package/libs/game/hitbox.ts +13 -9
  17. package/libs/game/pxt.json +1 -0
  18. package/libs/game/rotation.ts +194 -0
  19. package/libs/game/sprite.ts +102 -8
  20. package/libs/lcd/built/debug/binary.js +8 -8
  21. package/libs/light-spectrum-sensor/built/debug/binary.js +9 -9
  22. package/libs/lora/built/debug/binary.js +8 -8
  23. package/libs/matrix-keypad/built/debug/binary.js +8 -8
  24. package/libs/mqtt/built/debug/binary.js +176 -176
  25. package/libs/net/built/debug/binary.js +176 -176
  26. package/libs/net-game/built/debug/binary.js +11768 -10293
  27. package/libs/palette/built/debug/binary.js +9860 -8385
  28. package/libs/pixel/built/debug/binary.js +8 -8
  29. package/libs/power/built/debug/binary.js +8 -8
  30. package/libs/proximity/built/debug/binary.js +8 -8
  31. package/libs/radio/built/debug/binary.js +8 -8
  32. package/libs/radio-broadcast/built/debug/binary.js +8 -8
  33. package/libs/rotary-encoder/built/debug/binary.js +8 -8
  34. package/libs/screen/built/debug/binary.js +50 -50
  35. package/libs/screen/image.cpp +374 -0
  36. package/libs/screen/image.ts +42 -0
  37. package/libs/screen/sim/image.ts +406 -0
  38. package/libs/servo/built/debug/binary.js +8 -8
  39. package/libs/sprite-scaling/built/debug/binary.js +9860 -8385
  40. package/libs/storyboard/built/debug/binary.js +9860 -8385
  41. package/package.json +1 -1
@@ -12,7 +12,7 @@ An overlap with a sprite of a different or the same kind is detected. If you wan
12
12
 
13
13
  When an overlap is detected the sprite of the first kind is given to you in the **sprite** parameter of **handler**. The sprite for the second kind is in **otherSprite**.
14
14
 
15
- An overlap of two sprites is dectected when the first non-transparent pixel in the image of the first sprite overlaps the first non-transparent pixel of the second sprite. If a sprite has it's ``ghost`` flag set, any overlap with another sprite won't be noticed. Also, an overlap occurs even when the values of **Z** for the sprites are different.
15
+ An overlap of two sprites is detected when the first non-transparent pixel in the image of the first sprite overlaps the first non-transparent pixel of the second sprite. If a sprite has it's ``ghost`` flag set, any overlap with another sprite won't be noticed. Also, an overlap occurs even when the values of **Z** for the sprites are different.
16
16
 
17
17
  ## Parameters
18
18
 
@@ -24,95 +24,22 @@ An overlap of two sprites is dectected when the first non-transparent pixel in t
24
24
 
25
25
  ## Example #example
26
26
 
27
- ### Ghost bumper #ex1
28
-
29
- Create a ``Ghost`` sprite that is blasted by green balls. Let the balls go through the sprite until it's ``kind`` is changed to ``Mortal`` by pressing the **A** button. When the ``Ghost`` sprite is changed to ``Mortal``, any contact with the balls is detected in ``||sprites:on overlaps||``. Make the balls push the ``Mortal`` sprite off the screen.
27
+ Create a ``Ghost`` sprite that is blasted by yellow balls. Let the balls go through the sprite until it's ghost status is removed by pressing the **A** button. When the ``Ghost`` sprite is exposed, any contact with the balls is detected in ``||sprites:on overlap||``. Make the balls push the ``Ghost`` sprite off the screen.
30
28
 
31
29
  ```blocks
32
- namespace SpriteKind {
33
- export const Mortal = SpriteKind.create()
34
- export const Ghost = SpriteKind.create()
35
- export const Ball = SpriteKind.create()
36
- }
37
- let ghost: Sprite = null
38
- let projectile: Sprite = null
39
- ghost = sprites.create(img`
40
- . . . . . . d d d d d . . . . .
41
- . . . d d d d 1 1 1 d d d . . .
42
- . . d d 1 1 1 1 1 1 1 1 d d . .
43
- . . d 1 1 1 1 1 1 1 1 1 1 d . .
44
- . . d 1 1 1 1 1 1 1 1 1 1 d d .
45
- . d d 1 1 1 f 1 1 1 f 1 1 1 d .
46
- . d 1 1 1 1 1 1 1 1 1 1 1 1 d d
47
- . d 1 1 1 1 1 1 1 1 1 1 1 1 1 d
48
- . d 1 1 1 1 1 1 1 1 1 1 1 1 1 d
49
- d d 1 1 1 1 1 1 f f 1 1 1 1 1 d
50
- d 1 1 1 1 1 1 1 f f 1 1 1 1 1 d
51
- d 1 1 1 1 1 1 1 1 1 1 1 1 1 1 d
52
- d 1 1 1 1 1 1 1 d 1 1 1 1 1 1 d
53
- d 1 d d d 1 1 d d d d 1 d 1 1 d
54
- d d d . d d d d . . d d d d d d
55
- d d . . . d d . . . . d . . d d
56
- `, SpriteKind.Ghost)
57
- ghost.x = 40
58
- ghost.setFlag(SpriteFlag.AutoDestroy, true)
59
- game.onUpdateInterval(400, function () {
60
- projectile = sprites.createProjectile(img`
61
- . . 7 7 7 7 . .
62
- . 7 7 7 7 7 7 .
63
- 7 7 7 7 7 7 7 7
64
- 7 7 7 7 7 7 7 7
65
- 7 7 7 7 7 7 7 7
66
- 7 7 7 7 7 7 7 7
67
- . 7 7 7 7 7 7 .
68
- . . 7 7 7 7 . .
69
- `, -400, 0, SpriteKind.Ball)
70
- projectile.z = -1
71
- })
72
- sprites.onOverlap(SpriteKind.Mortal, SpriteKind.Ball, function (sprite, otherSprite) {
73
- sprite.say("Ouch!", 200)
74
- otherSprite.vx = otherSprite.vx * -1
75
- otherSprite.vy = Math.randomRange(-100, 100)
76
- sprite.x += -1
77
- })
78
30
  controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
79
- ghost.setKind(SpriteKind.Mortal)
31
+ ghost.setFlag(SpriteFlag.Ghost, false)
80
32
  })
81
- sprites.onDestroyed(SpriteKind.Mortal, function (sprite) {
82
- game.over()
33
+ sprites.onOverlap(SpriteKind.Player, SpriteKind.Projectile, function (sprite, otherSprite) {
34
+ otherSprite.setVelocity(-50, 50)
35
+ sprite.sayText("Ouch!", 200, false)
36
+ sprite.setPosition(sprite.x + 2, sprite.y + 2)
37
+ })
38
+ sprites.onDestroyed(SpriteKind.Player, function (sprite) {
39
+ game.gameOver(false)
83
40
  })
84
- ```
85
-
86
- ## Ghosting mode #ex2
87
-
88
- Use the **A** to blast a green ball at a ``Ghost`` sprite. Set the flag for the sprite to ``Ghost``. In the ``||sprites:on overlaps||`` block, try to detect the contact of the ball with the ghost. When button **B** is pressed, switch the value of the ``ghost`` flag and see if the ball hits the ghost sprite.
89
-
90
- ```blocks
91
- namespace SpriteKind {
92
- export const Mortal = SpriteKind.create()
93
- export const Ghost = SpriteKind.create()
94
- export const Ball = SpriteKind.create()
95
- }
96
41
  let projectile: Sprite = null
97
42
  let ghost: Sprite = null
98
- let mySprite = sprites.create(img`
99
- . . . . . . . . . . . . . . . .
100
- . . . . . . . . . . . . . . . .
101
- . . . . . . . . . . . . . . . .
102
- . . . . . . . . . . . . . . . .
103
- . . . . . . . . . . . . . . . .
104
- . . . . . . . . . . . . . . . .
105
- . . . . . . . . . . . . . . . .
106
- . . . . . . . . . . . . . . . .
107
- . . . . . . . . . . . . . . . .
108
- . . . . . . . . . . . . . . . .
109
- . . . . . . . . . . . . . . . .
110
- . . . . . . . . . . . . . . . .
111
- . . . . . . . . . . . . . . . .
112
- . . . . . . . . . . . . . . . .
113
- . . . . . . . . . . . . . . . .
114
- . . . . . . . . . . . . . . . .
115
- `, SpriteKind.Ball)
116
43
  ghost = sprites.create(img`
117
44
  . . . . . . d d d d d . . . . .
118
45
  . . . d d d d 1 1 1 d d d . . .
@@ -130,33 +57,29 @@ ghost = sprites.create(img`
130
57
  d 1 d d d 1 1 d d d d 1 d 1 1 d
131
58
  d d d . d d d d . . d d d d d d
132
59
  d d . . . d d . . . . d . . d d
133
- `, SpriteKind.Ghost)
134
- ghost.x = 40
60
+ `, SpriteKind.Player)
61
+ ghost.setPosition(60, 60)
135
62
  ghost.setFlag(SpriteFlag.AutoDestroy, true)
136
- game.onUpdateInterval(400, function () {
137
- projectile = sprites.createProjectile(img`
138
- . . 7 7 7 7 . .
139
- . 7 7 7 7 7 7 .
140
- 7 7 7 7 7 7 7 7
141
- 7 7 7 7 7 7 7 7
142
- 7 7 7 7 7 7 7 7
143
- 7 7 7 7 7 7 7 7
144
- . 7 7 7 7 7 7 .
145
- . . 7 7 7 7 . .
146
- `, -400, 0, SpriteKind.Ball)
147
- projectile.z = -1
148
- })
149
- controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
150
- ghost.setKind(SpriteKind.Mortal)
151
- })
152
- sprites.onDestroyed(SpriteKind.Mortal, function (sprite) {
153
- game.over(false)
154
- })
155
- sprites.onOverlap(SpriteKind.Mortal, SpriteKind.Ball, function (sprite, otherSprite) {
156
- sprite.say("Ouch!", 200)
157
- otherSprite.vx = otherSprite.vx * -1
158
- otherSprite.vy = Math.randomRange(-100, 100)
159
- sprite.x += -1
63
+ ghost.setFlag(SpriteFlag.Ghost, true)
64
+ game.onUpdateInterval(1000, function () {
65
+ projectile = sprites.createProjectileFromSide(img`
66
+ . . . . . . . . . . . . . . . .
67
+ . . . . . . . . . . . . . . . .
68
+ . . . . . . . . . . . . . . . .
69
+ . . . . . . . . . . . . . . . .
70
+ . . . . . . . . . . . . . . . .
71
+ . . . . . . . 5 5 . . . . . . .
72
+ . . . . . . 5 5 5 5 . . . . . .
73
+ . . . . . 5 5 5 5 5 5 . . . . .
74
+ . . . . . 5 5 5 5 5 5 . . . . .
75
+ . . . . . . 5 5 5 5 . . . . . .
76
+ . . . . . . . 5 5 . . . . . . .
77
+ . . . . . . . . . . . . . . . .
78
+ . . . . . . . . . . . . . . . .
79
+ . . . . . . . . . . . . . . . .
80
+ . . . . . . . . . . . . . . . .
81
+ . . . . . . . . . . . . . . . .
82
+ `, 50, 50)
160
83
  })
161
84
  ```
162
85
 
@@ -42,28 +42,53 @@ f e e e e e f f f f f e e e e f
42
42
  smiley.say("Hello!")
43
43
  ```
44
44
 
45
- ### Bounce message
45
+ ### Yellow message
46
46
 
47
- Send a sprite toward a barrier. When it contacts the barrier, have it bounce back to its starting position and briefly show the `"Bounce!"` caption.
47
+ Make a square yellow sprite in the middle of the screen. Send a moving person sprite around the screen. When the person sprite crosses the yellow square, make the person say "Yellow!".
48
48
 
49
49
  ```blocks
50
- let greenBoxGo: Sprite = null
51
- let barrier: Sprite = null
52
- let shield: Image = null
53
- let greenBox: Image = null
54
- greenBox = image.create(32, 32)
55
- greenBox.fill(7)
56
- shield = image.create(4, 64)
57
- shield.fill(10)
58
- barrier = sprites.createObstacle(shield)
59
- barrier.x = scene.screenWidth() - 4
60
- greenBoxGo = sprites.create(greenBox)
61
- greenBoxGo.x = 16
62
- greenBoxGo.ax = 80
63
- greenBoxGo.onCollision(CollisionDirection.Right, function (wall) {
64
- greenBoxGo.x = 16
65
- greenBoxGo.say("Bounce!", 400)
50
+ sprites.onOverlap(SpriteKind.Player, SpriteKind.Player, function (sprite, otherSprite) {
51
+ sprite.sayText("Yellow!", 100, false)
66
52
  })
53
+ scene.setBackgroundColor(13)
54
+ let box = sprites.create(img`
55
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
56
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
57
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
58
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
59
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
60
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
61
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
62
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
63
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
64
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
65
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
66
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
67
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
68
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
69
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
70
+ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
71
+ `, SpriteKind.Player)
72
+ let person = sprites.create(img`
73
+ . . . . . . f f f f . . . . . .
74
+ . . . . f f f 2 2 f f f . . . .
75
+ . . . f f f 2 2 2 2 f f f . . .
76
+ . . f f f e e e e e e f f f . .
77
+ . . f f e 2 2 2 2 2 2 e e f . .
78
+ . . f e 2 f f f f f f 2 e f . .
79
+ . . f f f f e e e e f f f f . .
80
+ . f f e f b f 4 4 f b f e f f .
81
+ . f e e 4 1 f d d f 1 4 e e f .
82
+ . . f e e d d d d d d e e f . .
83
+ . . . f e e 4 4 4 4 e e f . . .
84
+ . . e 4 f 2 2 2 2 2 2 f 4 e . .
85
+ . . 4 d f 2 2 2 2 2 2 f d 4 . .
86
+ . . 4 4 f 4 4 5 5 4 4 f 4 4 . .
87
+ . . . . . f f f f f f . . . . .
88
+ . . . . . f f . . f f . . . . .
89
+ `, SpriteKind.Player)
90
+ person.setBounceOnWall(true)
91
+ person.setVelocity(50, 50)
67
92
  ```
68
93
 
69
94
  ## #seealso
@@ -88,15 +88,15 @@ namespace game {
88
88
  overlapsWith(other: Hitbox): boolean {
89
89
  this.updateIfInvalid();
90
90
  other.updateIfInvalid();
91
- if (this.contains(other.left, other.top)) return true;
92
- if (this.contains(other.left, other.bottom)) return true;
93
- if (this.contains(other.right, other.top)) return true;
94
- if (this.contains(other.right, other.bottom)) return true;
95
- if (other.contains(this.left, this.top)) return true;
96
- if (other.contains(this.left, this.bottom)) return true;
97
- if (other.contains(this.right, this.top)) return true;
98
- if (other.contains(this.right, this.bottom)) return true;
99
- return false;
91
+ if (
92
+ this.left > other.right ||
93
+ this.top > other.bottom ||
94
+ this.right < other.left ||
95
+ this.bottom < other.top
96
+ ) {
97
+ return false;
98
+ }
99
+ return true;
100
100
  }
101
101
  }
102
102
 
@@ -105,6 +105,10 @@ namespace game {
105
105
  if (s._hitbox && s._hitbox.isValid())
106
106
  return s._hitbox;
107
107
 
108
+ if (s._rotatedBBox) {
109
+ return new Hitbox(s, Fx8(s._rotatedBBox.width), Fx8(s._rotatedBBox.height), Fx.zeroFx8, Fx.zeroFx8);
110
+ }
111
+
108
112
  const i = s.image;
109
113
  let minX = Fx8(i.width);
110
114
  let minY = Fx8(i.height);
@@ -14,6 +14,7 @@
14
14
  "hitbox.ts",
15
15
  "renderText.ts",
16
16
  "spritesay.ts",
17
+ "rotation.ts",
17
18
  "sprites.ts",
18
19
  "sprite.ts",
19
20
  "extendableSprite.ts",
@@ -0,0 +1,194 @@
1
+ namespace sprites {
2
+ let aabbPoints: number[];
3
+
4
+ export class RotatedBoundingBox {
5
+ protected _rotation: number;
6
+ protected _width: number;
7
+ protected _height: number;
8
+
9
+ protected points: number[];
10
+ protected cornerDistance: number;
11
+ protected cornerAngle: number;
12
+
13
+ public get x0(): number {
14
+ return this.points[0];
15
+ }
16
+
17
+ public get y0(): number {
18
+ return this.points[1];
19
+ }
20
+
21
+ public get x1(): number {
22
+ return this.points[2];
23
+ }
24
+
25
+ public get y1(): number {
26
+ return this.points[3];
27
+ }
28
+
29
+ public get x2(): number {
30
+ return this.points[4];
31
+ }
32
+
33
+ public get y2(): number {
34
+ return this.points[5];
35
+ }
36
+
37
+ public get x3(): number {
38
+ return this.points[6];
39
+ }
40
+
41
+ public get y3(): number {
42
+ return this.points[7];
43
+ }
44
+
45
+ public get rotation() {
46
+ return this._rotation;
47
+ }
48
+
49
+ public set rotation(value: number) {
50
+ this.setRotation(value);
51
+ }
52
+
53
+ public get width() {
54
+ return this._width;
55
+ }
56
+
57
+ public get height() {
58
+ return this._height;
59
+ }
60
+
61
+ constructor(
62
+ public anchor: Sprite,
63
+ width: number,
64
+ height: number
65
+ ) {
66
+ this.points = [];
67
+ this._rotation = 0;
68
+ this.setDimensions(width, height);
69
+ }
70
+
71
+ setDimensions(width: number, height: number) {
72
+ width /= 2;
73
+ height /= 2;
74
+
75
+ this.cornerDistance = Math.sqrt(
76
+ width * width + height * height
77
+ );
78
+ this.cornerAngle = Math.atan2(height, width);
79
+ this.setRotation(this._rotation);
80
+ }
81
+
82
+ setRotation(angle: number) {
83
+ this._rotation = angle;
84
+ this.points[0] = Math.cos(this.cornerAngle + angle) * this.cornerDistance;
85
+ this.points[1] = Math.sin(this.cornerAngle + angle) * this.cornerDistance;
86
+ this.points[2] = Math.cos(Math.PI - this.cornerAngle + angle) * this.cornerDistance;
87
+ this.points[3] = Math.sin(Math.PI - this.cornerAngle + angle) * this.cornerDistance;
88
+ this.points[4] = Math.cos(Math.PI + this.cornerAngle + angle) * this.cornerDistance;
89
+ this.points[5] = Math.sin(Math.PI + this.cornerAngle + angle) * this.cornerDistance;
90
+ this.points[6] = Math.cos(angle - this.cornerAngle) * this.cornerDistance;
91
+ this.points[7] = Math.sin(angle - this.cornerAngle) * this.cornerDistance;
92
+ this.updateWidthHeight();
93
+ }
94
+
95
+ overlaps(other: RotatedBoundingBox): boolean {
96
+ return doRectanglesIntersect(
97
+ this.points,
98
+ this.anchor.x,
99
+ this.anchor.y,
100
+ other.points,
101
+ other.anchor.x,
102
+ other.anchor.y
103
+ );
104
+ }
105
+
106
+ overlapsAABB(left: number, top: number, right: number, bottom: number) {
107
+ if (!aabbPoints) {
108
+ aabbPoints = [];
109
+ }
110
+
111
+ aabbPoints[0] = left;
112
+ aabbPoints[1] = top;
113
+ aabbPoints[2] = right;
114
+ aabbPoints[3] = top;
115
+ aabbPoints[4] = right;
116
+ aabbPoints[5] = bottom;
117
+ aabbPoints[6] = left;
118
+ aabbPoints[7] = bottom;
119
+ return doRectanglesIntersect(
120
+ this.points,
121
+ this.anchor.x,
122
+ this.anchor.y,
123
+ aabbPoints,
124
+ 0,
125
+ 0
126
+ );
127
+ }
128
+
129
+ protected updateWidthHeight() {
130
+ let minX = this.points[0];
131
+ let maxX = minX;
132
+ let minY = this.points[1];
133
+ let maxY = minY;
134
+
135
+ for (let i = 2; i < 8; i += 2) {
136
+ minX = Math.min(minX, this.points[i]);
137
+ maxX = Math.max(maxX, this.points[i]);
138
+ minY = Math.min(minY, this.points[i + 1]);
139
+ maxY = Math.max(maxY, this.points[i + 1]);
140
+ }
141
+
142
+ this._width = (maxX - minX) | 0;
143
+ this._height = (maxY - minY) | 0;
144
+ }
145
+ }
146
+
147
+ // adapted from https://stackoverflow.com/questions/10962379/how-to-check-intersection-between-2-rotated-rectangles
148
+ // but optimized for rectangles
149
+ function doRectanglesIntersect(a: number[], ax: number, ay: number, b: number[], bx: number, by: number) {
150
+ return !(checkForNonIntersection(a, ax, ay, b, bx, by) || checkForNonIntersection(b, bx, by, a, ax, ay));
151
+ }
152
+
153
+ function checkForNonIntersection(a: number[], ax: number, ay: number, b: number[], bx: number, by: number) {
154
+ // we only need to check the first two sides because the
155
+ // normals are the same for the other two
156
+ for (let pointIndex = 0; pointIndex < 4; pointIndex += 2) {
157
+ const normalX = a[pointIndex + 3] - a[pointIndex + 1];
158
+ const normalY = a[pointIndex] - a[pointIndex + 2];
159
+
160
+ let minA: number = undefined;
161
+ let maxA: number = undefined;
162
+ let minB: number = undefined;
163
+ let maxB: number = undefined;
164
+
165
+ for (let i = 0; i < 8; i += 2) {
166
+ const projected = normalX * (a[i] + ax) + normalY * (a[i + 1] + ay);
167
+
168
+ if (minA === undefined || projected < minA) {
169
+ minA = projected;
170
+ }
171
+ if (maxA == undefined || projected > maxA) {
172
+ maxA = projected;
173
+ }
174
+ }
175
+
176
+ for (let i = 0; i < 8; i += 2) {
177
+ const projected = normalX * (b[i] + bx) + normalY * (b[i + 1] + by);
178
+
179
+ if (minB === undefined || projected < minB) {
180
+ minB = projected;
181
+ }
182
+ if (maxB == undefined || projected > maxB) {
183
+ maxB = projected;
184
+ }
185
+ }
186
+
187
+ if (maxA < minB || maxB < minA) {
188
+ return true;
189
+ }
190
+ }
191
+
192
+ return false;
193
+ }
194
+ }
@@ -105,6 +105,7 @@ class Sprite extends sprites.BaseSprite {
105
105
  _sy: Fx8 // scale
106
106
  _width: Fx8 // scaled width
107
107
  _height: Fx8 // scaled height
108
+ _rotatedBBox: sprites.RotatedBoundingBox;
108
109
 
109
110
  //% group="Physics" blockSetVariable="mySprite"
110
111
  //% blockCombine block="x" callInDebugger
@@ -200,10 +201,12 @@ class Sprite extends sprites.BaseSprite {
200
201
  //% group="Physics" blockSetVariable="mySprite"
201
202
  //% blockCombine block="sx (scale x)"
202
203
  set sx(v: number) {
204
+ const y = this.y;
203
205
  const x = this.x;
204
206
  this._sx = Fx8(Math.max(0, v));
205
207
  this.recalcSize();
206
- this.left = x - this.width / 2;
208
+ this.y = y;
209
+ this.x = x;
207
210
  }
208
211
  //% group="Physics" blockSetVariable="mySprite"
209
212
  //% blockCombine block="sy (scale y)" callInDebugger
@@ -214,10 +217,13 @@ class Sprite extends sprites.BaseSprite {
214
217
  //% blockCombine block="sy (scale y)"
215
218
  set sy(v: number) {
216
219
  const y = this.y;
220
+ const x = this.x;
217
221
  this._sy = Fx8(Math.max(0, v));
218
222
  this.recalcSize();
219
- this.top = y - this.height / 2;
223
+ this.y = y;
224
+ this.x = x;
220
225
  }
226
+
221
227
  //% group="Physics" blockSetVariable="mySprite"
222
228
  //% blockCombine block="scale" callInDebugger
223
229
  get scale(): number {
@@ -229,6 +235,36 @@ class Sprite extends sprites.BaseSprite {
229
235
  this.sx = this.sy = v;
230
236
  }
231
237
 
238
+ //% group="Physics" blockSetVariable="mySprite"
239
+ //% blockCombine block="rotation (radians)" callInDebugger
240
+ get rotation(): number {
241
+ return this._rotatedBBox ? this._rotatedBBox.rotation : 0;
242
+ }
243
+ //% group="Physics" blockSetVariable="mySprite"
244
+ //% blockCombine block="rotation (radians)"
245
+ set rotation(v: number) {
246
+ const x = this.x;
247
+ const y = this.y;
248
+ if (!this._rotatedBBox) {
249
+ this._rotatedBBox = new sprites.RotatedBoundingBox(this, this.width, this.height);
250
+ }
251
+ this._rotatedBBox.setRotation(v);
252
+ this.recalcSize();
253
+ this.x = x;
254
+ this.y = y;
255
+ }
256
+
257
+ //% group="Physics" blockSetVariable="mySprite"
258
+ //% blockCombine block="rotation (degrees)" callInDebugger
259
+ get rotationDegrees(): number {
260
+ return this.rotation * 180 / Math.PI;
261
+ }
262
+ //% group="Physics" blockSetVariable="mySprite"
263
+ //% blockCombine block="rotation (degrees)"
264
+ set rotationDegrees(v: number) {
265
+ this.rotation = v * Math.PI / 180;
266
+ }
267
+
232
268
  private _data: any;
233
269
  /**
234
270
  * Custom data
@@ -338,7 +374,7 @@ class Sprite extends sprites.BaseSprite {
338
374
  }
339
375
 
340
376
  calcDimensionalHash() {
341
- return this._image.revision() + Fx.toIntShifted(this._width, 8) + Fx.toIntShifted(this._height, 16);
377
+ return this._image.revision() + Fx.toIntShifted(this._width, 8) + Fx.toIntShifted(this._height, 16) + this.rotation;
342
378
  }
343
379
 
344
380
  resetHitbox() {
@@ -363,8 +399,15 @@ class Sprite extends sprites.BaseSprite {
363
399
  }
364
400
 
365
401
  protected recalcSize(): void {
366
- this._width = Fx8(this._image.width * this.sx);
367
- this._height = Fx8(this._image.height * this.sy);
402
+ if (this._rotatedBBox) {
403
+ this._rotatedBBox.setDimensions(this._image.width * this.sx, this._image.height * this.sy);
404
+ this._width = Fx8(this._rotatedBBox.width);
405
+ this._height = Fx8(this._rotatedBBox.height);
406
+ }
407
+ else {
408
+ this._width = Fx8(this._image.width * this.sx);
409
+ this._height = Fx8(this._image.height * this.sy);
410
+ }
368
411
  this.resetHitbox();
369
412
  }
370
413
 
@@ -688,7 +731,7 @@ class Sprite extends sprites.BaseSprite {
688
731
  //% blockId=spriteoverlapswith block="%sprite(mySprite) overlaps with %other=variables_get(otherSprite)"
689
732
  //% help=sprites/sprite/overlaps-with
690
733
  //% weight=90
691
- overlapsWith(other: Sprite) {
734
+ overlapsWith(other: Sprite): boolean {
692
735
  control.enablePerfCounter("overlapsCPP")
693
736
  if (other == this) return false;
694
737
  if (this.flags & SPRITE_NO_SPRITE_OVERLAPS)
@@ -699,7 +742,47 @@ class Sprite extends sprites.BaseSprite {
699
742
  return other._hitbox.overlapsWith(this._hitbox);
700
743
  if (!other._hitbox.overlapsWith(this._hitbox))
701
744
  return false;
702
- if (!this.isScaled() && !other.isScaled()) {
745
+ else if (this._rotatedBBox) {
746
+ if (other._rotatedBBox) {
747
+ if (this._rotatedBBox.overlaps(other._rotatedBBox)) {
748
+ return helpers.checkOverlapsTwoScaledRotatedImages(
749
+ other.image,
750
+ this.left - other.left,
751
+ this.top - other.top,
752
+ other.sx,
753
+ other.sy,
754
+ other.rotation,
755
+ this.image,
756
+ this.sx,
757
+ this.sy,
758
+ this.rotation
759
+ );
760
+ }
761
+ else {
762
+ return false;
763
+ }
764
+ }
765
+ else {
766
+ if (this._rotatedBBox.overlapsAABB(other.left, other.top, other.right, other.bottom)) {
767
+ return helpers.checkOverlapsScaledRotatedImage(
768
+ other.image,
769
+ this.left - other.left,
770
+ this.top - other.top,
771
+ this.image,
772
+ this.sx,
773
+ this.sy,
774
+ this.rotation
775
+ );
776
+ }
777
+ else {
778
+ return false;
779
+ }
780
+ }
781
+ }
782
+ else if (other._rotatedBBox) {
783
+ return other.overlapsWith(this);
784
+ }
785
+ else if (!this.isScaled() && !other.isScaled()) {
703
786
  return other._image.overlapsWith(
704
787
  this._image,
705
788
  this.left - other.left,
@@ -1117,7 +1200,18 @@ class Sprite extends sprites.BaseSprite {
1117
1200
  }
1118
1201
 
1119
1202
  protected drawSprite(drawLeft: number, drawTop: number) {
1120
- if (!this.isScaled())
1203
+ if (this._rotatedBBox) {
1204
+ helpers.imageDrawScaledRotated(
1205
+ screen,
1206
+ drawLeft,
1207
+ drawTop,
1208
+ this._image,
1209
+ this.sx,
1210
+ this.sy,
1211
+ this.rotation
1212
+ );
1213
+ }
1214
+ else if (!this.isScaled())
1121
1215
  screen.drawTransparentImage(this._image, drawLeft, drawTop);
1122
1216
  else
1123
1217
  screen.blit(