-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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 1 commit
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
Collaborator
Author
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.
Owner
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,49 @@ 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) { | ||
| this.getLocalTransformation().transform([x, y], 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 centerX = this.getUnscaledCenterX(); | ||
| const centerY = this.getUnscaledCenterY(); | ||
| 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 angleInRadians = (this.angle * Math.PI) / 180; | ||
|
|
||
| this._localTransformation.setToTranslation(this.x, this.y); | ||
| this._localTransformation.scale(absScaleX, absScaleY); | ||
| this._localTransformation.rotateAround(angleInRadians, centerX, centerY); | ||
| if (this._flippedX) { | ||
| this._localTransformation.flipX(centerX); | ||
| } | ||
| if (this._flippedY) { | ||
| this._localTransformation.flipY(centerY); | ||
| } | ||
|
|
||
| this._localInverseTransformation.copyFrom(this._localTransformation); | ||
| this._localInverseTransformation.invert(); | ||
| this._isLocalTransformationDirty = false; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -290,39 +287,12 @@ 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 | ||
| ) { | ||
| this.getLocalInverseTransformation().transform([x, y], destination); | ||
| } | ||
|
|
||
| getDrawableX(): float { | ||
|
|
@@ -363,6 +333,9 @@ namespace gdjs { | |
| * @returns the center X from the local origin (0;0). | ||
| */ | ||
| getUnscaledCenterX(): float { | ||
| if (this._customCenter) { | ||
|
Owner
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?
Collaborator
Author
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 +346,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 +435,7 @@ namespace gdjs { | |
| return; | ||
| } | ||
| this.x = x; | ||
| this._isLocalTransformationDirty = true; | ||
| this.invalidateHitboxes(); | ||
| this.getRenderer().updateX(); | ||
| } | ||
|
|
@@ -426,6 +445,7 @@ namespace gdjs { | |
| return; | ||
| } | ||
| this.y = y; | ||
| this._isLocalTransformationDirty = true; | ||
| this.invalidateHitboxes(); | ||
| this.getRenderer().updateY(); | ||
| } | ||
|
|
@@ -435,6 +455,7 @@ namespace gdjs { | |
| return; | ||
| } | ||
| this.angle = angle; | ||
| this._isLocalTransformationDirty = true; | ||
| this.invalidateHitboxes(); | ||
| this.getRenderer().updateAngle(); | ||
| } | ||
|
|
@@ -456,6 +477,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 +495,7 @@ namespace gdjs { | |
| return; | ||
| } | ||
| this._scaleX = newScale * (this._flippedX ? -1 : 1); | ||
| this._isLocalTransformationDirty = true; | ||
| this.invalidateHitboxes(); | ||
| this.getRenderer().update(); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,13 @@ namespace gdjs { | |
| this._debuggerRenderer = new gdjs.DebuggerRenderer(this); | ||
| } | ||
|
|
||
| addLayer(layerData: LayerData) { | ||
| this._layers.put( | ||
| layerData.name, | ||
| new gdjs.RuntimeCustomObjectLayer(layerData, this) | ||
| ); | ||
| } | ||
|
|
||
| createObject(objectName: string): gdjs.RuntimeObject | null { | ||
| const result = super.createObject(objectName); | ||
| this._customObject.onChildrenLocationChanged(); | ||
|
|
@@ -293,21 +300,6 @@ namespace gdjs { | |
| this._customObject.onChildrenLocationChanged(); | ||
| } | ||
|
|
||
| /** | ||
| * Triggered when the object dimensions are changed. | ||
| * | ||
| * It adapts the layers camera positions. | ||
| */ | ||
| onObjectUnscaledCenterChanged(oldOriginX: float, oldOriginY: float): void { | ||
|
Owner
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. Nice! Why don't we need this anymore?
Collaborator
Author
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. The internal camera of custom objects was trying to stay at the center of the children (a bit like camera stays on the upper left corner of the scene). This way the transformation has no effect and children were not shifted. This system has been removed to avoid any side effects as it doesn't really make sense. |
||
| for (const name in this._layers.items) { | ||
| if (this._layers.items.hasOwnProperty(name)) { | ||
| /** @type gdjs.Layer */ | ||
| const theLayer: gdjs.Layer = this._layers.items[name]; | ||
| theLayer.onGameResolutionResized(oldOriginX, oldOriginY); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| convertCoords(x: float, y: float, result: FloatPoint): FloatPoint { | ||
| // The result parameter used to be optional. | ||
| let position = result || [0, 0]; | ||
|
|
||
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.RuntimeSceneLayerif 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