|
5 | 5 | * This file is part of the pixi-questions project (https://github.com/afarber/pixi-questions) |
6 | 6 | */ |
7 | 7 |
|
8 | | -import { Container, Graphics, Rectangle, Matrix } from 'pixi.js'; |
| 8 | +import { Container, Graphics, Rectangle } from 'pixi.js'; |
9 | 9 | import { Tween, Easing } from '@tweenjs/tween.js'; |
10 | 10 | import { Card, CARD_WIDTH, CARD_HEIGHT, TWEEN_DURATION } from './Card.js'; |
11 | 11 |
|
12 | 12 | // Maximum attempts to find a valid position with visible corner |
13 | 13 | const MAX_PLACEMENT_ATTEMPTS = 50; |
14 | 14 |
|
15 | | -// Reusable objects to avoid allocations |
16 | | -const tempMatrix = new Matrix(); |
17 | | -const hotCornerRect = new Rectangle(); |
18 | | -const cardRect = new Rectangle(); |
| 15 | +// All collision detection is done in local Table coordinates. |
| 16 | +// No global coordinate transforms or Matrix operations are needed |
| 17 | +// because all cards are children of the same Table container. |
19 | 18 |
|
20 | 19 | /** |
21 | 20 | * Table container for displaying cards in the center play area. |
@@ -115,102 +114,69 @@ export class Table extends Container { |
115 | 114 | } |
116 | 115 |
|
117 | 116 | /** |
118 | | - * Checks if a hot corner rectangle at given position intersects with a card at given position/angle. |
119 | | - * @param {number} cornerX - Hot corner center X in world coordinates |
120 | | - * @param {number} cornerY - Hot corner center Y in world coordinates |
121 | | - * @param {number} cardX - Card center X |
122 | | - * @param {number} cardY - Card center Y |
123 | | - * @param {number} cardAngle - Card rotation in degrees |
124 | | - * @returns {boolean} True if the hot corner overlaps with the card |
| 117 | + * Gets the two hot corner rectangles for a card (axis-aligned bounding boxes). |
| 118 | + * Since card rotation is small (-20 to +20 degrees), AABB approximation is sufficient. |
| 119 | + * @param {Card} card - The card to get hot corners for |
| 120 | + * @returns {Rectangle[]} Array of two Rectangle objects (top-left and bottom-right hot corners) |
125 | 121 | * @private |
126 | 122 | */ |
127 | | - _hotCornerIntersectsCard(cornerX, cornerY, cardX, cardY, cardAngle) { |
128 | | - // Hot corner dimensions - the rank/suit indicator area at card corners |
| 123 | + _getHotCornerRects(card) { |
129 | 124 | const hotCornerWidth = CARD_WIDTH / 6; |
130 | 125 | const hotCornerHeight = CARD_HEIGHT / 5; |
131 | 126 |
|
132 | | - // Set up hot corner rectangle |
133 | | - hotCornerRect.x = cornerX - hotCornerWidth / 2; |
134 | | - hotCornerRect.y = cornerY - hotCornerHeight / 2; |
135 | | - hotCornerRect.width = hotCornerWidth; |
136 | | - hotCornerRect.height = hotCornerHeight; |
137 | | - |
138 | | - // Set up card's transform matrix |
139 | | - const angleRad = (cardAngle * Math.PI) / 180; |
140 | | - tempMatrix.identity(); |
141 | | - tempMatrix.translate(-cardX, -cardY); |
142 | | - tempMatrix.rotate(-angleRad); |
143 | | - |
144 | | - // Set up card rectangle (centered at origin after transform) |
145 | | - cardRect.x = -CARD_WIDTH / 2; |
146 | | - cardRect.y = -CARD_HEIGHT / 2; |
147 | | - cardRect.width = CARD_WIDTH; |
148 | | - cardRect.height = CARD_HEIGHT; |
149 | | - |
150 | | - return hotCornerRect.intersects(cardRect, tempMatrix); |
| 127 | + // Top-left hot corner |
| 128 | + const tlRect = new Rectangle( |
| 129 | + card.x - CARD_WIDTH / 2, |
| 130 | + card.y - CARD_HEIGHT / 2, |
| 131 | + hotCornerWidth, |
| 132 | + hotCornerHeight |
| 133 | + ); |
| 134 | + |
| 135 | + // Bottom-right hot corner |
| 136 | + const brRect = new Rectangle( |
| 137 | + card.x + CARD_WIDTH / 2 - hotCornerWidth, |
| 138 | + card.y + CARD_HEIGHT / 2 - hotCornerHeight, |
| 139 | + hotCornerWidth, |
| 140 | + hotCornerHeight |
| 141 | + ); |
| 142 | + |
| 143 | + return [tlRect, brRect]; |
151 | 144 | } |
152 | 145 |
|
153 | 146 | /** |
154 | | - * Gets the two hot corner positions for a card at given position and angle. |
| 147 | + * Gets the bounding rectangle for a card at given position (axis-aligned). |
155 | 148 | * @param {number} x - Card center X |
156 | 149 | * @param {number} y - Card center Y |
157 | | - * @param {number} angle - Card rotation in degrees |
158 | | - * @returns {Array<{x: number, y: number}>} Array of two corner positions |
| 150 | + * @returns {Rectangle} The card's bounding rectangle |
159 | 151 | * @private |
160 | 152 | */ |
161 | | - _getHotCorners(x, y, angle) { |
162 | | - const hotCornerWidth = CARD_WIDTH / 6; |
163 | | - const hotCornerHeight = CARD_HEIGHT / 5; |
164 | | - |
165 | | - const angleRad = (angle * Math.PI) / 180; |
166 | | - const cos = Math.cos(angleRad); |
167 | | - const sin = Math.sin(angleRad); |
168 | | - |
169 | | - // Top-left corner offset (center of the hot corner area) |
170 | | - const tlOffsetX = -CARD_WIDTH / 2 + hotCornerWidth / 2; |
171 | | - const tlOffsetY = -CARD_HEIGHT / 2 + hotCornerHeight / 2; |
172 | | - |
173 | | - // Bottom-right corner offset (center of the hot corner area) |
174 | | - const brOffsetX = CARD_WIDTH / 2 - hotCornerWidth / 2; |
175 | | - const brOffsetY = CARD_HEIGHT / 2 - hotCornerHeight / 2; |
176 | | - |
177 | | - // Apply rotation to get world positions |
178 | | - return [ |
179 | | - { |
180 | | - x: x + tlOffsetX * cos - tlOffsetY * sin, |
181 | | - y: y + tlOffsetX * sin + tlOffsetY * cos |
182 | | - }, |
183 | | - { |
184 | | - x: x + brOffsetX * cos - brOffsetY * sin, |
185 | | - y: y + brOffsetX * sin + brOffsetY * cos |
186 | | - } |
187 | | - ]; |
| 153 | + _getCardRect(x, y) { |
| 154 | + return new Rectangle( |
| 155 | + x - CARD_WIDTH / 2, |
| 156 | + y - CARD_HEIGHT / 2, |
| 157 | + CARD_WIDTH, |
| 158 | + CARD_HEIGHT |
| 159 | + ); |
188 | 160 | } |
189 | 161 |
|
190 | 162 | /** |
191 | | - * Checks if placing a new card at given position would obscure all hot corners of any existing card. |
192 | | - * @param {number} x - New card center X |
193 | | - * @param {number} y - New card center Y |
194 | | - * @param {number} angle - New card rotation in degrees |
195 | | - * @returns {boolean} True if any existing card would have all its hot corners covered |
| 163 | + * Checks if placing a new card would obscure all hot corners of any existing card. |
| 164 | + * Only checks against the new card (previous placements were already validated). |
| 165 | + * @param {number} newX - New card center X |
| 166 | + * @param {number} newY - New card center Y |
| 167 | + * @returns {boolean} True if any existing card would have both hot corners obscured |
196 | 168 | * @private |
197 | 169 | */ |
198 | | - _wouldObscureExistingCards(x, y, angle) { |
| 170 | + _wouldObscureExistingCards(newX, newY) { |
199 | 171 | const existingCards = this.children.filter((child) => child instanceof Card); |
| 172 | + const newCardRect = this._getCardRect(newX, newY); |
200 | 173 |
|
201 | 174 | for (const card of existingCards) { |
202 | | - const corners = this._getHotCorners(card.x, card.y, card.angle); |
203 | | - let allCornersCovered = true; |
204 | | - |
205 | | - for (const corner of corners) { |
206 | | - // Check if the NEW card would cover this existing card's corner |
207 | | - if (!this._hotCornerIntersectsCard(corner.x, corner.y, x, y, angle)) { |
208 | | - allCornersCovered = false; |
209 | | - break; |
210 | | - } |
211 | | - } |
| 175 | + const [tlRect, brRect] = this._getHotCornerRects(card); |
212 | 176 |
|
213 | | - if (allCornersCovered) { |
| 177 | + // If BOTH hot corners intersect with the new card, the existing card would be obscured |
| 178 | + if (newCardRect.intersects(tlRect) && newCardRect.intersects(brRect)) { |
| 179 | + console.log(` ${card.textureKey} would be obscured by new card at (${Math.round(newX)}, ${Math.round(newY)})`); |
214 | 180 | return true; |
215 | 181 | } |
216 | 182 | } |
@@ -243,7 +209,7 @@ export class Table extends Container { |
243 | 209 | // Random angle between -20 and +20 degrees |
244 | 210 | angle = Math.random() * 40 - 20; |
245 | 211 |
|
246 | | - if (!this._wouldObscureExistingCards(x, y, angle)) { |
| 212 | + if (!this._wouldObscureExistingCards(x, y)) { |
247 | 213 | break; |
248 | 214 | } |
249 | 215 | } |
|
0 commit comments