Skip to content
Open
Changes from all commits
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
247 changes: 227 additions & 20 deletions extensions/community/Sprite3D.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"3d"
],
"authorIds": [
"IWykYNRvhCZBN3vEgKEbBPOR3Oc2"
"IWykYNRvhCZBN3vEgKEbBPOR3Oc2",
"sXdoMxxHF7hAXkEPRO8oZcfnBgC2"
],
"dependencies": [],
"globalVariables": [],
Expand All @@ -39,30 +40,77 @@
"if (gdjs.__sprite3DExtension) {",
" return;",
"}",
"",
"const vertexColors = [];",
"",
"class Sprite3DRenderer {",
" /** @type {gdjs.CustomRuntimeObject} */",
" object;",
" /** @type {THREE.Mesh} */",
" mesh;",
"",
" /** @type {number} */",
" depthOffset;",
" /** @type {boolean} */",
" autoRotate;",
" ",
" /**",
" * @param object {gdjs.CustomRuntimeObject}",
" */",
" constructor(object) {",
" this.object = object;",
"",
" this.depthOffset = 0;",
" this.autoRotate = true;",
" ",
" const geometry = new THREE.PlaneGeometry(1, -1);",
" const animationFrame = object.getAnimator().getCurrentFrame();",
" if (animationFrame) {",
" const material = animationFrame.texture;",
" ",
" // Enhanced transparency and depth settings",
" material.alphaTest = 0.5;",
" material.depthWrite = true;",
" material.depthTest = true;",
" material.transparent = true;",
" ",
" // Advanced professional improvements",
" material.side = THREE.DoubleSide;",
" material.shadowSide = THREE.FrontSide;",
" material.toneMapped = true;",
" material.fog = true;",
" material.premultipliedAlpha = true;",
" ",
" // Advanced depth settings to prevent z-fighting",
" material.depthFunc = THREE.LessEqualDepth;",
" material.polygonOffset = true;",
" material.polygonOffsetFactor = -1;",
" material.polygonOffsetUnits = -1;",
" ",
" // Professional visual effects",
" if (material.emissive) {",
" material.emissive = new THREE.Color(0x111111);",
" material.emissiveIntensity = 0.2;",
" }",
" ",
" material.needsUpdate = true;",
" ",
" this.mesh = new THREE.Mesh(geometry, material);",
" this.mesh.rotation.order = 'ZYX';",
" ",
" // Optimized rotation order for realism (YXZ is better for standing objects)",
" this.mesh.rotation.order = 'YXZ';",
" ",
" // Enable enhanced shadows",
" this.mesh.castShadow = true;",
" this.mesh.receiveShadow = true;",
" ",
" // Performance and rendering improvements",
" this.mesh.frustumCulled = true;",
" this.mesh.matrixAutoUpdate = true;",
" ",
" // Dynamic renderOrder based on Z position",
" this.updateRenderOrder();",
" ",
" object.get3DRendererObject().add(this.mesh);",
" // Ensure a forward compatibility when vertexColors will be set to true",
" // in the engine to allow to tint 3D sprites.",
Comment on lines -64 to -65
Copy link
Member

Choose a reason for hiding this comment

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

Get back the original comments.

