diff --git a/Extensions/3D/Cube3DRuntimeObject.ts b/Extensions/3D/Cube3DRuntimeObject.ts index 8f95a75267..1e4af4c771 100644 --- a/Extensions/3D/Cube3DRuntimeObject.ts +++ b/Extensions/3D/Cube3DRuntimeObject.ts @@ -3,28 +3,28 @@ namespace gdjs { export interface Cube3DObjectData extends Object3DData { /** The base parameters of the Cube3D object */ content: Object3DDataContent & { - enableTextureTransparency: boolean; - facesOrientation: 'Y' | 'Z'; + enableTextureTransparency: boolean | undefined; + facesOrientation: 'Y' | 'Z' | undefined; frontFaceResourceName: string; backFaceResourceName: string; - backFaceUpThroughWhichAxisRotation: 'X' | 'Y'; + backFaceUpThroughWhichAxisRotation: 'X' | 'Y' | undefined; leftFaceResourceName: string; rightFaceResourceName: string; topFaceResourceName: string; bottomFaceResourceName: string; - frontFaceResourceRepeat: boolean; - backFaceResourceRepeat: boolean; - leftFaceResourceRepeat: boolean; - rightFaceResourceRepeat: boolean; - topFaceResourceRepeat: boolean; - bottomFaceResourceRepeat: boolean; + 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; + tint: string | undefined; materialType: 'Basic' | 'StandardWithoutMetalness'; }; } @@ -308,7 +308,7 @@ namespace gdjs { ); } if (oldObjectData.content.tint !== newObjectData.content.tint) { - this.setColor(newObjectData.content.tint); + this.setColor(newObjectData.content.tint || '255;255;255'); } if ( @@ -362,7 +362,7 @@ namespace gdjs { ) { this.setRepeatTextureOnFace( 'front', - newObjectData.content.frontFaceResourceRepeat + newObjectData.content.frontFaceResourceRepeat || false ); } if ( @@ -371,7 +371,7 @@ namespace gdjs { ) { this.setRepeatTextureOnFace( 'back', - newObjectData.content.backFaceResourceRepeat + newObjectData.content.backFaceResourceRepeat || false ); } if ( @@ -380,7 +380,7 @@ namespace gdjs { ) { this.setRepeatTextureOnFace( 'left', - newObjectData.content.leftFaceResourceRepeat + newObjectData.content.leftFaceResourceRepeat || false ); } if ( @@ -389,7 +389,7 @@ namespace gdjs { ) { this.setRepeatTextureOnFace( 'right', - newObjectData.content.rightFaceResourceRepeat + newObjectData.content.rightFaceResourceRepeat || false ); } if ( @@ -398,7 +398,7 @@ namespace gdjs { ) { this.setRepeatTextureOnFace( 'top', - newObjectData.content.topFaceResourceRepeat + newObjectData.content.topFaceResourceRepeat || false ); } if ( @@ -407,7 +407,7 @@ namespace gdjs { ) { this.setRepeatTextureOnFace( 'bottom', - newObjectData.content.bottomFaceResourceRepeat + newObjectData.content.bottomFaceResourceRepeat || false ); } if ( @@ -415,14 +415,14 @@ namespace gdjs { newObjectData.content.backFaceUpThroughWhichAxisRotation ) { this.setBackFaceUpThroughWhichAxisRotation( - newObjectData.content.backFaceUpThroughWhichAxisRotation + newObjectData.content.backFaceUpThroughWhichAxisRotation || 'X' ); } if ( oldObjectData.content.facesOrientation !== newObjectData.content.facesOrientation ) { - this.setFacesOrientation(newObjectData.content.facesOrientation); + this.setFacesOrientation(newObjectData.content.facesOrientation || 'Y'); } if ( oldObjectData.content.materialType !== diff --git a/Extensions/3D/JsExtension.js b/Extensions/3D/JsExtension.js index 246cdc47dc..b15e833bbf 100644 --- a/Extensions/3D/JsExtension.js +++ b/Extensions/3D/JsExtension.js @@ -2099,6 +2099,10 @@ module.exports = { 3: [1, 0], }; + /** + * @param {*} objectConfiguration + * @returns {string | null} + */ const getFirstVisibleFaceResourceName = (objectConfiguration) => { const object = gd.castObject( objectConfiguration, @@ -2127,25 +2131,44 @@ module.exports = { return null; }; + /** @type {THREE.MeshBasicMaterial | null} */ let transparentMaterial = null; + /** + * @returns {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; + if (transparentMaterial) { + return transparentMaterial; + } + const newTransparentMaterial = 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, + }); + transparentMaterial = newTransparentMaterial; + return newTransparentMaterial; }; class RenderedCube3DObject2DInstance extends RenderedInstance { + /** @type {number} */ + _defaultWidth; + /** @type {number} */ + _defaultHeight; + /** @type {number} */ + _defaultDepth; /** @type {number} */ _centerX = 0; /** @type {number} */ _centerY = 0; + /** + * The name of the resource that is rendered. + * If no face is visible, this will be null. + * @type {string | null | undefined} + */ + _renderedResourceName = undefined; + _renderFallbackObject = false; constructor( project, @@ -2161,11 +2184,6 @@ module.exports = { pixiContainer, pixiResourcesLoader ); - - // Name of the resource that is rendered. - // If no face is visible, this will be null. - this._renderedResourceName = undefined; - const object = gd.castObject( this._associatedObjectConfiguration, gd.ObjectJsImplementation @@ -2182,7 +2200,6 @@ module.exports = { this._pixiObject.addChild(this._pixiTexturedObject); this._pixiObject.addChild(this._pixiFallbackObject); this._pixiContainer.addChild(this._pixiObject); - this._renderFallbackObject = false; this.updateTexture(); } @@ -2348,6 +2365,17 @@ module.exports = { } class RenderedCube3DObject3DInstance extends Rendered3DInstance { + _defaultWidth = 1; + _defaultHeight = 1; + _defaultDepth = 1; + _faceResourceNames = new Array(6).fill(null); + _faceVisibilities = new Array(6).fill(null); + _shouldRepeatTextureOnFace = new Array(6).fill(null); + _facesOrientation = 'Y'; + _backFaceUpThroughWhichAxisRotation = 'X'; + _shouldUseTransparentTexture = false; + _tint = ''; + constructor( project, instance, @@ -2364,22 +2392,9 @@ module.exports = { threeGroup, pixiResourcesLoader ); - - this._defaultWidth = 1; - this._defaultHeight = 1; - this._defaultDepth = 1; - this._pixiObject = new PIXI.Graphics(); this._pixiContainer.addChild(this._pixiObject); - this._faceResourceNames = new Array(6).fill(null); - this._faceVisibilities = new Array(6).fill(null); - this._shouldRepeatTextureOnFace = new Array(6).fill(null); - this._facesOrientation = 'Y'; - this._backFaceUpThroughWhichAxisRotation = 'X'; - this._shouldUseTransparentTexture = false; - this._tint = ''; - const geometry = new THREE.BoxGeometry(1, 1, 1); const materials = [ getTransparentMaterial(), @@ -2458,6 +2473,8 @@ module.exports = { } updateThreeObject() { + /** @type {gdjs.Cube3DObjectData} */ + //@ts-ignore This works because the properties are set to `content` in JavaScript. const object = gd.castObject( this._associatedObjectConfiguration, gd.ObjectJsImplementation @@ -2601,11 +2618,11 @@ module.exports = { * for the method to work. */ _updateTextureUvMapping() { + /** @type {THREE.BufferAttribute} */ // @ts-ignore - position is stored as a Float32BufferAttribute - /** @type {THREE.BufferAttribute} */ const pos = this._threeObject.geometry.getAttribute('position'); - // @ts-ignore - uv is stored as a Float32BufferAttribute /** @type {THREE.BufferAttribute} */ + // @ts-ignore - uv is stored as a Float32BufferAttribute const uvMapping = this._threeObject.geometry.getAttribute('uv'); const startIndex = 0; const endIndex = 23; @@ -2853,6 +2870,19 @@ module.exports = { const epsilon = 1 / (1 << 16); class Model3DRendered2DInstance extends RenderedInstance { + /** @type {number} */ + _defaultWidth; + /** @type {number} */ + _defaultHeight; + /** @type {number} */ + _defaultDepth; + + /** @type {[number, number, number] | null} */ + _originPoint; + /** @type {[number, number, number] | null} */ + _centerPoint; + + /** @type {[number, number, number]} */ _modelOriginPoint = [0, 0, 0]; constructor( @@ -3095,10 +3125,15 @@ module.exports = { } } + /** + * @param {[number, number, number] | null} point1 + * @param {[number, number, number] | null} point2 + * @returns {boolean} + */ const isSamePoint = (point1, point2) => { - if (!point1 && !point2) return true; - if (point1 && !point2) return false; - if (!point1 && point2) return false; + if (!!point1 !== !!point2) return false; + // At this point || or && doesn't matter and the type checking prefer ||. + if (!point1 || !point2) return true; return ( point1[0] === point2[0] && point1[1] === point2[1] && @@ -3106,6 +3141,10 @@ module.exports = { ); }; + /** + * @param {string} location + * @returns {[number, number, number] | null} + */ const getPointForLocation = (location) => { switch (location) { case 'ModelOrigin': @@ -3124,8 +3163,27 @@ module.exports = { }; class Model3DRendered3DInstance extends Rendered3DInstance { + _defaultWidth = 1; + _defaultHeight = 1; + _defaultDepth = 1; + _originalWidth = 1; + _originalHeight = 1; + _originalDepth = 1; + _rotationX = 0; + _rotationY = 0; + _rotationZ = 0; + _keepAspectRatio = false; + /** @type {[number, number, number] | null} */ + _originPoint = null; + /** @type {[number, number, number] | null} */ + _centerPoint = null; + + /** @type {[number, number, number]} */ _modelOriginPoint = [0, 0, 0]; + /** @type {THREE.Object3D | null} */ + _clonedModel3D = null; + constructor( project, instance, @@ -3143,29 +3201,12 @@ module.exports = { pixiResourcesLoader ); - this._defaultWidth = 1; - this._defaultHeight = 1; - this._defaultDepth = 1; - this._originalWidth = 1; - this._originalHeight = 1; - this._originalDepth = 1; - this._rotationX = 0; - this._rotationY = 0; - this._rotationZ = 0; - this._keepAspectRatio = false; - - this._originPoint = null; - this._centerPoint = null; - this._pixiObject = new PIXI.Graphics(); this._pixiContainer.addChild(this._pixiObject); this._threeObject = new THREE.Group(); this._threeObject.rotation.order = 'ZYX'; this._threeGroup.add(this._threeObject); - - this._threeModelGroup = null; - this._clonedModel3D = null; } getOriginX() { @@ -3207,27 +3248,30 @@ module.exports = { } _updateDefaultTransformation() { - if (!this._clonedModel3D) return; // Model is not ready - nothing to do. + if (!this._clonedModel3D || !this._threeModelGroup) { + // Model is not ready - nothing to do. + return; + } if (this._threeModelGroup) { // Remove any previous container as we will recreate it just below this._threeObject.clear(); } + // This group hold the rotation defined by properties. // Always restart from a new group to avoid miscomputing bounding boxes/sizes. - this._threeModelGroup = new THREE.Group(); - this._threeModelGroup.rotation.order = 'ZYX'; - this._threeModelGroup.add(this._clonedModel3D); + const threeModelGroup = new THREE.Group(); + this._threeModelGroup = threeModelGroup; + threeModelGroup.rotation.order = 'ZYX'; + threeModelGroup.add(this._clonedModel3D); - this._threeModelGroup.rotation.set( + threeModelGroup.rotation.set( (this._rotationX * Math.PI) / 180, (this._rotationY * Math.PI) / 180, (this._rotationZ * Math.PI) / 180 ); - this._threeModelGroup.updateMatrixWorld(true); - const boundingBox = new THREE.Box3().setFromObject( - this._threeModelGroup - ); + threeModelGroup.updateMatrixWorld(true); + const boundingBox = new THREE.Box3().setFromObject(threeModelGroup); const shouldKeepModelOrigin = !this._originPoint; if (shouldKeepModelOrigin) { @@ -3254,7 +3298,7 @@ module.exports = { // Center the model. const centerPoint = this._centerPoint; if (centerPoint) { - this._threeModelGroup.position.set( + threeModelGroup.position.set( -(boundingBox.min.x + modelWidth * centerPoint[0]), // The model is flipped on Y axis. -(boundingBox.min.y + modelHeight * (1 - centerPoint[1])), @@ -3263,8 +3307,8 @@ module.exports = { } // Rotate the model. - this._threeModelGroup.scale.set(1, 1, 1); - this._threeModelGroup.rotation.set( + threeModelGroup.scale.set(1, 1, 1); + threeModelGroup.rotation.set( (this._rotationX * Math.PI) / 180, (this._rotationY * Math.PI) / 180, (this._rotationZ * Math.PI) / 180 @@ -3279,8 +3323,8 @@ module.exports = { // Flip on Y because the Y axis is on the opposite side of direct basis. // It avoids models to be like a mirror refection. scaleMatrix.makeScale(scaleX, -scaleY, scaleZ); - this._threeModelGroup.updateMatrix(); - this._threeModelGroup.applyMatrix4(scaleMatrix); + threeModelGroup.updateMatrix(); + threeModelGroup.applyMatrix4(scaleMatrix); if (this._keepAspectRatio) { // Reduce the object dimensions to keep aspect ratio. @@ -3347,7 +3391,7 @@ module.exports = { this._defaultDepth = this._originalDepth; } - this._threeObject.add(this._threeModelGroup); + this._threeObject.add(threeModelGroup); } updateThreeObject() {