From c24cc7a9e9d80cd86aa1e7587bae382dfbd47b50 Mon Sep 17 00:00:00 2001 From: PANDAKO-GitHub <68310169+PANDAKO-GitHub@users.noreply.github.com> Date: Mon, 21 Sep 2026 00:11:02 +0900 Subject: [PATCH] [Experimental] [Advanced 3D Features] Added features such as split-screen functionality for 3D scenes. - Added an action to split the 3D scene view. - Added a behavior that renders the 3D camera view onto the texture of a 3D box. --- extensions/community/A3F.json | 1160 +++++++++++++++++++++++++-------- 1 file changed, 883 insertions(+), 277 deletions(-) diff --git a/extensions/community/A3F.json b/extensions/community/A3F.json index a4377ee6b..d95084031 100644 --- a/extensions/community/A3F.json +++ b/extensions/community/A3F.json @@ -9,30 +9,34 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQoJLnN0MXtmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO30NCgkuc3Qye2ZpbGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLWRhc2hhcnJheTo2LDY7fQ0KCS5zdDN7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtZGFzaGFycmF5OjQsNDt9DQoJLnN0NHtmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fQ0KCS5zdDV7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1kYXNoYXJyYXk6My4xMDgxLDMuMTA4MTt9DQoJDQoJCS5zdDZ7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2UtZGFzaGFycmF5OjQsMzt9DQo8L3N0eWxlPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSI0LDkuNyA0LDIzLjIgMTYsMzAgMjgsMjMuMiAyOCw5LjcgMTYsMyAiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjQsMTAuMiAxNiwxNyAyOCwxMC4yICIvPg0KPGxpbmUgY2xhc3M9InN0MCIgeDE9IjE2IiB5MT0iMzAiIHgyPSIxNiIgeTI9IjE3Ii8+DQo8L3N2Zz4NCg==", "name": "A3F", "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Line Hero Pack/Master/SVG/Graphic Design/f4c71080f9213188ee5556b1acb45ad46fe6e5225947301c363105b080fca008_Graphic Design_3d_cube_isometric.svg", - "shortDescription": "This extension adds features to the built-in 3D.", - "version": "1.4.3", + "shortDescription": "Adds advanced 3D features such as lighting, shadows, 3D audio, object and camera controls, split-screen rendering, and camera screens.", + "version": "1.5.0", "description": [ "3D features added by this extension: ", - "- Lighting", - "- Light color and intensity control", - "- Shadows", - "- 3D sound", - "- Local translation and rotation", - "- Blend modes", - "- Opacity", - "- Overlay objects", - "- Linear interpolation", - "- Look At", - "- Distance between 3D objects", - "- Angle between 3D objects", - "- Bone control", - "- Morph target control", - "- Access child objects of 3D objects", - "- Normalized direction", - "- Axes helper", - "- Light helper", - "- Shadow range helper", - "- Hierarchy output" + "- Add lights to 3D scenes", + "- Adjust light colors and intensity", + "- Cast shadows from 3D objects", + "- Play positional 3D audio", + "- Move and rotate along local axes", + "- Set object blend modes", + "- Adjust object opacity", + "- Position 2D overlays on 3D objects", + "- Smoothly interpolate positions and rotations", + "- Make objects or cameras look at a target", + "- Measure distances between 3D objects", + "- Measure angles between 3D objects", + "- Gradually rotate 3D objects toward a target", + "- Control model bones", + "- Adjust model morph targets", + "- Access child objects within 3D models", + "- Get normalized direction vectors", + "- Display multiple 3D camera views on one screen", + "- Display a 3D camera view on a 3D Box face", + "- Visualize object axes", + "- Visualize lights", + "- Visualize shadow coverage", + "- Output the 3D object hierarchy", + "" ], "tags": [ "3d", @@ -47,7 +51,8 @@ "bone", "morph", "sound", - "audio" + "audio", + "screen" ], "authorIds": [ "Zu55H5hcb9YmZTltIVOTAFDJQyB2" @@ -263,11 +268,14 @@ "gdjs._A3F.Eul1 = new THREE.Euler();\r", "gdjs._A3F.Eul2 = new THREE.Euler();\r", "\r", + "gdjs._A3F.Fru1 = new THREE.Frustum();\r", + "\r", + "gdjs._A3F.Mat1 = new THREE.Matrix4();\r", "" ], "parameterObjects": "", "useStrict": true, - "eventsSheetExpanded": true + "eventsSheetExpanded": false }, { "type": "BuiltinCommonInstructions::Comment", @@ -622,47 +630,53 @@ "const FactorP = eventsFunctionContext.getArgument(\"FactorP\");", "const FactorR = eventsFunctionContext.getArgument(\"FactorR\");", "const Viewpoint = eventsFunctionContext.getArgument(\"Viewpoint\");", - "//", - "const Camera3D = runtimeScene.getLayer(Layer).getRenderer().getThreeCamera();", - "if (!Camera3D) {", - " return;", - "}", - "if (Targets.length === 0) {", + "", + "const SceneLayer = runtimeScene.getLayer(Layer);", + "const Camera3D = SceneLayer.getRenderer().getThreeCamera();", + "if (!Camera3D || Targets.length === 0) {", " return;", "}", + "", "const Object3D = Targets[0].get3DRendererObject();", - "//", + "const A3F = gdjs._A3F;", + "", "if (FactorP) {", - " const Vec3 = Object3D.position.clone();", + " const Vec3 = A3F.Vec1.copy(Object3D.position);", " Vec3.y *= -1;", + "", " Camera3D.position.lerp(Vec3, FactorP);", - " runtimeScene.getLayer(Layer)._cameraX = Camera3D.position.x;", - " runtimeScene.getLayer(Layer)._cameraY = -Camera3D.position.y;", - " runtimeScene.getLayer(Layer).setCameraZ(Camera3D.position.z, null);", + " SceneLayer._cameraX = Camera3D.position.x;", + " SceneLayer._cameraY = -Camera3D.position.y;", + " SceneLayer.setCameraZ(Camera3D.position.z, null);", "}", + "", "if (FactorR) {", - " const Euler = Object3D.rotation.clone();", + " const Euler = A3F.Eul1.copy(Object3D.rotation);", " Euler.x *= -1;", " Euler.z *= -1;", - " const Quaternion = new THREE.Quaternion();", - " Quaternion.setFromEuler(Euler);", - " //", + "", + " const Quaternion = A3F.Qua1.setFromEuler(Euler);", + "", " if (Viewpoint) {", - " const EulerView = new THREE.Euler(gdjs.toRad(90), gdjs.toRad(-90), 0);", - " const QuaternionView = new THREE.Quaternion();", - " QuaternionView.setFromEuler(EulerView);", + " const EulerView = A3F.Eul2.set(", + " gdjs.toRad(90),", + " gdjs.toRad(-90),", + " 0,", + " \"XYZ\"", + " );", + " const QuaternionView = A3F.Qua2.setFromEuler(EulerView);", " Quaternion.multiply(QuaternionView);", " }", - " //", - " Camera3D.quaternion.slerp(Quaternion, FactorR);", - " runtimeScene.getLayer(Layer).setCameraRotation(gdjs.toDegrees(-Camera3D.rotation.z));", - "}", "", - "" + " Camera3D.quaternion.slerp(Quaternion, FactorR);", + " SceneLayer.setCameraRotation(", + " gdjs.toDegrees(-Camera3D.rotation.z)", + " );", + "}" ], "parameterObjects": "", "useStrict": true, - "eventsSheetExpanded": true + "eventsSheetExpanded": false } ], "parameters": [ @@ -776,7 +790,7 @@ ], "parameterObjects": "AnyObject", "useStrict": true, - "eventsSheetExpanded": true + "eventsSheetExpanded": false } ] } @@ -848,54 +862,67 @@ "const OffsetX = eventsFunctionContext.getArgument(\"OffsetX\");", "const OffsetY = eventsFunctionContext.getArgument(\"OffsetY\");", "const OffsetZ = eventsFunctionContext.getArgument(\"OffsetZ\");", - "const Offset3D = new THREE.Vector3(OffsetX, OffsetY, OffsetZ);", + "", "if (!Object3D) {", " return;", "}", + "", "const Layer3D = Object3D.layer;", "const RendererObject3D = Object3D.get3DRendererObject();", "const Camera3D = runtimeScene.getLayer(Layer3D).getRenderer().getThreeCamera();", + "", "if (!Camera3D) {", " return;", "}", - "//", - "let Position3D;", + "", + "const A3F = gdjs._A3F;", + "const Position3D = A3F.Vec1;", + "", "if (RendererObject3D) {", " // 3D", " if (Point == \"Origin point\") {", - " Position3D = new THREE.Vector3(Object3D.getX(), Object3D.getY(), Object3D.getZ());", + " Position3D.set(", + " Object3D.getX(),", + " Object3D.getY(),", + " Object3D.getZ()", + " );", " } else {", - " Position3D = RendererObject3D.position.clone();", + " Position3D.copy(RendererObject3D.position);", " }", "} else {", " // 2D", - " Position3D = new THREE.Vector3(Object3D.getX(), Object3D.getY(), 0);", + " Position3D.set(Object3D.getX(), Object3D.getY(), 0);", "}", - "Position3D.add(Offset3D);", + "", + "Position3D.x += OffsetX;", + "Position3D.y += OffsetY;", + "Position3D.z += OffsetZ;", "Position3D.y *= -1;", - "//", - "const Project = Position3D.clone().project(Camera3D);", - "const GameResolutionWidth = runtimeScene.getGame().getGameResolutionWidth();", - "const GameResolutionHeight = runtimeScene.getGame().getGameResolutionHeight();", + "", + "const Project = A3F.Vec2.copy(Position3D).project(Camera3D);", + "const Game = runtimeScene.getGame();", + "const GameResolutionWidth = Game.getGameResolutionWidth();", + "const GameResolutionHeight = Game.getGameResolutionHeight();", "const PointX = (GameResolutionWidth / 2) * (Project.x + 1.0);", "const PointY = (GameResolutionHeight / 2) * ((Project.y * -1) + 1.0);", - "//", + "", "if (Hide) {", - " const Frustum = new THREE.Frustum();", - " const CloneCamera = Camera3D.clone();", - " CloneCamera.rotateZ(gdjs.toRad(180));", - " Frustum.setFromProjectionMatrix(new THREE.Matrix4().multiplyMatrices(CloneCamera.projectionMatrix, CloneCamera.matrixWorldInverse));", + " const Matrix = A3F.Mat1.multiplyMatrices(", + " Camera3D.projectionMatrix,", + " Camera3D.matrixWorldInverse", + " );", + " const Frustum = A3F.Fru1;", + " Frustum.setFromProjectionMatrix(Matrix);", + "", " const Within = Frustum.containsPoint(Position3D);", " AnyObject.hide(!Within);", "}", - "//", - "AnyObject.setPosition(PointX, PointY);", "", - "" + "AnyObject.setPosition(PointX, PointY);" ], "parameterObjects": "AnyObject", "useStrict": true, - "eventsSheetExpanded": true + "eventsSheetExpanded": false } ] } @@ -973,23 +1000,27 @@ "", "Object3D.updateMatrixWorld(false);", "const Elements = Object3D.matrixWorld.elements;", - "let WorldDirection = new THREE.Vector3();", + "const WorldDirection = gdjs._A3F.Vec1;", + "", "if (Axis == \"X\") {", - " WorldDirection.set(Elements[0], Elements[1], Elements[2]);", - " WorldDirection = WorldDirection.normalize();", + " WorldDirection.set(", + " Elements[0], Elements[1], Elements[2]", + " ).normalize();", "} else if (Axis == \"Y\") {", - " WorldDirection.set(Elements[4], Elements[5], Elements[6]);", - " WorldDirection = WorldDirection.normalize();", + " WorldDirection.set(", + " Elements[4], Elements[5], Elements[6]", + " ).normalize();", "} else {", " Object3D.getWorldDirection(WorldDirection);", "}", + "", "Object2D.setX(Object2D.getX() + (WorldDirection.x * Distance));", "Object2D.setY(Object2D.getY() + (WorldDirection.y * Distance * -1));", "Object2D.setZ(Object2D.getZ() + (WorldDirection.z * Distance));" ], "parameterObjects": "Object", "useStrict": true, - "eventsSheetExpanded": true + "eventsSheetExpanded": false } ] } @@ -1223,43 +1254,46 @@ "const Targets = eventsFunctionContext.getObjects(\"Target\");", "const FactorP = eventsFunctionContext.getArgument(\"FactorP\");", "const FactorR = eventsFunctionContext.getArgument(\"FactorR\");", - "//", + "", "if (Targets.length === 0) {", " return;", "}", - "const Target3D = Targets[0].get3DRendererObject();", - "//", + "", + "const Target = Targets[0];", + "const Target3D = Target.get3DRendererObject();", + "", "if (FactorP) {", - " const ObjectPosition = new THREE.Vector3(Object2D.getX(), Object2D.getY(), Object2D.getZ());", - " const TargetPosition = new THREE.Vector3(Targets[0].getX(), Targets[0].getY(), Targets[0].getZ());", + " const ObjectPosition = gdjs._A3F.Vec1.set(", + " Object2D.getX(),", + " Object2D.getY(),", + " Object2D.getZ()", + " );", + " const TargetPosition = gdjs._A3F.Vec2.set(", + " Target.getX(),", + " Target.getY(),", + " Target.getZ()", + " );", + "", " ObjectPosition.lerp(TargetPosition, FactorP);", + "", " Object2D.x = ObjectPosition.x;", " Object2D.y = ObjectPosition.y;", " Object2D._z = ObjectPosition.z;", " Object2D.getRenderer().updatePosition();", - " /*", - " Object3D.position.lerp(Target3D.position, FactorP);", - " //", - " Object2D.x = Object3D.position.x;", - " Object2D.y = Object3D.position.y;", - " Object2D._z = Object3D.position.z;", - " Object2D.getRenderer().updatePosition();", - " */", "}", + "", "if (FactorR) {", " Object3D.quaternion.slerp(Target3D.quaternion, FactorR);", - " //", + "", " Object2D._rotationX = gdjs.toDegrees(Object3D.rotation.x);", " Object2D._rotationY = gdjs.toDegrees(Object3D.rotation.y);", " Object2D.angle = gdjs.toDegrees(Object3D.rotation.z);", " Object2D.getRenderer().updateRotation();", - "}", - "", - "" + "}" ], "parameterObjects": "Object", "useStrict": true, - "eventsSheetExpanded": true + "eventsSheetExpanded": false } ] } @@ -2333,51 +2367,65 @@ "const ChildName = eventsFunctionContext.getArgument(\"ChildName\");", "const FactorP = eventsFunctionContext.getArgument(\"FactorP\");", "const FactorR = eventsFunctionContext.getArgument(\"FactorR\");", - "//", + "", "if (Targets.length === 0) {", " return;", "}", + "", "const Target3D = Targets[0].get3DRendererObject();", "const TargetChild = Target3D.getObjectByName(ChildName);", - "if (!TargetChild) {return};", - "//", + "", + "if (!TargetChild) {", + " return;", + "}", + "", + "const A3F = gdjs._A3F;", + "", "TargetChild.updateMatrixWorld(false);", + "", "if (FactorP) {", - " const TargetChildPosition = new THREE.Vector3();", + " const TargetChildPosition = A3F.Vec1;", " TargetChild.getWorldPosition(TargetChildPosition);", " TargetChildPosition.y = -TargetChildPosition.y;", - " //", - " const ObjectPosition = new THREE.Vector3(Object2D.getX(), Object2D.getY(), Object2D.getZ());", + "", + " const ObjectPosition = A3F.Vec2.set(", + " Object2D.getX(),", + " Object2D.getY(),", + " Object2D.getZ()", + " );", " ObjectPosition.lerp(TargetChildPosition, FactorP);", - " //", + "", " Object2D.x = ObjectPosition.x;", " Object2D.y = ObjectPosition.y;", " Object2D._z = ObjectPosition.z;", " Object2D.getRenderer().updatePosition();", "}", + "", "if (FactorR) {", - " const TargetChildQuaternion = new THREE.Quaternion();", + " const TargetChildQuaternion = A3F.Qua1;", " TargetChild.getWorldQuaternion(TargetChildQuaternion);", - " const Euler = new THREE.Euler().setFromQuaternion(TargetChildQuaternion);", + "", + " const Euler = A3F.Eul1.setFromQuaternion(", + " TargetChildQuaternion,", + " \"XYZ\"", + " );", " Euler.x *= -1;", " Euler.z *= -1;", - " //Euler.z += Math.PI; // 180", + " // Euler.z += Math.PI; // 180", + "", " TargetChildQuaternion.setFromEuler(Euler);", - " //", + "", " Object3D.quaternion.slerp(TargetChildQuaternion, FactorR);", - " //", + "", " Object2D._rotationX = gdjs.toDegrees(Object3D.rotation.x);", " Object2D._rotationY = gdjs.toDegrees(Object3D.rotation.y);", - " // Object2D.setAngle(gdjs.toDegrees(Object3D.rotation.z));", " Object2D.angle = gdjs.toDegrees(Object3D.rotation.z);", " Object2D.getRenderer().updateRotation();", - "}", - "", - "" + "}" ], "parameterObjects": "Object", "useStrict": true, - "eventsSheetExpanded": true + "eventsSheetExpanded": false } ] } @@ -3044,36 +3092,58 @@ "const ObjectPoint = eventsFunctionContext.getArgument(\"ObjectPoint\");", "const TargetObjects = eventsFunctionContext.getObjects(\"TargetObject\");", "const TargetPoint = eventsFunctionContext.getArgument(\"TargetPoint\");", + "", "if (objects.length == 0 || TargetObjects.length == 0) {", " return;", "}", + "", "const Object2D = objects[0];", "const Target2D = TargetObjects[0];", "const Object3D = Object2D.get3DRendererObject();", "const Target3D = Target2D.get3DRendererObject();", + "", "let Distance = 0;", + "", "if (Object3D && Target3D) {", - " let ObjectPos;", + " const ObjectPos = gdjs._A3F.Vec1;", + " const TargetPos = gdjs._A3F.Vec2;", + "", " if (ObjectPoint === \"Center point\") {", - " ObjectPos = new THREE.Vector3(Object2D.getCenterXInScene(), Object2D.getCenterYInScene(), Object2D.getCenterZInScene());", + " ObjectPos.set(", + " Object2D.getCenterXInScene(),", + " Object2D.getCenterYInScene(),", + " Object2D.getCenterZInScene()", + " );", " } else {", - " ObjectPos = new THREE.Vector3(Object2D.getX(), Object2D.getY(), Object2D.getZ());", + " ObjectPos.set(", + " Object2D.getX(),", + " Object2D.getY(),", + " Object2D.getZ()", + " );", " }", - " let TargetPos;", + "", " if (TargetPoint === \"Center point\") {", - " TargetPos = new THREE.Vector3(Target2D.getCenterXInScene(), Target2D.getCenterYInScene(), Target2D.getCenterZInScene());", + " TargetPos.set(", + " Target2D.getCenterXInScene(),", + " Target2D.getCenterYInScene(),", + " Target2D.getCenterZInScene()", + " );", " } else {", - " TargetPos = new THREE.Vector3(Target2D.getX(), Target2D.getY(), Target2D.getZ());", + " TargetPos.set(", + " Target2D.getX(),", + " Target2D.getY(),", + " Target2D.getZ()", + " );", " }", + "", " Distance = ObjectPos.distanceTo(TargetPos);", "}", - "eventsFunctionContext.returnValue = Distance;", "", - "" + "eventsFunctionContext.returnValue = Distance;" ], "parameterObjects": "Object", "useStrict": true, - "eventsSheetExpanded": true + "eventsSheetExpanded": false } ], "expressionType": { @@ -3230,55 +3300,79 @@ "if (objects.length == 0 || TargetObjects.length == 0) {", " return;", "}", + "", "const ObjectsLists = eventsFunctionContext.getObjectsLists(\"Object\");", "const ObjectPoint = eventsFunctionContext.getArgument(\"ObjectPoint\");", "const TargetsLists = eventsFunctionContext.getObjectsLists(\"TargetObject\");", "const TargetPoint = eventsFunctionContext.getArgument(\"TargetPoint\");", "const Operator = eventsFunctionContext.getArgument(\"Operator\");", "const Distance = eventsFunctionContext.getArgument(\"Distance\");", - "const Arguments = {Ope: Operator, Dis: Distance, ObjP: ObjectPoint, TarP: TargetPoint};", - "//", + "const Arguments = {", + " Ope: Operator,", + " Dis: Distance,", + " ObjP: ObjectPoint,", + " TarP: TargetPoint", + "};", + "", "eventsFunctionContext.returnValue = gdjs.evtTools.object.twoListsTest(", " (Object2D, Target2D, Arg) => {", - " let ObjectPos;", + " const ObjectPos = gdjs._A3F.Vec1;", + " const TargetPos = gdjs._A3F.Vec2;", + "", " if (Arg.ObjP === \"Center point\") {", - " ObjectPos = new THREE.Vector3(Object2D.getCenterXInScene(), Object2D.getCenterYInScene(), Object2D.getCenterZInScene());", + " ObjectPos.set(", + " Object2D.getCenterXInScene(),", + " Object2D.getCenterYInScene(),", + " Object2D.getCenterZInScene()", + " );", " } else {", - " ObjectPos = new THREE.Vector3(Object2D.getX(), Object2D.getY(), Object2D.getZ());", + " ObjectPos.set(", + " Object2D.getX(),", + " Object2D.getY(),", + " Object2D.getZ()", + " );", " }", - " let TargetPos;", + "", " if (Arg.TarP === \"Center point\") {", - " TargetPos = new THREE.Vector3(Target2D.getCenterXInScene(), Target2D.getCenterYInScene(), Target2D.getCenterZInScene());", + " TargetPos.set(", + " Target2D.getCenterXInScene(),", + " Target2D.getCenterYInScene(),", + " Target2D.getCenterZInScene()", + " );", " } else {", - " TargetPos = new THREE.Vector3(Target2D.getX(), Target2D.getY(), Target2D.getZ());", + " TargetPos.set(", + " Target2D.getX(),", + " Target2D.getY(),", + " Target2D.getZ()", + " );", " }", + "", " const Dis = ObjectPos.distanceTo(TargetPos);", + "", " if (Arg.Ope == \"=\") {", - " return (Dis == Arg.Dis);", + " return Dis == Arg.Dis;", " } else if (Arg.Ope == \"<\") {", - " return (Dis < Arg.Dis);", + " return Dis < Arg.Dis;", " } else if (Arg.Ope == \">\") {", - " return (Dis > Arg.Dis);", + " return Dis > Arg.Dis;", " } else if (Arg.Ope == \"≤\") {", - " return (Dis <= Arg.Dis);", + " return Dis <= Arg.Dis;", " } else if (Arg.Ope == \"≥\") {", - " return (Dis >= Arg.Dis);", + " return Dis >= Arg.Dis;", " } else {", " // ≠", - " return (Dis != Arg.Dis);", + " return Dis != Arg.Dis;", " }", " },", " ObjectsLists,", " TargetsLists,", " false,", " Arguments", - ");", - "", - "" + ");" ], "parameterObjects": "Object", "useStrict": true, - "eventsSheetExpanded": true + "eventsSheetExpanded": false } ], "parameters": [ @@ -3400,7 +3494,7 @@ "type": "behavior" }, { - "description": "Angle (in degrees)", + "description": "Rotation angle (in degrees)", "name": "Angle", "type": "expression" }, @@ -3522,7 +3616,7 @@ "type": "stringWithSelector" }, { - "description": "Angle (in degrees)", + "description": "Rotation angle (in degrees)", "name": "Angle", "type": "expression" }, @@ -3639,7 +3733,7 @@ "type": "stringWithSelector" }, { - "description": "Angle (in degrees)", + "description": "Rotation angle (in degrees)", "name": "Angle", "type": "expression" }, @@ -3767,7 +3861,7 @@ "type": "stringWithSelector" }, { - "description": "Angle (in degrees)", + "description": "Rotation angle (in degrees)", "name": "Angle", "type": "expression" }, @@ -3790,6 +3884,316 @@ } ], "objectGroups": [] + }, + { + "description": "Display the 3D scene from Camera 1’s layer through two cameras. Run once to enable, or again to update the settings. No need to run every frame. Use the \"Disable multi 3D camera rendering\" action to return to normal rendering.\n\nX and Y set each view’s top-left corner. Width and height set its size. All values are proportions of the screen, from 0 to 1. For example, X = 0.5 starts halfway across the screen.\n\n⚠️ Usage notes:\n- Both camera layers must support 3D rendering. Both views show the objects and lights from Camera 1’s layer. Camera 2’s layer provides only the second camera.\n- Both views must fit within the screen and have a width and height greater than 0.\n- Camera 2 appears in front of Camera 1 where the views overlap. Views do not support transparent blending with lower layers.\n- Post-processing effects on Camera 1’s layer are not applied while multi-camera rendering is enabled.\n- UI and mouse/touch coordinates are not automatically adjusted for each view.\n- Running this action again replaces the previous settings.\n- Invalid layers or view sizes restore normal rendering. Run again with valid settings to re-enable.\n- Rendering the scene twice may reduce performance.\n- Experimental: compatibility with future GDevelop versions or other extensions that modify rendering is not guaranteed.", + "fullName": "🎥 Enable multi 3D camera rendering (Experimental)", + "functionType": "Action", + "name": "EnableMultiCamera", + "sentence": "🎥 Enable multi 3D camera rendering (_PARAM1_, _PARAM2_, _PARAM3_, _PARAM4_, _PARAM5_, _PARAM6_, _PARAM7_, _PARAM8_, _PARAM9_, _PARAM10_)", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const Cam1Layer = eventsFunctionContext.getArgument(\"Cam1Layer\") || \"\";", + "const Cam1X = eventsFunctionContext.getArgument(\"Cam1X\") ?? 0;", + "const Cam1Y = eventsFunctionContext.getArgument(\"Cam1Y\") ?? 0;", + "const Cam1W = eventsFunctionContext.getArgument(\"Cam1W\") ?? 0.5;", + "const Cam1H = eventsFunctionContext.getArgument(\"Cam1H\") ?? 1;", + "const Cam2Layer = eventsFunctionContext.getArgument(\"Cam2Layer\") || \"\";", + "const Cam2X = eventsFunctionContext.getArgument(\"Cam2X\") ?? 0.5;", + "const Cam2Y = eventsFunctionContext.getArgument(\"Cam2Y\") ?? 0;", + "const Cam2W = eventsFunctionContext.getArgument(\"Cam2W\") ?? 0.5;", + "const Cam2H = eventsFunctionContext.getArgument(\"Cam2H\") ?? 1;", + "const WORLD_LAYER = Cam1Layer; // 映す3D空間。空文字はベースレイヤー。", + "// x,yは左上の位置。width,heightはサイズ。すべて画面に対する割合(0~1)。", + "// 配列の後ろにある映像が手前に描画されます。", + "const VIEWS = [", + " { cameraLayer: Cam1Layer, x: Cam1X, y: Cam1Y, width: Cam1W, height: Cam1H },", + " { cameraLayer: Cam2Layer, x: Cam2X, y: Cam2Y, width: Cam2W, height: Cam2H },", + "];", + "", + "const KEY = \"__A3FSplitScreen\";", + "const sceneRenderer = runtimeScene.getRenderer();", + "let state = runtimeScene[KEY];", + "if (!state) {", + " state = {", + " initialized: false,", + " loggedMessages: new Set(),", + " logger:", + " typeof gdjs !== \"undefined\" && typeof gdjs.Logger === \"function\"", + " ? new gdjs.Logger(\"Multi 3D Camera\")", + " : null,", + " };", + " runtimeScene[KEY] = state;", + "}", + "// 設定ミスでゲームを停止させず、同じ内容はシーンにつき一度だけ記録する。", + "const logMessage = message => {", + " if (state.loggedMessages.has(message)) return;", + " state.loggedMessages.add(message);", + " if (state.logger) state.logger.log(message);", + " else console.warn(\"[Multi 3D Camera] \" + message);", + "};", + "// 検証に失敗した場合は通常描画へ戻す。以前のレイヤー参照は使わない。", + "// フックは維持し、次回すべての設定が有効になったときに自動復帰する。", + "state.active = false;", + "state.renderer = null;", + "state.worldRenderer = null;", + "state.views = null;", + "if (typeof THREE === \"undefined\") {", + " logMessage(\"Three.js is not available. Add a 3D object to the scene.\");", + " return;", + "}", + "const getLayerRenderer = name => {", + " if (runtimeScene.hasLayer && !runtimeScene.hasLayer(name)) {", + " logMessage(`Layer not found: ${name || \"Base layer\"}.`);", + " return null;", + " }", + " let lr;", + " try {", + " lr = runtimeScene.getLayer(name).getRenderer();", + " } catch (error) {", + " logMessage(`Could not access layer: ${name || \"Base layer\"}.`);", + " return null;", + " }", + " if (!lr.getThreeScene?.() || !lr.getThreeCamera?.()) {", + " logMessage(`Layer is not configured for 3D rendering: ${name || \"Base layer\"}.`);", + " return null;", + " }", + " return lr;", + "};", + "const worldRenderer = getLayerRenderer(WORLD_LAYER);", + "if (!worldRenderer) return;", + "if (typeof worldRenderer.hasPostProcessingPass !== \"function\") {", + " logMessage(\"The required post-processing API is not available.\");", + " return;", + "}", + "const renderer = runtimeScene.getGame().getRenderer().getThreeRenderer?.();", + "if (!renderer || typeof sceneRenderer.render !== \"function\") {", + " logMessage(\"The required rendering API is not available.\");", + " return;", + "}", + "if (!VIEWS.length) {", + " logMessage(\"VIEWS must contain at least one camera view.\");", + " return;", + "}", + "const views = [];", + "for (const view of VIEWS) {", + " const { x, y, width, height } = view;", + " if (![x, y, width, height].every(Number.isFinite) ||", + " x < 0 || y < 0 || width <= 0 || height <= 0 ||", + " x + width > 1.000000001 || y + height > 1.000000001) {", + " logMessage(\"Every view rectangle must use values from 0 to 1 and fit within the screen.\");", + " return;", + " }", + " const layerRenderer = getLayerRenderer(view.cameraLayer);", + " if (!layerRenderer) return;", + " const camera = layerRenderer.getThreeCamera();", + " if (!camera?.isPerspectiveCamera && !camera?.isOrthographicCamera) {", + " logMessage(\"A camera must use perspective or orthographic projection.\");", + " return;", + " }", + " views.push({ ...view, layerRenderer });", + "}", + "", + "if (!state.initialized) {", + " Object.assign(state, {", + " original: sceneRenderer.render,", + " cameras: [],", + " viewport: new THREE.Vector4(),", + " scissor: new THREE.Vector4(),", + " size: new THREE.Vector2(),", + " });", + " state.wrapper = function (...args) {", + " if (!state.active) return state.original.apply(this, args);", + " const r = state.renderer;", + " // 分割対象外の描画ではポストエフェクトの経路も変更しない。", + " if (r.getRenderTarget() !== null || r.xr?.isPresenting) {", + " return state.original.apply(this, args);", + " }", + " const layerRenderer = state.worldRenderer;", + " const postProcessingDescriptor = Object.getOwnPropertyDescriptor(", + " layerRenderer, \"hasPostProcessingPass\"", + " );", + " const originalRender = r.render;", + " const world = state.worldRenderer.getThreeScene();", + " const mainCamera = state.worldRenderer.getThreeCamera();", + " r.render = function (scene, sourceCamera) {", + " if (scene !== world || sourceCamera !== mainCamera ||", + " r.getRenderTarget() !== null || r.xr?.isPresenting) {", + " return originalRender.apply(this, arguments);", + " }", + " r.getViewport(state.viewport);", + " r.getScissor(state.scissor);", + " const oldScissorTest = r.getScissorTest();", + " const oldAutoClear = r.autoClear;", + " r.getDrawingBufferSize(state.size);", + " const pixelWidth = state.size.x;", + " const pixelHeight = state.size.y;", + " const ratio = r.getPixelRatio();", + " if (pixelWidth < 1 || pixelHeight < 1) return;", + " try {", + " r.autoClear = false;", + " // 全画面は消去せず、ビュー外の下位レイヤーの映像を残す。", + " // 各ビュー内の色・深度・ステンシルは、矩形を設定してから消去する。", + " r.setScissorTest(true);", + " for (let i = 0; i < state.views.length; i++) {", + " const view = state.views[i];", + " // 描画時に読むため、同フレームの通常カメラアクションが反映される。", + " const source = view.layerRenderer.getThreeCamera();", + " if (!source?.isPerspectiveCamera && !source?.isOrthographicCamera) {", + " logMessage(\"A camera must use perspective or orthographic projection.\");", + " continue;", + " }", + " let camera = state.cameras[i];", + " if (!camera || camera.type !== source.type) {", + " camera = state.cameras[i] = source.clone(false);", + " }", + " camera.copy(source, false);", + " const x0 = Math.round(view.x * pixelWidth);", + " const x1 = Math.round(Math.min(1, view.x + view.width) * pixelWidth);", + " const top = Math.round(view.y * pixelHeight);", + " const bottom = Math.round(Math.min(1, view.y + view.height) * pixelHeight);", + " const w = x1 - x0;", + " const h = bottom - top;", + " if (w < 1 || h < 1) continue;", + " if (camera.isPerspectiveCamera) {", + " camera.aspect = w / h;", + " } else {", + " const center = (source.left + source.right) / 2;", + " const halfWidth = (source.top - source.bottom) * w / h / 2;", + " camera.left = center - halfWidth;", + " camera.right = center + halfWidth;", + " }", + " camera.updateProjectionMatrix();", + " camera.updateMatrixWorld(true);", + " // 設定は左上原点、WebGLは左下原点なのでYを変換。", + " const x = x0 / ratio;", + " const y = (pixelHeight - bottom) / ratio;", + " r.setViewport(x, y, w / ratio, h / ratio);", + " r.setScissor(x, y, w / ratio, h / ratio);", + " // 重ねた小窓でも、下の映像の色・深度を引き継がない。", + " r.clear(true, true, true);", + " originalRender.call(r, world, camera);", + " }", + " } finally {", + " r.autoClear = oldAutoClear;", + " r.setViewport(state.viewport);", + " r.setScissor(state.scissor);", + " r.setScissorTest(oldScissorTest);", + " }", + " };", + " try {", + " // このシーン描画中だけ対象レイヤーを通常の3D描画経路へ切り替える。", + " // エフェクトの設定・パス・有効状態自体には触れない。", + " layerRenderer.hasPostProcessingPass = () => false;", + " return state.original.apply(this, args);", + " } finally {", + " r.render = originalRender;", + " // 継承したメソッドの場合も、元のプロパティ構成に戻す。", + " if (postProcessingDescriptor) {", + " Object.defineProperty(layerRenderer, \"hasPostProcessingPass\", postProcessingDescriptor);", + " } else {", + " delete layerRenderer.hasPostProcessingPass;", + " }", + " }", + " };", + " sceneRenderer.render = state.wrapper;", + " state.initialized = true;", + "}", + "state.renderer = renderer;", + "state.worldRenderer = worldRenderer;", + "state.views = views;", + "// 有効な設定をすべて反映してから複数カメラ描画を許可する。", + "state.active = true;", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Camera 1 layer", + "name": "Cam1Layer", + "type": "layer" + }, + { + "description": "Camera 1 X position (0 to 1)", + "name": "Cam1X", + "type": "expression" + }, + { + "description": "Camera 1 Y position (0 to 1)", + "name": "Cam1Y", + "type": "expression" + }, + { + "description": "Camera 1 width (0 to 1)", + "name": "Cam1W", + "type": "expression" + }, + { + "description": "Camera 1 height (0 to 1)", + "name": "Cam1H", + "type": "expression" + }, + { + "description": "Camera 2 layer", + "name": "Cam2Layer", + "type": "layer" + }, + { + "description": "Camera 2 X position (0 to 1)", + "name": "Cam2X", + "type": "expression" + }, + { + "description": "Camera 2 Y position (0 to 1)", + "name": "Cam2Y", + "type": "expression" + }, + { + "description": "Camera 2 width (0 to 1)", + "name": "Cam2W", + "type": "expression" + }, + { + "description": "Camera 2 height (0 to 1)", + "name": "Cam2H", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "🎥 Disable multi 3D camera rendering (Experimental)", + "functionType": "Action", + "name": "DisableMultiCamera", + "sentence": "🎥 Disable multi 3D camera rendering", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const state = runtimeScene[\"__A3FSplitScreen\"];", + "if (!state) return;", + "", + "// ポストエフェクトの迂回は描画中だけなので、エフェクト設定の復元操作は不要。", + "state.active = false;", + "state.renderer = null;", + "state.worldRenderer = null;", + "state.views = null;", + "if (state.cameras) state.cameras.length = 0;", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] } ], "eventsFunctionsFolderStructure": { @@ -3970,6 +4374,12 @@ "functionName": "SetListenerForObject" } ] + }, + { + "functionName": "EnableMultiCamera" + }, + { + "functionName": "DisableMultiCamera" } ] }, @@ -4092,141 +4502,6 @@ ] } ] - }, - { - "type": "BuiltinCommonInstructions::Comment", - "color": { - "b": 109, - "g": 230, - "r": 255, - "textB": 0, - "textG": 0, - "textR": 0 - }, - "comment": "Deprecated" - }, - { - "disabled": true, - "type": "BuiltinCommonInstructions::Standard", - "conditions": [ - { - "type": { - "value": "BooleanVariable" - }, - "parameters": [ - "CastShadow", - "True", - "" - ] - }, - { - "type": { - "value": "BooleanVariable" - }, - "parameters": [ - "ReceiveShadow", - "False", - "" - ] - } - ], - "actions": [ - { - "type": { - "value": "A3F::ChangeShadow" - }, - "parameters": [ - "", - "Object", - "Capability3d", - "yes", - "", - "" - ] - } - ] - }, - { - "disabled": true, - "type": "BuiltinCommonInstructions::Standard", - "conditions": [ - { - "type": { - "value": "BooleanVariable" - }, - "parameters": [ - "CastShadow", - "False", - "" - ] - }, - { - "type": { - "value": "BooleanVariable" - }, - "parameters": [ - "ReceiveShadow", - "True", - "" - ] - } - ], - "actions": [ - { - "type": { - "value": "A3F::ChangeShadow" - }, - "parameters": [ - "", - "Object", - "Capability3d", - "no", - "yes", - "" - ] - } - ] - }, - { - "disabled": true, - "type": "BuiltinCommonInstructions::Standard", - "conditions": [ - { - "type": { - "value": "BooleanVariable" - }, - "parameters": [ - "CastShadow", - "True", - "" - ] - }, - { - "type": { - "value": "BooleanVariable" - }, - "parameters": [ - "ReceiveShadow", - "True", - "" - ] - } - ], - "actions": [ - { - "type": { - "value": "A3F::ChangeShadow" - }, - "parameters": [ - "", - "Object", - "Capability3d", - "yes", - "yes", - "" - ] - } - ] } ], "parameters": [ @@ -4402,7 +4677,7 @@ "inlineCode": [ "const Object2D = objects[0];", "const Object3D = Object2D.get3DRendererObject();", - "const Behavior = Object2D.getBehavior(\"A3L\");", + "const Behavior = Object2D.getBehavior(eventsFunctionContext.getBehaviorName(\"Behavior\"));", "const Renderer = runtimeScene.getGame().getRenderer().getThreeRenderer();//runtimeScene.getRenderer()._threeRenderer;", "const Scene = runtimeScene.getLayer(Object2D.getLayer()).getRenderer().getThreeScene();//runtimeScene.getLayer(Object2D.getLayer()).get3DRendererObject();", "//", @@ -5246,6 +5521,337 @@ "propertiesFolderStructure": { "folderName": "__ROOT" } + }, + { + "description": "Display a 3D camera’s view on a face of a 3D Box.", + "fullName": "Advanced 3D Camera Screen", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQoJLnN0MXtmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO30NCgkuc3Qye2ZpbGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLWRhc2hhcnJheTo2LDY7fQ0KCS5zdDN7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtZGFzaGFycmF5OjQsNDt9DQoJLnN0NHtmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7fQ0KCS5zdDV7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1kYXNoYXJyYXk6My4xMDgxLDMuMTA4MTt9DQoJDQoJCS5zdDZ7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2UtZGFzaGFycmF5OjQsMzt9DQo8L3N0eWxlPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSI0LDkuNyA0LDIzLjIgMTYsMzAgMjgsMjMuMiAyOCw5LjcgMTYsMyAiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjQsMTAuMiAxNiwxNyAyOCwxMC4yICIvPg0KPGxpbmUgY2xhc3M9InN0MCIgeDE9IjE2IiB5MT0iMzAiIHgyPSIxNiIgeTI9IjE3Ii8+DQo8L3N2Zz4NCg==", + "name": "A3CS", + "objectType": "Scene3D::Cube3DObject", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Line Hero Pack/Master/SVG/Graphic Design/f4c71080f9213188ee5556b1acb45ad46fe6e5225947301c363105b080fca008_Graphic Design_3d_cube_isometric.svg", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const Object2D = objects[0];", + "const Object3D = Object2D.get3DRendererObject();", + "const Behavior = Object2D.getBehavior(eventsFunctionContext.getBehaviorName(\"Behavior\"));", + "const CAPTURE_ID = Object2D.getName();", + "const WORLD_LAYER = Behavior._getCaptureLayer() || \"\";", + "const CAMERA_LAYER = Behavior._getCameraLayer() || \"\";", + "const FACE = Behavior._getFace() || \"front\";", + "const WIDTH = Behavior._getWidth() ?? 512;", + "const HEIGHT = Behavior._getHeight() ?? 288;", + "const CAPTURE_FPS = Behavior._getFps() ?? 0; // 0以下は毎フレーム。実時間基準、上限は描画頻度。", + "const KEY = \"__A3CS\";", + "", + "if (typeof THREE === \"undefined\") return;", + "let state = runtimeScene[KEY];", + "if (state?.entries.has(CAPTURE_ID)) return;", + "const monitor = Object2D;", + "if (!monitor) return;", + "const mesh = monitor.getRenderer().get3DRendererObject?.();", + "const faceIndex = { right: 0, left: 1, bottom: 2, top: 3, front: 4, back: 5 }[FACE];", + "const material = mesh?.material?.[faceIndex];", + "if (!material) return;", + "// 同じ素材への異なる映像の登録は拒否する。複製は行わない。", + "if (state && [...state.entries.values()].some(entry => entry.material === material)) {", + " console.warn(\"[Advanced 3D Camera Screen] Please assign a different, dedicated image to the target face: \" + CAPTURE_ID);", + " return;", + "}", + "const world = runtimeScene.getLayer(WORLD_LAYER).getRenderer().getThreeScene();", + "const cameraLayer = runtimeScene.getLayer(CAMERA_LAYER).getRenderer();", + "const sourceCamera = cameraLayer.getThreeCamera();", + "const renderer = runtimeScene.getGame().getRenderer().getThreeRenderer();", + "if (!world || !sourceCamera || !renderer) return;", + "", + "if (!state) {", + " const sceneRenderer = runtimeScene.getRenderer();", + " const originalSceneRender = sceneRenderer.render;", + " const viewport = new THREE.Vector4();", + " const scissor = new THREE.Vector4();", + " const clearColor = new THREE.Color();", + " state = { entries: new Map(), stop: null };", + " runtimeScene[KEY] = state;", + "", + " // 全撮影設定で1つの描画フックを共有する。", + " const captureRender = function (...args) {", + " if (!state.entries.size) return originalSceneRender.apply(this, args);", + " const now = performance.now();", + " const originalRender = renderer.render;", + " const captured = new Set();", + " renderer.render = function (scene, viewCamera) {", + " // EffectComposerの中間バッファへの描画も撮影のきっかけにする。", + " // 撮影自体はoriginalRenderを呼ぶため、このフックには再入しない。", + " {", + " for (const entry of state.entries.values()) {", + " if (scene !== entry.world || captured.has(entry) ||", + " (entry.nextTime !== null && now < entry.nextTime)) continue;", + " captured.add(entry);", + " const { camera, target } = entry;", + " camera.copy(entry.cameraLayer.getThreeCamera(), false);", + " if (camera.isPerspectiveCamera) camera.aspect = target.width / target.height;", + " camera.updateProjectionMatrix();", + " camera.updateMatrixWorld(true);", + " const previousTarget = renderer.getRenderTarget();", + " const cubeFace = renderer.getActiveCubeFace();", + " const mipLevel = renderer.getActiveMipmapLevel();", + " const autoClear = renderer.autoClear;", + " const scissorTest = renderer.getScissorTest();", + " const clearAlpha = renderer.getClearAlpha();", + " renderer.getClearColor(clearColor);", + " const overrideMaterial = entry.world.overrideMaterial;", + " renderer.getViewport(viewport);", + " renderer.getScissor(scissor);", + " try {", + " renderer.setRenderTarget(target);", + " renderer.setScissorTest(false);", + " renderer.autoClear = true;", + " entry.world.overrideMaterial = null;", + " originalRender.call(renderer, entry.world, camera);", + " entry.nextTime = entry.interval === 0 ? null", + " : entry.nextTime === null ? now + entry.interval", + " : now + entry.interval - ((now - entry.nextTime) % entry.interval);", + " } finally {", + " entry.world.overrideMaterial = overrideMaterial;", + " renderer.setClearColor(clearColor, clearAlpha);", + " renderer.setRenderTarget(previousTarget, cubeFace, mipLevel);", + " // 中間バッファのviewport/scissorはsetRenderTargetが復元する。", + " // 画面用の値で上書きすると、解像度の異なるエフェクトが崩れる。", + " if (previousTarget === null) {", + " renderer.setViewport(viewport);", + " renderer.setScissor(scissor);", + " }", + " renderer.setScissorTest(scissorTest);", + " renderer.autoClear = autoClear;", + " }", + " }", + " }", + " return originalRender.call(this, scene, viewCamera);", + " };", + " try {", + " return originalSceneRender.apply(this, args);", + " } finally {", + " renderer.render = originalRender;", + " }", + " };", + " sceneRenderer.render = captureRender;", + "", + " // ID指定で個別停止。引数なしで全停止。最後の停止でフックも解除。", + " state.stop = function (id) {", + " const ids = id === undefined ? [...state.entries.keys()] : [id];", + " for (const key of ids) {", + " const entry = state.entries.get(key);", + " if (!entry) continue;", + " if (entry.material.map === entry.target.texture) {", + " entry.material.map = entry.originalMap;", + " entry.material.needsUpdate = true;", + " }", + " entry.target.dispose();", + " state.entries.delete(key);", + " }", + " if (!state.entries.size) {", + " if (sceneRenderer.render === captureRender) sceneRenderer.render = originalSceneRender;", + " if (runtimeScene[KEY] === state) delete runtimeScene[KEY];", + " }", + " };", + "}", + "", + "const target = new THREE.WebGLRenderTarget(WIDTH, HEIGHT);", + "state.entries.set(CAPTURE_ID, {", + " world, cameraLayer, camera: sourceCamera.clone(), target, material,", + " originalMap: material.map,", + " interval: Number.isFinite(CAPTURE_FPS) && CAPTURE_FPS > 0 ? 1000 / CAPTURE_FPS : 0,", + " nextTime: null,", + "});", + "material.map = target.texture;", + "material.needsUpdate = true;", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Scene3D::Cube3DObject", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "A3F::A3CS", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const Object2D = objects[0];\r", + "const CAPTURE_ID = Object2D.getName();\r", + "const KEY = \"__A3CS\";\r", + "runtimeScene[KEY]?.stop(CAPTURE_ID);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Scene3D::Cube3DObject", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "A3F::A3CS", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "onDestroy" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Layer", + "label": "Camera layer", + "description": "The camera on the \"Camera layer\" captures the \"Capture target layer\".", + "group": "Capture", + "name": "CameraLayer" + }, + { + "value": "", + "type": "Layer", + "label": "Capture target layer", + "group": "Capture", + "name": "CaptureLayer" + }, + { + "value": "", + "type": "Choice", + "label": "Display face", + "description": "⚠ The camera view also appears on any faces that share the same material as the Display face. \n⚠ Recursive views that capture the Display face itself are not supported. This does not cause an error, but should be avoided whenever possible. \n⚠ Each camera adds rendering load. Resources are still used even when the screen is hidden or off-screen.", + "group": "Display", + "choices": [ + { + "label": "Front", + "value": "front" + }, + { + "label": "Back", + "value": "back" + }, + { + "label": "Left", + "value": "left" + }, + { + "label": "Right", + "value": "right" + }, + { + "label": "Top", + "value": "top" + }, + { + "label": "Bottom", + "value": "bottom" + } + ], + "name": "Face" + }, + { + "value": "0", + "type": "Number", + "label": "Capture frame rate (FPS)", + "description": "Set to 0 to update every frame.", + "group": "Capture", + "name": "Fps" + }, + { + "value": "512", + "type": "Number", + "unit": "Pixel", + "label": "Capture width", + "description": "The aspect ratio of the image displayed on the Display face is determined by both the Capture width / height ratio and the Display face’s width-to-height ratio.", + "group": "Capture", + "name": "Width" + }, + { + "value": "288", + "type": "Number", + "unit": "Pixel", + "label": "Capture height", + "group": "Capture", + "name": "Height" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "folderName": "Capture", + "children": [ + { + "propertyName": "CameraLayer" + }, + { + "propertyName": "CaptureLayer" + }, + { + "propertyName": "Fps" + }, + { + "propertyName": "Width" + }, + { + "propertyName": "Height" + } + ] + }, + { + "folderName": "Display", + "children": [ + { + "propertyName": "Face" + } + ] + } + ] + } } ], "eventsBasedObjects": []