" ",
" // Add vertex colors for advanced coloring",
" vertexColors.length = geometry.attributes.position.count * 3;",
" vertexColors.fill(1);",
" geometry.setAttribute(",
Expand All @@ -73,41 +121,162 @@
" this.updateFrame();",
" object.getAnimator().setOnFrameChangeCallback(() => this.updateFrame());",
" }",
"",
" ",
" /**",
" * Update render order based on depth",
" */",
" updateRenderOrder() {",
" if (!this.mesh) return;",
" ",
" // Calculate renderOrder based on Z and Y position to ensure correct rendering",
" const zPos = this.object.getZ ? this.object.getZ() : 0;",
" const yPos = this.object.getY ? this.object.getY() : 0;",
" ",
" // Use complex formula to get precise ordering",
" this.mesh.renderOrder = Math.floor(zPos * 1000 + yPos);",
" ",
" // Apply additional depth offset",
" this.mesh.position.z = this.depthOffset;",
" }",
" ",
" /**",
" * Apply automatic rotation angles based on position and velocity",
" */",
" applyAutoRotation() {",
" if (!this.autoRotate || !this.mesh) return;",
" ",
" const obj = this.object;",
" ",
" // Get velocity and direction",
" const velocityX = (obj._customState && obj._customState.velocityX) || 0;",
" const velocityY = (obj._customState && obj._customState.velocityY) || 0;",
" const angle = obj.getAngle ? obj.getAngle() : 0;",
" ",
" // Calculate automatic tilt on X axis (pitch) based on vertical velocity",
" const maxPitchAngle = 15; // degrees",
" const pitchFactor = Math.min(Math.abs(velocityY) / 500, 1);",
" const targetPitch = (velocityY < 0 ? -1 : 1) * pitchFactor * maxPitchAngle;",
" ",
" // Calculate tilt on Y axis (yaw) based on direction",
" const maxYawAngle = 10; // degrees",
" const yawFactor = Math.min(Math.abs(velocityX) / 500, 1);",
" const targetYaw = (velocityX < 0 ? -1 : 1) * yawFactor * maxYawAngle;",
" ",
" // Apply rotation smoothly (lerp)",
" const lerpFactor = 0.1;",
" const currentRotation = this.mesh.rotation;",
" ",
" // Convert base angle to Z rotation",
" const targetZ = THREE.MathUtils.degToRad(-angle);",
" ",
" // Apply smooth rotation",
" currentRotation.x = THREE.MathUtils.lerp(",
" currentRotation.x,",
" THREE.MathUtils.degToRad(targetPitch),",
" lerpFactor",
" );",
" ",
" currentRotation.y = THREE.MathUtils.lerp(",
" currentRotation.y,",
" THREE.MathUtils.degToRad(targetYaw),",
" lerpFactor",
" );",
" ",
" currentRotation.z = THREE.MathUtils.lerp(",
" currentRotation.z,",
" targetZ,",
" lerpFactor",
" );",
" }",
" ",
" /**",
" * Apply deep perspective effect",
" */",
" applyDepthPerspective() {",
" if (!this.mesh) return;",
" ",
" const obj = this.object;",
" const yPos = obj.getY ? obj.getY() : 0;",
" const zPos = obj.getZ ? obj.getZ() : 0;",
" ",
" // Calculate scale factor based on depth (perspective)",
" const perspectiveFactor = 1 - (zPos * 0.0001); // Reduce size of distant objects",
" const scaleFactor = Math.max(0.5, Math.min(1.5, perspectiveFactor));",
" ",
" // Apply additional factor based on Y (apparent height)",
" const heightFactor = 1 + (yPos * 0.00005);",
" ",
" // Update scale while maintaining original proportions",
" const frame = this.object.getAnimator().getCurrentFrame();",
" if (frame) {",
" const image = frame.texture.map.image;",
" const width = image.width;",
" const height = image.height;",
" ",
" this.mesh.scale.set(",
" width * scaleFactor * heightFactor,",
" height * scaleFactor * heightFactor,",
" 1",
" );",
" }",
" }",
" ",
" updateFrame() {",
" const frame = this.object.getAnimator().getCurrentFrame();",
" if (!frame) {",
" return;",
" }",
" const material = frame.texture;",
"",
" ",
" // Update basic settings",
" material.alphaTest = 0.5;",
" material.depthWrite = true;",
" material.depthTest = true;",
" material.transparent = true;",
" material.side = THREE.DoubleSide;",
Copy link
Member

@Bouh Bouh Dec 17, 2025

Choose a reason for hiding this comment

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

material.side = THREE.DoubleSide;",

This wasn’t the default behavior, so all previous projects using the extension will now have their sprites visible on both faces. From creator point of view if this wasn’t intended, it introduces a regression in the extension and breaks compatibility with existing projects.

" material.shadowSide = THREE.FrontSide;",
" material.toneMapped = true;",
" material.fog = true;",
" material.premultipliedAlpha = true;",
" material.depthFunc = THREE.LessEqualDepth;",
" material.polygonOffset = true;",
" material.polygonOffsetFactor = -1;",
" material.polygonOffsetUnits = -1;",
" ",
" // Professional visual effects",
" if (material.emissive) {",
" material.emissive = new THREE.Color(0x111111);",
" material.emissiveIntensity = 0.2;",
" }",
Comment on lines +246 to +250
Copy link
Member

