@@ -27,11 +27,15 @@ namespace gdjs {
2727 _untransformedHitBoxes : gdjs . Polygon [ ] = [ ] ;
2828 /** The dimension of this object is calculated from its children AABBs. */
2929 _unrotatedAABB : AABB = { min : [ 0 , 0 ] , max : [ 0 , 0 ] } ;
30- _scaleX : number = 1 ;
31- _scaleY : number = 1 ;
30+ _scaleX : float = 1 ;
31+ _scaleY : float = 1 ;
3232 _flippedX : boolean = false ;
3333 _flippedY : boolean = false ;
3434 opacity : float = 255 ;
35+ _customCenter : FloatPoint | null = null ;
36+ _localTransformation : gdjs . AffineTransformation = new gdjs . AffineTransformation ( ) ;
37+ _localInverseTransformation : gdjs . AffineTransformation = new gdjs . AffineTransformation ( ) ;
38+ _isLocalTransformationDirty : boolean = true ;
3539
3640 /**
3741 * @param parent The container the object belongs to
@@ -149,8 +153,9 @@ namespace gdjs {
149153 this . _updateUntransformedHitBoxes ( ) ;
150154 }
151155
152- //Update the current hitboxes with the frame custom hit boxes
153- //and apply transformations.
156+ // Update the current hitboxes with the frame custom hit boxes
157+ // and apply transformations.
158+ const localTransformation = this . getLocalTransformation ( ) ;
154159 for ( let i = 0 ; i < this . _untransformedHitBoxes . length ; ++ i ) {
155160 if ( i >= this . hitBoxes . length ) {
156161 this . hitBoxes . push ( new gdjs . Polygon ( ) ) ;
@@ -163,9 +168,8 @@ namespace gdjs {
163168 if ( j >= this . hitBoxes [ i ] . vertices . length ) {
164169 this . hitBoxes [ i ] . vertices . push ( [ 0 , 0 ] ) ;
165170 }
166- this . applyObjectTransformation (
167- this . _untransformedHitBoxes [ i ] . vertices [ j ] [ 0 ] ,
168- this . _untransformedHitBoxes [ i ] . vertices [ j ] [ 1 ] ,
171+ localTransformation . transform (
172+ this . _untransformedHitBoxes [ i ] . vertices [ j ] ,
169173 this . hitBoxes [ i ] . vertices [ j ]
170174 ) ;
171175 }
@@ -181,11 +185,6 @@ namespace gdjs {
181185 _updateUntransformedHitBoxes ( ) {
182186 this . _isUntransformedHitBoxesDirty = false ;
183187
184- const oldUnrotatedMinX = this . _unrotatedAABB . min [ 0 ] ;
185- const oldUnrotatedMinY = this . _unrotatedAABB . min [ 1 ] ;
186- const oldUnrotatedMaxX = this . _unrotatedAABB . max [ 0 ] ;
187- const oldUnrotatedMaxY = this . _unrotatedAABB . max [ 1 ] ;
188-
189188 this . _untransformedHitBoxes . length = 0 ;
190189 if ( this . _instanceContainer . getAdhocListOfAllInstances ( ) . length === 0 ) {
191190 this . _unrotatedAABB . min [ 0 ] = 0 ;
@@ -218,20 +217,6 @@ namespace gdjs {
218217 }
219218 this . hitBoxes . length = this . _untransformedHitBoxes . length ;
220219 }
221-
222- // The default camera center depends on the object dimensions so checking
223- // the AABB center is not enough.
224- if (
225- this . _unrotatedAABB . min [ 0 ] !== oldUnrotatedMinX ||
226- this . _unrotatedAABB . min [ 1 ] !== oldUnrotatedMinY ||
227- this . _unrotatedAABB . max [ 0 ] !== oldUnrotatedMaxX ||
228- this . _unrotatedAABB . max [ 1 ] !== oldUnrotatedMaxY
229- ) {
230- this . _instanceContainer . onObjectUnscaledCenterChanged (
231- ( oldUnrotatedMinX + oldUnrotatedMaxX ) / 2 ,
232- ( oldUnrotatedMinY + oldUnrotatedMaxY ) / 2
233- ) ;
234- }
235220 }
236221
237222 // Position:
@@ -246,37 +231,52 @@ namespace gdjs {
246231 * @param result Array that will be updated with the result
247232 * (x and y position of the point in parent coordinates).
248233 */
249- applyObjectTransformation ( x : float , y : float , result : number [ ] ) {
250- let cx = this . getCenterX ( ) ;
251- let cy = this . getCenterY ( ) ;
234+ applyObjectTransformation ( x : float , y : float , destination : FloatPoint ) {
235+ const source = destination ;
236+ source [ 0 ] = x ;
237+ source [ 1 ] = y ;
238+ this . getLocalTransformation ( ) . transform ( source , destination ) ;
239+ }
252240
253- // Flipping
254- if ( this . _flippedX ) {
255- x = x + ( cx - x ) * 2 ;
241+ /**
242+ * Return the affine transformation that represents
243+ * flipping, scale, rotation and translation of the object.
244+ * @returns the affine transformation.
245+ */
246+ getLocalTransformation ( ) : gdjs . AffineTransformation {
247+ if ( this . _isLocalTransformationDirty ) {
248+ this . _updateLocalTransformation ( ) ;
256249 }
257- if ( this . _flippedY ) {
258- y = y + ( cy - y ) * 2 ;
250+ return this . _localTransformation ;
251+ }
252+
253+ getLocalInverseTransformation ( ) : gdjs . AffineTransformation {
254+ if ( this . _isLocalTransformationDirty ) {
255+ this . _updateLocalTransformation ( ) ;
259256 }
257+ return this . _localInverseTransformation ;
258+ }
260259
261- // Scale
260+ _updateLocalTransformation ( ) {
262261 const absScaleX = Math . abs ( this . _scaleX ) ;
263262 const absScaleY = Math . abs ( this . _scaleY ) ;
264- x *= absScaleX ;
265- y *= absScaleY ;
266- cx *= absScaleX ;
267- cy *= absScaleY ;
268-
269- // Rotation
270- const angleInRadians = ( this . angle / 180 ) * Math . PI ;
271- const cosValue = Math . cos ( angleInRadians ) ;
272- const sinValue = Math . sin ( angleInRadians ) ;
273- const xToCenterXDelta = x - cx ;
274- const yToCenterYDelta = y - cy ;
275- x = cx + cosValue * xToCenterXDelta - sinValue * yToCenterYDelta ;
276- y = cy + sinValue * xToCenterXDelta + cosValue * yToCenterYDelta ;
277- result . length = 2 ;
278- result [ 0 ] = x + this . x ;
279- result [ 1 ] = y + this . y ;
263+ const centerX = this . getUnscaledCenterX ( ) * absScaleX ;
264+ const centerY = this . getUnscaledCenterY ( ) * absScaleY ;
265+ const angleInRadians = ( this . angle * Math . PI ) / 180 ;
266+
267+ this . _localTransformation . setToTranslation ( this . x , this . y ) ;
268+ this . _localTransformation . rotateAround ( angleInRadians , centerX , centerY ) ;
269+ if ( this . _flippedX ) {
270+ this . _localTransformation . flipX ( centerX ) ;
271+ }
272+ if ( this . _flippedY ) {
273+ this . _localTransformation . flipY ( centerY ) ;
274+ }
275+ this . _localTransformation . scale ( absScaleX , absScaleY ) ;
276+
277+ this . _localInverseTransformation . copyFrom ( this . _localTransformation ) ;
278+ this . _localInverseTransformation . invert ( ) ;
279+ this . _isLocalTransformationDirty = false ;
280280 }
281281
282282 /**
@@ -290,53 +290,51 @@ namespace gdjs {
290290 * @param result Array that will be updated with the result
291291 * (x and y position of the point in object coordinates).
292292 */
293- applyObjectInverseTransformation ( x : float , y : float , result : number [ ] ) {
294- x -= this . getCenterXInScene ( ) ;
295- y -= this . getCenterYInScene ( ) ;
296-
297- const absScaleX = Math . abs ( this . _scaleX ) ;
298- const absScaleY = Math . abs ( this . _scaleY ) ;
299-
300- // Rotation
301- const angleInRadians = ( this . angle / 180 ) * Math . PI ;
302- const cosValue = Math . cos ( - angleInRadians ) ;
303- const sinValue = Math . sin ( - angleInRadians ) ;
304- const oldX = x ;
305- x = cosValue * x - sinValue * y ;
306- y = sinValue * oldX + cosValue * y ;
307-
308- // Scale
309- x /= absScaleX ;
310- y /= absScaleY ;
311-
312- // Flipping
313- if ( this . _flippedX ) {
314- x = - x ;
315- }
316- if ( this . _flippedY ) {
317- y = - y ;
318- }
319-
320- const positionToCenterX =
321- this . getUnscaledWidth ( ) / 2 + this . _unrotatedAABB . min [ 0 ] ;
322- const positionToCenterY =
323- this . getUnscaledHeight ( ) / 2 + this . _unrotatedAABB . min [ 1 ] ;
324- result [ 0 ] = x + positionToCenterX ;
325- result [ 1 ] = y + positionToCenterY ;
293+ applyObjectInverseTransformation (
294+ x : float ,
295+ y : float ,
296+ destination : FloatPoint
297+ ) {
298+ const source = destination ;
299+ source [ 0 ] = x ;
300+ source [ 1 ] = y ;
301+ this . getLocalInverseTransformation ( ) . transform ( source , destination ) ;
326302 }
327303
328304 getDrawableX ( ) : float {
329305 if ( this . _isUntransformedHitBoxesDirty ) {
330306 this . _updateUntransformedHitBoxes ( ) ;
331307 }
332- return this . x + this . _unrotatedAABB . min [ 0 ] * this . _scaleX ;
308+ const absScaleX = this . getScaleX ( ) ;
309+ if ( ! this . _flippedX ) {
310+ return this . x + this . _unrotatedAABB . min [ 0 ] * absScaleX ;
311+ } else {
312+ return (
313+ this . x +
314+ ( - this . _unrotatedAABB . min [ 0 ] -
315+ this . getUnscaledWidth ( ) +
316+ 2 * this . getUnscaledCenterX ( ) ) *
317+ absScaleX
318+ ) ;
319+ }
333320 }
334321
335322 getDrawableY ( ) : float {
336323 if ( this . _isUntransformedHitBoxesDirty ) {
337324 this . _updateUntransformedHitBoxes ( ) ;
338325 }
339- return this . y + this . _unrotatedAABB . min [ 1 ] * this . _scaleY ;
326+ const absScaleY = this . getScaleY ( ) ;
327+ if ( ! this . _flippedY ) {
328+ return this . y + this . _unrotatedAABB . min [ 1 ] * absScaleY ;
329+ } else {
330+ return (
331+ this . y +
332+ ( - this . _unrotatedAABB . min [ 1 ] -
333+ this . getUnscaledHeight ( ) +
334+ 2 * this . getUnscaledCenterY ( ) ) *
335+ absScaleY
336+ ) ;
337+ }
340338 }
341339
342340 /**
@@ -363,6 +361,9 @@ namespace gdjs {
363361 * @returns the center X from the local origin (0;0).
364362 */
365363 getUnscaledCenterX ( ) : float {
364+ if ( this . _customCenter ) {
365+ return this . _customCenter [ 0 ] ;
366+ }
366367 if ( this . _isUntransformedHitBoxesDirty ) {
367368 this . _updateUntransformedHitBoxes ( ) ;
368369 }
@@ -373,12 +374,57 @@ namespace gdjs {
373374 * @returns the center Y from the local origin (0;0).
374375 */
375376 getUnscaledCenterY ( ) : float {
377+ if ( this . _customCenter ) {
378+ return this . _customCenter [ 1 ] ;
379+ }
376380 if ( this . _isUntransformedHitBoxesDirty ) {
377381 this . _updateUntransformedHitBoxes ( ) ;
378382 }
379383 return ( this . _unrotatedAABB . min [ 1 ] + this . _unrotatedAABB . max [ 1 ] ) / 2 ;
380384 }
381385
386+ /**
387+ * The center of rotation is defined relatively to the origin (the object
388+ * position).
389+ * This avoids the center to move when children push the bounds.
390+ *
391+ * When no custom center is defined, it will move
392+ * to stay at the center of the children bounds.
393+ *
394+ * @param x coordinate of the custom center
395+ * @param y coordinate of the custom center
396+ */
397+ setRotationCenter ( x : float , y : float ) {
398+ if ( ! this . _customCenter ) {
399+ this . _customCenter = [ 0 , 0 ] ;
400+ }
401+ this . _customCenter [ 0 ] = x ;
402+ this . _customCenter [ 1 ] = y ;
403+
404+ this . _isLocalTransformationDirty = true ;
405+ this . invalidateHitboxes ( ) ;
406+ }
407+
408+ getCenterX ( ) : float {
409+ if ( this . _isUntransformedHitBoxesDirty ) {
410+ this . _updateUntransformedHitBoxes ( ) ;
411+ }
412+ return (
413+ ( this . getUnscaledCenterX ( ) - this . _unrotatedAABB . min [ 0 ] ) *
414+ this . getScaleX ( )
415+ ) ;
416+ }
417+
418+ getCenterY ( ) : float {
419+ if ( this . _isUntransformedHitBoxesDirty ) {
420+ this . _updateUntransformedHitBoxes ( ) ;
421+ }
422+ return (
423+ ( this . getUnscaledCenterY ( ) - this . _unrotatedAABB . min [ 1 ] ) *
424+ this . getScaleY ( )
425+ ) ;
426+ }
427+
382428 getWidth ( ) : float {
383429 return this . getUnscaledWidth ( ) * this . getScaleX ( ) ;
384430 }
@@ -417,6 +463,7 @@ namespace gdjs {
417463 return ;
418464 }
419465 this . x = x ;
466+ this . _isLocalTransformationDirty = true ;
420467 this . invalidateHitboxes ( ) ;
421468 this . getRenderer ( ) . updateX ( ) ;
422469 }
@@ -426,6 +473,7 @@ namespace gdjs {
426473 return ;
427474 }
428475 this . y = y ;
476+ this . _isLocalTransformationDirty = true ;
429477 this . invalidateHitboxes ( ) ;
430478 this . getRenderer ( ) . updateY ( ) ;
431479 }
@@ -435,6 +483,7 @@ namespace gdjs {
435483 return ;
436484 }
437485 this . angle = angle ;
486+ this . _isLocalTransformationDirty = true ;
438487 this . invalidateHitboxes ( ) ;
439488 this . getRenderer ( ) . updateAngle ( ) ;
440489 }
@@ -456,6 +505,7 @@ namespace gdjs {
456505 }
457506 this . _scaleX = newScale * ( this . _flippedX ? - 1 : 1 ) ;
458507 this . _scaleY = newScale * ( this . _flippedY ? - 1 : 1 ) ;
508+ this . _isLocalTransformationDirty = true ;
459509 this . invalidateHitboxes ( ) ;
460510 this . getRenderer ( ) . update ( ) ;
461511 }
@@ -473,6 +523,7 @@ namespace gdjs {
473523 return ;
474524 }
475525 this . _scaleX = newScale * ( this . _flippedX ? - 1 : 1 ) ;
526+ this . _isLocalTransformationDirty = true ;
476527 this . invalidateHitboxes ( ) ;
477528 this . getRenderer ( ) . update ( ) ;
478529 }
0 commit comments