mirror of
https://github.com/Heretek-AI/GDevelop.git
synced 2026-07-24 20:05:39 -04:00
deb802cfec
* For now, this is limited to instances inside custom objects. This will be made available in the future for all instances in scenes if this works well.
561 lines
15 KiB
TypeScript
561 lines
15 KiB
TypeScript
namespace gdjs {
|
|
const getValidDimensionValue = (value: float | undefined) =>
|
|
value === undefined ? 100 : value <= 0 ? 1 : value;
|
|
|
|
type Object3DNetworkSyncDataType = {
|
|
// z is position on the Z axis, different from zo, which is Z order
|
|
z: number;
|
|
d: number;
|
|
rx: number;
|
|
ry: number;
|
|
// no need for rz, as it is the angle from gdjs.RuntimeObject
|
|
flipX: boolean;
|
|
flipY: boolean;
|
|
flipZ: boolean;
|
|
};
|
|
|
|
/** @category Objects > 3D Objects */
|
|
export type Object3DNetworkSyncData = ObjectNetworkSyncData &
|
|
Object3DNetworkSyncDataType;
|
|
|
|
/**
|
|
* Base class for 3D objects.
|
|
* @category Objects > 3D Objects
|
|
*/
|
|
export abstract class RuntimeObject3D
|
|
extends gdjs.RuntimeObject
|
|
implements gdjs.Resizable, gdjs.Scalable, gdjs.Flippable, gdjs.Base3DHandler
|
|
{
|
|
/**
|
|
* Position on the Z axis.
|
|
*/
|
|
private _z: float = 0;
|
|
/**
|
|
* `_width` takes this value when the scale equals 1.
|
|
*
|
|
* It can't be 0.
|
|
*/
|
|
private _originalWidth: float;
|
|
/**
|
|
* `_height` takes this value when the scale equals 1.
|
|
*
|
|
* It can't be 0.
|
|
*/
|
|
private _originalHeight: float;
|
|
/**
|
|
* `depth` takes this value when the scale equals 1.
|
|
*
|
|
* It can't be 0.
|
|
*/
|
|
private _originalDepth: float;
|
|
private _width: float;
|
|
private _height: float;
|
|
private _depth: float;
|
|
private _flippedX: boolean = false;
|
|
private _flippedY: boolean = false;
|
|
private _flippedZ: boolean = false;
|
|
/**
|
|
* Euler angle with the `ZYX` order.
|
|
*
|
|
* Note that `_rotationZ` is `angle` from `gdjs.RuntimeObject`.
|
|
*/
|
|
private _rotationX: float = 0;
|
|
/**
|
|
* Euler angle with the `ZYX` order.
|
|
*
|
|
* Note that `_rotationZ` is `angle` from `gdjs.RuntimeObject`.
|
|
*/
|
|
private _rotationY: float = 0;
|
|
private static _temporaryVector = new THREE.Vector3();
|
|
|
|
constructor(
|
|
instanceContainer: gdjs.RuntimeInstanceContainer,
|
|
objectData: gdjs.Object3DData,
|
|
instanceData: InstanceData | undefined
|
|
) {
|
|
super(instanceContainer, objectData, instanceData);
|
|
// TODO Should 0 be replaced by 0.01 instead of using the default value?
|
|
this._width = this._originalWidth = getValidDimensionValue(
|
|
objectData.content.width
|
|
);
|
|
this._height = this._originalHeight = getValidDimensionValue(
|
|
objectData.content.height
|
|
);
|
|
this._depth = this._originalDepth = getValidDimensionValue(
|
|
objectData.content.depth
|
|
);
|
|
}
|
|
|
|
abstract getRenderer(): gdjs.RuntimeObject3DRenderer;
|
|
|
|
getRendererObject() {
|
|
return null;
|
|
}
|
|
|
|
get3DRendererObject() {
|
|
return this.getRenderer().get3DRendererObject();
|
|
}
|
|
|
|
updateFromObjectData(
|
|
oldObjectData: Object3DData,
|
|
newObjectData: Object3DData
|
|
): boolean {
|
|
this.updateOriginalDimensionsFromObjectData(oldObjectData, newObjectData);
|
|
return true;
|
|
}
|
|
|
|
updateOriginalDimensionsFromObjectData(
|
|
oldObjectData: Object3DData,
|
|
newObjectData: Object3DData
|
|
): void {
|
|
// There is no need to check if they changed because events can't modify them.
|
|
this._setOriginalWidth(
|
|
getValidDimensionValue(newObjectData.content.width)
|
|
);
|
|
this._setOriginalHeight(
|
|
getValidDimensionValue(newObjectData.content.height)
|
|
);
|
|
this._setOriginalDepth(
|
|
getValidDimensionValue(newObjectData.content.depth)
|
|
);
|
|
}
|
|
|
|
getNetworkSyncData(
|
|
syncOptions: GetNetworkSyncDataOptions
|
|
): Object3DNetworkSyncData {
|
|
return {
|
|
...super.getNetworkSyncData(syncOptions),
|
|
z: this.getZ(),
|
|
d: this.getDepth(),
|
|
rx: this.getRotationX(),
|
|
ry: this.getRotationY(),
|
|
flipX: this.isFlippedX(),
|
|
flipY: this.isFlippedY(),
|
|
flipZ: this.isFlippedZ(),
|
|
};
|
|
}
|
|
|
|
updateFromNetworkSyncData(
|
|
networkSyncData: Object3DNetworkSyncData,
|
|
options: UpdateFromNetworkSyncDataOptions
|
|
) {
|
|
super.updateFromNetworkSyncData(networkSyncData, options);
|
|
if (networkSyncData.z !== undefined) this.setZ(networkSyncData.z);
|
|
if (networkSyncData.d !== undefined) this.setDepth(networkSyncData.d);
|
|
if (networkSyncData.rx !== undefined)
|
|
this.setRotationX(networkSyncData.rx);
|
|
if (networkSyncData.ry !== undefined)
|
|
this.setRotationY(networkSyncData.ry);
|
|
if (networkSyncData.flipX !== undefined)
|
|
this.flipX(networkSyncData.flipX);
|
|
if (networkSyncData.flipY !== undefined)
|
|
this.flipY(networkSyncData.flipY);
|
|
if (networkSyncData.flipZ !== undefined)
|
|
this.flipZ(networkSyncData.flipZ);
|
|
}
|
|
|
|
extraInitializationFromInitialInstance(initialInstanceData: InstanceData) {
|
|
if (initialInstanceData.customSize) {
|
|
this.setWidth(initialInstanceData.width);
|
|
this.setHeight(initialInstanceData.height);
|
|
}
|
|
if (initialInstanceData.depth !== undefined) {
|
|
this.setDepth(initialInstanceData.depth);
|
|
}
|
|
this.flipX(!!initialInstanceData.flippedX);
|
|
this.flipY(!!initialInstanceData.flippedY);
|
|
this.flipZ(!!initialInstanceData.flippedZ);
|
|
}
|
|
|
|
setX(x: float): void {
|
|
super.setX(x);
|
|
this.getRenderer().updatePosition();
|
|
}
|
|
|
|
setY(y: float): void {
|
|
super.setY(y);
|
|
this.getRenderer().updatePosition();
|
|
}
|
|
|
|
/**
|
|
* Set the object position on the Z axis.
|
|
*/
|
|
setZ(z: float): void {
|
|
if (z === this._z) return;
|
|
this._z = z;
|
|
this.getRenderer().updatePosition();
|
|
}
|
|
|
|
/**
|
|
* Get the object position on the Z axis.
|
|
*/
|
|
getZ(): float {
|
|
return this._z;
|
|
}
|
|
|
|
/**
|
|
* Get the Z position of the rendered object.
|
|
*
|
|
* For most objects, this will returns the same value as getZ(). But if the
|
|
* object has an origin that is not the same as the point (0,0,0) of the
|
|
* object displayed, getDrawableZ will differ.
|
|
*
|
|
* @return The Z position of the rendered object.
|
|
*/
|
|
getDrawableZ(): float {
|
|
return this._z;
|
|
}
|
|
|
|
/**
|
|
* Return the bottom Z of the object.
|
|
* Rotations around X and Y are not taken into account.
|
|
*/
|
|
getUnrotatedAABBMinZ(): number {
|
|
return this.getDrawableZ();
|
|
}
|
|
|
|
/**
|
|
* Return the top Z of the object.
|
|
* Rotations around X and Y are not taken into account.
|
|
*/
|
|
getUnrotatedAABBMaxZ(): number {
|
|
return this.getDrawableZ() + this.getDepth();
|
|
}
|
|
|
|
/**
|
|
* Return the Z position of the object center, **relative to the object Z
|
|
* position** (`getDrawableX`).
|
|
*
|
|
* Use `getCenterZInScene` to get the position of the center in the scene.
|
|
*
|
|
* @return the Z position of the object center, relative to
|
|
* `getDrawableZ()`.
|
|
*/
|
|
getCenterZ(): float {
|
|
return this.getDepth() / 2;
|
|
}
|
|
|
|
getCenterZInScene(): float {
|
|
return this.getDrawableZ() + this.getCenterZ();
|
|
}
|
|
|
|
setCenterZInScene(z: float): void {
|
|
this.setZ(z + this._z - (this.getDrawableZ() + this.getCenterZ()));
|
|
}
|
|
|
|
setAngle(angle: float): void {
|
|
super.setAngle(angle);
|
|
this.getRenderer().updateRotation();
|
|
}
|
|
|
|
/**
|
|
* Set the object rotation on the X axis.
|
|
*
|
|
* This is an Euler angle. Objects use the `ZYX` order.
|
|
*/
|
|
setRotationX(angle: float): void {
|
|
this._rotationX = angle;
|
|
this.getRenderer().updateRotation();
|
|
}
|
|
|
|
/**
|
|
* Set the object rotation on the Y axis.
|
|
*
|
|
* This is an Euler angle. Objects use the `ZYX` order.
|
|
*/
|
|
setRotationY(angle: float): void {
|
|
this._rotationY = angle;
|
|
this.getRenderer().updateRotation();
|
|
}
|
|
|
|
/**
|
|
* Get the object rotation on the X axis.
|
|
*
|
|
* This is an Euler angle. Objects use the `ZYX` order.
|
|
*/
|
|
getRotationX(): float {
|
|
return this._rotationX;
|
|
}
|
|
|
|
/**
|
|
* Get the object rotation on the Y axis.
|
|
*
|
|
* This is an Euler angle. Objects use the `ZYX` order.
|
|
*/
|
|
getRotationY(): float {
|
|
return this._rotationY;
|
|
}
|
|
|
|
/**
|
|
* Turn the object around the scene x axis at its center.
|
|
* @param deltaAngle the rotation angle
|
|
*/
|
|
turnAroundX(deltaAngle: float): void {
|
|
const axisX = gdjs.RuntimeObject3D._temporaryVector;
|
|
axisX.set(1, 0, 0);
|
|
|
|
const mesh = this.getRenderer().get3DRendererObject();
|
|
mesh.rotateOnWorldAxis(axisX, gdjs.toRad(deltaAngle));
|
|
this._rotationX = gdjs.toDegrees(mesh.rotation.x);
|
|
this._rotationY = gdjs.toDegrees(mesh.rotation.y);
|
|
this.setAngle(gdjs.toDegrees(mesh.rotation.z));
|
|
}
|
|
|
|
/**
|
|
* Turn the object around the scene y axis at its center.
|
|
* @param deltaAngle the rotation angle
|
|
*/
|
|
turnAroundY(deltaAngle: float): void {
|
|
const axisY = gdjs.RuntimeObject3D._temporaryVector;
|
|
axisY.set(0, 1, 0);
|
|
|
|
const mesh = this.getRenderer().get3DRendererObject();
|
|
mesh.rotateOnWorldAxis(axisY, gdjs.toRad(deltaAngle));
|
|
this._rotationX = gdjs.toDegrees(mesh.rotation.x);
|
|
this._rotationY = gdjs.toDegrees(mesh.rotation.y);
|
|
this.setAngle(gdjs.toDegrees(mesh.rotation.z));
|
|
}
|
|
|
|
/**
|
|
* Turn the object around the scene z axis at its center.
|
|
* @param deltaAngle the rotation angle
|
|
*/
|
|
turnAroundZ(deltaAngle: float): void {
|
|
const axisZ = gdjs.RuntimeObject3D._temporaryVector;
|
|
axisZ.set(0, 0, 1);
|
|
|
|
const mesh = this.getRenderer().get3DRendererObject();
|
|
mesh.rotateOnWorldAxis(axisZ, gdjs.toRad(deltaAngle));
|
|
this._rotationX = gdjs.toDegrees(mesh.rotation.x);
|
|
this._rotationY = gdjs.toDegrees(mesh.rotation.y);
|
|
this.setAngle(gdjs.toDegrees(mesh.rotation.z));
|
|
}
|
|
|
|
override getOriginalWidth(): float {
|
|
return this._originalWidth;
|
|
}
|
|
|
|
override getOriginalHeight(): float {
|
|
return this._originalHeight;
|
|
}
|
|
|
|
getOriginalDepth(): float {
|
|
return this._originalDepth;
|
|
}
|
|
|
|
getWidth(): float {
|
|
return this._width;
|
|
}
|
|
|
|
getHeight(): float {
|
|
return this._height;
|
|
}
|
|
|
|
/**
|
|
* Get the object size on the Z axis (called "depth").
|
|
*/
|
|
getDepth(): float {
|
|
return this._depth;
|
|
}
|
|
|
|
setWidth(width: float): void {
|
|
if (this._width === width) return;
|
|
|
|
this._width = width;
|
|
this.getRenderer().updateSize();
|
|
this.invalidateHitboxes();
|
|
}
|
|
|
|
setHeight(height: float): void {
|
|
if (this._height === height) return;
|
|
|
|
this._height = height;
|
|
this.getRenderer().updateSize();
|
|
this.invalidateHitboxes();
|
|
}
|
|
|
|
setSize(newWidth: number, newHeight: number): void {
|
|
this.setWidth(newWidth);
|
|
this.setHeight(newHeight);
|
|
}
|
|
|
|
/**
|
|
* Set the object size on the Z axis (called "depth").
|
|
*/
|
|
setDepth(depth: float): void {
|
|
if (this._depth === depth) return;
|
|
|
|
this._depth = depth;
|
|
this.getRenderer().updateSize();
|
|
}
|
|
|
|
/**
|
|
* Set the width of the object for a scale of 1.
|
|
*/
|
|
_setOriginalWidth(originalWidth: float): void {
|
|
if (originalWidth <= 0) {
|
|
originalWidth = 1;
|
|
}
|
|
const oldOriginalWidth = this._originalWidth;
|
|
this._originalWidth = originalWidth;
|
|
if (oldOriginalWidth === this._width) {
|
|
this.setWidth(originalWidth);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the height of the object for a scale of 1.
|
|
*/
|
|
_setOriginalHeight(originalHeight: float): void {
|
|
if (originalHeight <= 0) {
|
|
originalHeight = 1;
|
|
}
|
|
const oldOriginalHeight = this._originalHeight;
|
|
this._originalHeight = originalHeight;
|
|
if (oldOriginalHeight === this._height) {
|
|
this.setHeight(originalHeight);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the object size on the Z axis (called "depth") when the scale equals 1.
|
|
*/
|
|
_setOriginalDepth(originalDepth: float): void {
|
|
if (originalDepth <= 0) {
|
|
originalDepth = 1;
|
|
}
|
|
const oldOriginalDepth = this._originalDepth;
|
|
this._originalDepth = originalDepth;
|
|
if (oldOriginalDepth === this._depth) {
|
|
this.setDepth(originalDepth);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Change the scale on X, Y and Z axis of the object.
|
|
*
|
|
* @param newScale The new scale (must be greater than 0).
|
|
*/
|
|
setScale(newScale: number): void {
|
|
this.setScaleX(newScale);
|
|
this.setScaleY(newScale);
|
|
this.setScaleZ(newScale);
|
|
}
|
|
|
|
/**
|
|
* Change the scale on X axis of the object (changing its width).
|
|
*
|
|
* @param newScale The new scale (must be greater than 0).
|
|
*/
|
|
setScaleX(newScale: number): void {
|
|
if (newScale < 0) {
|
|
newScale = 0;
|
|
}
|
|
this.setWidth(this._originalWidth * newScale);
|
|
}
|
|
|
|
/**
|
|
* Change the scale on Y axis of the object (changing its height).
|
|
*
|
|
* @param newScale The new scale (must be greater than 0).
|
|
*/
|
|
setScaleY(newScale: number): void {
|
|
if (newScale < 0) {
|
|
newScale = 0;
|
|
}
|
|
this.setHeight(this._originalHeight * newScale);
|
|
}
|
|
|
|
/**
|
|
* Change the scale on Z axis of the object (changing its height).
|
|
*
|
|
* @param newScale The new scale (must be greater than 0).
|
|
*/
|
|
setScaleZ(newScale: number): void {
|
|
if (newScale < 0) {
|
|
newScale = 0;
|
|
}
|
|
this.setDepth(this._originalDepth * newScale);
|
|
}
|
|
|
|
/**
|
|
* Get the scale of the object (or the geometric average of X, Y and Z scale in case they are different).
|
|
*
|
|
* @return the scale of the object (or the geometric average of X, Y and Z scale in case they are different).
|
|
*/
|
|
getScale(): number {
|
|
const scaleX = this.getScaleX();
|
|
const scaleY = this.getScaleY();
|
|
const scaleZ = this.getScaleZ();
|
|
return scaleX === scaleY && scaleX === scaleZ
|
|
? scaleX
|
|
: Math.pow(scaleX * scaleY * scaleZ, 1 / 3);
|
|
}
|
|
|
|
/**
|
|
* Get the scale of the object on X axis.
|
|
*
|
|
* @return the scale of the object on X axis
|
|
*/
|
|
getScaleX(): float {
|
|
return Math.abs(this._width / this._originalWidth);
|
|
}
|
|
|
|
/**
|
|
* Get the scale of the object on Y axis.
|
|
*
|
|
* @return the scale of the object on Y axis
|
|
*/
|
|
getScaleY(): float {
|
|
return Math.abs(this._height / this._originalHeight);
|
|
}
|
|
|
|
/**
|
|
* Get the scale of the object on Z axis.
|
|
*
|
|
* @return the scale of the object on Z axis
|
|
*/
|
|
getScaleZ(): float {
|
|
return Math.abs(this._depth / this._originalDepth);
|
|
}
|
|
|
|
flipX(enable: boolean) {
|
|
if (enable !== this._flippedX) {
|
|
this._flippedX = enable;
|
|
this.getRenderer().updateSize();
|
|
}
|
|
}
|
|
|
|
flipY(enable: boolean) {
|
|
if (enable !== this._flippedY) {
|
|
this._flippedY = enable;
|
|
this.getRenderer().updateSize();
|
|
}
|
|
}
|
|
|
|
flipZ(enable: boolean) {
|
|
if (enable !== this._flippedZ) {
|
|
this._flippedZ = enable;
|
|
this.getRenderer().updateSize();
|
|
}
|
|
}
|
|
|
|
isFlippedX(): boolean {
|
|
return this._flippedX;
|
|
}
|
|
|
|
isFlippedY(): boolean {
|
|
return this._flippedY;
|
|
}
|
|
|
|
isFlippedZ(): boolean {
|
|
return this._flippedZ;
|
|
}
|
|
|
|
hide(enable: boolean): void {
|
|
super.hide(enable);
|
|
this.getRenderer().updateVisibility();
|
|
}
|
|
}
|
|
}
|