-
Notifications
You must be signed in to change notification settings - Fork 970
Allow event-based objects to define a rotation center point #4910
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,11 +27,15 @@ namespace gdjs { | |
_untransformedHitBoxes: gdjs.Polygon[] = []; | ||
/** The dimension of this object is calculated from its children AABBs. */ | ||
_unrotatedAABB: AABB = { min: [0, 0], max: [0, 0] }; | ||
_scaleX: number = 1; | ||
_scaleY: number = 1; | ||
_scaleX: float = 1; | ||
_scaleY: float = 1; | ||
_flippedX: boolean = false; | ||
_flippedY: boolean = false; | ||
opacity: float = 255; | ||
_customCenter: FloatPoint | null = null; | ||
_localTransformation: gdjs.AffineTransformation = new gdjs.AffineTransformation(); | ||
_localInverseTransformation: gdjs.AffineTransformation = new gdjs.AffineTransformation(); | ||
_isLocalTransformationDirty: boolean = true; | ||
Comment on lines
+36
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It will take more memory but the code is easier to maintain and maybe faster because an affine transformation is 8 arithmetical operations versus something like 25 for the hand crafted transformation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that's fine and fair to have these. Custom objects are not designed to be super lightweight objects working as particles for example. |
||
|
||
/** | ||
* @param parent The container the object belongs to | ||
|
@@ -149,8 +153,9 @@ namespace gdjs { | |
this._updateUntransformedHitBoxes(); | ||
} | ||
|
||
//Update the current hitboxes with the frame custom hit boxes | ||
//and apply transformations. | ||
// Update the current hitboxes with the frame custom hit boxes | ||
// and apply transformations. | ||
const localTransformation = this.getLocalTransformation(); | ||
for (let i = 0; i < this._untransformedHitBoxes.length; ++i) { | ||
if (i >= this.hitBoxes.length) { | ||
this.hitBoxes.push(new gdjs.Polygon()); | ||
|
@@ -163,9 +168,8 @@ namespace gdjs { | |
if (j >= this.hitBoxes[i].vertices.length) { | ||
this.hitBoxes[i].vertices.push([0, 0]); | ||
} | ||
this.applyObjectTransformation( | ||
this._untransformedHitBoxes[i].vertices[j][0], | ||
this._untransformedHitBoxes[i].vertices[j][1], | ||
localTransformation.transform( | ||
this._untransformedHitBoxes[i].vertices[j], | ||
this.hitBoxes[i].vertices[j] | ||
); | ||
} | ||
|
@@ -181,11 +185,6 @@ namespace gdjs { | |
_updateUntransformedHitBoxes() { | ||
this._isUntransformedHitBoxesDirty = false; | ||
|
||
const oldUnrotatedMinX = this._unrotatedAABB.min[0]; | ||
const oldUnrotatedMinY = this._unrotatedAABB.min[1]; | ||
const oldUnrotatedMaxX = this._unrotatedAABB.max[0]; | ||
const oldUnrotatedMaxY = this._unrotatedAABB.max[1]; | ||
|
||
this._untransformedHitBoxes.length = 0; | ||
if (this._instanceContainer.getAdhocListOfAllInstances().length === 0) { | ||
this._unrotatedAABB.min[0] = 0; | ||
|
@@ -218,20 +217,6 @@ namespace gdjs { | |
} | ||
this.hitBoxes.length = this._untransformedHitBoxes.length; | ||
} | ||
|
||
// The default camera center depends on the object dimensions so checking | ||
// the AABB center is not enough. | ||
if ( | ||
this._unrotatedAABB.min[0] !== oldUnrotatedMinX || | ||
this._unrotatedAABB.min[1] !== oldUnrotatedMinY || | ||
this._unrotatedAABB.max[0] !== oldUnrotatedMaxX || | ||
this._unrotatedAABB.max[1] !== oldUnrotatedMaxY | ||
) { | ||
this._instanceContainer.onObjectUnscaledCenterChanged( | ||
(oldUnrotatedMinX + oldUnrotatedMaxX) / 2, | ||
(oldUnrotatedMinY + oldUnrotatedMaxY) / 2 | ||
); | ||
} | ||
} | ||
|
||
// Position: | ||
|
@@ -246,37 +231,52 @@ namespace gdjs { | |
* @param result Array that will be updated with the result | ||
* (x and y position of the point in parent coordinates). | ||
*/ | ||
applyObjectTransformation(x: float, y: float, result: number[]) { | ||
let cx = this.getCenterX(); | ||
let cy = this.getCenterY(); | ||
applyObjectTransformation(x: float, y: float, destination: FloatPoint) { | ||
const source = destination; | ||
source[0] = x; | ||
source[1] = y; | ||
this.getLocalTransformation().transform(source, destination); | ||
} | ||
|
||
// Flipping | ||
if (this._flippedX) { | ||
x = x + (cx - x) * 2; | ||
/** | ||
* Return the affine transformation that represents | ||
* flipping, scale, rotation and translation of the object. | ||
* @returns the affine transformation. | ||
*/ | ||
getLocalTransformation(): gdjs.AffineTransformation { | ||
if (this._isLocalTransformationDirty) { | ||
this._updateLocalTransformation(); | ||
} | ||
if (this._flippedY) { | ||
y = y + (cy - y) * 2; | ||
return this._localTransformation; | ||
} | ||
|
||
getLocalInverseTransformation(): gdjs.AffineTransformation { | ||
if (this._isLocalTransformationDirty) { | ||
this._updateLocalTransformation(); | ||
} | ||
return this._localInverseTransformation; | ||
} | ||
|
||
// Scale | ||
_updateLocalTransformation() { | ||
const absScaleX = Math.abs(this._scaleX); | ||
const absScaleY = Math.abs(this._scaleY); | ||
x *= absScaleX; | ||
y *= absScaleY; | ||
cx *= absScaleX; | ||
cy *= absScaleY; | ||
|
||
// Rotation | ||
const angleInRadians = (this.angle / 180) * Math.PI; | ||
const cosValue = Math.cos(angleInRadians); | ||
const sinValue = Math.sin(angleInRadians); | ||
const xToCenterXDelta = x - cx; | ||
const yToCenterYDelta = y - cy; | ||
x = cx + cosValue * xToCenterXDelta - sinValue * yToCenterYDelta; | ||
y = cy + sinValue * xToCenterXDelta + cosValue * yToCenterYDelta; | ||
result.length = 2; | ||
result[0] = x + this.x; | ||
result[1] = y + this.y; | ||
const centerX = this.getUnscaledCenterX() * absScaleX; | ||
const centerY = this.getUnscaledCenterY() * absScaleY; | ||
const angleInRadians = (this.angle * Math.PI) / 180; | ||
|
||
this._localTransformation.setToTranslation(this.x, this.y); | ||
this._localTransformation.rotateAround(angleInRadians, centerX, centerY); | ||
if (this._flippedX) { | ||
this._localTransformation.flipX(centerX); | ||
} | ||
if (this._flippedY) { | ||
this._localTransformation.flipY(centerY); | ||
} | ||
this._localTransformation.scale(absScaleX, absScaleY); | ||
|
||
this._localInverseTransformation.copyFrom(this._localTransformation); | ||
this._localInverseTransformation.invert(); | ||
this._isLocalTransformationDirty = false; | ||
} | ||
|
||
/** | ||
|
@@ -290,53 +290,51 @@ namespace gdjs { | |
* @param result Array that will be updated with the result | ||
* (x and y position of the point in object coordinates). | ||
*/ | ||
applyObjectInverseTransformation(x: float, y: float, result: number[]) { | ||
x -= this.getCenterXInScene(); | ||
y -= this.getCenterYInScene(); | ||
|
||
const absScaleX = Math.abs(this._scaleX); | ||
const absScaleY = Math.abs(this._scaleY); | ||
|
||
// Rotation | ||
const angleInRadians = (this.angle / 180) * Math.PI; | ||
const cosValue = Math.cos(-angleInRadians); | ||
const sinValue = Math.sin(-angleInRadians); | ||
const oldX = x; | ||
x = cosValue * x - sinValue * y; | ||
y = sinValue * oldX + cosValue * y; | ||
|
||
// Scale | ||
x /= absScaleX; | ||
y /= absScaleY; | ||
|
||
// Flipping | ||
if (this._flippedX) { | ||
x = -x; | ||
} | ||
if (this._flippedY) { | ||
y = -y; | ||
} | ||
|
||
const positionToCenterX = | ||
this.getUnscaledWidth() / 2 + this._unrotatedAABB.min[0]; | ||
const positionToCenterY = | ||
this.getUnscaledHeight() / 2 + this._unrotatedAABB.min[1]; | ||
result[0] = x + positionToCenterX; | ||
result[1] = y + positionToCenterY; | ||
applyObjectInverseTransformation( | ||
x: float, | ||
y: float, | ||
destination: FloatPoint | ||
) { | ||
const source = destination; | ||
source[0] = x; | ||
source[1] = y; | ||
this.getLocalInverseTransformation().transform(source, destination); | ||
} | ||
|
||
getDrawableX(): float { | ||
if (this._isUntransformedHitBoxesDirty) { | ||
this._updateUntransformedHitBoxes(); | ||
} | ||
return this.x + this._unrotatedAABB.min[0] * this._scaleX; | ||
const absScaleX = this.getScaleX(); | ||
if (!this._flippedX) { | ||
return this.x + this._unrotatedAABB.min[0] * absScaleX; | ||
} else { | ||
return ( | ||
this.x + | ||
(-this._unrotatedAABB.min[0] - | ||
this.getUnscaledWidth() + | ||
2 * this.getUnscaledCenterX()) * | ||
absScaleX | ||
); | ||
} | ||
} | ||
|
||
getDrawableY(): float { | ||
if (this._isUntransformedHitBoxesDirty) { | ||
this._updateUntransformedHitBoxes(); | ||
} | ||
return this.y + this._unrotatedAABB.min[1] * this._scaleY; | ||
const absScaleY = this.getScaleY(); | ||
if (!this._flippedY) { | ||
return this.y + this._unrotatedAABB.min[1] * absScaleY; | ||
} else { | ||
return ( | ||
this.y + | ||
(-this._unrotatedAABB.min[1] - | ||
this.getUnscaledHeight() + | ||
2 * this.getUnscaledCenterY()) * | ||
absScaleY | ||
); | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -363,6 +361,9 @@ namespace gdjs { | |
* @returns the center X from the local origin (0;0). | ||
*/ | ||
getUnscaledCenterX(): float { | ||
if (this._customCenter) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was this a bug? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a feature I forgot to add. Though, we had already done it for the shape painter version of the slider. |
||
return this._customCenter[0]; | ||
} | ||
if (this._isUntransformedHitBoxesDirty) { | ||
this._updateUntransformedHitBoxes(); | ||
} | ||
|
@@ -373,12 +374,57 @@ namespace gdjs { | |
* @returns the center Y from the local origin (0;0). | ||
*/ | ||
getUnscaledCenterY(): float { | ||
if (this._customCenter) { | ||
return this._customCenter[1]; | ||
} | ||
if (this._isUntransformedHitBoxesDirty) { | ||
this._updateUntransformedHitBoxes(); | ||
} | ||
return (this._unrotatedAABB.min[1] + this._unrotatedAABB.max[1]) / 2; | ||
} | ||
|
||
/** | ||
* The center of rotation is defined relatively to the origin (the object | ||
* position). | ||
* This avoids the center to move when children push the bounds. | ||
* | ||
* When no custom center is defined, it will move | ||
* to stay at the center of the children bounds. | ||
* | ||
* @param x coordinate of the custom center | ||
* @param y coordinate of the custom center | ||
*/ | ||
setRotationCenter(x: float, y: float) { | ||
if (!this._customCenter) { | ||
this._customCenter = [0, 0]; | ||
} | ||
this._customCenter[0] = x; | ||
this._customCenter[1] = y; | ||
|
||
this._isLocalTransformationDirty = true; | ||
this.invalidateHitboxes(); | ||
} | ||
|
||
getCenterX(): float { | ||
if (this._isUntransformedHitBoxesDirty) { | ||
this._updateUntransformedHitBoxes(); | ||
} | ||
return ( | ||
(this.getUnscaledCenterX() - this._unrotatedAABB.min[0]) * | ||
this.getScaleX() | ||
); | ||
} | ||
|
||
getCenterY(): float { | ||
if (this._isUntransformedHitBoxesDirty) { | ||
this._updateUntransformedHitBoxes(); | ||
} | ||
return ( | ||
(this.getUnscaledCenterY() - this._unrotatedAABB.min[1]) * | ||
this.getScaleY() | ||
); | ||
} | ||
|
||
getWidth(): float { | ||
return this.getUnscaledWidth() * this.getScaleX(); | ||
} | ||
|
@@ -417,6 +463,7 @@ namespace gdjs { | |
return; | ||
} | ||
this.x = x; | ||
this._isLocalTransformationDirty = true; | ||
this.invalidateHitboxes(); | ||
this.getRenderer().updateX(); | ||
} | ||
|
@@ -426,6 +473,7 @@ namespace gdjs { | |
return; | ||
} | ||
this.y = y; | ||
this._isLocalTransformationDirty = true; | ||
this.invalidateHitboxes(); | ||
this.getRenderer().updateY(); | ||
} | ||
|
@@ -435,6 +483,7 @@ namespace gdjs { | |
return; | ||
} | ||
this.angle = angle; | ||
this._isLocalTransformationDirty = true; | ||
this.invalidateHitboxes(); | ||
this.getRenderer().updateAngle(); | ||
} | ||
|
@@ -456,6 +505,7 @@ namespace gdjs { | |
} | ||
this._scaleX = newScale * (this._flippedX ? -1 : 1); | ||
this._scaleY = newScale * (this._flippedY ? -1 : 1); | ||
this._isLocalTransformationDirty = true; | ||
this.invalidateHitboxes(); | ||
this.getRenderer().update(); | ||
} | ||
|
@@ -473,6 +523,7 @@ namespace gdjs { | |
return; | ||
} | ||
this._scaleX = newScale * (this._flippedX ? -1 : 1); | ||
this._isLocalTransformationDirty = true; | ||
this.invalidateHitboxes(); | ||
this.getRenderer().update(); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be sure I understand: ideally, from the beginning, gdjs.Layer would have been
gdjs.RuntimeSceneLayer
if it was perfectly named, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly