Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Extensions/PrimitiveDrawing/shapepainterruntimeobject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ namespace gdjs {
/**
* The center of rotation is defined relatively
* to the drawing origin (the object position).
* This avoid the center to move on the drawing
* This avoids the center to move on the drawing
* when new shapes push the bounds.
*
* When no custom center is defined, it will move
Expand Down
3 changes: 2 additions & 1 deletion GDJS/GDJS/IDE/ExporterHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -589,8 +589,9 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
InsertUnique(includesFiles, "runtimescene.js");
InsertUnique(includesFiles, "scenestack.js");
InsertUnique(includesFiles, "force.js");
InsertUnique(includesFiles, "RuntimeLayer.js");
InsertUnique(includesFiles, "layer.js");
Copy link
Owner

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly

InsertUnique(includesFiles, "RuntimeSceneLayer.js");
InsertUnique(includesFiles, "RuntimeCustomObjectLayer.js");
InsertUnique(includesFiles, "timer.js");
InsertUnique(includesFiles, "runtimewatermark.js");
InsertUnique(includesFiles, "runtimegame.js");
Expand Down
191 changes: 107 additions & 84 deletions GDJS/Runtime/CustomRuntimeObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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.

Copy link
Owner

Choose a reason for hiding this comment

The 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
Expand Down Expand Up @@ -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());
Expand All @@ -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]
);
}
Expand All @@ -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;
Expand Down Expand Up @@ -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:
Expand All @@ -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;
}

/**
Expand All @@ -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 {
Expand Down Expand Up @@ -363,6 +333,9 @@ namespace gdjs {
* @returns the center X from the local origin (0;0).
*/
getUnscaledCenterX(): float {
if (this._customCenter) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this a bug?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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();
}
Expand All @@ -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();
}
Expand Down Expand Up @@ -417,6 +435,7 @@ namespace gdjs {
return;
}
this.x = x;
this._isLocalTransformationDirty = true;
this.invalidateHitboxes();
this.getRenderer().updateX();
}
Expand All @@ -426,6 +445,7 @@ namespace gdjs {
return;
}
this.y = y;
this._isLocalTransformationDirty = true;
this.invalidateHitboxes();
this.getRenderer().updateY();
}
Expand All @@ -435,6 +455,7 @@ namespace gdjs {
return;
}
this.angle = angle;
this._isLocalTransformationDirty = true;
this.invalidateHitboxes();
this.getRenderer().updateAngle();
}
Expand All @@ -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();
}
Expand All @@ -473,6 +495,7 @@ namespace gdjs {
return;
}
this._scaleX = newScale * (this._flippedX ? -1 : 1);
this._isLocalTransformationDirty = true;
this.invalidateHitboxes();
this.getRenderer().update();
}
Expand Down
22 changes: 7 additions & 15 deletions GDJS/Runtime/CustomRuntimeObjectInstanceContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Why don't we need this anymore?

Copy link
Collaborator Author

@D8H D8H Feb 8, 2023

Choose a reason for hiding this comment

The 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];
Expand Down
Loading