Choose a reason for hiding this comment

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

This will override the existing material if any.
Not recommended to set these values in stone.

" ",
" material.needsUpdate = true;",
" ",
" const image = material.map.image;",
" const width = image.width;",
" const height = image.height;",
" const origin = frame.origin;",
" this.mesh.position.set(-origin.x + width / 2, -origin.y + height / 2, 0);",
" this.mesh.scale.set(width, height, 1);",
"",
" const center = frame.center;",
" this.object.setRotationCenter(center.x - origin.x, center.y - origin.y);",
"",
" this.mesh.material = material;",
"",
" ",
" // Apply automatic improvements",
" this.updateRenderOrder();",
" this.applyAutoRotation();",
" this.applyDepthPerspective();",
" ",
" const hitBoxes = this.object._untransformedHitBoxes;",
" if (frame.hasCustomCollisionMask) {",
" let i = 0;",
" for (let len = frame.customCollisionMask.length; i < len; ++i) {",
" const polygonData = frame.customCollisionMask[i];",
"",
" // Add a polygon, if necessary (Avoid recreating a polygon if it already exists).",
Comment on lines -101 to -102
Copy link
Member

Choose a reason for hiding this comment

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

Put this comment back.

" if (i >= hitBoxes.length) {",
" hitBoxes.push(new gdjs.Polygon());",
" }",
" let j = 0;",
" for (const len2 = polygonData.length; j < len2; ++j) {",
" const pointData = polygonData[j];",
"",
" // Add a point, if necessary (Avoid recreating a point if it already exists).",
Comment on lines -109 to -110
Copy link
Member

Choose a reason for hiding this comment

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

Put this comment back.

" if (j >= hitBoxes[i].vertices.length) {",
" hitBoxes[i].vertices.push([0, 0]);",
" }",
Expand All @@ -127,21 +296,57 @@
" vertices.push([-origin.x + width, -origin.y + height]);",
" vertices.push([-origin.x, -origin.y + height]);",
" }",
"",
" const aabb = this.object._unrotatedAABB;",
" aabb.min[0] = -origin.x;",
" aabb.min[1] = -origin.y;",
" aabb.max[0] = -origin.x + width;",
" aabb.max[1] = -origin.y + height;",
"",
" this.object._isUntransformedHitBoxesDirty = false;",
" }",
" ",
" /**",
" * Manually set depth offset",
" * @param {number} offset",
" */",
" setDepthOffset(offset) {",
" this.depthOffset = offset;",
" this.updateRenderOrder();",
" }",
" ",
" /**",
" * Enable/disable automatic rotation",
" * @param {boolean} enabled",
" */",
" setAutoRotation(enabled) {",
" this.autoRotate = enabled;",
" }",
Comment on lines +320 to +322
Copy link
Member

Choose a reason for hiding this comment

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

This is never used?

" ",
" /**",
" * Set a specific angle on a specific axis",
" * @param {string} axis - 'x', 'y', or 'z'",
" * @param {number} angle - Angle in degrees",
" */",
" setRotationAngle(axis, angle) {",
" if (!this.mesh) return;",
" ",
" const radians = THREE.MathUtils.degToRad(angle);",
" switch(axis.toLowerCase()) {",
" case 'x':",
" this.mesh.rotation.x = radians;",
" break;",
" case 'y':",
" this.mesh.rotation.y = radians;",
" break;",
" case 'z':",
" this.mesh.rotation.z = radians;",
" break;",
" }",
" }",
"}",
"",
"gdjs.__sprite3DExtension = {",
" Sprite3DRenderer",
"};",
""
"};"
],
"parameterObjects": "",
"useStrict": true,
Expand Down Expand Up @@ -178,6 +383,7 @@
"ambientLightColorB": 200,
"ambientLightColorG": 200,
"ambientLightColorR": 200,
"camera2DPlaneMaxDrawingDistance": 5000,
"camera3DFarPlaneDistance": 10000,
"camera3DFieldOfView": 45,
"camera3DNearPlaneDistance": 3,
Expand All @@ -204,6 +410,7 @@
}
],
"instances": [],
"editionSettings": {},
"eventsFunctions": [
{
"fullName": "",
Expand Down