Files
GDevelop/Extensions/3D/Cube3DRuntimeObject.ts

547 lines
18 KiB
TypeScript

namespace gdjs {
/** Base parameters for {@link gdjs.Cube3DRuntimeObject} */
export interface Cube3DObjectData extends Object3DData {
/** The base parameters of the Cube3D object */
content: Object3DDataContent & {
enableTextureTransparency: boolean | undefined;
facesOrientation: 'Y' | 'Z' | undefined;
frontFaceResourceName: string;
backFaceResourceName: string;
backFaceUpThroughWhichAxisRotation: 'X' | 'Y' | undefined;
leftFaceResourceName: string;
rightFaceResourceName: string;
topFaceResourceName: string;
bottomFaceResourceName: string;
frontFaceResourceRepeat: boolean | undefined;
backFaceResourceRepeat: boolean | undefined;
leftFaceResourceRepeat: boolean | undefined;
rightFaceResourceRepeat: boolean | undefined;
topFaceResourceRepeat: boolean | undefined;
bottomFaceResourceRepeat: boolean | undefined;
frontFaceVisible: boolean;
backFaceVisible: boolean;
leftFaceVisible: boolean;
rightFaceVisible: boolean;
topFaceVisible: boolean;
bottomFaceVisible: boolean;
tint: string | undefined;
materialType: 'Basic' | 'StandardWithoutMetalness';
};
}
type FaceName = 'front' | 'back' | 'left' | 'right' | 'top' | 'bottom';
const faceNameToBitmaskIndex = {
front: 0,
back: 1,
left: 2,
right: 3,
top: 4,
bottom: 5,
};
type Cube3DObjectNetworkSyncDataType = {
fo: 'Y' | 'Z';
bfu: 'X' | 'Y';
vfb: integer;
trfb: integer;
frn: [string, string, string, string, string, string];
mt: number;
tint: string;
};
type Cube3DObjectNetworkSyncData = Object3DNetworkSyncData &
Cube3DObjectNetworkSyncDataType;
/**
* Shows a 3D box object.
*/
export class Cube3DRuntimeObject extends gdjs.RuntimeObject3D {
private _renderer: Cube3DRuntimeObjectRenderer;
private _facesOrientation: 'Y' | 'Z';
private _backFaceUpThroughWhichAxisRotation: 'X' | 'Y';
private _shouldUseTransparentTexture: boolean;
// `_rotationZ` is `angle` from `gdjs.RuntimeObject`.
private _visibleFacesBitmask: integer;
private _textureRepeatFacesBitmask: integer;
private _faceResourceNames: [
string,
string,
string,
string,
string,
string,
];
_materialType: gdjs.Cube3DRuntimeObject.MaterialType =
gdjs.Cube3DRuntimeObject.MaterialType.Basic;
_tint: string;
constructor(
instanceContainer: gdjs.RuntimeInstanceContainer,
objectData: Cube3DObjectData
) {
super(instanceContainer, objectData);
this._shouldUseTransparentTexture =
objectData.content.enableTextureTransparency || false;
this._facesOrientation = objectData.content.facesOrientation || 'Y';
this._visibleFacesBitmask = 0;
if (objectData.content.frontFaceVisible)
this._visibleFacesBitmask |= 1 << faceNameToBitmaskIndex['front'];
if (objectData.content.backFaceVisible)
this._visibleFacesBitmask |= 1 << faceNameToBitmaskIndex['back'];
if (objectData.content.leftFaceVisible)
this._visibleFacesBitmask |= 1 << faceNameToBitmaskIndex['left'];
if (objectData.content.rightFaceVisible)
this._visibleFacesBitmask |= 1 << faceNameToBitmaskIndex['right'];
if (objectData.content.topFaceVisible)
this._visibleFacesBitmask |= 1 << faceNameToBitmaskIndex['top'];
if (objectData.content.bottomFaceVisible)
this._visibleFacesBitmask |= 1 << faceNameToBitmaskIndex['bottom'];
this._textureRepeatFacesBitmask = 0;
if (objectData.content.frontFaceResourceRepeat)
this._textureRepeatFacesBitmask |= 1 << faceNameToBitmaskIndex['front'];
if (objectData.content.backFaceResourceRepeat)
this._textureRepeatFacesBitmask |= 1 << faceNameToBitmaskIndex['back'];
if (objectData.content.leftFaceResourceRepeat)
this._textureRepeatFacesBitmask |= 1 << faceNameToBitmaskIndex['left'];
if (objectData.content.rightFaceResourceRepeat)
this._textureRepeatFacesBitmask |= 1 << faceNameToBitmaskIndex['right'];
if (objectData.content.topFaceResourceRepeat)
this._textureRepeatFacesBitmask |= 1 << faceNameToBitmaskIndex['top'];
if (objectData.content.bottomFaceResourceRepeat)
this._textureRepeatFacesBitmask |=
1 << faceNameToBitmaskIndex['bottom'];
this._backFaceUpThroughWhichAxisRotation =
objectData.content.backFaceUpThroughWhichAxisRotation || 'X';
this._faceResourceNames = [
objectData.content.frontFaceResourceName,
objectData.content.backFaceResourceName,
objectData.content.leftFaceResourceName,
objectData.content.rightFaceResourceName,
objectData.content.topFaceResourceName,
objectData.content.bottomFaceResourceName,
];
this._tint = objectData.content.tint || '255;255;255';
this._materialType = this._convertMaterialType(
objectData.content.materialType
);
this._renderer = new gdjs.Cube3DRuntimeObjectRenderer(
this,
instanceContainer
);
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
this.onCreated();
}
/**
* Sets the visibility of a face of the 3D box.
*
* @param faceName - The name of the face to set visibility for.
* @param enable - The visibility value to set.
*/
setFaceVisibility(faceName: FaceName, enable: boolean) {
const faceIndex = faceNameToBitmaskIndex[faceName];
if (faceIndex === undefined) {
return;
}
if (enable === this.isFaceAtIndexVisible(faceIndex)) {
return;
}
if (enable) {
this._visibleFacesBitmask |= 1 << faceIndex;
} else {
this._visibleFacesBitmask &= ~(1 << faceIndex);
}
this._renderer.updateFace(faceIndex);
}
/**
* Sets the texture repeat of a face of the 3D box.
*
* @param faceName - The name of the face to set visibility for.
* @param enable - The visibility value to set.
*/
setRepeatTextureOnFace(faceName: FaceName, enable: boolean) {
const faceIndex = faceNameToBitmaskIndex[faceName];
if (faceIndex === undefined) {
return;
}
if (enable === this.shouldRepeatTextureOnFaceAtIndex(faceIndex)) {
return;
}
if (enable) {
this._textureRepeatFacesBitmask |= 1 << faceIndex;
} else {
this._textureRepeatFacesBitmask &= ~(1 << faceIndex);
}
this._renderer.updateFace(faceIndex);
}
isFaceVisible(faceName: FaceName): boolean {
const faceIndex = faceNameToBitmaskIndex[faceName];
if (faceIndex === undefined) {
return false;
}
return this.isFaceAtIndexVisible(faceIndex);
}
/** @internal */
isFaceAtIndexVisible(faceIndex): boolean {
return (this._visibleFacesBitmask & (1 << faceIndex)) !== 0;
}
/** @internal */
shouldRepeatTextureOnFaceAtIndex(faceIndex): boolean {
return (this._textureRepeatFacesBitmask & (1 << faceIndex)) !== 0;
}
setFaceResourceName(faceName: FaceName, resourceName: string): void {
const faceIndex = faceNameToBitmaskIndex[faceName];
if (faceIndex === undefined) {
return;
}
if (this._faceResourceNames[faceIndex] === resourceName) {
return;
}
this._faceResourceNames[faceIndex] = resourceName;
this._renderer.updateFace(faceIndex);
}
setColor(tint: string): void {
if (this._tint === tint) {
return;
}
this._tint = tint;
this._renderer.updateTint();
}
getColor(): string {
return this._tint;
}
/** @internal */
getFaceAtIndexResourceName(faceIndex: integer): string {
return this._faceResourceNames[faceIndex];
}
getRenderer(): gdjs.RuntimeObject3DRenderer {
return this._renderer;
}
getBackFaceUpThroughWhichAxisRotation(): 'X' | 'Y' {
return this._backFaceUpThroughWhichAxisRotation;
}
setBackFaceUpThroughWhichAxisRotation(axis: 'X' | 'Y'): void {
this._backFaceUpThroughWhichAxisRotation = axis;
this._renderer.updateFace(faceNameToBitmaskIndex['back']);
}
getFacesOrientation(): 'Y' | 'Z' {
return this._facesOrientation;
}
setFacesOrientation(orientation: 'Y' | 'Z'): void {
this._facesOrientation = orientation;
this._renderer.updateFace(faceNameToBitmaskIndex['left']);
this._renderer.updateFace(faceNameToBitmaskIndex['right']);
this._renderer.updateFace(faceNameToBitmaskIndex['top']);
// Bottom texture should not change based on that setting.
}
updateFromObjectData(
oldObjectData: Cube3DObjectData,
newObjectData: Cube3DObjectData
): boolean {
super.updateFromObjectData(oldObjectData, newObjectData);
if (
oldObjectData.content.frontFaceVisible !==
newObjectData.content.frontFaceVisible
) {
this.setFaceVisibility('front', newObjectData.content.frontFaceVisible);
}
if (
oldObjectData.content.backFaceVisible !==
newObjectData.content.backFaceVisible
) {
this.setFaceVisibility('back', newObjectData.content.backFaceVisible);
}
if (
oldObjectData.content.leftFaceVisible !==
newObjectData.content.leftFaceVisible
) {
this.setFaceVisibility('left', newObjectData.content.leftFaceVisible);
}
if (
oldObjectData.content.rightFaceVisible !==
newObjectData.content.rightFaceVisible
) {
this.setFaceVisibility('right', newObjectData.content.rightFaceVisible);
}
if (
oldObjectData.content.topFaceVisible !==
newObjectData.content.topFaceVisible
) {
this.setFaceVisibility('top', newObjectData.content.topFaceVisible);
}
if (
oldObjectData.content.bottomFaceVisible !==
newObjectData.content.bottomFaceVisible
) {
this.setFaceVisibility(
'bottom',
newObjectData.content.bottomFaceVisible
);
}
if (
oldObjectData.content.frontFaceResourceName !==
newObjectData.content.frontFaceResourceName
) {
this.setFaceResourceName(
'front',
newObjectData.content.frontFaceResourceName
);
}
if (oldObjectData.content.tint !== newObjectData.content.tint) {
this.setColor(newObjectData.content.tint || '255;255;255');
}
if (
oldObjectData.content.backFaceResourceName !==
newObjectData.content.backFaceResourceName
) {
this.setFaceResourceName(
'back',
newObjectData.content.backFaceResourceName
);
}
if (
oldObjectData.content.leftFaceResourceName !==
newObjectData.content.leftFaceResourceName
) {
this.setFaceResourceName(
'left',
newObjectData.content.leftFaceResourceName
);
}
if (
oldObjectData.content.rightFaceResourceName !==
newObjectData.content.rightFaceResourceName
) {
this.setFaceResourceName(
'right',
newObjectData.content.rightFaceResourceName
);
}
if (
oldObjectData.content.topFaceResourceName !==
newObjectData.content.topFaceResourceName
) {
this.setFaceResourceName(
'top',
newObjectData.content.topFaceResourceName
);
}
if (
oldObjectData.content.bottomFaceResourceName !==
newObjectData.content.bottomFaceResourceName
) {
this.setFaceResourceName(
'bottom',
newObjectData.content.bottomFaceResourceName
);
}
if (
oldObjectData.content.frontFaceResourceRepeat !==
newObjectData.content.frontFaceResourceRepeat
) {
this.setRepeatTextureOnFace(
'front',
newObjectData.content.frontFaceResourceRepeat || false
);
}
if (
oldObjectData.content.backFaceResourceRepeat !==
newObjectData.content.backFaceResourceRepeat
) {
this.setRepeatTextureOnFace(
'back',
newObjectData.content.backFaceResourceRepeat || false
);
}
if (
oldObjectData.content.leftFaceResourceRepeat !==
newObjectData.content.leftFaceResourceRepeat
) {
this.setRepeatTextureOnFace(
'left',
newObjectData.content.leftFaceResourceRepeat || false
);
}
if (
oldObjectData.content.rightFaceResourceRepeat !==
newObjectData.content.rightFaceResourceRepeat
) {
this.setRepeatTextureOnFace(
'right',
newObjectData.content.rightFaceResourceRepeat || false
);
}
if (
oldObjectData.content.topFaceResourceRepeat !==
newObjectData.content.topFaceResourceRepeat
) {
this.setRepeatTextureOnFace(
'top',
newObjectData.content.topFaceResourceRepeat || false
);
}
if (
oldObjectData.content.bottomFaceResourceRepeat !==
newObjectData.content.bottomFaceResourceRepeat
) {
this.setRepeatTextureOnFace(
'bottom',
newObjectData.content.bottomFaceResourceRepeat || false
);
}
if (
oldObjectData.content.backFaceUpThroughWhichAxisRotation !==
newObjectData.content.backFaceUpThroughWhichAxisRotation
) {
this.setBackFaceUpThroughWhichAxisRotation(
newObjectData.content.backFaceUpThroughWhichAxisRotation || 'X'
);
}
if (
oldObjectData.content.facesOrientation !==
newObjectData.content.facesOrientation
) {
this.setFacesOrientation(newObjectData.content.facesOrientation || 'Y');
}
if (
oldObjectData.content.materialType !==
newObjectData.content.materialType
) {
this.setMaterialType(newObjectData.content.materialType);
}
return true;
}
getNetworkSyncData(
syncOptions: GetNetworkSyncDataOptions
): Cube3DObjectNetworkSyncData {
return {
...super.getNetworkSyncData(syncOptions),
mt: this._materialType,
fo: this._facesOrientation,
bfu: this._backFaceUpThroughWhichAxisRotation,
vfb: this._visibleFacesBitmask,
trfb: this._textureRepeatFacesBitmask,
frn: this._faceResourceNames,
tint: this._tint,
};
}
updateFromNetworkSyncData(
networkSyncData: Cube3DObjectNetworkSyncData,
options: UpdateFromNetworkSyncDataOptions
): void {
super.updateFromNetworkSyncData(networkSyncData, options);
if (networkSyncData.mt !== undefined) {
this._materialType = networkSyncData.mt;
}
if (networkSyncData.fo !== undefined) {
if (this._facesOrientation !== networkSyncData.fo) {
this.setFacesOrientation(networkSyncData.fo);
}
}
if (networkSyncData.bfu !== undefined) {
if (this._backFaceUpThroughWhichAxisRotation !== networkSyncData.bfu) {
this.setBackFaceUpThroughWhichAxisRotation(networkSyncData.bfu);
}
}
if (networkSyncData.vfb !== undefined) {
// If it is different, update all the faces.
if (this._visibleFacesBitmask !== networkSyncData.vfb) {
this._visibleFacesBitmask = networkSyncData.vfb;
for (let i = 0; i < this._faceResourceNames.length; i++) {
this._renderer.updateFace(i);
}
}
}
if (networkSyncData.trfb !== undefined) {
// If it is different, update all the faces.
if (this._textureRepeatFacesBitmask !== networkSyncData.trfb) {
this._textureRepeatFacesBitmask = networkSyncData.trfb;
for (let i = 0; i < this._faceResourceNames.length; i++) {
this._renderer.updateFace(i);
}
}
}
if (networkSyncData.frn !== undefined) {
// If one element is different, update all the faces.
if (
!this._faceResourceNames.every(
(value, index) => value === networkSyncData.frn[index]
)
) {
this._faceResourceNames = networkSyncData.frn;
// Update all faces. (Could optimize to only update the changed ones)
for (let i = 0; i < this._faceResourceNames.length; i++) {
this._renderer.updateFace(i);
}
}
}
if (networkSyncData.tint !== undefined) {
if (this._tint !== networkSyncData.tint) {
this._tint = networkSyncData.tint;
this._renderer.updateTint();
}
}
}
/**
* Return true if the texture transparency should be enabled.
*/
shouldUseTransparentTexture(): boolean {
return this._shouldUseTransparentTexture;
}
_convertMaterialType(
materialTypeString: string
): gdjs.Cube3DRuntimeObject.MaterialType {
if (materialTypeString === 'StandardWithoutMetalness') {
return gdjs.Cube3DRuntimeObject.MaterialType.StandardWithoutMetalness;
} else {
return gdjs.Cube3DRuntimeObject.MaterialType.Basic;
}
}
setMaterialType(materialTypeString: string) {
const newMaterialType = this._convertMaterialType(materialTypeString);
if (this._materialType === newMaterialType) {
return;
}
this._materialType = newMaterialType;
this._renderer._updateMaterials();
}
}
export namespace Cube3DRuntimeObject {
export enum MaterialType {
Basic,
StandardWithoutMetalness,
}
}
gdjs.registerObject('Scene3D::Cube3DObject', gdjs.Cube3DRuntimeObject);
}