mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
344 lines
11 KiB
TypeScript
344 lines
11 KiB
TypeScript
namespace gdjs {
|
|
// Three.js materials for a cube and the order of faces in the object is different,
|
|
// so we keep the mapping from one to the other.
|
|
const faceIndexToMaterialIndex = {
|
|
3: 0, // right
|
|
2: 1, // left
|
|
5: 2, // bottom
|
|
4: 3, // top
|
|
0: 4, // front
|
|
1: 5, // back
|
|
};
|
|
const materialIndexToFaceIndex = {
|
|
0: 3,
|
|
1: 2,
|
|
2: 5,
|
|
3: 4,
|
|
4: 0,
|
|
5: 1,
|
|
};
|
|
|
|
const noRepeatTextureVertexIndexToUvMapping = {
|
|
0: [0, 0],
|
|
1: [1, 0],
|
|
2: [0, 1],
|
|
3: [1, 1],
|
|
};
|
|
|
|
const noRepeatTextureVertexIndexToUvMappingForLeftAndRightFacesTowardsZ = {
|
|
0: [0, 1],
|
|
1: [0, 0],
|
|
2: [1, 1],
|
|
3: [1, 0],
|
|
};
|
|
|
|
let transparentMaterial: THREE.MeshBasicMaterial;
|
|
const getTransparentMaterial = () => {
|
|
if (!transparentMaterial)
|
|
transparentMaterial = new THREE.MeshBasicMaterial({
|
|
transparent: true,
|
|
opacity: 0,
|
|
// Set the alpha test to to ensure the faces behind are rendered
|
|
// (no "back face culling" that would still be done if alphaTest is not set).
|
|
alphaTest: 1,
|
|
});
|
|
|
|
return transparentMaterial;
|
|
};
|
|
|
|
const getFaceMaterial = (
|
|
runtimeObject: gdjs.Cube3DRuntimeObject,
|
|
faceIndex: integer
|
|
) => {
|
|
if (!runtimeObject.isFaceAtIndexVisible(faceIndex))
|
|
return getTransparentMaterial();
|
|
|
|
return runtimeObject
|
|
.getInstanceContainer()
|
|
.getGame()
|
|
.getImageManager()
|
|
.getThreeMaterial(runtimeObject.getFaceAtIndexResourceName(faceIndex), {
|
|
useTransparentTexture: runtimeObject.shouldUseTransparentTexture(),
|
|
forceBasicMaterial:
|
|
runtimeObject._materialType ===
|
|
gdjs.Cube3DRuntimeObject.MaterialType.Basic,
|
|
vertexColors: true,
|
|
});
|
|
};
|
|
|
|
class Cube3DRuntimeObjectPixiRenderer extends gdjs.RuntimeObject3DRenderer {
|
|
private _cube3DRuntimeObject: gdjs.Cube3DRuntimeObject;
|
|
private _boxMesh: THREE.Mesh;
|
|
|
|
constructor(
|
|
runtimeObject: gdjs.Cube3DRuntimeObject,
|
|
instanceContainer: gdjs.RuntimeInstanceContainer
|
|
) {
|
|
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
|
|
|
const materials: THREE.Material[] = new Array(6)
|
|
.fill(0)
|
|
.map((_, index) =>
|
|
getFaceMaterial(runtimeObject, materialIndexToFaceIndex[index])
|
|
);
|
|
const boxMesh = new THREE.Mesh(geometry, materials);
|
|
|
|
super(runtimeObject, instanceContainer, boxMesh);
|
|
this._boxMesh = boxMesh;
|
|
this._cube3DRuntimeObject = runtimeObject;
|
|
|
|
boxMesh.receiveShadow = this._cube3DRuntimeObject._isReceivingShadow;
|
|
boxMesh.castShadow = this._cube3DRuntimeObject._isCastingShadow;
|
|
this.updateSize();
|
|
this.updatePosition();
|
|
this.updateRotation();
|
|
this.updateTint();
|
|
}
|
|
|
|
updateTint() {
|
|
const tints: number[] = [];
|
|
|
|
const normalizedTint = gdjs
|
|
.rgbOrHexToRGBColor(this._cube3DRuntimeObject.getColor())
|
|
.map((component) => component / 255);
|
|
|
|
for (
|
|
let i = 0;
|
|
i < this._boxMesh.geometry.attributes.position.count;
|
|
i++
|
|
) {
|
|
tints.push(...normalizedTint);
|
|
}
|
|
|
|
this._boxMesh.geometry.setAttribute(
|
|
'color',
|
|
new THREE.BufferAttribute(new Float32Array(tints), 3)
|
|
);
|
|
}
|
|
updateShadowCasting() {
|
|
this._boxMesh.castShadow = this._cube3DRuntimeObject._isCastingShadow;
|
|
}
|
|
updateShadowReceiving() {
|
|
this._boxMesh.receiveShadow =
|
|
this._cube3DRuntimeObject._isReceivingShadow;
|
|
}
|
|
|
|
updateFace(faceIndex: integer) {
|
|
const materialIndex = faceIndexToMaterialIndex[faceIndex];
|
|
if (materialIndex === undefined) return;
|
|
|
|
this._boxMesh.material[materialIndex] = getFaceMaterial(
|
|
this._cube3DRuntimeObject,
|
|
faceIndex
|
|
);
|
|
if (this._cube3DRuntimeObject.isFaceAtIndexVisible(faceIndex)) {
|
|
this.updateTextureUvMapping(faceIndex);
|
|
}
|
|
}
|
|
|
|
updateSize(): void {
|
|
super.updateSize();
|
|
this.updateTextureUvMapping();
|
|
}
|
|
|
|
/**
|
|
* Updates the UV mapping of the geometry in order to repeat a material
|
|
* over the different faces of the cube.
|
|
* The mesh must be configured with a list of materials in order
|
|
* for the method to work.
|
|
* @param faceIndex The face index to update. If undefined, updates all the faces.
|
|
*/
|
|
updateTextureUvMapping(faceIndex?: number) {
|
|
// @ts-ignore - position is stored as a Float32BufferAttribute
|
|
const pos: THREE.BufferAttribute =
|
|
this._boxMesh.geometry.getAttribute('position');
|
|
// @ts-ignore - uv is stored as a Float32BufferAttribute
|
|
const uvMapping: THREE.BufferAttribute =
|
|
this._boxMesh.geometry.getAttribute('uv');
|
|
const startIndex =
|
|
faceIndex === undefined ? 0 : faceIndexToMaterialIndex[faceIndex] * 4;
|
|
const endIndex =
|
|
faceIndex === undefined
|
|
? 23
|
|
: faceIndexToMaterialIndex[faceIndex] * 4 + 3;
|
|
for (
|
|
let vertexIndex = startIndex;
|
|
vertexIndex <= endIndex;
|
|
vertexIndex++
|
|
) {
|
|
const materialIndex = Math.floor(
|
|
vertexIndex /
|
|
// Each face of the cube has 4 points
|
|
4
|
|
);
|
|
const material = this._boxMesh.material[materialIndex];
|
|
if (!material || !material.map) {
|
|
continue;
|
|
}
|
|
|
|
const shouldRepeatTexture =
|
|
this._cube3DRuntimeObject.shouldRepeatTextureOnFaceAtIndex(
|
|
materialIndexToFaceIndex[materialIndex]
|
|
);
|
|
|
|
const shouldOrientateFacesTowardsY =
|
|
this._cube3DRuntimeObject.getFacesOrientation() === 'Y';
|
|
|
|
let x: float, y: float;
|
|
switch (materialIndex) {
|
|
case 0:
|
|
// Right face
|
|
if (shouldRepeatTexture) {
|
|
if (shouldOrientateFacesTowardsY) {
|
|
x =
|
|
-(this._boxMesh.scale.z / material.map.source.data.width) *
|
|
(pos.getZ(vertexIndex) - 0.5);
|
|
y =
|
|
-(this._boxMesh.scale.y / material.map.source.data.height) *
|
|
(pos.getY(vertexIndex) + 0.5);
|
|
} else {
|
|
x =
|
|
-(this._boxMesh.scale.y / material.map.source.data.width) *
|
|
(pos.getY(vertexIndex) - 0.5);
|
|
y =
|
|
(this._boxMesh.scale.z / material.map.source.data.height) *
|
|
(pos.getZ(vertexIndex) - 0.5);
|
|
}
|
|
} else {
|
|
if (shouldOrientateFacesTowardsY) {
|
|
[x, y] = noRepeatTextureVertexIndexToUvMapping[vertexIndex % 4];
|
|
} else {
|
|
[x, y] =
|
|
noRepeatTextureVertexIndexToUvMappingForLeftAndRightFacesTowardsZ[
|
|
vertexIndex % 4
|
|
];
|
|
}
|
|
}
|
|
break;
|
|
case 1:
|
|
// Left face
|
|
if (shouldRepeatTexture) {
|
|
if (shouldOrientateFacesTowardsY) {
|
|
x =
|
|
(this._boxMesh.scale.z / material.map.source.data.width) *
|
|
(pos.getZ(vertexIndex) + 0.5);
|
|
y =
|
|
-(this._boxMesh.scale.y / material.map.source.data.height) *
|
|
(pos.getY(vertexIndex) + 0.5);
|
|
} else {
|
|
x =
|
|
(this._boxMesh.scale.y / material.map.source.data.width) *
|
|
(pos.getY(vertexIndex) + 0.5);
|
|
y =
|
|
(this._boxMesh.scale.z / material.map.source.data.height) *
|
|
(pos.getZ(vertexIndex) - 0.5);
|
|
}
|
|
} else {
|
|
if (shouldOrientateFacesTowardsY) {
|
|
[x, y] = noRepeatTextureVertexIndexToUvMapping[vertexIndex % 4];
|
|
} else {
|
|
[x, y] =
|
|
noRepeatTextureVertexIndexToUvMappingForLeftAndRightFacesTowardsZ[
|
|
vertexIndex % 4
|
|
];
|
|
x = -x;
|
|
y = -y;
|
|
}
|
|
}
|
|
break;
|
|
case 2:
|
|
// Bottom face
|
|
if (shouldRepeatTexture) {
|
|
x =
|
|
(this._boxMesh.scale.x / material.map.source.data.width) *
|
|
(pos.getX(vertexIndex) + 0.5);
|
|
y =
|
|
(this._boxMesh.scale.z / material.map.source.data.height) *
|
|
(pos.getZ(vertexIndex) - 0.5);
|
|
} else {
|
|
[x, y] = noRepeatTextureVertexIndexToUvMapping[vertexIndex % 4];
|
|
}
|
|
break;
|
|
case 3:
|
|
// Top face
|
|
if (shouldRepeatTexture) {
|
|
if (shouldOrientateFacesTowardsY) {
|
|
x =
|
|
(this._boxMesh.scale.x / material.map.source.data.width) *
|
|
(pos.getX(vertexIndex) + 0.5);
|
|
y =
|
|
-(this._boxMesh.scale.z / material.map.source.data.height) *
|
|
(pos.getZ(vertexIndex) + 0.5);
|
|
} else {
|
|
x =
|
|
-(this._boxMesh.scale.x / material.map.source.data.width) *
|
|
(pos.getX(vertexIndex) - 0.5);
|
|
y =
|
|
(this._boxMesh.scale.z / material.map.source.data.height) *
|
|
(pos.getZ(vertexIndex) - 0.5);
|
|
}
|
|
} else {
|
|
[x, y] = noRepeatTextureVertexIndexToUvMapping[vertexIndex % 4];
|
|
if (!shouldOrientateFacesTowardsY) {
|
|
x = -x;
|
|
y = -y;
|
|
}
|
|
}
|
|
break;
|
|
case 4:
|
|
// Front face
|
|
if (shouldRepeatTexture) {
|
|
x =
|
|
(this._boxMesh.scale.x / material.map.source.data.width) *
|
|
(pos.getX(vertexIndex) + 0.5);
|
|
y =
|
|
-(this._boxMesh.scale.y / material.map.source.data.height) *
|
|
(pos.getY(vertexIndex) + 0.5);
|
|
} else {
|
|
[x, y] = noRepeatTextureVertexIndexToUvMapping[vertexIndex % 4];
|
|
}
|
|
break;
|
|
case 5:
|
|
// Back face
|
|
const shouldBackFaceBeUpThroughXAxisRotation =
|
|
this._cube3DRuntimeObject.getBackFaceUpThroughWhichAxisRotation() ===
|
|
'X';
|
|
|
|
if (shouldRepeatTexture) {
|
|
x =
|
|
(shouldBackFaceBeUpThroughXAxisRotation ? 1 : -1) *
|
|
(this._boxMesh.scale.x / material.map.source.data.width) *
|
|
(pos.getX(vertexIndex) +
|
|
(shouldBackFaceBeUpThroughXAxisRotation ? 1 : -1) * 0.5);
|
|
y =
|
|
(shouldBackFaceBeUpThroughXAxisRotation ? 1 : -1) *
|
|
(this._boxMesh.scale.y / material.map.source.data.height) *
|
|
(pos.getY(vertexIndex) +
|
|
(shouldBackFaceBeUpThroughXAxisRotation ? -1 : 1) * 0.5);
|
|
} else {
|
|
[x, y] = noRepeatTextureVertexIndexToUvMapping[vertexIndex % 4];
|
|
if (shouldBackFaceBeUpThroughXAxisRotation) {
|
|
x = -x;
|
|
y = -y;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
[x, y] = noRepeatTextureVertexIndexToUvMapping[vertexIndex % 4];
|
|
}
|
|
uvMapping.setXY(vertexIndex, x, y);
|
|
}
|
|
uvMapping.needsUpdate = true;
|
|
}
|
|
|
|
_updateMaterials() {
|
|
for (let index = 0; index < 6; index++) {
|
|
this.updateFace(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
export const Cube3DRuntimeObjectRenderer = Cube3DRuntimeObjectPixiRenderer;
|
|
export type Cube3DRuntimeObjectRenderer = Cube3DRuntimeObjectPixiRenderer;
|
|
}
|