From c521851b802c91c7bffa317f23f48c095e94b211 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Aug 2026 15:56:38 +0000 Subject: [PATCH 1/6] Add a 3D light playground example, with gameplay tests Four scenes, one per kind of 3D light, so each can be looked at on its own: the scene's directional light and its shadows, three coloured point lights at night, two spot lights aimed at the ground, and twenty-four point lights at once - more than a device should light in one frame. Its gameplay tests read the lights back out of the renderer rather than trusting the look: that each kind arrives as the right kind of light, that it keeps the colour it was given, that it casts a shadow, and - in the crowded scene - that the number of lights of each kind stays the same while lights move, which is what keeps the renderer from compiling a new shader mid-game. Each test also takes screenshots, so a change in how lighting looks shows up as a change in the pictures. Two things the tests pinned down while being written, both noted in the README because they are easy to trip over: - A light's colour, intensity and range belong to the object, not the instance: GDevelop reads only `animation` from an instance's properties for an events-based object, so per-instance overrides are silently ignored. Three colours means three objects. The first version of the point light test placed one object three times and failed with three white lights, which is how this was found. - A spot light shines along its own +X axis, so it needs a Y rotation of 90 to point at the ground. Without it the cones shine off sideways and the scene looks almost unlit. The runner's download directory is also added to .gitignore: it holds the GDevelop build it fetches, which is hundreds of megabytes. --- .gitignore | 3 + .../3d-light-playground.json | 7925 +++++++++++++++++ examples/3d-light-playground/README.md | 31 + .../assets/LightPlaygroundSurface.png | Bin 0 -> 151 bytes 4 files changed, 7959 insertions(+) create mode 100644 examples/3d-light-playground/3d-light-playground.json create mode 100644 examples/3d-light-playground/README.md create mode 100644 examples/3d-light-playground/assets/LightPlaygroundSurface.png diff --git a/.gitignore b/.gitignore index 5a272b424..60fca0582 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ /dist *.autosave +# The GDevelop build the gameplay test runner downloads (hundreds of MB). +/.gameplay-tests-work + # Artefacts written next to a game by the gameplay test runner. gameplay-test-results.json gameplay-test-screenshots/ diff --git a/examples/3d-light-playground/3d-light-playground.json b/examples/3d-light-playground/3d-light-playground.json new file mode 100644 index 000000000..a2df3f922 --- /dev/null +++ b/examples/3d-light-playground/3d-light-playground.json @@ -0,0 +1,7925 @@ +{ + "firstLayout": "Sun and Shadows", + "gdVersion": { + "build": 279, + "major": 5, + "minor": 6, + "revision": 0 + }, + "properties": { + "adaptGameResolutionAtRuntime": true, + "antialiasingMode": "MSAA", + "antialisingEnabledOnMobile": false, + "folderProject": false, + "orientation": "landscape", + "packageName": "io.gdevelop.examples.lightplayground", + "pixelsRounding": false, + "projectUuid": "7b1f6f4e-9c2a-4c37-9d21-0f5a3b8c41d2", + "scaleMode": "linear", + "sizeOnStartupMode": "", + "templateSlug": "", + "version": "1.0.0", + "name": "3D light playground", + "description": "A playground for the 3D lights: the directional light and its shadows, coloured point lights, spot lights, and many lights at once.", + "author": "", + "windowWidth": 1280, + "windowHeight": 720, + "latestCompilationDirectory": "", + "maxFPS": 60, + "minFPS": 20, + "verticalSync": false, + "platformSpecificAssets": { + "android-icon-144": "", + "android-icon-192": "", + "android-icon-36": "", + "android-icon-48": "", + "android-icon-72": "", + "android-icon-96": "", + "android-windowSplashScreenAnimatedIcon": "", + "desktop-icon-512": "", + "ios-icon-100": "", + "ios-icon-1024": "", + "ios-icon-114": "", + "ios-icon-120": "", + "ios-icon-144": "", + "ios-icon-152": "", + "ios-icon-167": "", + "ios-icon-180": "", + "ios-icon-20": "", + "ios-icon-29": "", + "ios-icon-40": "", + "ios-icon-50": "", + "ios-icon-57": "", + "ios-icon-58": "", + "ios-icon-60": "", + "ios-icon-72": "", + "ios-icon-76": "", + "ios-icon-80": "", + "ios-icon-87": "" + }, + "loadingScreen": { + "backgroundColor": 0, + "backgroundFadeInDuration": 0.2, + "backgroundImageResourceName": "", + "gdevelopLogoStyle": "light", + "logoAndProgressFadeInDuration": 0.2, + "logoAndProgressLogoFadeInDelay": 0, + "minDuration": 1.5, + "progressBarColor": 16777215, + "progressBarHeight": 20, + "progressBarMaxWidth": 200, + "progressBarMinWidth": 40, + "progressBarWidthPercent": 30, + "showGDevelopSplash": true, + "showProgressBar": true + }, + "watermark": { + "placement": "bottom-left", + "showWatermark": true + }, + "authorIds": [], + "authorUsernames": [], + "categories": [], + "playableDevices": [ + "mobile" + ], + "extensionProperties": [], + "platforms": [ + { + "name": "GDevelop JS platform" + } + ], + "currentPlatform": "GDevelop JS platform" + }, + "resources": { + "resources": [ + { + "file": "assets/LightPlaygroundSurface.png", + "kind": "image", + "metadata": "", + "name": "assets/LightPlaygroundSurface.png", + "userAdded": false, + "smoothed": true, + "alwaysLoaded": false + } + ], + "resourceFolders": [] + }, + "objects": [], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [] + }, + "objectsGroups": [], + "variables": [], + "layouts": [ + { + "b": 190, + "disableInputWhenNotFocused": true, + "mangledName": "Sun_and_Shadows", + "name": "Sun and Shadows", + "r": 120, + "standardSortMethod": true, + "stopSoundsOnStartup": true, + "title": "", + "v": 150, + "uiSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 32, + "gridHeight": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": true, + "zoomFactor": 0.35, + "windowMask": false + }, + "objectsGroups": [], + "variables": [], + "instances": [ + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 20, + "height": 2400, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Ground", + "persistentUuid": "b090e0d0-06ca-2518-98fa-c63d498a7de4", + "width": 2400, + "x": -1200, + "y": -1200, + "z": -20, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 460, + "height": 110, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Pillar", + "persistentUuid": "2b7f7d92-a9e2-c4c8-bb48-5d71fcd16b84", + "width": 110, + "x": -55, + "y": -55, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 320, + "height": 60, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Backdrop", + "persistentUuid": "19ddede8-766d-ab48-ce0a-80a2f23bc81d", + "width": 900, + "x": -450, + "y": -900, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "3fdda8ff-dc0e-7c1a-f754-d933f0f4d28a", + "width": 140, + "x": -520, + "y": -420, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "fd2c00d2-250c-def9-2903-66670a15015c", + "width": 140, + "x": 380, + "y": -480, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "57363f1e-d65d-3e58-ee58-8c3137384388", + "width": 140, + "x": -600, + "y": 320, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "87224692-c2ad-dcb7-b6b7-71b0135f42b6", + "width": 140, + "x": 460, + "y": 300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "eafbcc09-5057-5dca-8e2d-a35b8111fa27", + "width": 140, + "x": -80, + "y": -640, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "f818a1a3-593b-2d99-78b2-ca893240b86d", + "width": 140, + "x": -60, + "y": 480, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "objects": [ + { + "assetStoreId": "", + "name": "Ground", + "persistentUuid": "e46f1434-2c55-2bca-2ccf-742ed8e26b67", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 2400, + "height": 2400, + "depth": 20, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "170;175;180", + "isCastingShadow": false, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "Crate", + "persistentUuid": "df5c6f4b-aedc-6655-8ef5-10c3d9b3294a", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 140, + "height": 140, + "depth": 140, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "196;146;92", + "isCastingShadow": true, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "Pillar", + "persistentUuid": "bcda4ad2-b8bf-a7ee-c150-3cb4ff8649d2", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 110, + "height": 110, + "depth": 460, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "205;208;212", + "isCastingShadow": true, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "Backdrop", + "persistentUuid": "405800cc-89d5-f501-5a8d-23977b035d4d", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 900, + "height": 60, + "depth": 320, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "150;154;162", + "isCastingShadow": true, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Ground" + }, + { + "objectName": "Crate" + }, + { + "objectName": "Pillar" + }, + { + "objectName": "Backdrop" + } + ] + }, + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "// Frame the props the same way in every scene and on every run, so the", + "// screenshots the gameplay tests take can be compared between versions.", + "if (!runtimeScene.__cameraPlaced) {", + " runtimeScene.__cameraPlaced = true;", + " gdjs.evtTools.camera.setCameraX(runtimeScene, 0, '', 0);", + " gdjs.evtTools.camera.setCameraY(runtimeScene, 1450, '', 0);", + " gdjs.scene3d.camera.setCameraZ(runtimeScene, 880, '', 0);", + " gdjs.scene3d.camera.setCameraRotationX(runtimeScene, 59, '', 0);", + " gdjs.scene3d.camera.setCameraRotationY(runtimeScene, 0, '', 0);", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "layers": [ + { + "ambientLightColorR": 120, + "ambientLightColorG": 130, + "ambientLightColorB": 150, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [ + { + "effectType": "Scene3D::AmbientLight", + "name": "Ambient light", + "doubleParameters": { + "intensity": 0.22 + }, + "stringParameters": { + "color": "255;255;255" + }, + "booleanParameters": {} + }, + { + "effectType": "Scene3D::DirectionalLight", + "name": "Sun", + "doubleParameters": { + "distanceFromCamera": 1500, + "elevation": 50, + "frustumSize": 3000, + "intensity": 1.4, + "minimumShadowBias": 0, + "rotation": 35 + }, + "stringParameters": { + "color": "255;246;224", + "shadowQuality": "high", + "top": "Z+" + }, + "booleanParameters": { + "isCastingShadow": true + } + } + ] + } + ], + "behaviorsSharedData": [] + }, + { + "b": 12, + "disableInputWhenNotFocused": true, + "mangledName": "Point_Lights", + "name": "Point Lights", + "r": 6, + "standardSortMethod": true, + "stopSoundsOnStartup": true, + "title": "", + "v": 7, + "uiSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 32, + "gridHeight": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": true, + "zoomFactor": 0.35, + "windowMask": false + }, + "objectsGroups": [], + "variables": [], + "instances": [ + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 20, + "height": 2400, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Ground", + "persistentUuid": "21d1f279-4719-1f6a-cbbb-722acfb1f6de", + "width": 2400, + "x": -1200, + "y": -1200, + "z": -20, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 460, + "height": 110, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Pillar", + "persistentUuid": "1e06c145-943f-f5a1-f990-12cdaa0825b5", + "width": 110, + "x": -55, + "y": -55, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 320, + "height": 60, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Backdrop", + "persistentUuid": "b5bb5390-66f6-b9c4-1e41-87e5df707d65", + "width": 900, + "x": -450, + "y": -900, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "1140e6b9-97f9-67c3-4213-3ec20f479dc8", + "width": 140, + "x": -520, + "y": -420, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "1feaa7a2-bd77-4fae-7133-7c976038df6c", + "width": 140, + "x": 380, + "y": -480, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "dd956ced-4997-5ff3-a7b8-1293e572377b", + "width": 140, + "x": -600, + "y": 320, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "97993a28-b8fc-b200-aa00-112a1d8816dd", + "width": 140, + "x": 460, + "y": 300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "82992fdc-febb-527b-8ca2-32dd3bd57df4", + "width": 140, + "x": -80, + "y": -640, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "b675e5ea-db07-5902-72fe-4fd5f66e2190", + "width": 140, + "x": -60, + "y": 480, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "PointLightWarm", + "persistentUuid": "95051515-fb64-4ca5-6b63-80a275dc630a", + "width": 0, + "x": -500, + "y": -300, + "z": 180, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "PointLightCool", + "persistentUuid": "14004069-1b93-12bc-cfe0-9a63856a1578", + "width": 0, + "x": 480, + "y": -320, + "z": 180, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "PointLightMagenta", + "persistentUuid": "08a2515f-7cc8-93e4-7f3d-271a1e6a60e0", + "width": 0, + "x": -40, + "y": 420, + "z": 180, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "objects": [ + { + "assetStoreId": "", + "name": "Ground", + "persistentUuid": "e46f1434-2c55-2bca-2ccf-742ed8e26b67", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 2400, + "height": 2400, + "depth": 20, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "170;175;180", + "isCastingShadow": false, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "Crate", + "persistentUuid": "df5c6f4b-aedc-6655-8ef5-10c3d9b3294a", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 140, + "height": 140, + "depth": 140, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "196;146;92", + "isCastingShadow": true, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "Pillar", + "persistentUuid": "bcda4ad2-b8bf-a7ee-c150-3cb4ff8649d2", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 110, + "height": 110, + "depth": 460, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "205;208;212", + "isCastingShadow": true, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "Backdrop", + "persistentUuid": "405800cc-89d5-f501-5a8d-23977b035d4d", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 900, + "height": 60, + "depth": 320, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "150;154;162", + "isCastingShadow": true, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "PointLightWarm", + "persistentUuid": "c5016e19-f69f-1ed8-e1c8-ded2f9101305", + "type": "Light3D::PointLight3D", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Color": "255;140;70", + "Intensity": 2.2, + "Decay": 2, + "IsCastingShadow": true, + "ShadowQuality": "Medium", + "ShadowCameraNearPlane": 20, + "ShadowCameraFarPlane": 2000, + "Range": 1400, + "ShadowBias": 0 + } + }, + { + "assetStoreId": "", + "name": "PointLightCool", + "persistentUuid": "8dc7336a-006c-23e0-f388-b946f5803983", + "type": "Light3D::PointLight3D", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Color": "90;190;255", + "Intensity": 2.2, + "Decay": 2, + "IsCastingShadow": true, + "ShadowQuality": "Medium", + "ShadowCameraNearPlane": 20, + "ShadowCameraFarPlane": 2000, + "Range": 1400, + "ShadowBias": 0 + } + }, + { + "assetStoreId": "", + "name": "PointLightMagenta", + "persistentUuid": "e33287e0-e515-f368-29c2-c6fc3edb11c7", + "type": "Light3D::PointLight3D", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Color": "255;100;200", + "Intensity": 2.2, + "Decay": 2, + "IsCastingShadow": true, + "ShadowQuality": "Medium", + "ShadowCameraNearPlane": 20, + "ShadowCameraFarPlane": 2000, + "Range": 1400, + "ShadowBias": 0 + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Ground" + }, + { + "objectName": "Crate" + }, + { + "objectName": "Pillar" + }, + { + "objectName": "Backdrop" + }, + { + "objectName": "PointLightWarm" + }, + { + "objectName": "PointLightCool" + }, + { + "objectName": "PointLightMagenta" + } + ] + }, + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "// Frame the props the same way in every scene and on every run, so the", + "// screenshots the gameplay tests take can be compared between versions.", + "if (!runtimeScene.__cameraPlaced) {", + " runtimeScene.__cameraPlaced = true;", + " gdjs.evtTools.camera.setCameraX(runtimeScene, 0, '', 0);", + " gdjs.evtTools.camera.setCameraY(runtimeScene, 1450, '', 0);", + " gdjs.scene3d.camera.setCameraZ(runtimeScene, 880, '', 0);", + " gdjs.scene3d.camera.setCameraRotationX(runtimeScene, 59, '', 0);", + " gdjs.scene3d.camera.setCameraRotationY(runtimeScene, 0, '', 0);", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "layers": [ + { + "ambientLightColorR": 14, + "ambientLightColorG": 16, + "ambientLightColorB": 24, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [ + { + "effectType": "Scene3D::AmbientLight", + "name": "Ambient light", + "doubleParameters": { + "intensity": 0.05 + }, + "stringParameters": { + "color": "255;255;255" + }, + "booleanParameters": {} + } + ] + } + ], + "behaviorsSharedData": [] + }, + { + "b": 12, + "disableInputWhenNotFocused": true, + "mangledName": "Spot_Lights", + "name": "Spot Lights", + "r": 6, + "standardSortMethod": true, + "stopSoundsOnStartup": true, + "title": "", + "v": 7, + "uiSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 32, + "gridHeight": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": true, + "zoomFactor": 0.35, + "windowMask": false + }, + "objectsGroups": [], + "variables": [], + "instances": [ + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 20, + "height": 2400, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Ground", + "persistentUuid": "ed514924-d2e4-5b2d-c862-19a99c1db74b", + "width": 2400, + "x": -1200, + "y": -1200, + "z": -20, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 460, + "height": 110, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Pillar", + "persistentUuid": "49a5c3e6-d6e6-bfb4-d8fe-6b193c5cef0c", + "width": 110, + "x": -55, + "y": -55, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 320, + "height": 60, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Backdrop", + "persistentUuid": "5e510294-7b95-b338-1670-2ceba2955432", + "width": 900, + "x": -450, + "y": -900, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "2b92439c-dea7-1970-fb7e-3227a8cae86c", + "width": 140, + "x": -520, + "y": -420, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "730511a8-9565-22bb-db96-51698cd16cda", + "width": 140, + "x": 380, + "y": -480, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "afa68705-e9c9-4f8f-b8ed-471122f3e33f", + "width": 140, + "x": -600, + "y": 320, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "88cc375e-a0fd-7e69-7764-ce5823d83558", + "width": 140, + "x": 460, + "y": 300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "9aa9982a-7ab0-b8e1-86c1-740ec8926dcb", + "width": 140, + "x": -80, + "y": -640, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "2f0fccc2-50a8-95be-f958-80b02a9b87e0", + "width": 140, + "x": -60, + "y": 480, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 90, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "SpotLightWarm", + "persistentUuid": "2dab3063-cac1-5782-6165-e23ffc4a7530", + "width": 0, + "x": -450, + "y": -250, + "z": 620, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 90, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "SpotLightCool", + "persistentUuid": "04800480-b81c-1c1d-ce15-518cb3a1cc45", + "width": 0, + "x": 450, + "y": -250, + "z": 620, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "objects": [ + { + "assetStoreId": "", + "name": "Ground", + "persistentUuid": "e46f1434-2c55-2bca-2ccf-742ed8e26b67", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 2400, + "height": 2400, + "depth": 20, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "170;175;180", + "isCastingShadow": false, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "Crate", + "persistentUuid": "df5c6f4b-aedc-6655-8ef5-10c3d9b3294a", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 140, + "height": 140, + "depth": 140, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "196;146;92", + "isCastingShadow": true, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "Pillar", + "persistentUuid": "bcda4ad2-b8bf-a7ee-c150-3cb4ff8649d2", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 110, + "height": 110, + "depth": 460, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "205;208;212", + "isCastingShadow": true, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "Backdrop", + "persistentUuid": "405800cc-89d5-f501-5a8d-23977b035d4d", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 900, + "height": 60, + "depth": 320, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "150;154;162", + "isCastingShadow": true, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "SpotLightWarm", + "persistentUuid": "85ffde62-c943-b149-de4d-69dc43d73aee", + "type": "Light3D::SpotLight3D", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Color": "255;236;180", + "ConeAngle": 34, + "Intensity": 14, + "Smoothing": 1, + "Decay": 2, + "Image": "", + "IsCastingShadow": true, + "ShadowQuality": "Medium", + "ShadowCameraNearPlane": 20, + "ShadowCameraFarPlane": 2000, + "ConeLength": 2000, + "ShadowBias": 0 + } + }, + { + "assetStoreId": "", + "name": "SpotLightCool", + "persistentUuid": "434cde1f-be0b-f340-da83-67b1cb0619aa", + "type": "Light3D::SpotLight3D", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Color": "180;220;255", + "ConeAngle": 34, + "Intensity": 14, + "Smoothing": 1, + "Decay": 2, + "Image": "", + "IsCastingShadow": true, + "ShadowQuality": "Medium", + "ShadowCameraNearPlane": 20, + "ShadowCameraFarPlane": 2000, + "ConeLength": 2000, + "ShadowBias": 0 + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Ground" + }, + { + "objectName": "Crate" + }, + { + "objectName": "Pillar" + }, + { + "objectName": "Backdrop" + }, + { + "objectName": "SpotLightWarm" + }, + { + "objectName": "SpotLightCool" + } + ] + }, + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "// Frame the props the same way in every scene and on every run, so the", + "// screenshots the gameplay tests take can be compared between versions.", + "if (!runtimeScene.__cameraPlaced) {", + " runtimeScene.__cameraPlaced = true;", + " gdjs.evtTools.camera.setCameraX(runtimeScene, 0, '', 0);", + " gdjs.evtTools.camera.setCameraY(runtimeScene, 1450, '', 0);", + " gdjs.scene3d.camera.setCameraZ(runtimeScene, 880, '', 0);", + " gdjs.scene3d.camera.setCameraRotationX(runtimeScene, 59, '', 0);", + " gdjs.scene3d.camera.setCameraRotationY(runtimeScene, 0, '', 0);", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "layers": [ + { + "ambientLightColorR": 14, + "ambientLightColorG": 16, + "ambientLightColorB": 24, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [ + { + "effectType": "Scene3D::AmbientLight", + "name": "Ambient light", + "doubleParameters": { + "intensity": 0.05 + }, + "stringParameters": { + "color": "255;255;255" + }, + "booleanParameters": {} + } + ] + } + ], + "behaviorsSharedData": [] + }, + { + "b": 10, + "disableInputWhenNotFocused": true, + "mangledName": "Many_Lights", + "name": "Many Lights", + "r": 5, + "standardSortMethod": true, + "stopSoundsOnStartup": true, + "title": "", + "v": 6, + "uiSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 32, + "gridHeight": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": true, + "zoomFactor": 0.35, + "windowMask": false + }, + "objectsGroups": [], + "variables": [], + "instances": [ + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 20, + "height": 2400, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Ground", + "persistentUuid": "6697f33c-05c5-ea0e-56ea-dd7b9637f87b", + "width": 2400, + "x": -1200, + "y": -1200, + "z": -20, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 460, + "height": 110, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Pillar", + "persistentUuid": "22dfa7b3-d332-db2e-6f60-a5f0c98ad1cf", + "width": 110, + "x": -55, + "y": -55, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 320, + "height": 60, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Backdrop", + "persistentUuid": "e875554f-6f48-045f-77dd-e007ec681281", + "width": 900, + "x": -450, + "y": -900, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "fb74f53b-2e94-d59d-a8b4-b317842a75d8", + "width": 140, + "x": -520, + "y": -420, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "b67acebc-c8ac-e50f-c6f5-2017cd8d2107", + "width": 140, + "x": 380, + "y": -480, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "499db8ef-1794-f035-b870-0fbe178a8d40", + "width": 140, + "x": -600, + "y": 320, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "fc9913d1-5edb-39ee-d907-075d3f622cfe", + "width": 140, + "x": 460, + "y": 300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "e08f7f62-9efd-2c0b-a0c2-9785ba5d2b37", + "width": 140, + "x": -80, + "y": -640, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "eec06225-61ec-4647-c12b-ae6b1f1b98a9", + "width": 140, + "x": -60, + "y": 480, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightShadow", + "persistentUuid": "d976a244-aadd-5742-bba9-d35ab2568465", + "width": 0, + "x": 340, + "y": 0, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightShadow", + "persistentUuid": "ab1970e9-5eb4-3852-b150-26d552a2d3f3", + "width": 0, + "x": 240, + "y": 240, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightShadow", + "persistentUuid": "049feb8a-a32e-1d93-a9f7-dc1a6965d462", + "width": 0, + "x": 0, + "y": 340, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightShadow", + "persistentUuid": "34214cda-67bc-c49b-4f68-11d1aae4bb51", + "width": 0, + "x": -240, + "y": 240, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightShadow", + "persistentUuid": "591288f4-88f2-350f-f301-f579e673a03c", + "width": 0, + "x": -340, + "y": 0, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightShadow", + "persistentUuid": "94ff4de1-22c7-0196-d87b-71cc119aff4c", + "width": 0, + "x": -240, + "y": -240, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightWarm", + "persistentUuid": "e0e9e274-4cc1-3019-a049-267e669c7d64", + "width": 0, + "x": 0, + "y": -340, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightCool", + "persistentUuid": "ebf66795-7cf2-bdf4-23d7-79400bba5e10", + "width": 0, + "x": 240, + "y": -240, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightWarm", + "persistentUuid": "9a702679-4bc8-5b8f-9e12-14726f321298", + "width": 0, + "x": 630, + "y": 0, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightCool", + "persistentUuid": "244be702-6b6a-8a42-a284-f7164d8f8b9b", + "width": 0, + "x": 445, + "y": 445, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightWarm", + "persistentUuid": "1392a405-3592-c7ea-b7c2-55b538b9a0d7", + "width": 0, + "x": 0, + "y": 630, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightCool", + "persistentUuid": "a980b446-1fc5-7c4a-85d7-8a2b01912fab", + "width": 0, + "x": -445, + "y": 445, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightWarm", + "persistentUuid": "23502fcf-1c47-165a-ec9b-f1060770a131", + "width": 0, + "x": -630, + "y": 0, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightCool", + "persistentUuid": "ceb10a30-edd9-540c-5f36-0c6e30f249cf", + "width": 0, + "x": -445, + "y": -445, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightWarm", + "persistentUuid": "d1d1da37-9c09-a782-3b97-44dea2cb51d0", + "width": 0, + "x": 0, + "y": -630, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightCool", + "persistentUuid": "d71e3361-7ee3-c6ac-efad-bd4053dbd4e5", + "width": 0, + "x": 445, + "y": -445, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightWarm", + "persistentUuid": "9c35be70-c8e7-2aa3-2d9f-e980b98a31d5", + "width": 0, + "x": 920, + "y": 0, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightCool", + "persistentUuid": "b53c7850-2186-23f2-eaae-05abd0497c35", + "width": 0, + "x": 650, + "y": 650, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightWarm", + "persistentUuid": "7d1e615d-ce68-8d3f-dfd9-7b6369a79d3c", + "width": 0, + "x": 0, + "y": 920, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightCool", + "persistentUuid": "421cb74f-3386-8ef1-9c65-d043d302e8cb", + "width": 0, + "x": -650, + "y": 650, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightWarm", + "persistentUuid": "d29b0ff9-1509-feb8-e22a-578506534d01", + "width": 0, + "x": -920, + "y": 0, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightCool", + "persistentUuid": "e60591b7-6b99-e7f7-845d-cf586a028a6e", + "width": 0, + "x": -650, + "y": -650, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightWarm", + "persistentUuid": "0d7a9528-5bac-d515-6028-070055d67a6b", + "width": 0, + "x": 0, + "y": -920, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "ManyLightCool", + "persistentUuid": "cd93fb9c-82ec-8285-13a7-d9ecb08470d9", + "width": 0, + "x": 650, + "y": -650, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "objects": [ + { + "assetStoreId": "", + "name": "Ground", + "persistentUuid": "e46f1434-2c55-2bca-2ccf-742ed8e26b67", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 2400, + "height": 2400, + "depth": 20, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "170;175;180", + "isCastingShadow": false, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "Crate", + "persistentUuid": "df5c6f4b-aedc-6655-8ef5-10c3d9b3294a", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 140, + "height": 140, + "depth": 140, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "196;146;92", + "isCastingShadow": true, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "Pillar", + "persistentUuid": "bcda4ad2-b8bf-a7ee-c150-3cb4ff8649d2", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 110, + "height": 110, + "depth": 460, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "205;208;212", + "isCastingShadow": true, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "Backdrop", + "persistentUuid": "405800cc-89d5-f501-5a8d-23977b035d4d", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 900, + "height": 60, + "depth": 320, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "150;154;162", + "isCastingShadow": true, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "ManyLightShadow", + "persistentUuid": "d9b5850c-4ba6-6924-bb12-3a71d641af26", + "type": "Light3D::PointLight3D", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Color": "255;190;120", + "Intensity": 1.6, + "Decay": 2, + "IsCastingShadow": true, + "ShadowQuality": "Medium", + "ShadowCameraNearPlane": 20, + "ShadowCameraFarPlane": 2000, + "Range": 800, + "ShadowBias": 0 + } + }, + { + "assetStoreId": "", + "name": "ManyLightWarm", + "persistentUuid": "3f5d7912-6537-ef1f-fe3d-b050d6dbd394", + "type": "Light3D::PointLight3D", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Color": "255;120;80", + "Intensity": 1.4, + "Decay": 2, + "IsCastingShadow": false, + "ShadowQuality": "Medium", + "ShadowCameraNearPlane": 20, + "ShadowCameraFarPlane": 2000, + "Range": 700, + "ShadowBias": 0 + } + }, + { + "assetStoreId": "", + "name": "ManyLightCool", + "persistentUuid": "a13deb4c-0901-17be-ca1a-d875b1e16807", + "type": "Light3D::PointLight3D", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Color": "110;200;255", + "Intensity": 1.4, + "Decay": 2, + "IsCastingShadow": false, + "ShadowQuality": "Medium", + "ShadowCameraNearPlane": 20, + "ShadowCameraFarPlane": 2000, + "Range": 700, + "ShadowBias": 0 + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Ground" + }, + { + "objectName": "Crate" + }, + { + "objectName": "Pillar" + }, + { + "objectName": "Backdrop" + }, + { + "objectName": "ManyLightShadow" + }, + { + "objectName": "ManyLightWarm" + }, + { + "objectName": "ManyLightCool" + } + ] + }, + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "// Frame the props the same way in every scene and on every run, so the", + "// screenshots the gameplay tests take can be compared between versions.", + "if (!runtimeScene.__cameraPlaced) {", + " runtimeScene.__cameraPlaced = true;", + " gdjs.evtTools.camera.setCameraX(runtimeScene, 0, '', 0);", + " gdjs.evtTools.camera.setCameraY(runtimeScene, 1450, '', 0);", + " gdjs.scene3d.camera.setCameraZ(runtimeScene, 880, '', 0);", + " gdjs.scene3d.camera.setCameraRotationX(runtimeScene, 59, '', 0);", + " gdjs.scene3d.camera.setCameraRotationY(runtimeScene, 0, '', 0);", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "layers": [ + { + "ambientLightColorR": 12, + "ambientLightColorG": 14, + "ambientLightColorB": 20, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [ + { + "effectType": "Scene3D::AmbientLight", + "name": "Ambient light", + "doubleParameters": { + "intensity": 0.05 + }, + "stringParameters": { + "color": "255;255;255" + }, + "booleanParameters": {} + } + ] + } + ], + "behaviorsSharedData": [] + } + ], + "externalEvents": [], + "externalLayouts": [], + "eventsFunctionsExtensions": [ + { + "author": "", + "category": "Visual effect", + "dimension": "3D", + "extensionNamespace": "", + "fullName": "3D lights", + "gdevelopVersion": ">=5.5.222", + "helpPath": "/objects/3d-light", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLWxpZ2h0YnVsYi1vbi1vdXRsaW5lIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTIwLDExSDIzVjEzSDIwVjExTTEsMTFINFYxM0gxVjExTTEzLDFWNEgxMVYxSDEzTTQuOTIsMy41TDcuMDUsNS42NEw1LjYzLDcuMDVMMy41LDQuOTNMNC45MiwzLjVNMTYuOTUsNS42M0wxOS4wNywzLjVMMjAuNSw0LjkzTDE4LjM3LDcuMDVMMTYuOTUsNS42M00xMiw2QTYsNiAwIDAsMSAxOCwxMkMxOCwxNC4yMiAxNi43OSwxNi4xNiAxNSwxNy4yVjE5QTEsMSAwIDAsMSAxNCwyMEgxMEExLDEgMCAwLDEgOSwxOVYxNy4yQzcuMjEsMTYuMTYgNiwxNC4yMiA2LDEyQTYsNiAwIDAsMSAxMiw2TTE0LDIxVjIyQTEsMSAwIDAsMSAxMywyM0gxMUExLDEgMCAwLDEgMTAsMjJWMjFIMTRNMTEsMThIMTNWMTUuODdDMTQuNzMsMTUuNDMgMTYsMTMuODYgMTYsMTJBNCw0IDAgMCwwIDEyLDhBNCw0IDAgMCwwIDgsMTJDOCwxMy44NiA5LjI3LDE1LjQzIDExLDE1Ljg3VjE4WiIgLz48L3N2Zz4=", + "name": "Light3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/f237ae4e3b857c556846c7b2c0b132556fd4bcdeff217034b4d9c97dc1aab1d6_lightbulb-on-outline.svg", + "shortDescription": "A collection of light object for 3D.", + "version": "1.2.1", + "description": "A collection of light object for 3D.", + "origin": { + "identifier": "Light3D", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "light" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "description": "Define spot light helper classes JavaScript code.", + "fullName": "Define spot light helper classes", + "functionType": "Action", + "name": "DefineSpotLightHelperClasses", + "private": true, + "sentence": "Define spot light helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "//@ts-ignore", + "if (gdjs.__light3DExtension.spot) {", + " //@ts-ignore", + " return;", + "}", + "", + "/**", + " * @typedef {gdjs.CustomRuntimeObject3D & {__light3D: THREE.SpotLight, _getIsCastingShadow: () => boolean, _getConeAngle: () => number, _getColor: () => string}} SpotLightRuntimeObject", + " */", + "", + "const game = runtimeScene.getGame();", + "", + "const coneLength = 64;", + "", + "class SpotLightHelper extends THREE.Object3D {", + " /** @type {SpotLightRuntimeObject} */", + " object;", + " /** @type {THREE.LineSegments} */", + " cone;", + " /** @type {THREE.LineSegments} */", + " centerLine;", + " /** @type {THREE.Mesh} */", + " originBox;", + "", + "\t/**", + " * @param {SpotLightRuntimeObject} object", + "\t */", + " constructor(object) {", + " super();", + " this.object = object;", + " this.type = 'SpotLightHelper';", + "", + " const coneGeometry = new THREE.BufferGeometry();", + " const conePositions = [", + " 0, 0, 0, 1, 0, 0,", + " 0, 0, 0, 1, 1, 0,", + " 0, 0, 0, 1, - 1, 0,", + " 0, 0, 0, 1, 0, 1,", + " 0, 0, 0, 1, 0, - 1", + " ];", + " for (let i = 0, j = 1, l = 32; i < l; i++ , j++) {", + " const p1 = (i / l) * Math.PI * 2;", + " const p2 = (j / l) * Math.PI * 2;", + " conePositions.push(", + " 1, Math.cos(p1), Math.sin(p1),", + " 1, Math.cos(p2), Math.sin(p2)", + " );", + " }", + " coneGeometry.setAttribute('position', new THREE.Float32BufferAttribute(conePositions, 3));", + " this.cone = new THREE.LineSegments(", + " coneGeometry,", + " new THREE.LineBasicMaterial({ fog: false, toneMapped: false })", + " );", + " this.add(this.cone);", + " const centerLineGeometry = new THREE.BufferGeometry();", + " centerLineGeometry.setAttribute('position', new THREE.Float32BufferAttribute([", + " 0, 0, 0, coneLength, 0, 0", + " ], 3));", + " this.centerLine = new THREE.LineSegments(", + " centerLineGeometry,", + " new THREE.LineBasicMaterial({ fog: false, toneMapped: false })", + " );", + " this.add(this.centerLine);", + "", + " this.originBox = new THREE.Mesh(", + " new THREE.BoxGeometry(16, 16, 16),", + " new THREE.MeshBasicMaterial({ fog: false, toneMapped: false })", + " );", + " this.add(this.originBox);", + " this.update();", + "", + " //@ts-ignore", + " this.originBox.gdjsRuntimeObject = object;", + " }", + "", + " dispose() {", + " this.cone.geometry.dispose();", + " this.cone.material.dispose();", + " this.centerLine.geometry.dispose();", + " this.centerLine.material.dispose();", + " this.originBox.geometry.dispose();", + " this.originBox.material.dispose();", + " }", + "", + " update() {", + " /** @type {number} */", + " const coneAngle = gdjs.toRad(this.object._getConeAngle());", + " const coneWidth = coneLength * Math.sin(coneAngle);", + " const coneHeight = coneLength * Math.cos(coneAngle);", + " this.cone.scale.set(coneHeight, coneWidth, coneWidth);", + " const color = gdjs.rgbOrHexStringToNumber(this.object._getColor());", + " this.cone.material.color.set(color);", + " this.centerLine.material.color.set(color);", + " this.originBox.material.color.set(color);", + " }", + "}", + "", + "/**", + " * @param {string} colorString", + " * @param {THREE.Color} threeColor", + " */", + "const setThreeColor = (colorString, threeColor) => {", + " const integerColor = gdjs.rgbOrHexToRGBColor(colorString);", + " threeColor.r = integerColor[0] / 255;", + " threeColor.g = integerColor[1] / 255;", + " threeColor.b = integerColor[2] / 255;", + "};", + "", + "class SpotLightAdapter {", + " /**", + " * @param object {gdjs.CustomRuntimeObject3D}", + " * @param spotLight {THREE.SpotLight}", + " */", + " constructor(object, spotLight) {", + " this.object = object;", + " this.spotLight = spotLight;", + " }", + "", + " /**", + " * @param targetX {number}", + " * @param targetY {number}", + " * @param targetZ {number}", + " */", + " lookAtPosition(targetX, targetY, targetZ) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + "", + " // Remove from the parent to avoid the scene scale of -1 on Y to mess with the formula.", + " const parent = this.spotLight.parent;", + " this.spotLight.parent = null;", + " this.spotLight.lookAt(", + " targetX * inverseWorldScale,", + " targetY * inverseWorldScale,", + " targetZ * inverseWorldScale);", + " this.spotLight.parent = parent;", + "", + " // Angle setters update Three.js angles, so we save them first.", + " const rotationX = gdjs.toDegrees(this.spotLight.rotation.x);", + " const rotationY = gdjs.toDegrees(this.spotLight.rotation.y);", + " const rotationZ = gdjs.toDegrees(this.spotLight.rotation.z);", + " this.object.setRotationX(rotationX);", + " this.object.setRotationY(rotationY);", + " this.object.setAngle(rotationZ + 90);", + " }", + "", + " /**", + " * @param color {string}", + " */", + " setColor(color) {", + " setThreeColor(color, this.spotLight.color);", + " }", + "", + " /**", + " * @param intensity {number}", + " */", + " setIntensity(intensity) {", + " this.spotLight.intensity = intensity;", + " }", + "", + " /**", + " * @param decay {number}", + " */", + " setDecay(decay) {", + " this.spotLight.decay = decay;", + " }", + "", + " /**", + " * @param value {number}", + " */", + " setConeLength(value) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + " this.spotLight.distance = value * inverseWorldScale;", + " }", + "", + " /**", + " * @param coneAngle {number}", + " */", + " setConeAngle(coneAngle) {", + " this.spotLight.angle = coneAngle * Math.PI / 180;", + " }", + "", + " /**", + " * @param smoothing {number}", + " */", + " setSmoothing(smoothing) {", + " this.spotLight.penumbra = smoothing;", + " }", + "", + " /**", + " * @param isCastingShadow {boolean}", + " */", + " setCastingShadow(isCastingShadow) {", + " // This is applied by applyVisibilityAndShadow", + " }", + "", + " /**", + " * @param shadowQuality {\"Low\" | \"Medium\" | \"High\" | \"VeryHigh\"}", + " */", + " setShadowQuality(shadowQuality) {", + " let size = 512;", + " switch (shadowQuality) {", + " case \"Low\":", + " size = 256;", + " break;", + " case \"Medium\":", + " size = 512;", + " break;", + " case \"High\":", + " size = 1024;", + " break;", + " case \"VeryHigh\":", + " size = 2048;", + " break;", + " }", + " const shadow = this.spotLight.shadow;", + " const mapSize = shadow.mapSize;", + " if (mapSize.width !== size) {", + " mapSize.set(size, size);", + "", + " // Force the recreation of the shadow map texture:", + " if (shadow.map) {", + " shadow.map.dispose();", + " shadow.map = null;", + " }", + " shadow.needsUpdate = true;", + " }", + " }", + "", + " /**", + " * @param value {number}", + " */", + " setShadowCameraNearPlane(value) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + " this.spotLight.shadow.camera.near = value * inverseWorldScale;", + " }", + "", + " /**", + " * @param value {number}", + " */", + " setShadowCameraFarPlane(value) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + " this.spotLight.shadow.camera.far = value * inverseWorldScale;", + " }", + "", + " /**", + " * @param value {number}", + " * @param quality {\"Low\" | \"Medium\" | \"High\" | \"VeryHigh\"}", + " * @param coneAngle {number}", + " */", + " setShadowBias(value, quality, coneAngle) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + " const biasMultiplier =", + " quality === 'Low'", + " ? 8", + " : quality === 'Medium'", + " ? 4", + " : quality === 'High'", + " ? 2 : 1;", + " // Multiply by 0.125 to make values around 0.001 work best.", + " this.spotLight.shadow.bias = -value * 0.125 * biasMultiplier *", + " Math.tan(gdjs.toRad(gdjs.evtTools.common.clamp(coneAngle, 0, 89))) *", + " inverseWorldScale;", + " }", + "", + " /**", + " * @param resourceName {string}", + " */", + " setImage(resourceName) {", + " if (!resourceName) {", + " this.spotLight.map = null;", + " return;", + " }", + " const texture = game.getImageManager().getThreeTexture(resourceName);", + " this.spotLight.map = texture;", + " }", + "}", + "", + "//@ts-ignore", + "gdjs.__light3DExtension.spot = {", + " SpotLightAdapter,", + " SpotLightHelper,", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Define point light helper classes JavaScript code.", + "fullName": "Define point light helper classes", + "functionType": "Action", + "name": "DefinePointLightHelperClasses", + "private": true, + "sentence": "Define point light helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "//@ts-ignore", + "if (gdjs.__light3DExtension.point) {", + " //@ts-ignore", + " return;", + "}", + "", + "/**", + " * @typedef {gdjs.CustomRuntimeObject3D & {__light3D: THREE.PointLight, _getIsCastingShadow: () => boolean, _getColor: () => string}} PointLightRuntimeObject", + " */", + "", + "const game = runtimeScene.getGame();", + "", + "/**", + " * @extends {THREE.Mesh}", + " */", + "class PointLightHelper extends THREE.Mesh {", + " /** @type {PointLightRuntimeObject} */", + " object;", + "", + "\t/**", + " * @param {PointLightRuntimeObject} object", + "\t */", + " constructor(object) {", + " const geometry = new THREE.SphereGeometry(16, 4, 2);", + " const material = new THREE.MeshBasicMaterial({ wireframe: true, fog: false, toneMapped: false });", + " super(geometry, material);", + " this.object = object;", + " this.update();", + "", + " //@ts-ignore", + " this.gdjsRuntimeObject = object;", + " }", + "", + " dispose() {", + " this.geometry.dispose();", + " this.material.dispose();", + " }", + "", + " update() {", + " const color = gdjs.rgbOrHexStringToNumber(this.object._getColor());", + " this.material.color.set(color);", + " }", + "}", + "", + "/**", + " * @param {string} colorString", + " * @param {THREE.Color} threeColor", + " */", + "const setThreeColor = (colorString, threeColor) => {", + " const integerColor = gdjs.rgbOrHexToRGBColor(colorString);", + " threeColor.r = integerColor[0] / 255;", + " threeColor.g = integerColor[1] / 255;", + " threeColor.b = integerColor[2] / 255;", + "};", + "", + "class PointLightAdapter {", + " /**", + " * @param object {gdjs.CustomRuntimeObject3D}", + " * @param pointLight {THREE.PointLight}", + " */", + " constructor(object, pointLight) {", + " this.object = object;", + " this.pointLight = pointLight;", + " }", + "", + " /**", + " * @param color {string}", + " */", + " setColor(color) {", + " setThreeColor(color, this.pointLight.color);", + " }", + "", + " /**", + " * @param intensity {number}", + " */", + " setIntensity(intensity) {", + " this.pointLight.intensity = intensity;", + " }", + "", + " /**", + " * @param decay {number}", + " */", + " setDecay(decay) {", + " this.pointLight.decay = decay;", + " }", + "", + " /**", + " * @param value {number}", + " */", + " setRange(value) {", + " this.pointLight.distance = value;", + " }", + "", + " /**", + " * @param isCastingShadow {boolean}", + " */", + " setCastingShadow(isCastingShadow) {", + " // This is applied by applyVisibilityAndShadow", + " }", + "", + " /**", + " * @param shadowQuality {\"Low\" | \"Medium\" | \"High\"}", + " */", + " setShadowQuality(shadowQuality) {", + " let size = 512;", + " switch (shadowQuality) {", + " case \"Low\":", + " size = 512;", + " break;", + " case \"Medium\":", + " size = 1024;", + " break;", + " case \"High\":", + " size = 2048;", + " break;", + " }", + " const shadow = this.pointLight.shadow;", + " const mapSize = shadow.mapSize;", + " if (mapSize.width !== size) {", + " mapSize.set(size, size);", + "", + " // Force the recreation of the shadow map texture:", + " if (shadow.map) {", + " shadow.map.dispose();", + " shadow.map = null;", + " }", + " shadow.needsUpdate = true;", + " }", + " }", + "", + " /**", + " * @param value {number}", + " */", + " setShadowCameraNearPlane(value) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + "", + " this.pointLight.shadow.camera.near = value * inverseWorldScale;", + " }", + "", + " /**", + " * @param value {number}", + " */", + " setShadowCameraFarPlane(value) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + "", + " this.pointLight.shadow.camera.far = value * inverseWorldScale;", + " }", + "", + " /**", + " * @param value {number}", + " * @param quality {\"Low\" | \"Medium\" | \"High\"}", + " */", + " setShadowBias(value, quality) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + "", + " const biasMultiplier =", + " quality === 'Low'", + " ? 4", + " : quality === 'Medium'", + " ? 2", + " : 1;", + " this.pointLight.shadow.bias = -value * biasMultiplier * inverseWorldScale;", + " }", + "}", + "", + "//@ts-ignore", + "gdjs.__light3DExtension.point = {", + " PointLightAdapter,", + " PointLightHelper,", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Define helper classes JavaScript code.", + "fullName": "Define helper classes", + "functionType": "Action", + "name": "DefineHelperClasses", + "private": true, + "sentence": "Define helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "//@ts-ignore", + "if (gdjs.__light3DExtension) {", + " //@ts-ignore", + " return;", + "}", + "", + "/**", + " * @typedef {gdjs.CustomRuntimeObject3D & {__light3D: THREE.SpotLight | THREE.PointLight, _getIsCastingShadow: () => boolean}} LightRuntimeObject", + " */", + "", + "const game = runtimeScene.getGame();", + "const isInGameEdition = game.isInGameEdition && game.isInGameEdition();", + "const logger = new gdjs.Logger('3D lights');", + "", + "", + "class Light3DRenderer extends gdjs.CustomRuntimeObject3DRenderer {", + "", + " constructor(", + " object,", + " instanceContainer,", + " parent", + " ) {", + " super(object, instanceContainer, parent);", + " }", + "", + " _updateThreeGroup() {", + " /** @type {LightRuntimeObject} */", + " //@ts-ignore", + " const object = this._object;", + " const threeObject3D = this.get3DRendererObject();", + "", + " threeObject3D.rotation.set(", + " gdjs.toRad(object.getRotationX()),", + " gdjs.toRad(object.getRotationY()),", + " gdjs.toRad(object.angle)", + " );", + "", + " threeObject3D.position.set(", + " object.getX(),", + " object.getY(),", + " object.getZ()", + " );", + "", + " // Force the scale to 1 because the light doesn't really has a size.", + " threeObject3D.scale.set(", + " object.isFlippedX() ? -1 : 1,", + " object.isFlippedY() ? -1 : 1,", + " object.isFlippedZ() ? -1 : 1", + " );", + "", + " threeObject3D.visible = !this._object.hidden;", + "", + " const spotLight = object.__light3D;", + "", + " const editor = game.getInGameEditor ? game.getInGameEditor() : null;", + "", + " spotLight.castShadow = false;", + " spotLight.visible = false;", + "", + " if (!object.isHidden()) {", + " let isSelected = false;", + " if (editor) {", + " const selectedObject = editor._selection.getLastSelectedObject();", + " let parentObject = object;", + " isSelected = parentObject === selectedObject;", + " while (!isSelected && parentObject.getInstanceContainer()", + " //@ts-ignore", + " .getOwner) {", + " parentObject = parentObject.getInstanceContainer()", + " //@ts-ignore", + " .getOwner();", + " isSelected = parentObject === selectedObject;", + " }", + " }", + " if (!editor || !editor.areEffectsHidden() || isSelected) {", + " let rootObject = object;", + " while (rootObject.getInstanceContainer()", + " //@ts-ignore", + " .getOwner) {", + " rootObject = rootObject.getInstanceContainer()", + " //@ts-ignore", + " .getOwner();", + " }", + " const runtimeScene = rootObject.getRuntimeScene();", + " let distanceSq = 0;", + " if (!isSelected) {", + " const layerName = rootObject.getLayer();", + " const cameraX = gdjs.evtTools.camera.getCameraX(runtimeScene, layerName, 0);", + " const cameraY = gdjs.evtTools.camera.getCameraY(runtimeScene, layerName, 0);", + " const cameraZ = gdjs.scene3d.camera.getCameraZ(runtimeScene, layerName, 0);", + " const deltaX = rootObject.getX() - cameraX;", + " const deltaY = rootObject.getY() - cameraY;", + " const deltaZ = rootObject.getZ() - cameraZ;", + " distanceSq = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;", + " }", + " getLightManager(", + " //@ts-ignore", + " runtimeScene", + " ).applyVisibilityAndShadow(object, distanceSq);", + " }", + " }", + "", + " // Force the visibility to be checked every frame", + " this._isContainerDirty = true;", + " }", + "}", + "", + "const editorLightCountMax = {", + " value: 20", + "};", + "", + "const lightCountMax = {", + " value: 20", + "};", + "", + "const editorLightShadowCountMax = {", + " value: 4", + "};", + "", + "const lightShadowCountMax = {", + " value: 4", + "};", + "", + "gdjs.registerRuntimeScenePreEventsCallback(", + " runtimeScene => {", + " getLightManager(", + " //@ts-ignore", + " runtimeScene.getScene()", + " ).clear();", + " });", + "if (gdjs.registerInGameEditorPostStepCallback) {", + " gdjs.registerInGameEditorPostStepCallback(", + " inGameEditor => {", + " getLightManager(", + " //@ts-ignore", + " inGameEditor.getEditedInstanceContainer().getScene()", + " ).clear();", + " });", + "}", + "", + "/**", + " * How many lights of each kind this device can take.", + " *", + " * These are hard limits rather than performance trade-offs. three.js binds one", + " * texture unit per shadow map and one more for a spot light's projected", + " * texture, and spends a varying on every shadow casting light. Going over what", + " * the driver allows does not make the scene slower - the shader fails to link", + " * and everything using that material renders black. Only 16 fragment texture", + " * units and 16 varyings are guaranteed across devices, and materials need most", + " * of those, so the budget is read from the device instead of being fixed.", + " *", + " * It is computed once, and applied per light kind, so the numbers three.js", + " * compiles into its shaders still never move from frame to frame.", + " *", + " * @param {THREE.WebGLRenderer | null} threeRenderer", + " * @returns {{shadowCount: number, mapCount: number, count: number}}", + " */", + "function computeDeviceLightBudget(threeRenderer) {", + " // When the renderer cannot be reached, assume the guaranteed minimums", + " // rather than the device this happens to be running on.", + " const capabilities = (threeRenderer && threeRenderer.capabilities) || {};", + " const maxTextures = capabilities.maxTextures || 16;", + " const maxVaryings = capabilities.maxVaryings || 16;", + " const maxFragmentUniforms = capabilities.maxFragmentUniforms || 224;", + "", + " // Left for the material itself: colour, normal, roughness, metalness,", + " // emissive, ambient occlusion and the environment map.", + " const textureUnitsForMaterials = 8;", + " // Varyings already spent on positions, normals, uvs and fog.", + " const varyingsForGeometry = 12;", + " // Uniform vectors already spent on matrices, material properties and the", + " // uv transforms of a material's own textures.", + " const uniformVectorsForMaterials = 64;", + " // Measured on a MeshStandardMaterial: a point light costs 4 uniform", + " // vectors, a spot light 7, and casting a shadow adds around 11 more on", + " // top of either. Take the dearest of them, since which kinds a scene", + " // uses is not known here.", + " const uniformVectorsPerLight = 8;", + " const uniformVectorsPerShadow = 12;", + "", + " // Point lights, spot lights and spot lights with a projected texture.", + " const kindCount = 3;", + "", + " const availableTextureUnits = Math.max(", + " kindCount, maxTextures - textureUnitsForMaterials);", + " const availableVaryings = Math.max(", + " kindCount, maxVaryings - varyingsForGeometry);", + " const availableUniformVectors = Math.max(", + " 16, maxFragmentUniforms - uniformVectorsForMaterials);", + "", + " // Every shadow caster costs a texture unit and a varying, whichever kind", + " // it is. Keep a share of the texture units back for projected textures.", + " const shadowCount = Math.max(0, Math.min(", + " Math.floor(availableTextureUnits / (kindCount + 1)),", + " Math.floor(availableVaryings / kindCount)", + " ));", + " // What is left bounds how many spot lights can project a texture. Lights", + " // that neither cast a shadow nor project a texture cost no texture unit.", + " const mapCount = Math.max(0,", + " availableTextureUnits - shadowCount * kindCount);", + " // Shadow casters are paid for first, then the uniform vectors left over", + " // decide how many lights of each kind can be lit at all.", + " const uniformVectorsForShadows =", + " shadowCount * kindCount * uniformVectorsPerShadow;", + " const count = Math.max(1, Math.floor(", + " Math.max(uniformVectorsPerLight * kindCount,", + " availableUniformVectors - uniformVectorsForShadows)", + " / (uniformVectorsPerLight * kindCount)));", + "", + " return { shadowCount, mapCount, count };", + "}", + "", + "/**", + " * A capacity that never goes past what the device can take, however high the", + " * game sets its own maximum. Read through a getter so that changing the", + " * game's maximum at runtime still takes effect.", + " *", + " * @param {{value: number}} requested", + " * @param {number} deviceMax", + " * @returns {{value: number}}", + " */", + "function makeDeviceCappedCapacity(requested, deviceMax) {", + " return {", + " get value() {", + " return Math.min(requested.value, deviceMax);", + " }", + " };", + "}", + "", + "/**", + " * Get the light manager of a scene.", + " * @param {gdjs.RuntimeScene & {__lightManager: LightManager}} runtimeScene", + " */", + "function getLightManager(runtimeScene) {", + " if (!runtimeScene.__lightManager) {", + " // Create the shared manager if necessary.", + " const threeRenderer = runtimeScene", + " .getGame()", + " .getRenderer()", + " .getThreeRenderer();", + " const budget = computeDeviceLightBudget(threeRenderer);", + " const requestedCount = isInGameEdition", + " ? editorLightCountMax : lightCountMax;", + " const requestedShadowCount = isInGameEdition", + " ? editorLightShadowCountMax : lightShadowCountMax;", + "", + " if (budget.shadowCount < requestedShadowCount.value", + " || budget.count < requestedCount.value) {", + " logger.info(", + " 'This device limits 3D lights to ' + budget.count", + " + ' per kind (' + budget.shadowCount", + " + ' casting a shadow, ' + budget.mapCount", + " + ' projecting a texture). Going over what it reports would '", + " + 'stop the shaders from linking.'", + " );", + " }", + "", + " runtimeScene.__lightManager = new LightManager(", + " requestedCount, requestedShadowCount, budget);", + " }", + " return runtimeScene.__lightManager;", + "}", + "", + "/**", + " * Lights kept sorted by their distance to the camera, capped to a fixed", + " * number of entries.", + " *", + " * The cap is a *count* and not a weighted cost, on purpose: three.js compiles", + " * the number of lights of each type into its shaders, so a list whose length", + " * changes from one frame to the next forces a shader recompilation every time", + " * a new length is seen. A count based cap keeps the length pinned to", + " * `capacity` for as long as enough lights are candidates.", + " */", + "class CappedLightList {", + " /** @type {Array<{object: LightRuntimeObject, sortDistance: number}>} */", + " objects = [];", + " /** @type {{value: number}} */", + " capacity;", + " /** @type {(object: LightRuntimeObject) => void} */", + " onInsertion;", + " /** @type {(object: LightRuntimeObject) => void} */", + " onDeletion;", + "", + " /**", + " * @param {{value: number}} capacity", + " * @param {(object: LightRuntimeObject) => void} onInsertion", + " * @param {(object: LightRuntimeObject) => void} onDeletion", + " */", + " constructor(capacity, onInsertion, onDeletion) {", + " this.capacity = capacity;", + " this.onInsertion = onInsertion;", + " this.onDeletion = onDeletion;", + " }", + "", + " clear() {", + " this.objects.length = 0;", + " }", + "", + " /**", + " * @param object {LightRuntimeObject}", + " * @param sortDistance {number} The squared distance to the camera, which", + " * more expensive lights can inflate to lose ties against cheaper ones.", + " */", + " insertByDistance(object, sortDistance) {", + " let insertionIndex = 0;", + " for (let index = this.objects.length - 1; index >= 0; index--) {", + " if (sortDistance >= this.objects[index].sortDistance) {", + " insertionIndex = index + 1;", + " break;", + " }", + " }", + " if (insertionIndex >= this.capacity.value) {", + " // Farther away than every light already filling the list.", + " return;", + " }", + "", + " // Make room first, reusing an evicted pair to avoid allocating one", + " // on every frame.", + " let pair = null;", + " while (this.objects.length >= this.capacity.value) {", + " pair = this.objects.pop();", + " this.onDeletion(pair.object);", + " }", + " if (pair) {", + " pair.object = object;", + " pair.sortDistance = sortDistance;", + " }", + " else {", + " pair = { object, sortDistance };", + " }", + " this.objects.splice(insertionIndex, 0, pair);", + " this.onInsertion(object);", + " }", + "}", + "", + "/** @param object {LightRuntimeObject} */", + "const showLight = (object) => { object.__light3D.visible = true; };", + "/** @param object {LightRuntimeObject} */", + "const hideLight = (object) => { object.__light3D.visible = false; };", + "/** @param object {LightRuntimeObject} */", + "const enableShadow = (object) => { object.__light3D.castShadow = true; };", + "/** @param object {LightRuntimeObject} */", + "const disableShadow = (object) => { object.__light3D.castShadow = false; };", + "", + "/**", + " * The lights of a scene, budgeted so that the numbers three.js compiles into", + " * its shaders stay put.", + " *", + " * three.js keys its shader programs on how many lights of each kind it can", + " * see - NUM_POINT_LIGHTS, NUM_SPOT_LIGHTS, NUM_SPOT_LIGHT_MAPS and the", + " * matching shadow counts. A single budget shared by every kind keeps the", + " * *total* pinned to the cap but lets the mix drift as the camera moves, and", + " * each mix that has not been seen before costs a shader compilation - a", + " * stutter the player feels. So each kind gets its own budget instead, which", + " * pins each of those numbers individually.", + " */", + "class LightManager {", + " /** @type {Map} Visible lights, by light kind. */", + " visibleLists = new Map();", + " /** @type {Map} Shadow casters, by light kind. */", + " shadowLists = new Map();", + " /** @type {Map} Lit light capacity, by light kind. */", + " maxCountByKind;", + " /** @type {Map} Shadow caster capacity, by light kind. */", + " shadowCountByKind;", + "", + " /**", + " * @param maxCount {{value: number}} The maximum number of lit lights, for", + " * each light kind, as the game asked for it.", + " * @param shadowCount {{value: number}} The maximum number of shadow", + " * casting lights, for each light kind, as the game asked for it.", + " * @param deviceBudget {{shadowCount: number, mapCount: number, count: number}}", + " * What the device can actually take, from `computeDeviceLightBudget`.", + " */", + " constructor(maxCount, shadowCount, deviceBudget) {", + " // Spot lights that project a texture already spend a texture unit on", + " // that texture, so they get the tighter of the two budgets.", + " this.maxCountByKind = new Map([", + " ['point', makeDeviceCappedCapacity(maxCount, deviceBudget.count)],", + " ['spot', makeDeviceCappedCapacity(maxCount, deviceBudget.count)],", + " ['spotWithMap', makeDeviceCappedCapacity(", + " maxCount, Math.min(deviceBudget.count, deviceBudget.mapCount))],", + " ]);", + " this.shadowCountByKind = new Map([", + " ['point', makeDeviceCappedCapacity(shadowCount, deviceBudget.shadowCount)],", + " ['spot', makeDeviceCappedCapacity(shadowCount, deviceBudget.shadowCount)],", + " ['spotWithMap', makeDeviceCappedCapacity(shadowCount, deviceBudget.shadowCount)],", + " ]);", + " }", + "", + " clear() {", + " for (const list of this.visibleLists.values()) {", + " list.clear();", + " }", + " for (const list of this.shadowLists.values()) {", + " list.clear();", + " }", + " }", + "", + " /**", + " * The kind of a light, as far as the shaders are concerned. Lights of", + " * different kinds are counted separately by three.js, so they must be", + " * budgeted separately too.", + " * @param light {THREE.SpotLight | THREE.PointLight}", + " * @returns {string}", + " */", + " static getLightKind(light) {", + " //@ts-ignore - `isSpotLight` is set by three.js on SpotLight instances.", + " if (!light.isSpotLight) {", + " return 'point';", + " }", + " // A projected texture adds to NUM_SPOT_LIGHT_MAPS, so a spot light", + " // that has one is a different kind from a spot light that has not.", + " //@ts-ignore - only spot lights have a projected texture.", + " return light.map ? 'spotWithMap' : 'spot';", + " }", + "", + " /**", + " * @param lists {Map}", + " * @param kind {string}", + " * @param capacity {{value: number}}", + " * @param onInsertion {(object: LightRuntimeObject) => void}", + " * @param onDeletion {(object: LightRuntimeObject) => void}", + " * @returns {CappedLightList}", + " */", + " static _getListForKind(lists, kind, capacity, onInsertion, onDeletion) {", + " let list = lists.get(kind);", + " if (!list) {", + " list = new CappedLightList(capacity, onInsertion, onDeletion);", + " lists.set(kind, list);", + " }", + " return list;", + " }", + "", + " /**", + " * @param object {LightRuntimeObject}", + " * @param distance {number} The squared distance to the camera.", + " */", + " applyVisibilityAndShadow(object, distance) {", + " const light = object.__light3D;", + " const kind = LightManager.getLightKind(light);", + "", + " if (object._getIsCastingShadow()) {", + " LightManager._getListForKind(", + " this.shadowLists, kind, this.shadowCountByKind.get(kind),", + " enableShadow, disableShadow", + " ).insertByDistance(object, distance);", + " }", + " LightManager._getListForKind(", + " this.visibleLists, kind, this.maxCountByKind.get(kind),", + " showLight, hideLight", + " ).insertByDistance(object, distance);", + " }", + "}", + "", + "//@ts-ignore", + "gdjs.__light3DExtension = {", + " Light3DRenderer,", + " lightCountMax,", + " lightShadowCountMax,", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "the maximum number of nearest lights displayed simultaneously.", + "fullName": "Max lights count", + "functionType": "ExpressionAndCondition", + "name": "LightCountMax", + "sentence": "max lights count", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs.__light3DExtension.lightCountMax.value;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "LightCountMax", + "name": "SetLightCountMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const value = eventsFunctionContext.getArgument(\"Value\");", + "", + "gdjs.__light3DExtension.lightCountMax.value = value;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "the maximum number of nearest lights displayed with shadow simultaneously.", + "fullName": "Max lights with shadow count", + "functionType": "ExpressionAndCondition", + "name": "LightShadowCountMax", + "sentence": "max lights with shadow count", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs.__light3DExtension.lightShadowCountMax.value;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "LightShadowCountMax", + "name": "SetLightShadowCountMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const value = eventsFunctionContext.getArgument(\"Value\");", + "", + "gdjs.__light3DExtension.lightShadowCountMax.value = value;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "DefineHelperClasses" + }, + { + "functionName": "DefineSpotLightHelperClasses" + }, + { + "functionName": "DefinePointLightHelperClasses" + }, + { + "functionName": "LightCountMax" + }, + { + "functionName": "SetLightCountMax" + }, + { + "functionName": "LightShadowCountMax" + }, + { + "functionName": "SetLightShadowCountMax" + } + ] + }, + "eventsBasedBehaviors": [], + "eventsBasedObjects": [ + { + "areaMaxX": 20, + "areaMaxY": 20, + "areaMaxZ": 20, + "areaMinX": -20, + "areaMinY": -20, + "areaMinZ": -20, + "defaultName": "SpotLight", + "description": "Light up a cone like a flashlight.", + "fullName": "3D spot light", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLWZsYXNobGlnaHQiIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNOSwxMEw2LDVIMThMMTUsMTBIOU0xOCw0SDZWMkgxOFY0TTksMjJWMTFIMTVWMjJIOU0xMiwxM0ExLDEgMCAwLDAgMTEsMTRBMSwxIDAgMCwwIDEyLDE1QTEsMSAwIDAsMCAxMywxNEExLDEgMCAwLDAgMTIsMTNaIiAvPjwvc3ZnPg==", + "is3D": true, + "isUsingLegacyInstancesRenderer": false, + "name": "SpotLight3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/b627f8e676b3aac26945b2c38db0cfa787d34d2eff23755d4eb1289452f6cd28_flashlight.svg", + "objects": [ + { + "assetStoreId": "", + "name": "Placeholder", + "persistentUuid": "6786eefd-3c18-4fcb-b3ae-95b07f953045", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 80, + "height": 80, + "depth": 80, + "enableTextureTransparency": false, + "facesOrientation": "Z", + "frontFaceResourceName": "", + "backFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "", + "rightFaceResourceName": "", + "topFaceResourceName": "", + "bottomFaceResourceName": "", + "frontFaceVisible": true, + "backFaceVisible": true, + "leftFaceVisible": true, + "rightFaceVisible": true, + "topFaceVisible": true, + "bottomFaceVisible": true, + "frontFaceResourceRepeat": false, + "backFaceResourceRepeat": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceRepeat": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceRepeat": false, + "materialType": "Basic", + "tint": "255;255;255", + "isCastingShadow": false, + "isReceivingShadow": false + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Placeholder" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "depth": 40, + "height": 40, + "layer": "", + "name": "Placeholder", + "persistentUuid": "1310c768-04d4-4133-98d9-37d836f8a990", + "width": 40, + "x": -20, + "y": -20, + "z": -20, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 32, + "gridHeight": 32, + "gridDepth": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridOffsetZ": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": false, + "zoomFactor": 9.06473532531222, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "instances-editor" + }, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + }, + { + "type": { + "value": "Light3D::DefineSpotLightHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const {", + " Light3DRenderer,", + " //@ts-ignore", + "} = gdjs.__light3DExtension;", + "const {", + " SpotLightAdapter,", + " SpotLightHelper,", + " //@ts-ignore", + "} = gdjs.__light3DExtension.spot;", + "", + "/** @type {gdjs.CustomRuntimeObject3D} */", + "//@ts-ignore", + "const object = objects[0];", + "", + "const game = runtimeScene.getGame();", + "", + "// This is a hack that may break in future releases.", + "// Replace the group that would hold children objects by the light.", + "const layer = object.getInstanceContainer().getLayer(object.getLayer());", + "const group = object.getRenderer()._threeGroup;", + "layer.getRenderer().remove3DRendererObject(group);", + "", + "const spotLight = new THREE.SpotLight(0xffffff);", + "spotLight.position.copy(group.position);", + "spotLight.rotation.copy(group.rotation);", + "spotLight.add(spotLight.target);", + "spotLight.target.position.x = 100;", + "spotLight.up.set(0, 0, 1);", + "spotLight.shadow.camera.up.set(0, 0, 1);", + "//@ts-ignore", + "object.__light3D = spotLight;", + "//@ts-ignore", + "object.__spotLightAdapter = new SpotLightAdapter(object, spotLight);", + "", + "const spotLight3DRenderer = new Light3DRenderer(object, object._instanceContainer, object.getInstanceContainer());", + "object._renderer = spotLight3DRenderer;", + "if (game.isInGameEdition && game.isInGameEdition()) {", + " const spotLightHelper = new SpotLightHelper(object);", + " spotLightHelper.rotation.order = 'ZYX';", + " spotLightHelper.add(spotLight);", + " spotLight3DRenderer._threeGroup = spotLightHelper;", + " layer.getRenderer().add3DRendererObject(spotLightHelper);", + "}", + "else {", + " spotLight3DRenderer._threeGroup = spotLight;", + " layer.getRenderer().add3DRendererObject(spotLight);", + "}", + "", + "spotLight.updateMatrixWorld(true);", + "", + "// Allow to tween the light color.", + "//@ts-ignore", + "if (!object.setColor && !object.getColor) {", + " const prototype = Object.getPrototypeOf(object);", + " prototype.setColor = function(tint) {", + " this._setColor(tint);", + " this.__spotLightAdapter.setColor(tint);", + " }", + " prototype.getColor = function() {", + " return this._getColor(eventsFunctionContext);", + " }", + "}", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::SpotLight3D::UpdateFromProperties" + }, + "parameters": [ + "Object", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject} */", + "const object = objects[0];", + "// Here runtimeScene is the gdjs.CustomRuntimeObjectInstanceContainer inside the custom object.", + "const gameScene = object.getRuntimeScene();", + "", + "object.__light3D.dispose();", + "object.get3DRendererObject().dispose();", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::SpotLight3D::UpdateFromProperties" + }, + "parameters": [ + "Object", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Update from properties.", + "fullName": "Update from properties", + "functionType": "Action", + "name": "UpdateFromProperties", + "private": true, + "sentence": "Update from properties of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::SpotLight3D::SetColor" + }, + "parameters": [ + "Object", + "=", + "Color", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetIntensity" + }, + "parameters": [ + "Object", + "=", + "Intensity", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetDecay" + }, + "parameters": [ + "Object", + "=", + "Decay", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetConeLength" + }, + "parameters": [ + "Object", + "=", + "ConeLength", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetConeAngle" + }, + "parameters": [ + "Object", + "=", + "ConeAngle", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetSmoothing" + }, + "parameters": [ + "Object", + "=", + "Smoothing", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetShadowQuality" + }, + "parameters": [ + "Object", + "=", + "ShadowQuality", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetShadowCameraNearPlane" + }, + "parameters": [ + "Object", + "=", + "ShadowCameraNearPlane", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetShadowCameraFarPlane" + }, + "parameters": [ + "Object", + "=", + "ShadowCameraFarPlane", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetShadowBias" + }, + "parameters": [ + "Object", + "=", + "ShadowBias", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::UpdateImage" + }, + "parameters": [ + "Object", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::UpdateHelper" + }, + "parameters": [ + "Object", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "=", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Light3D::SpotLight3D::SetCastingShadow" + }, + "parameters": [ + "Object", + "no", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Light3D::SpotLight3D::SetCastingShadow" + }, + "parameters": [ + "Object", + "yes", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "Update helper", + "functionType": "Action", + "name": "UpdateHelper", + "private": true, + "sentence": "Update graphical helper of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const game = runtimeScene.getGame();", + "if (game.isInGameEdition && game.isInGameEdition()) {", + " const spotLightHelper = objects[0].get3DRendererObject();", + " spotLightHelper.update();", + "}", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "Update image", + "functionType": "Action", + "name": "UpdateImage", + "private": true, + "sentence": "Update the image of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */\r", + "const object = objects[0];\r", + "const resourceName = object._getImage();\r", + "object.__spotLightAdapter.setImage(resourceName);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the cone angle of the light.", + "fullName": "Cone angle", + "functionType": "ExpressionAndCondition", + "name": "ConeAngle", + "sentence": "the cone angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ConeAngle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ConeAngle", + "name": "SetConeAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ConeAngle", + "=", + "Value" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::ApplyShadowBias" + }, + "parameters": [ + "Object", + "ShadowBias", + "ShadowQuality", + "ConeAngle", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const coneAngle = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setConeAngle(coneAngle);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the color of the light.", + "fullName": "Color", + "functionType": "ExpressionAndCondition", + "name": "Color", + "sentence": "the color", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "Color" + ] + } + ] + } + ], + "expressionType": { + "type": "color" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Color", + "name": "SetColor", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "Color", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const color = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setColor(color);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the smoothing of the light. Percent of the spotlight cone that is attenuated due to penumbra (between 0 and 1).", + "fullName": "Smoothing", + "functionType": "ExpressionAndCondition", + "name": "Smoothing", + "sentence": "the smoothing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Smoothing" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Smoothing", + "name": "SetSmoothing", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Smoothing", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setSmoothing(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the intensity of the light.", + "fullName": "Intensity", + "functionType": "ExpressionAndCondition", + "name": "Intensity", + "sentence": "the intensity", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Intensity" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Intensity", + "name": "SetIntensity", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Intensity", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setIntensity(value);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the decay of the light. The amount the light dims along the distance of the light.", + "fullName": "Decay", + "functionType": "ExpressionAndCondition", + "name": "Decay", + "sentence": "the decay", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Decay" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Decay", + "name": "SetDecay", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Decay", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setDecay(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the light is casting shadows.", + "fullName": "Shadow casting", + "functionType": "Condition", + "group": "3D spot light shadow configuration", + "name": "IsCastingShadow", + "sentence": "_PARAM0_ casting shadows", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Change if the light is casting shadows.", + "fullName": "Shadow casting", + "functionType": "Action", + "group": "3D spot light shadow configuration", + "name": "SetCastingShadow", + "sentence": "_PARAM0_ casting shadows: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "False", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setCastingShadow(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::SpotLight3D::SetConeLength" + }, + "parameters": [ + "Object", + "=", + "ConeLength", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + }, + { + "defaultValue": "yes", + "description": "Shadow casting", + "name": "Value", + "optional": true, + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Rotate the light to light up a position.", + "fullName": "Look at position", + "functionType": "Action", + "group": "Angle", + "name": "LookAtPosition", + "sentence": "_PARAM0_ look at _PARAM1_ ; _PARAM2_ ; _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const targetX = eventsFunctionContext.getArgument(\"TargetX\");\r", + "const targetY = eventsFunctionContext.getArgument(\"TargetY\");\r", + "const targetZ = eventsFunctionContext.getArgument(\"TargetZ\");\r", + "\r", + "object.__spotLightAdapter.lookAtPosition(targetX, targetY, targetZ);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + }, + { + "description": "Target X", + "name": "TargetX", + "type": "expression" + }, + { + "description": "Target Y", + "name": "TargetY", + "type": "expression" + }, + { + "description": "Target Z", + "name": "TargetZ", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Rotate the light to light up an object.", + "fullName": "Look at object", + "functionType": "Action", + "group": "Angle", + "name": "LookAtObject", + "sentence": "_PARAM0_ look at _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::SpotLight3D::LookAtPosition" + }, + "parameters": [ + "Object", + "Target.CenterX()", + "Target.CenterY()", + "Target.Object3D::CenterZ()", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + }, + { + "description": "Object", + "name": "Target", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow quality of the light.", + "fullName": "Shadow quality", + "functionType": "ExpressionAndCondition", + "group": "3D spot light shadow configuration", + "name": "ShadowQuality", + "sentence": "the shadow quality", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "ShadowQuality" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"Low\",\"Medium\",\"High\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowQuality", + "group": "3D spot light shadow configuration", + "name": "SetShadowQuality", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ShadowQuality", + "=", + "Value" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::ApplyShadowBias" + }, + "parameters": [ + "Object", + "ShadowBias", + "ShadowQuality", + "ConeAngle", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setShadowQuality(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow camera near plane of the light.", + "fullName": "Shadow camera near plane", + "functionType": "ExpressionAndCondition", + "group": "3D spot light shadow configuration", + "name": "ShadowCameraNearPlane", + "sentence": "the shadow camera near plane", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ShadowCameraNearPlane" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowCameraNearPlane", + "group": "3D spot light shadow configuration", + "name": "SetShadowCameraNearPlane", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ShadowCameraNearPlane", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setShadowCameraNearPlane(value);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow camera far plane of the light.", + "fullName": "Shadow camera far plane", + "functionType": "ExpressionAndCondition", + "group": "3D spot light shadow configuration", + "name": "ShadowCameraFarPlane", + "sentence": "the shadow camera far plane", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ShadowCameraFarPlane" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowCameraFarPlane", + "group": "3D spot light shadow configuration", + "name": "SetShadowCameraFarPlane", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ShadowCameraFarPlane", + "=", + "Value" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::ApplyShadowCameraFarPlane" + }, + "parameters": [ + "Object", + "min(Value, ConeLength)", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the cone length of the light. 0 means no limit.", + "fullName": "Cone length", + "functionType": "ExpressionAndCondition", + "name": "ConeLength", + "sentence": "the cone length", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ConeLength" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ConeLength", + "name": "SetConeLength", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ConeLength", + "=", + "Value" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setConeLength(value);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Three.js reduces the far plane to match the light range.\nSo, we need to set it again or it will stay at the lowest value the range took.\nWe do the min() on our side to in order:\n- to avoid a side effect when Three.js is upgraded\n- because Three.js keeps the old value in cache and doesn't apply it back unless we set the lower value explicitly ourselves." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::SpotLight3D::ApplyShadowCameraFarPlane" + }, + "parameters": [ + "Object", + "min(Value, ShadowCameraFarPlane)", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "Apply shadow camera far plane", + "functionType": "Action", + "group": "3D spot light shadow configuration", + "name": "ApplyShadowCameraFarPlane", + "private": true, + "sentence": "Apply shadow camera far plane of _PARAM0_ to _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setShadowCameraFarPlane(value);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow bias of the light. Use this to avoid \"shadow acne\" due to depth buffer precision. Choose a value small enough like 0.001 to avoid creating distance between shadows and objects but not too small to avoid shadow glitches on low/medium quality. This value is used for high quality, and multiplied by 2 for medium quality and 4 for low quality.", + "fullName": "Shadow bias", + "functionType": "ExpressionAndCondition", + "group": "3D spot light shadow configuration", + "name": "ShadowBias", + "sentence": "the shadow bias", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ShadowBias" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowBias", + "group": "3D spot light shadow configuration", + "name": "SetShadowBias", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ShadowBias", + "=", + "Value" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::ApplyShadowBias" + }, + "parameters": [ + "Object", + "Value", + "ShadowQuality", + "ConeAngle", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "Apply shadow bias", + "functionType": "Action", + "group": "3D spot light shadow configuration", + "name": "ApplyShadowBias", + "private": true, + "sentence": "Apply shadow bias of _PARAM0_ to _PARAM1_ for a _PARAM2_ quality and cone angle _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "const quality = eventsFunctionContext.getArgument(\"Quality\");\r", + "const coneAngle = eventsFunctionContext.getArgument(\"Angle\");\r", + "\r", + "object.__spotLightAdapter.setShadowBias(value, quality, coneAngle);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + }, + { + "description": "Quality", + "name": "Quality", + "type": "string" + }, + { + "description": "Cone angle", + "name": "Angle", + "type": "expression" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "onDestroy" + }, + { + "functionName": "onHotReloading" + }, + { + "functionName": "UpdateFromProperties" + }, + { + "functionName": "UpdateHelper" + }, + { + "functionName": "UpdateImage" + }, + { + "folderName": "Angle", + "children": [ + { + "functionName": "LookAtPosition" + }, + { + "functionName": "LookAtObject" + } + ] + }, + { + "functionName": "Color" + }, + { + "functionName": "SetColor" + }, + { + "functionName": "Intensity" + }, + { + "functionName": "SetIntensity" + }, + { + "functionName": "Decay" + }, + { + "functionName": "SetDecay" + }, + { + "functionName": "ConeLength" + }, + { + "functionName": "SetConeLength" + }, + { + "functionName": "ConeAngle" + }, + { + "functionName": "SetConeAngle" + }, + { + "functionName": "Smoothing" + }, + { + "functionName": "SetSmoothing" + }, + { + "folderName": "3D spot light shadow configuration", + "children": [ + { + "functionName": "IsCastingShadow" + }, + { + "functionName": "SetCastingShadow" + }, + { + "functionName": "ShadowQuality" + }, + { + "functionName": "SetShadowQuality" + }, + { + "functionName": "ShadowCameraNearPlane" + }, + { + "functionName": "SetShadowCameraNearPlane" + }, + { + "functionName": "ShadowCameraFarPlane" + }, + { + "functionName": "SetShadowCameraFarPlane" + }, + { + "functionName": "ApplyShadowCameraFarPlane" + }, + { + "functionName": "ShadowBias" + }, + { + "functionName": "SetShadowBias" + }, + { + "functionName": "ApplyShadowBias" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "255;255;255", + "type": "Color", + "label": "Color", + "name": "Color" + }, + { + "value": "30", + "type": "Number", + "unit": "DegreeAngle", + "label": "Cone angle", + "group": "Cone", + "name": "ConeAngle" + }, + { + "value": "1", + "type": "Number", + "label": "Intensity", + "name": "Intensity" + }, + { + "value": "1", + "type": "Number", + "label": "Smoothing", + "description": "Percent of the spotlight cone that is attenuated due to penumbra (between 0 and 1).", + "group": "Cone", + "name": "Smoothing" + }, + { + "value": "2", + "type": "Number", + "label": "Decay", + "description": "The amount the light dims along the distance of the light.", + "name": "Decay" + }, + { + "value": "", + "type": "Resource", + "label": "Image", + "description": "Shadow casting must be enabled for the image to have any effect.", + "group": "Shadow", + "extraInformation": [ + "image" + ], + "choices": [], + "hidden": true, + "name": "Image" + }, + { + "value": "true", + "type": "Boolean", + "label": "Shadow casting", + "group": "Shadow", + "name": "IsCastingShadow" + }, + { + "value": "Medium", + "type": "Choice", + "label": "Shadow quality", + "group": "Shadow", + "choices": [ + { + "label": "Low", + "value": "Low" + }, + { + "label": "Medium", + "value": "Medium" + }, + { + "label": "High", + "value": "High" + }, + { + "label": "Very high", + "value": "VeryHigh" + } + ], + "name": "ShadowQuality" + }, + { + "value": "20", + "type": "Number", + "unit": "Pixel", + "label": "Shadow camera near plane", + "group": "Shadow", + "advanced": true, + "name": "ShadowCameraNearPlane" + }, + { + "value": "2000", + "type": "Number", + "unit": "Pixel", + "label": "Shadow camera far plane", + "group": "Shadow", + "advanced": true, + "name": "ShadowCameraFarPlane" + }, + { + "value": "2000", + "type": "Number", + "unit": "Pixel", + "label": "Cone length", + "description": "0 means no limit.", + "group": "Cone", + "name": "ConeLength" + }, + { + "value": "0", + "type": "Number", + "label": "Shadow bias", + "description": "Use this to avoid \"shadow acne\" due to depth buffer precision. Choose a value small enough like 0.001 to avoid creating distance between shadows and objects but not too small to avoid shadow glitches on low/medium quality. This value is used for high quality, and multiplied by 2 for medium quality and 4 for low quality.", + "group": "Shadow", + "advanced": true, + "name": "ShadowBias" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Color" + }, + { + "propertyName": "Intensity" + }, + { + "propertyName": "Decay" + }, + { + "folderName": "Cone", + "children": [ + { + "propertyName": "ConeAngle" + }, + { + "propertyName": "ConeLength" + }, + { + "propertyName": "Smoothing" + } + ] + }, + { + "folderName": "Shadow", + "children": [ + { + "propertyName": "IsCastingShadow" + }, + { + "propertyName": "ShadowQuality" + }, + { + "propertyName": "Image" + }, + { + "propertyName": "ShadowCameraNearPlane" + }, + { + "propertyName": "ShadowCameraFarPlane" + }, + { + "propertyName": "ShadowBias" + } + ] + } + ] + } + }, + { + "areaMaxX": 20, + "areaMaxY": 20, + "areaMaxZ": 20, + "areaMinX": -20, + "areaMinY": -20, + "areaMinZ": -20, + "defaultName": "PointLight", + "description": "Light up in all directions like a fire.", + "fullName": "3D point light", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXRvcmNoIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTguNiA5LjZDOSAxMC4yIDkuNSAxMC43IDEwLjIgMTFIMTQuMkMxNC41IDEwLjkgMTQuNyAxMC43IDE0LjkgMTAuNUMxNS45IDkuNSAxNi4zIDggMTUuOCA2LjdMMTUuNyA2LjVDMTUuNiA2LjIgMTUuNCA2IDE1LjIgNS44QzE1LjEgNS42IDE0LjkgNS41IDE0LjggNS4zQzE0LjQgNSAxNCA0LjcgMTMuNiA0LjNDMTIuNyAzLjQgMTIuNiAyIDEzLjEgMUMxMi42IDEuMSAxMi4xIDEuNCAxMS43IDEuOEMxMC4yIDMgOS42IDUuMSAxMC4zIDdWNy4yQzEwLjMgNy4zIDEwLjIgNy40IDEwLjEgNy41QzEwIDcuNiA5LjggNy41IDkuNyA3LjRMOS42IDcuM0M5IDYuNSA4LjkgNS4zIDkuMyA0LjNDOC40IDUuMSA3LjkgNi40IDggNy43QzggOCA4LjEgOC4zIDguMiA4LjZDOC4yIDguOSA4LjQgOS4zIDguNiA5LjZNMTIuMyA4LjFDMTIuNCA3LjYgMTIuMiA3LjIgMTIuMSA2LjhDMTIgNi40IDEyIDYgMTIuMiA1LjZMMTIuNSA2LjJDMTIuOSA2LjggMTMuNiA3IDEzLjggNy44VjguMUMxMy44IDguNiAxMy42IDkuMSAxMy4zIDkuNEMxMy4xIDkuNSAxMi45IDkuNyAxMi43IDkuN0MxMi4xIDkuOSAxMS40IDkuNiAxMSA5LjJDMTEuOCA5LjIgMTIuMiA4LjYgMTIuMyA4LjFNMTUgMTJWMTRIMTRMMTMgMjJIMTFMMTAgMTRIOVYxMkgxNVoiIC8+PC9zdmc+", + "is3D": true, + "isUsingLegacyInstancesRenderer": false, + "name": "PointLight3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/bf9158c96f4346dabd9e8ae0ddd737809652797812eb50376bae62463ac7fc42_torch.svg", + "objects": [ + { + "assetStoreId": "", + "name": "Placeholder", + "persistentUuid": "6786eefd-3c18-4fcb-b3ae-95b07f953045", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 80, + "height": 80, + "depth": 80, + "enableTextureTransparency": false, + "facesOrientation": "Z", + "frontFaceResourceName": "", + "backFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "", + "rightFaceResourceName": "", + "topFaceResourceName": "", + "bottomFaceResourceName": "", + "frontFaceVisible": true, + "backFaceVisible": true, + "leftFaceVisible": true, + "rightFaceVisible": true, + "topFaceVisible": true, + "bottomFaceVisible": true, + "frontFaceResourceRepeat": false, + "backFaceResourceRepeat": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceRepeat": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceRepeat": false, + "materialType": "Basic", + "tint": "255;255;255", + "isCastingShadow": false, + "isReceivingShadow": false + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Placeholder" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "depth": 40, + "height": 40, + "layer": "", + "name": "Placeholder", + "persistentUuid": "1310c768-04d4-4133-98d9-37d836f8a990", + "width": 40, + "x": -20, + "y": -20, + "z": -20, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 32, + "gridHeight": 32, + "gridDepth": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridOffsetZ": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": false, + "zoomFactor": 9.06473532531222, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "instances-editor" + }, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + }, + { + "type": { + "value": "Light3D::DefinePointLightHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const {", + " Light3DRenderer,", + " //@ts-ignore", + "} = gdjs.__light3DExtension;", + "const {", + " PointLightAdapter,", + " PointLightHelper,", + " //@ts-ignore", + "} = gdjs.__light3DExtension.point;", + "", + "/** @type {gdjs.CustomRuntimeObject3D} */", + "//@ts-ignore", + "const object = objects[0];", + "", + "const game = runtimeScene.getGame();", + "", + "// This is a hack that may break in future releases.", + "// Replace the group that would hold children objects by the light.", + "const layer = object.getInstanceContainer().getLayer(object.getLayer());", + "const group = object.getRenderer()._threeGroup;", + "layer.getRenderer().remove3DRendererObject(group);", + "", + "const pointLight = new THREE.PointLight(0xffffff);", + "pointLight.position.copy(group.position);", + "pointLight.rotation.copy(group.rotation);", + "pointLight.up.set(0, 0, 1);", + "pointLight.shadow.camera.up.set(0, 0, 1);", + "//@ts-ignore", + "object.__light3D = pointLight;", + "//@ts-ignore", + "object.__pointLightAdapter = new PointLightAdapter(object, pointLight);", + "", + "const pointLight3DRenderer = new Light3DRenderer(object, object._instanceContainer, object.getInstanceContainer());", + "object._renderer = pointLight3DRenderer;", + "if (game.isInGameEdition && game.isInGameEdition()) {", + " const pointLightHelper = new PointLightHelper(object);", + " pointLightHelper.rotation.order = 'ZYX';", + " pointLightHelper.add(pointLight);", + " pointLight3DRenderer._threeGroup = pointLightHelper;", + " layer.getRenderer().add3DRendererObject(pointLightHelper);", + "}", + "else {", + " pointLight3DRenderer._threeGroup = pointLight;", + " layer.getRenderer().add3DRendererObject(pointLight);", + "}", + "", + "pointLight.updateMatrixWorld(true);", + "", + "// Allow to tween the light color.", + "//@ts-ignore", + "if (!object.setColor && !object.getColor) {", + " const prototype = Object.getPrototypeOf(object);", + " prototype.setColor = function(tint) {", + " this._setColor(tint);", + " this.__pointLightAdapter.setColor(tint);", + " }", + " prototype.getColor = function() {", + " return this._getColor(eventsFunctionContext);", + " }", + "}", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::PointLight3D::UpdateFromProperties" + }, + "parameters": [ + "Object", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject} */", + "const object = objects[0];", + "// Here runtimeScene is the gdjs.CustomRuntimeObjectInstanceContainer inside the custom object.", + "const gameScene = object.getRuntimeScene();", + "", + "object.__light3D.dispose();", + "object.get3DRendererObject().dispose();", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::PointLight3D::UpdateFromProperties" + }, + "parameters": [ + "Object", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Update from properties.", + "fullName": "Update from properties", + "functionType": "Action", + "name": "UpdateFromProperties", + "private": true, + "sentence": "Update from properties of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::PointLight3D::SetColor" + }, + "parameters": [ + "Object", + "=", + "Color", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetIntensity" + }, + "parameters": [ + "Object", + "=", + "Intensity", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetDecay" + }, + "parameters": [ + "Object", + "=", + "Decay", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetRange" + }, + "parameters": [ + "Object", + "=", + "Range", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetShadowQuality" + }, + "parameters": [ + "Object", + "=", + "ShadowQuality", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetShadowCameraNearPlane" + }, + "parameters": [ + "Object", + "=", + "ShadowCameraNearPlane", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetShadowCameraFarPlane" + }, + "parameters": [ + "Object", + "=", + "ShadowCameraFarPlane", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetShadowBias" + }, + "parameters": [ + "Object", + "=", + "ShadowBias", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::UpdateHelper" + }, + "parameters": [ + "Object", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "=", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Light3D::PointLight3D::SetCastingShadow" + }, + "parameters": [ + "Object", + "no", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Light3D::PointLight3D::SetCastingShadow" + }, + "parameters": [ + "Object", + "yes", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "Update helper", + "functionType": "Action", + "name": "UpdateHelper", + "private": true, + "sentence": "Update graphical helper of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const game = runtimeScene.getGame();", + "if (game.isInGameEdition && game.isInGameEdition()) {", + " const pointLightHelper = objects[0].get3DRendererObject();", + " pointLightHelper.update();", + "}", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the color of the light.", + "fullName": "Color", + "functionType": "ExpressionAndCondition", + "name": "Color", + "sentence": "the color", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "Color" + ] + } + ] + } + ], + "expressionType": { + "type": "color" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Color", + "name": "SetColor", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "Color", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const color = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setColor(color);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the intensity of the light.", + "fullName": "Intensity", + "functionType": "ExpressionAndCondition", + "name": "Intensity", + "sentence": "the intensity", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Intensity" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Intensity", + "name": "SetIntensity", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Intensity", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setIntensity(value);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the decay of the light. The amount the light dims along the distance of the light.", + "fullName": "Decay", + "functionType": "ExpressionAndCondition", + "name": "Decay", + "sentence": "the decay", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Decay" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Decay", + "name": "SetDecay", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Decay", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setDecay(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the light is casting shadows.", + "fullName": "Shadow casting", + "functionType": "Condition", + "group": "3D point light shadow configuration", + "name": "IsCastingShadow", + "sentence": "_PARAM0_ casting shadows", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetRange" + }, + "parameters": [ + "Object", + "=", + "Range", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Change if the light is casting shadows.", + "fullName": "Shadow casting", + "functionType": "Action", + "group": "3D point light shadow configuration", + "name": "SetCastingShadow", + "sentence": "_PARAM0_ casting shadows: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "False", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setCastingShadow(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + }, + { + "defaultValue": "yes", + "description": "Shadow casting", + "name": "Value", + "optional": true, + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow quality of the light.", + "fullName": "Shadow quality", + "functionType": "ExpressionAndCondition", + "group": "3D point light shadow configuration", + "name": "ShadowQuality", + "sentence": "the shadow quality", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "ShadowQuality" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"Low\",\"Medium\",\"High\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowQuality", + "group": "3D point light shadow configuration", + "name": "SetShadowQuality", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ShadowQuality", + "=", + "Value" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::ApplyShadowBias" + }, + "parameters": [ + "Object", + "ShadowBias", + "ShadowQuality", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setShadowQuality(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow camera near plane of the light.", + "fullName": "Shadow camera near plane", + "functionType": "ExpressionAndCondition", + "group": "3D point light shadow configuration", + "name": "ShadowCameraNearPlane", + "sentence": "the shadow camera near plane", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ShadowCameraNearPlane" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowCameraNearPlane", + "group": "3D point light shadow configuration", + "name": "SetShadowCameraNearPlane", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ShadowCameraNearPlane", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setShadowCameraNearPlane(value);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::PointLight3D::SetRange" + }, + "parameters": [ + "Object", + "=", + "Range", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow camera far plane of the light.", + "fullName": "Shadow camera far plane", + "functionType": "ExpressionAndCondition", + "group": "3D point light shadow configuration", + "name": "ShadowCameraFarPlane", + "sentence": "the shadow camera far plane", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ShadowCameraFarPlane" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowCameraFarPlane", + "group": "3D point light shadow configuration", + "name": "SetShadowCameraFarPlane", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ShadowCameraFarPlane", + "=", + "Value" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::ApplyShadowCameraFarPlane" + }, + "parameters": [ + "Object", + "min(Value, Range)", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the range of the light. 0 means no limit.", + "fullName": "Range", + "functionType": "ExpressionAndCondition", + "name": "Range", + "sentence": "the range", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Range" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Range", + "name": "SetRange", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Range", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setRange(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Three.js reduces the far plane to match the light range.\nSo, we need to set it again or it will stay at the lowest value the range took.\nWe do the min() on our side to in order:\n- to avoid a side effect when Three.js is upgraded\n- because Three.js keeps the old value in cache and doesn't apply it back unless we set the lower value explicitly ourselves." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::PointLight3D::ApplyShadowCameraFarPlane" + }, + "parameters": [ + "Object", + "min(Value, ShadowCameraFarPlane)", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "Apply shadow camera far plane", + "functionType": "Action", + "group": "3D point light shadow configuration", + "name": "ApplyShadowCameraFarPlane", + "private": true, + "sentence": "Apply shadow camera far plane of _PARAM0_ to _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setShadowCameraFarPlane(value);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow bias of the light. Use this to avoid \"shadow acne\" due to depth buffer precision. Choose a value small enough like 0.001 to avoid creating distance between shadows and objects but not too small to avoid shadow glitches on low/medium quality. This value is used for high quality, and multiplied by 2 for medium quality and 4 for low quality.", + "fullName": "Shadow bias", + "functionType": "ExpressionAndCondition", + "group": "3D point light shadow configuration", + "name": "ShadowBias", + "sentence": "the shadow bias", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ShadowBias" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowBias", + "group": "3D point light shadow configuration", + "name": "SetShadowBias", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ShadowBias", + "=", + "Value" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::ApplyShadowBias" + }, + "parameters": [ + "Object", + "Value", + "ShadowQuality", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "Apply shadow bias", + "functionType": "Action", + "group": "3D point light shadow configuration", + "name": "ApplyShadowBias", + "private": true, + "sentence": "Apply shadow bias of _PARAM0_ to _PARAM1_ for a _PARAM2_ quality", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "const quality = eventsFunctionContext.getArgument(\"Quality\");\r", + "\r", + "object.__pointLightAdapter.setShadowBias(value, quality);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + }, + { + "description": "Quality", + "name": "Quality", + "type": "string" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "onDestroy" + }, + { + "functionName": "onHotReloading" + }, + { + "functionName": "UpdateFromProperties" + }, + { + "functionName": "UpdateHelper" + }, + { + "functionName": "Color" + }, + { + "functionName": "SetColor" + }, + { + "functionName": "Intensity" + }, + { + "functionName": "SetIntensity" + }, + { + "functionName": "Decay" + }, + { + "functionName": "SetDecay" + }, + { + "functionName": "Range" + }, + { + "functionName": "SetRange" + }, + { + "folderName": "3D point light shadow configuration", + "children": [ + { + "functionName": "IsCastingShadow" + }, + { + "functionName": "SetCastingShadow" + }, + { + "functionName": "ShadowQuality" + }, + { + "functionName": "SetShadowQuality" + }, + { + "functionName": "ShadowCameraNearPlane" + }, + { + "functionName": "SetShadowCameraNearPlane" + }, + { + "functionName": "ShadowCameraFarPlane" + }, + { + "functionName": "SetShadowCameraFarPlane" + }, + { + "functionName": "ApplyShadowCameraFarPlane" + }, + { + "functionName": "ShadowBias" + }, + { + "functionName": "SetShadowBias" + }, + { + "functionName": "ApplyShadowBias" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "255;255;255", + "type": "Color", + "label": "Color", + "name": "Color" + }, + { + "value": "1", + "type": "Number", + "label": "Intensity", + "name": "Intensity" + }, + { + "value": "2", + "type": "Number", + "label": "Decay", + "description": "The amount the light dims along the distance of the light.", + "name": "Decay" + }, + { + "value": "true", + "type": "Boolean", + "label": "Shadow casting", + "group": "Shadow", + "name": "IsCastingShadow" + }, + { + "value": "Medium", + "type": "Choice", + "label": "Shadow quality", + "group": "Shadow", + "choices": [ + { + "label": "", + "value": "Low" + }, + { + "label": "", + "value": "Medium" + }, + { + "label": "", + "value": "High" + } + ], + "name": "ShadowQuality" + }, + { + "value": "20", + "type": "Number", + "unit": "Pixel", + "label": "Shadow camera near plane", + "group": "Shadow", + "advanced": true, + "name": "ShadowCameraNearPlane" + }, + { + "value": "2000", + "type": "Number", + "unit": "Pixel", + "label": "Shadow camera far plane", + "group": "Shadow", + "advanced": true, + "name": "ShadowCameraFarPlane" + }, + { + "value": "2000", + "type": "Number", + "unit": "Pixel", + "label": "Range", + "description": "0 means no limit.", + "name": "Range" + }, + { + "value": "0", + "type": "Number", + "label": "Shadow Bias", + "description": "Use this to avoid \"shadow acne\" due to depth buffer precision. Choose a value small enough like 0.001 to avoid creating distance between shadows and objects but not too small to avoid shadow glitches on low/medium quality. This value is used for high quality, and multiplied by 2 for medium quality and 4 for low quality.", + "group": "Shadow", + "advanced": true, + "name": "ShadowBias" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Color" + }, + { + "propertyName": "Intensity" + }, + { + "propertyName": "Decay" + }, + { + "propertyName": "Range" + }, + { + "folderName": "Shadow", + "children": [ + { + "propertyName": "IsCastingShadow" + }, + { + "propertyName": "ShadowQuality" + }, + { + "propertyName": "ShadowCameraNearPlane" + }, + { + "propertyName": "ShadowCameraFarPlane" + }, + { + "propertyName": "ShadowBias" + } + ] + } + ] + } + } + ] + } + ], + "tests": [ + { + "name": "The sun lights the scene and casts shadows", + "description": "The directional light of the scene reaches the 3D renderer with shadow casting on, and lights the props.", + "type": "gameplay", + "source": [ + "// Look at the lights the 3D renderer can actually see, the way three.js does:", + "// hidden subtrees are skipped entirely, so this agrees with what the shaders", + "// were given rather than with what is in the scene graph.", + "const readLights = () => {", + " const threeScene = harness.getRuntimeLayer('').getRenderer().getThreeScene();", + " const lights = { point: [], spot: [], spotWithMap: [], directional: [], shadows: 0 };", + " if (!threeScene) return lights;", + " const walk = (object) => {", + " if (!object.visible) return;", + " if (object.isLight) {", + " const colour = object.color ? object.color.getHexString() : '';", + " if (object.isSpotLight) lights[object.map ? 'spotWithMap' : 'spot'].push(colour);", + " else if (object.isPointLight) lights.point.push(colour);", + " else if (object.isDirectionalLight) lights.directional.push(colour);", + " if (object.castShadow) lights.shadows++;", + " }", + " for (const child of object.children) walk(child);", + " };", + " walk(threeScene);", + " return lights;", + "};", + "const describe = (l) => JSON.stringify({ point: l.point.length,", + " spot: l.spot.length, spotWithMap: l.spotWithMap.length,", + " directional: l.directional.length, shadows: l.shadows });", + "", + "await harness.goToScene('Sun and Shadows');", + "await harness.stepFrames(10);", + "", + "const lights = readLights();", + "console.log('lights=' + describe(lights));", + "harness.assert(lights.directional.length === 1,", + " 'Exactly one directional light reaches the renderer (found ' + lights.directional.length + ').');", + "harness.assert(lights.shadows >= 1, 'The directional light casts a shadow.');", + "harness.assert(lights.point.length === 0 && lights.spot.length === 0,", + " 'This scene is lit by the sun alone.');", + "", + "harness.assert(harness.getObjects('Crate').length === 6, 'The six crates are in the scene.');", + "harness.assert(!!harness.getObjects('Ground')[0], 'The ground is in the scene.');", + "await harness.takeScreenshot('sun and shadows');" + ] + }, + { + "name": "Point lights light the scene at night", + "description": "The three point lights reach the 3D renderer, keep the colour each was given, and stay lit when one of them moves.", + "type": "gameplay", + "source": [ + "// Look at the lights the 3D renderer can actually see, the way three.js does:", + "// hidden subtrees are skipped entirely, so this agrees with what the shaders", + "// were given rather than with what is in the scene graph.", + "const readLights = () => {", + " const threeScene = harness.getRuntimeLayer('').getRenderer().getThreeScene();", + " const lights = { point: [], spot: [], spotWithMap: [], directional: [], shadows: 0 };", + " if (!threeScene) return lights;", + " const walk = (object) => {", + " if (!object.visible) return;", + " if (object.isLight) {", + " const colour = object.color ? object.color.getHexString() : '';", + " if (object.isSpotLight) lights[object.map ? 'spotWithMap' : 'spot'].push(colour);", + " else if (object.isPointLight) lights.point.push(colour);", + " else if (object.isDirectionalLight) lights.directional.push(colour);", + " if (object.castShadow) lights.shadows++;", + " }", + " for (const child of object.children) walk(child);", + " };", + " walk(threeScene);", + " return lights;", + "};", + "const describe = (l) => JSON.stringify({ point: l.point.length,", + " spot: l.spot.length, spotWithMap: l.spotWithMap.length,", + " directional: l.directional.length, shadows: l.shadows });", + "", + "await harness.goToScene('Point Lights');", + "await harness.stepFrames(10);", + "", + "const lights = readLights();", + "console.log('lights=' + describe(lights));", + "console.log('pointColours=' + JSON.stringify(lights.point.slice().sort()));", + "harness.assert(lights.point.length === 3,", + " 'All three point lights reach the renderer (found ' + lights.point.length + ').');", + "harness.assert(lights.spot.length === 0, 'No spot light is lit in this scene.');", + "harness.assert(lights.shadows >= 1, 'At least one point light casts a shadow.');", + "// Each light is a different object with its own colour: if the colour never", + "// reached the renderer they would all come back white.", + "harness.assert(new Set(lights.point).size === 3,", + " 'Each point light keeps its own colour (' + lights.point.join(', ') + ').');", + "await harness.takeScreenshot('three coloured point lights');", + "", + "// The budget is recomputed every frame, so a light that moves must stay lit.", + "const warm = harness.getObjects('PointLightWarm')[0];", + "harness.setObjectPosition(warm.id, 0, -680, 220);", + "await harness.stepFrames(10);", + "harness.assert(readLights().point.length === 3,", + " 'The point lights stay lit after one of them moves.');", + "await harness.takeScreenshot('after moving one point light');" + ] + }, + { + "name": "Spot lights light the scene and cast shadows", + "description": "Both spot lights reach the 3D renderer as spot lights, keep their own colour, and cast shadows.", + "type": "gameplay", + "source": [ + "// Look at the lights the 3D renderer can actually see, the way three.js does:", + "// hidden subtrees are skipped entirely, so this agrees with what the shaders", + "// were given rather than with what is in the scene graph.", + "const readLights = () => {", + " const threeScene = harness.getRuntimeLayer('').getRenderer().getThreeScene();", + " const lights = { point: [], spot: [], spotWithMap: [], directional: [], shadows: 0 };", + " if (!threeScene) return lights;", + " const walk = (object) => {", + " if (!object.visible) return;", + " if (object.isLight) {", + " const colour = object.color ? object.color.getHexString() : '';", + " if (object.isSpotLight) lights[object.map ? 'spotWithMap' : 'spot'].push(colour);", + " else if (object.isPointLight) lights.point.push(colour);", + " else if (object.isDirectionalLight) lights.directional.push(colour);", + " if (object.castShadow) lights.shadows++;", + " }", + " for (const child of object.children) walk(child);", + " };", + " walk(threeScene);", + " return lights;", + "};", + "const describe = (l) => JSON.stringify({ point: l.point.length,", + " spot: l.spot.length, spotWithMap: l.spotWithMap.length,", + " directional: l.directional.length, shadows: l.shadows });", + "", + "await harness.goToScene('Spot Lights');", + "await harness.stepFrames(10);", + "", + "const lights = readLights();", + "console.log('lights=' + describe(lights));", + "const spots = lights.spot.concat(lights.spotWithMap);", + "harness.assert(spots.length === 2,", + " 'Both spot lights reach the renderer (found ' + spots.length + ').');", + "harness.assert(lights.point.length === 0, 'No point light is lit in this scene.');", + "harness.assert(lights.shadows >= 1, 'At least one spot light casts a shadow.');", + "harness.assert(new Set(spots).size === 2,", + " 'Each spot light keeps its own colour (' + spots.join(', ') + ').');", + "await harness.takeScreenshot('two spot lights with shadows');" + ] + }, + { + "name": "Many lights stay within a stable budget", + "description": "With far more lights placed than a device should light at once, the number of lights of each kind that are lit stays the same from frame to frame. A number that changes makes the renderer compile a new shader, which drops a frame.", + "type": "gameplay", + "source": [ + "// Look at the lights the 3D renderer can actually see, the way three.js does:", + "// hidden subtrees are skipped entirely, so this agrees with what the shaders", + "// were given rather than with what is in the scene graph.", + "const readLights = () => {", + " const threeScene = harness.getRuntimeLayer('').getRenderer().getThreeScene();", + " const lights = { point: [], spot: [], spotWithMap: [], directional: [], shadows: 0 };", + " if (!threeScene) return lights;", + " const walk = (object) => {", + " if (!object.visible) return;", + " if (object.isLight) {", + " const colour = object.color ? object.color.getHexString() : '';", + " if (object.isSpotLight) lights[object.map ? 'spotWithMap' : 'spot'].push(colour);", + " else if (object.isPointLight) lights.point.push(colour);", + " else if (object.isDirectionalLight) lights.directional.push(colour);", + " if (object.castShadow) lights.shadows++;", + " }", + " for (const child of object.children) walk(child);", + " };", + " walk(threeScene);", + " return lights;", + "};", + "const describe = (l) => JSON.stringify({ point: l.point.length,", + " spot: l.spot.length, spotWithMap: l.spotWithMap.length,", + " directional: l.directional.length, shadows: l.shadows });", + "", + "await harness.goToScene('Many Lights');", + "await harness.stepFrames(10);", + "", + "const placed = harness.getObjects('ManyLightShadow')", + " .concat(harness.getObjects('ManyLightWarm'))", + " .concat(harness.getObjects('ManyLightCool'));", + "harness.assert(placed.length === 24, '24 point lights are placed (' + placed.length + ').');", + "", + "const first = readLights();", + "console.log('lights=' + describe(first));", + "harness.assert(first.point.length > 0, 'Some of the point lights are lit.');", + "harness.assert(first.point.length <= placed.length,", + " 'No more lights are lit than were placed (' + first.point.length + ' of ' + placed.length + ').');", + "await harness.takeScreenshot('many point lights at once');", + "", + "// Move lights around: which ones are nearest changes, but how many of each", + "// kind are lit must not - that is what keeps the shaders from recompiling.", + "const seen = new Set();", + "for (let step = 0; step < 5; step++) {", + " harness.setObjectPosition(placed[step].id, -700 + step * 300, -500 + step * 220, 150);", + " await harness.stepFrames(3);", + " const l = readLights();", + " seen.add(l.point.length + '/' + l.spot.length + '/' + l.shadows);", + "}", + "console.log('combinations=' + JSON.stringify([...seen]));", + "harness.assert(seen.size === 1,", + " 'The number of lights of each kind stays the same while lights move (saw ' +", + " seen.size + ': ' + [...seen].join(', ') + ').');", + "await harness.takeScreenshot('budget held while lights moved');" + ] + } + ] +} diff --git a/examples/3d-light-playground/README.md b/examples/3d-light-playground/README.md new file mode 100644 index 000000000..45d3cee4b --- /dev/null +++ b/examples/3d-light-playground/README.md @@ -0,0 +1,31 @@ +# 3D Light Playground + +A playground for GDevelop's 3D lights, with one scene per kind of light so each can +be looked at on its own. + +- **Sun and Shadows** — the directional light of the scene, casting shadows across + the ground, the crates and the backdrop. +- **Point Lights** — three point lights, each with its own colour, lighting an + otherwise dark scene and casting shadows away from themselves. +- **Spot Lights** — two spot lights aimed at the ground, showing the cone, the + falloff towards its edge, and the shadows underneath. +- **Many Lights** — twenty-four point lights at once, more than a device should + light in one frame, to see how the budget behaves. + +Point and spot lights come from the **3D lights** extension. The scene's +directional and ambient lights are effects on the layer, which is where to find +them in the editor. + +Two things worth knowing if you build on this: + +- A light's colour, intensity and range belong to the **object**, not to the + instance. GDevelop reads only the animation from an instance's properties for + an events-based object, so placing two lights of different colours means two + objects, not one object placed twice. +- A spot light shines along its own **+X** axis. Point it at the ground by giving + the instance a Y rotation of 90. + +The gameplay tests of this project check that each kind of light reaches the +renderer, keeps the colour it was given, and casts shadows — and take screenshots +of every scene, so a change in how lighting looks shows up as a change in the +pictures. diff --git a/examples/3d-light-playground/assets/LightPlaygroundSurface.png b/examples/3d-light-playground/assets/LightPlaygroundSurface.png new file mode 100644 index 0000000000000000000000000000000000000000..2ec0cc395b8995127552be97e1d1814204e75fd2 GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGJMI8PVHkcwMxuW#gKP~dQN{5Jox zZG-5V0~IP|X4C&XJ6ic?QT)9-+x`QULcwMR+XN>VhoQ=We}({z*C2O+S&aq8`@p_{ YVZ|ZSOEZ;c%m#^hy85}Sb4q9e0I4M>p#T5? literal 0 HcmV?d00001 From d0c44076a34c8ff728212a9360ea3d075f082c7b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Aug 2026 16:18:43 +0000 Subject: [PATCH 2/6] Make the light playground's crowded scene an actual regression test The crowded scene held twenty-four point lights, and its test passed with the light budget both before and after being fixed - so it guarded nothing. Two reasons, both now addressed: A single kind of light is pinned to the cap by any budget, shared or per kind, so the counts never moved. The scene now mixes point and spot lights (26 lights, two kinds three.js counts separately), which is the shape where a shared budget keeps the total pinned while letting the split drift. And moving one light at a time barely perturbs the distance ordering. The test now sweeps the camera across the field, the way a player would, which reshuffles which lights are nearest entirely. Checked against both versions of the extension, same project, same run: v1.0.2 three splits seen - 11/9, 10/10, 11/9 point/spot with differing shadow counts - 20 of 26 lights lit FAILS v1.2.1 one split - 14/12 with 4 shadows - 26 of 26 lit PASSES The assertion is on the counts holding still, not on how many lights are lit: lighting more of them is an intended consequence of budgeting per kind, not a defect, so the test reports it rather than requiring it. The scene dropped the spot lights that project a texture, which would have been a third kind. Setting that resource property in the object's content does not reach the property getter at runtime - `_getImage()` comes back empty while Color and Intensity from the same content arrive fine - so it could not be set up declaratively here. Not needed to reproduce the drift, so it is left out. --- .../3d-light-playground.json | 387 +++++++++++------- examples/3d-light-playground/README.md | 18 +- 2 files changed, 248 insertions(+), 157 deletions(-) diff --git a/examples/3d-light-playground/3d-light-playground.json b/examples/3d-light-playground/3d-light-playground.json index a2df3f922..50de4f9d7 100644 --- a/examples/3d-light-playground/3d-light-playground.json +++ b/examples/3d-light-playground/3d-light-playground.json @@ -1730,8 +1730,8 @@ { "b": 10, "disableInputWhenNotFocused": true, - "mangledName": "Many_Lights", - "name": "Many Lights", + "mangledName": "Mixed_Lights", + "name": "Mixed Lights", "r": 5, "standardSortMethod": true, "stopSoundsOnStartup": true, @@ -1764,7 +1764,7 @@ "layer": "", "locked": false, "name": "Ground", - "persistentUuid": "6697f33c-05c5-ea0e-56ea-dd7b9637f87b", + "persistentUuid": "22dfa7b3-d332-db2e-6f60-a5f0c98ad1cf", "width": 2400, "x": -1200, "y": -1200, @@ -1785,7 +1785,7 @@ "layer": "", "locked": false, "name": "Pillar", - "persistentUuid": "22dfa7b3-d332-db2e-6f60-a5f0c98ad1cf", + "persistentUuid": "e875554f-6f48-045f-77dd-e007ec681281", "width": 110, "x": -55, "y": -55, @@ -1806,7 +1806,7 @@ "layer": "", "locked": false, "name": "Backdrop", - "persistentUuid": "e875554f-6f48-045f-77dd-e007ec681281", + "persistentUuid": "fb74f53b-2e94-d59d-a8b4-b317842a75d8", "width": 900, "x": -450, "y": -900, @@ -1827,7 +1827,7 @@ "layer": "", "locked": false, "name": "Crate", - "persistentUuid": "fb74f53b-2e94-d59d-a8b4-b317842a75d8", + "persistentUuid": "b67acebc-c8ac-e50f-c6f5-2017cd8d2107", "width": 140, "x": -520, "y": -420, @@ -1848,7 +1848,7 @@ "layer": "", "locked": false, "name": "Crate", - "persistentUuid": "b67acebc-c8ac-e50f-c6f5-2017cd8d2107", + "persistentUuid": "499db8ef-1794-f035-b870-0fbe178a8d40", "width": 140, "x": 380, "y": -480, @@ -1869,7 +1869,7 @@ "layer": "", "locked": false, "name": "Crate", - "persistentUuid": "499db8ef-1794-f035-b870-0fbe178a8d40", + "persistentUuid": "fc9913d1-5edb-39ee-d907-075d3f622cfe", "width": 140, "x": -600, "y": 320, @@ -1890,7 +1890,7 @@ "layer": "", "locked": false, "name": "Crate", - "persistentUuid": "fc9913d1-5edb-39ee-d907-075d3f622cfe", + "persistentUuid": "e08f7f62-9efd-2c0b-a0c2-9785ba5d2b37", "width": 140, "x": 460, "y": 300, @@ -1911,7 +1911,7 @@ "layer": "", "locked": false, "name": "Crate", - "persistentUuid": "e08f7f62-9efd-2c0b-a0c2-9785ba5d2b37", + "persistentUuid": "eec06225-61ec-4647-c12b-ae6b1f1b98a9", "width": 140, "x": -80, "y": -640, @@ -1932,7 +1932,7 @@ "layer": "", "locked": false, "name": "Crate", - "persistentUuid": "eec06225-61ec-4647-c12b-ae6b1f1b98a9", + "persistentUuid": "d976a244-aadd-5742-bba9-d35ab2568465", "width": 140, "x": -60, "y": 480, @@ -1952,32 +1952,11 @@ "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightShadow", - "persistentUuid": "d976a244-aadd-5742-bba9-d35ab2568465", - "width": 0, - "x": 340, - "y": 0, - "z": 150, - "zOrder": 1, - "numberProperties": [], - "stringProperties": [], - "initialVariables": [] - }, - { - "angle": 0, - "rotationX": 0, - "rotationY": 0, - "customSize": false, - "depth": 0, - "height": 0, - "keepRatio": true, - "layer": "", - "locked": false, - "name": "ManyLightShadow", + "name": "MixedPoint", "persistentUuid": "ab1970e9-5eb4-3852-b150-26d552a2d3f3", "width": 0, - "x": 240, - "y": 240, + "x": 300, + "y": 0, "z": 150, "zOrder": 1, "numberProperties": [], @@ -1994,11 +1973,11 @@ "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightShadow", + "name": "MixedPoint", "persistentUuid": "049feb8a-a32e-1d93-a9f7-dc1a6965d462", "width": 0, - "x": 0, - "y": 340, + "x": 478, + "y": 250, "z": 150, "zOrder": 1, "numberProperties": [], @@ -2015,11 +1994,11 @@ "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightShadow", + "name": "MixedPoint", "persistentUuid": "34214cda-67bc-c49b-4f68-11d1aae4bb51", "width": 0, - "x": -240, - "y": 240, + "x": 443, + "y": 641, "z": 150, "zOrder": 1, "numberProperties": [], @@ -2036,11 +2015,11 @@ "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightShadow", + "name": "MixedPoint", "persistentUuid": "591288f4-88f2-350f-f301-f579e673a03c", "width": 0, - "x": -340, - "y": 0, + "x": 122, + "y": 1012, "z": 150, "zOrder": 1, "numberProperties": [], @@ -2057,11 +2036,11 @@ "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightShadow", + "name": "MixedPoint", "persistentUuid": "94ff4de1-22c7-0196-d87b-71cc119aff4c", "width": 0, - "x": -240, - "y": -240, + "x": -106, + "y": 280, "z": 150, "zOrder": 1, "numberProperties": [], @@ -2078,11 +2057,11 @@ "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightWarm", + "name": "MixedPoint", "persistentUuid": "e0e9e274-4cc1-3019-a049-267e669c7d64", "width": 0, - "x": 0, - "y": -340, + "x": -404, + "y": 358, "z": 150, "zOrder": 1, "numberProperties": [], @@ -2099,11 +2078,11 @@ "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightCool", + "name": "MixedPoint", "persistentUuid": "ebf66795-7cf2-bdf4-23d7-79400bba5e10", "width": 0, - "x": 240, - "y": -240, + "x": -757, + "y": 186, "z": 150, "zOrder": 1, "numberProperties": [], @@ -2120,11 +2099,11 @@ "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightWarm", + "name": "MixedPoint", "persistentUuid": "9a702679-4bc8-5b8f-9e12-14726f321298", "width": 0, - "x": 630, - "y": 0, + "x": -990, + "y": -244, "z": 150, "zOrder": 1, "numberProperties": [], @@ -2141,11 +2120,11 @@ "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightCool", + "name": "MixedPoint", "persistentUuid": "244be702-6b6a-8a42-a284-f7164d8f8b9b", "width": 0, - "x": 445, - "y": 445, + "x": -224, + "y": -198, "z": 150, "zOrder": 1, "numberProperties": [], @@ -2162,11 +2141,11 @@ "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightWarm", + "name": "MixedPoint", "persistentUuid": "1392a405-3592-c7ea-b7c2-55b538b9a0d7", "width": 0, - "x": 0, - "y": 630, + "x": -191, + "y": -504, "z": 150, "zOrder": 1, "numberProperties": [], @@ -2183,11 +2162,11 @@ "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightCool", + "name": "MixedPoint", "persistentUuid": "a980b446-1fc5-7c4a-85d7-8a2b01912fab", "width": 0, - "x": -445, - "y": 445, + "x": 94, + "y": -774, "z": 150, "zOrder": 1, "numberProperties": [], @@ -2204,11 +2183,11 @@ "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightWarm", + "name": "MixedPoint", "persistentUuid": "23502fcf-1c47-165a-ec9b-f1060770a131", "width": 0, - "x": -630, - "y": 0, + "x": 579, + "y": -839, "z": 150, "zOrder": 1, "numberProperties": [], @@ -2225,11 +2204,11 @@ "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightCool", + "name": "MixedPointShadow", "persistentUuid": "ceb10a30-edd9-540c-5f36-0c6e30f249cf", "width": 0, - "x": -445, - "y": -445, + "x": 265, + "y": -139, "z": 150, "zOrder": 1, "numberProperties": [], @@ -2246,11 +2225,11 @@ "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightWarm", + "name": "MixedPointShadow", "persistentUuid": "d1d1da37-9c09-a782-3b97-44dea2cb51d0", "width": 0, - "x": 0, - "y": -630, + "x": 540, + "y": 0, "z": 150, "zOrder": 1, "numberProperties": [], @@ -2260,19 +2239,19 @@ { "angle": 0, "rotationX": 0, - "rotationY": 0, + "rotationY": 90, "customSize": false, "depth": 0, "height": 0, "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightCool", + "name": "MixedSpot", "persistentUuid": "d71e3361-7ee3-c6ac-efad-bd4053dbd4e5", "width": 0, - "x": 445, - "y": -445, - "z": 150, + "x": 690, + "y": 362, + "z": 560, "zOrder": 1, "numberProperties": [], "stringProperties": [], @@ -2281,19 +2260,19 @@ { "angle": 0, "rotationX": 0, - "rotationY": 0, + "rotationY": 90, "customSize": false, "depth": 0, "height": 0, "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightWarm", + "name": "MixedSpot", "persistentUuid": "9c35be70-c8e7-2aa3-2d9f-e980b98a31d5", "width": 0, - "x": 920, - "y": 0, - "z": 150, + "x": 579, + "y": 839, + "z": 560, "zOrder": 1, "numberProperties": [], "stringProperties": [], @@ -2302,19 +2281,19 @@ { "angle": 0, "rotationX": 0, - "rotationY": 0, + "rotationY": 90, "customSize": false, "depth": 0, "height": 0, "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightCool", + "name": "MixedSpot", "persistentUuid": "b53c7850-2186-23f2-eaae-05abd0497c35", "width": 0, - "x": 650, - "y": 650, - "z": 150, + "x": 36, + "y": 297, + "z": 560, "zOrder": 1, "numberProperties": [], "stringProperties": [], @@ -2323,19 +2302,19 @@ { "angle": 0, "rotationX": 0, - "rotationY": 0, + "rotationY": 90, "customSize": false, "depth": 0, "height": 0, "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightWarm", + "name": "MixedSpot", "persistentUuid": "7d1e615d-ce68-8d3f-dfd9-7b6369a79d3c", "width": 0, - "x": 0, - "y": 920, - "z": 150, + "x": -191, + "y": 504, + "z": 560, "zOrder": 1, "numberProperties": [], "stringProperties": [], @@ -2344,19 +2323,19 @@ { "angle": 0, "rotationX": 0, - "rotationY": 0, + "rotationY": 90, "customSize": false, "depth": 0, "height": 0, "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightCool", + "name": "MixedSpot", "persistentUuid": "421cb74f-3386-8ef1-9c65-d043d302e8cb", "width": 0, - "x": -650, - "y": 650, - "z": 150, + "x": -583, + "y": 517, + "z": 560, "zOrder": 1, "numberProperties": [], "stringProperties": [], @@ -2365,19 +2344,19 @@ { "angle": 0, "rotationX": 0, - "rotationY": 0, + "rotationY": 90, "customSize": false, "depth": 0, "height": 0, "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightWarm", + "name": "MixedSpot", "persistentUuid": "d29b0ff9-1509-feb8-e22a-578506534d01", "width": 0, - "x": -920, - "y": 0, - "z": 150, + "x": -990, + "y": 244, + "z": 560, "zOrder": 1, "numberProperties": [], "stringProperties": [], @@ -2386,19 +2365,19 @@ { "angle": 0, "rotationX": 0, - "rotationY": 0, + "rotationY": 90, "customSize": false, "depth": 0, "height": 0, "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightCool", + "name": "MixedSpot", "persistentUuid": "e60591b7-6b99-e7f7-845d-cf586a028a6e", "width": 0, - "x": -650, - "y": -650, - "z": 150, + "x": -291, + "y": -71, + "z": 560, "zOrder": 1, "numberProperties": [], "stringProperties": [], @@ -2407,19 +2386,19 @@ { "angle": 0, "rotationX": 0, - "rotationY": 0, + "rotationY": 90, "customSize": false, "depth": 0, "height": 0, "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightWarm", + "name": "MixedSpot", "persistentUuid": "0d7a9528-5bac-d515-6028-070055d67a6b", "width": 0, - "x": 0, - "y": -920, - "z": 150, + "x": -404, + "y": -358, + "z": 560, "zOrder": 1, "numberProperties": [], "stringProperties": [], @@ -2428,19 +2407,82 @@ { "angle": 0, "rotationX": 0, - "rotationY": 0, + "rotationY": 90, "customSize": false, "depth": 0, "height": 0, "keepRatio": true, "layer": "", "locked": false, - "name": "ManyLightCool", + "name": "MixedSpot", "persistentUuid": "cd93fb9c-82ec-8285-13a7-d9ecb08470d9", "width": 0, - "x": 650, - "y": -650, - "z": 150, + "x": -276, + "y": -729, + "z": 560, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 90, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "MixedSpot", + "persistentUuid": "0856657d-867f-48c5-fd6b-a93e05a3c038", + "width": 0, + "x": 122, + "y": -1012, + "z": 560, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 90, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "MixedSpotShadow", + "persistentUuid": "13bb79a9-9155-795c-1ac2-04512763cfe7", + "width": 0, + "x": 170, + "y": -246, + "z": 560, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 90, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "MixedSpotShadow", + "persistentUuid": "79357827-b406-c6e4-9c08-45e541722891", + "width": 0, + "x": 478, + "y": -250, + "z": 560, "zOrder": 1, "numberProperties": [], "stringProperties": [], @@ -2606,61 +2648,87 @@ }, { "assetStoreId": "", - "name": "ManyLightShadow", + "name": "MixedPoint", "persistentUuid": "d9b5850c-4ba6-6924-bb12-3a71d641af26", "type": "Light3D::PointLight3D", "variables": [], "effects": [], "behaviors": [], "content": { - "Color": "255;190;120", - "Intensity": 1.6, + "Color": "255;170;110", + "Intensity": 1.5, "Decay": 2, - "IsCastingShadow": true, + "IsCastingShadow": false, "ShadowQuality": "Medium", "ShadowCameraNearPlane": 20, "ShadowCameraFarPlane": 2000, - "Range": 800, + "Range": 750, "ShadowBias": 0 } }, { "assetStoreId": "", - "name": "ManyLightWarm", + "name": "MixedPointShadow", "persistentUuid": "3f5d7912-6537-ef1f-fe3d-b050d6dbd394", "type": "Light3D::PointLight3D", "variables": [], "effects": [], "behaviors": [], "content": { - "Color": "255;120;80", - "Intensity": 1.4, + "Color": "255;210;150", + "Intensity": 1.6, "Decay": 2, - "IsCastingShadow": false, + "IsCastingShadow": true, "ShadowQuality": "Medium", "ShadowCameraNearPlane": 20, "ShadowCameraFarPlane": 2000, - "Range": 700, + "Range": 800, "ShadowBias": 0 } }, { "assetStoreId": "", - "name": "ManyLightCool", + "name": "MixedSpot", "persistentUuid": "a13deb4c-0901-17be-ca1a-d875b1e16807", - "type": "Light3D::PointLight3D", + "type": "Light3D::SpotLight3D", "variables": [], "effects": [], "behaviors": [], "content": { - "Color": "110;200;255", - "Intensity": 1.4, + "Color": "150;205;255", + "ConeAngle": 40, + "Intensity": 5, + "Smoothing": 1, "Decay": 2, + "Image": "", "IsCastingShadow": false, "ShadowQuality": "Medium", "ShadowCameraNearPlane": 20, "ShadowCameraFarPlane": 2000, - "Range": 700, + "ConeLength": 2000, + "ShadowBias": 0 + } + }, + { + "assetStoreId": "", + "name": "MixedSpotShadow", + "persistentUuid": "6697f33c-05c5-ea0e-56ea-dd7b9637f87b", + "type": "Light3D::SpotLight3D", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Color": "190;225;255", + "ConeAngle": 40, + "Intensity": 5, + "Smoothing": 1, + "Decay": 2, + "Image": "", + "IsCastingShadow": true, + "ShadowQuality": "Medium", + "ShadowCameraNearPlane": 20, + "ShadowCameraFarPlane": 2000, + "ConeLength": 2000, "ShadowBias": 0 } } @@ -2681,13 +2749,16 @@ "objectName": "Backdrop" }, { - "objectName": "ManyLightShadow" + "objectName": "MixedPoint" + }, + { + "objectName": "MixedPointShadow" }, { - "objectName": "ManyLightWarm" + "objectName": "MixedSpot" }, { - "objectName": "ManyLightCool" + "objectName": "MixedSpotShadow" } ] }, @@ -7861,8 +7932,8 @@ ] }, { - "name": "Many lights stay within a stable budget", - "description": "With far more lights placed than a device should light at once, the number of lights of each kind that are lit stays the same from frame to frame. A number that changes makes the renderer compile a new shader, which drops a frame.", + "name": "Mixed light kinds stay within a stable budget", + "description": "With more lights placed than one budget can hold, and of three kinds the shaders count separately (point, spot, spot with a projected texture), the number of each kind that is lit must stay the same while the lights move. A count that moves makes the renderer compile a new shader, which drops a frame.", "type": "gameplay", "source": [ "// Look at the lights the 3D renderer can actually see, the way three.js does:", @@ -7890,35 +7961,41 @@ " spot: l.spot.length, spotWithMap: l.spotWithMap.length,", " directional: l.directional.length, shadows: l.shadows });", "", - "await harness.goToScene('Many Lights');", + "await harness.goToScene('Mixed Lights');", "await harness.stepFrames(10);", "", - "const placed = harness.getObjects('ManyLightShadow')", - " .concat(harness.getObjects('ManyLightWarm'))", - " .concat(harness.getObjects('ManyLightCool'));", - "harness.assert(placed.length === 24, '24 point lights are placed (' + placed.length + ').');", + "const names = ['MixedPoint', 'MixedPointShadow', 'MixedSpot', 'MixedSpotShadow'];", + "const placed = names.reduce((all, n) => all.concat(harness.getObjects(n)), []);", + "harness.assert(placed.length === 26, '26 lights are placed (' + placed.length + ').');", "", "const first = readLights();", "console.log('lights=' + describe(first));", - "harness.assert(first.point.length > 0, 'Some of the point lights are lit.');", - "harness.assert(first.point.length <= placed.length,", - " 'No more lights are lit than were placed (' + first.point.length + ' of ' + placed.length + ').');", - "await harness.takeScreenshot('many point lights at once');", + "harness.assert(first.point.length > 0, 'Point lights are lit.');", + "harness.assert(first.spot.length > 0, 'Spot lights are lit.');", + "console.log('litTotal=' + (first.point.length + first.spot.length) +", + " ' of ' + placed.length + ' placed');", + "await harness.takeScreenshot('mixed light kinds at once');", "", - "// Move lights around: which ones are nearest changes, but how many of each", - "// kind are lit must not - that is what keeps the shaders from recompiling.", + "// Sweep the camera across the field, which is what a player does and what", + "// reshuffles the whole distance ordering: which lights are nearest changes", + "// completely. How many of each kind are lit must not change with it - a", + "// count that moves makes the renderer compile another shader.", + "const scene = harness.getCurrentRuntimeScene();", "const seen = new Set();", - "for (let step = 0; step < 5; step++) {", - " harness.setObjectPosition(placed[step].id, -700 + step * 300, -500 + step * 220, 150);", + "for (let step = 0; step < 8; step++) {", + " const t = step / 7;", + " gdjs.evtTools.camera.setCameraX(scene, -900 + t * 1800, '', 0);", + " gdjs.evtTools.camera.setCameraY(scene, 1450 - t * 700, '', 0);", " await harness.stepFrames(3);", " const l = readLights();", - " seen.add(l.point.length + '/' + l.spot.length + '/' + l.shadows);", + " seen.add(l.point.length + '/' + l.spot.length + '/' + l.spotWithMap.length +", + " '/' + l.shadows);", "}", "console.log('combinations=' + JSON.stringify([...seen]));", "harness.assert(seen.size === 1,", - " 'The number of lights of each kind stays the same while lights move (saw ' +", + " 'The number of lights of each kind stays the same as the camera moves (saw ' +", " seen.size + ': ' + [...seen].join(', ') + ').');", - "await harness.takeScreenshot('budget held while lights moved');" + "await harness.takeScreenshot('budget held while the camera swept');" ] } ] diff --git a/examples/3d-light-playground/README.md b/examples/3d-light-playground/README.md index 45d3cee4b..fae287817 100644 --- a/examples/3d-light-playground/README.md +++ b/examples/3d-light-playground/README.md @@ -9,8 +9,8 @@ be looked at on its own. otherwise dark scene and casting shadows away from themselves. - **Spot Lights** — two spot lights aimed at the ground, showing the cone, the falloff towards its edge, and the shadows underneath. -- **Many Lights** — twenty-four point lights at once, more than a device should - light in one frame, to see how the budget behaves. +- **Mixed Lights** — twenty-six lights of two kinds at once (point and spot), + more than one shared budget can hold, to see how the budget behaves. Point and spot lights come from the **3D lights** extension. The scene's directional and ambient lights are effects on the layer, which is where to find @@ -29,3 +29,17 @@ The gameplay tests of this project check that each kind of light reaches the renderer, keeps the colour it was given, and casts shadows — and take screenshots of every scene, so a change in how lighting looks shows up as a change in the pictures. + +The **Mixed Lights** test is the one that guards a real regression. It sweeps the +camera across the field, which changes which lights are nearest, and asserts that +the number of lights *of each kind* that are lit does not change with it. three.js +compiles those counts into its shaders, so a count that moves costs a shader +compilation and a dropped frame. Budgeting all kinds from one shared pool keeps +the total pinned while letting the split drift, and this test fails when it does: +with the 3D lights extension at v1.0.2 it sees three different splits +(`11/9`, `10/10`, `11/9` point/spot with different shadow counts); from v1.1.0 on +it sees one. + +Note that the crowded scene has to mix *kinds* for this to bite. A scene of +twenty-four point lights passes either way, because a single kind is pinned to the +cap by any budget — which is why this scene has both point and spot lights. From 5caa6b1143c7f20df61fe6acf6fd313802b95f58 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 11:42:00 +0000 Subject: [PATCH 3/6] Add a single light kind budget test to the light playground A new "Crowded Point Lights" scene places eight shadow casting point lights and nothing else, so one kind holds the whole device budget. Its test pins what that scene may lose: all eight lit, at least three casting a shadow - three being the floor across conforming WebGL2 devices - and both counts holding still while the camera sweeps. Note that this assertion cannot fail on hardware reporting 32 fragment texture units, which is what the CI runner has: there the budget never binds below the four casters the game asks for, whether it is split per used kind or three ways. It fails on a device reporting 16, where splitting three ways leaves two. Also correct the mixed scene's description: its spot lights have no projected texture, so it exercises two kinds the shaders count separately, not three. Light3D is synced to 1.3.0, which is what makes the new test pass on a 16 texture unit device. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011chSAxDN9jSEwWkowwzDBm --- .../3d-light-playground.json | 844 +++++++++++++++++- 1 file changed, 804 insertions(+), 40 deletions(-) diff --git a/examples/3d-light-playground/3d-light-playground.json b/examples/3d-light-playground/3d-light-playground.json index 50de4f9d7..ba563d211 100644 --- a/examples/3d-light-playground/3d-light-playground.json +++ b/examples/3d-light-playground/3d-light-playground.json @@ -2826,6 +2826,653 @@ } ], "behaviorsSharedData": [] + }, + { + "b": 10, + "disableInputWhenNotFocused": true, + "mangledName": "Crowded_Point_Lights", + "name": "Crowded Point Lights", + "r": 5, + "standardSortMethod": true, + "stopSoundsOnStartup": true, + "title": "", + "v": 6, + "uiSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 32, + "gridHeight": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": true, + "zoomFactor": 0.35, + "windowMask": false + }, + "objectsGroups": [], + "variables": [], + "instances": [ + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 20, + "height": 2400, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Ground", + "persistentUuid": "22dfa7b3-d332-db2e-6f60-a5f0c98ad1cf", + "width": 2400, + "x": -1200, + "y": -1200, + "z": -20, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 460, + "height": 110, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Pillar", + "persistentUuid": "e875554f-6f48-045f-77dd-e007ec681281", + "width": 110, + "x": -55, + "y": -55, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 320, + "height": 60, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Backdrop", + "persistentUuid": "fb74f53b-2e94-d59d-a8b4-b317842a75d8", + "width": 900, + "x": -450, + "y": -900, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "b67acebc-c8ac-e50f-c6f5-2017cd8d2107", + "width": 140, + "x": -520, + "y": -420, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "499db8ef-1794-f035-b870-0fbe178a8d40", + "width": 140, + "x": 380, + "y": -480, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "fc9913d1-5edb-39ee-d907-075d3f622cfe", + "width": 140, + "x": -600, + "y": 320, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "e08f7f62-9efd-2c0b-a0c2-9785ba5d2b37", + "width": 140, + "x": 460, + "y": 300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "eec06225-61ec-4647-c12b-ae6b1f1b98a9", + "width": 140, + "x": -80, + "y": -640, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": true, + "depth": 140, + "height": 140, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "Crate", + "persistentUuid": "d976a244-aadd-5742-bba9-d35ab2568465", + "width": 140, + "x": -60, + "y": 480, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "CrowdedPointShadow", + "persistentUuid": "41a578a3-0e8e-4af5-81ce-2810d7c55432", + "width": 0, + "x": 620, + "y": 0, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "CrowdedPointShadow", + "persistentUuid": "08c119c4-8fc7-4232-a70c-d9c43181f2c1", + "width": 0, + "x": 438, + "y": 438, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "CrowdedPointShadow", + "persistentUuid": "aceb6eb8-0c1c-473b-bfdf-cdffdccfd9cd", + "width": 0, + "x": 0, + "y": 620, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "CrowdedPointShadow", + "persistentUuid": "ee7abeab-387e-4761-a427-b7e05e50442a", + "width": 0, + "x": -438, + "y": 438, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "CrowdedPointShadow", + "persistentUuid": "d4e10e44-ae1d-4eae-a1a6-102eebd60e48", + "width": 0, + "x": -620, + "y": 0, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "CrowdedPointShadow", + "persistentUuid": "d6e2e318-0c7f-4425-bf35-48704f62f167", + "width": 0, + "x": -438, + "y": -438, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "CrowdedPointShadow", + "persistentUuid": "dbbe65db-8b5b-45ac-8faa-9ed8109abb84", + "width": 0, + "x": 0, + "y": -620, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "rotationX": 0, + "rotationY": 0, + "customSize": false, + "depth": 0, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": false, + "name": "CrowdedPointShadow", + "persistentUuid": "cdfc7464-a19f-495f-ae6b-e6a3b404784d", + "width": 0, + "x": 438, + "y": -438, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "objects": [ + { + "assetStoreId": "", + "name": "Ground", + "persistentUuid": "e46f1434-2c55-2bca-2ccf-742ed8e26b67", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 2400, + "height": 2400, + "depth": 20, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "170;175;180", + "isCastingShadow": false, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "Crate", + "persistentUuid": "df5c6f4b-aedc-6655-8ef5-10c3d9b3294a", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 140, + "height": 140, + "depth": 140, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "196;146;92", + "isCastingShadow": true, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "Pillar", + "persistentUuid": "bcda4ad2-b8bf-a7ee-c150-3cb4ff8649d2", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 110, + "height": 110, + "depth": 460, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "205;208;212", + "isCastingShadow": true, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "Backdrop", + "persistentUuid": "405800cc-89d5-f501-5a8d-23977b035d4d", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 900, + "height": 60, + "depth": 320, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "StandardWithoutMetalness", + "tint": "150;154;162", + "isCastingShadow": true, + "isReceivingShadow": true, + "frontFaceResourceName": "assets/LightPlaygroundSurface.png", + "frontFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceName": "assets/LightPlaygroundSurface.png", + "backFaceVisible": true, + "backFaceResourceRepeat": true, + "leftFaceResourceName": "assets/LightPlaygroundSurface.png", + "leftFaceVisible": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceName": "assets/LightPlaygroundSurface.png", + "rightFaceVisible": true, + "rightFaceResourceRepeat": true, + "topFaceResourceName": "assets/LightPlaygroundSurface.png", + "topFaceVisible": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceName": "assets/LightPlaygroundSurface.png", + "bottomFaceVisible": true, + "bottomFaceResourceRepeat": true + } + }, + { + "assetStoreId": "", + "name": "CrowdedPointShadow", + "persistentUuid": "c4164626-0bc3-4ec5-8e5d-33fdab7d9bcd", + "type": "Light3D::PointLight3D", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Color": "255;205;150", + "Intensity": 1.8, + "Decay": 2, + "IsCastingShadow": true, + "ShadowQuality": "Medium", + "ShadowCameraNearPlane": 20, + "ShadowCameraFarPlane": 2000, + "Range": 900, + "ShadowBias": 0 + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Ground" + }, + { + "objectName": "Crate" + }, + { + "objectName": "Pillar" + }, + { + "objectName": "Backdrop" + }, + { + "objectName": "CrowdedPointShadow" + } + ] + }, + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "// Frame the props the same way in every scene and on every run, so the", + "// screenshots the gameplay tests take can be compared between versions.", + "if (!runtimeScene.__cameraPlaced) {", + " runtimeScene.__cameraPlaced = true;", + " gdjs.evtTools.camera.setCameraX(runtimeScene, 0, '', 0);", + " gdjs.evtTools.camera.setCameraY(runtimeScene, 1450, '', 0);", + " gdjs.scene3d.camera.setCameraZ(runtimeScene, 880, '', 0);", + " gdjs.scene3d.camera.setCameraRotationX(runtimeScene, 59, '', 0);", + " gdjs.scene3d.camera.setCameraRotationY(runtimeScene, 0, '', 0);", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "layers": [ + { + "ambientLightColorR": 12, + "ambientLightColorG": 14, + "ambientLightColorB": 20, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [ + { + "effectType": "Scene3D::AmbientLight", + "name": "Ambient light", + "doubleParameters": { + "intensity": 0.05 + }, + "stringParameters": { + "color": "255;255;255" + }, + "booleanParameters": {} + } + ] + } + ], + "behaviorsSharedData": [] } ], "externalEvents": [], @@ -2843,7 +3490,7 @@ "name": "Light3D", "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/f237ae4e3b857c556846c7b2c0b132556fd4bcdeff217034b4d9c97dc1aab1d6_lightbulb-on-outline.svg", "shortDescription": "A collection of light object for 3D.", - "version": "1.2.1", + "version": "1.3.0", "description": "A collection of light object for 3D.", "origin": { "identifier": "Light3D", @@ -3518,21 +4165,24 @@ " * texture, and spends a varying on every shadow casting light. Going over what", " * the driver allows does not make the scene slower - the shader fails to link", " * and everything using that material renders black. Only 16 fragment texture", - " * units and 16 varyings are guaranteed across devices, and materials need most", + " * units and 15 varyings are guaranteed across devices, and materials need most", " * of those, so the budget is read from the device instead of being fixed.", " *", - " * It is computed once, and applied per light kind, so the numbers three.js", - " * compiles into its shaders still never move from frame to frame.", + " * The budget is shared between the light kinds a scene uses, so it is given", + " * how many of them there are rather than assuming all of them: a scene lit by", + " * point lights alone would otherwise have three quarters of its texture units", + " * held back for kinds that never show up.", " *", " * @param {THREE.WebGLRenderer | null} threeRenderer", + " * @param {number} kindCount How many light kinds the scene uses, at least one.", " * @returns {{shadowCount: number, mapCount: number, count: number}}", " */", - "function computeDeviceLightBudget(threeRenderer) {", + "function computeDeviceLightBudget(threeRenderer, kindCount) {", " // When the renderer cannot be reached, assume the guaranteed minimums", " // rather than the device this happens to be running on.", " const capabilities = (threeRenderer && threeRenderer.capabilities) || {};", " const maxTextures = capabilities.maxTextures || 16;", - " const maxVaryings = capabilities.maxVaryings || 16;", + " const maxVaryings = capabilities.maxVaryings || 15;", " const maxFragmentUniforms = capabilities.maxFragmentUniforms || 224;", "", " // Left for the material itself: colour, normal, roughness, metalness,", @@ -3545,14 +4195,11 @@ " const uniformVectorsForMaterials = 64;", " // Measured on a MeshStandardMaterial: a point light costs 4 uniform", " // vectors, a spot light 7, and casting a shadow adds around 11 more on", - " // top of either. Take the dearest of them, since which kinds a scene", - " // uses is not known here.", + " // top of either. Take the dearest of them, since a kind count says how", + " // many kinds a scene uses but not which ones.", " const uniformVectorsPerLight = 8;", " const uniformVectorsPerShadow = 12;", "", - " // Point lights, spot lights and spot lights with a projected texture.", - " const kindCount = 3;", - "", " const availableTextureUnits = Math.max(", " kindCount, maxTextures - textureUnitsForMaterials);", " const availableVaryings = Math.max(", @@ -3584,17 +4231,18 @@ "", "/**", " * A capacity that never goes past what the device can take, however high the", - " * game sets its own maximum. Read through a getter so that changing the", - " * game's maximum at runtime still takes effect.", + " * game sets its own maximum. Both sides are read through getters, so that", + " * changing the game's maximum at runtime still takes effect, and so that a", + " * device budget recomputed for more light kinds is picked up as well.", " *", " * @param {{value: number}} requested", - " * @param {number} deviceMax", + " * @param {() => number} getDeviceMax", " * @returns {{value: number}}", " */", - "function makeDeviceCappedCapacity(requested, deviceMax) {", + "function makeDeviceCappedCapacity(requested, getDeviceMax) {", " return {", " get value() {", - " return Math.min(requested.value, deviceMax);", + " return Math.min(requested.value, getDeviceMax());", " }", " };", "}", @@ -3610,25 +4258,14 @@ " .getGame()", " .getRenderer()", " .getThreeRenderer();", - " const budget = computeDeviceLightBudget(threeRenderer);", " const requestedCount = isInGameEdition", " ? editorLightCountMax : lightCountMax;", " const requestedShadowCount = isInGameEdition", " ? editorLightShadowCountMax : lightShadowCountMax;", "", - " if (budget.shadowCount < requestedShadowCount.value", - " || budget.count < requestedCount.value) {", - " logger.info(", - " 'This device limits 3D lights to ' + budget.count", - " + ' per kind (' + budget.shadowCount", - " + ' casting a shadow, ' + budget.mapCount", - " + ' projecting a texture). Going over what it reports would '", - " + 'stop the shaders from linking.'", - " );", - " }", - "", " runtimeScene.__lightManager = new LightManager(", - " requestedCount, requestedShadowCount, budget);", + " requestedCount, requestedShadowCount,", + " kindCount => computeDeviceLightBudget(threeRenderer, kindCount));", " }", " return runtimeScene.__lightManager;", "}", @@ -3735,31 +4372,83 @@ " maxCountByKind;", " /** @type {Map} Shadow caster capacity, by light kind. */", " shadowCountByKind;", + " /** @type {Set} The light kinds this scene has actually used. */", + " usedKinds = new Set();", + " /** @type {{shadowCount: number, mapCount: number, count: number}} */", + " deviceBudget;", + " /** @type {(kindCount: number) => {shadowCount: number, mapCount: number, count: number}} */", + " _computeDeviceBudget;", + " /** @type {{value: number}} */", + " _requestedCount;", + " /** @type {{value: number}} */", + " _requestedShadowCount;", "", " /**", " * @param maxCount {{value: number}} The maximum number of lit lights, for", " * each light kind, as the game asked for it.", " * @param shadowCount {{value: number}} The maximum number of shadow", " * casting lights, for each light kind, as the game asked for it.", - " * @param deviceBudget {{shadowCount: number, mapCount: number, count: number}}", - " * What the device can actually take, from `computeDeviceLightBudget`.", + " * @param computeDeviceBudget {(kindCount: number) => {shadowCount: number, mapCount: number, count: number}}", + " * What the device can take, for a given number of light kinds.", " */", - " constructor(maxCount, shadowCount, deviceBudget) {", + " constructor(maxCount, shadowCount, computeDeviceBudget) {", + " this._computeDeviceBudget = computeDeviceBudget;", + " this._requestedCount = maxCount;", + " this._requestedShadowCount = shadowCount;", + " // A scene that uses lights at all uses at least one kind of them.", + " this.deviceBudget = computeDeviceBudget(1);", + "", " // Spot lights that project a texture already spend a texture unit on", " // that texture, so they get the tighter of the two budgets.", " this.maxCountByKind = new Map([", - " ['point', makeDeviceCappedCapacity(maxCount, deviceBudget.count)],", - " ['spot', makeDeviceCappedCapacity(maxCount, deviceBudget.count)],", - " ['spotWithMap', makeDeviceCappedCapacity(", - " maxCount, Math.min(deviceBudget.count, deviceBudget.mapCount))],", + " ['point', makeDeviceCappedCapacity(", + " maxCount, () => this.deviceBudget.count)],", + " ['spot', makeDeviceCappedCapacity(", + " maxCount, () => this.deviceBudget.count)],", + " ['spotWithMap', makeDeviceCappedCapacity(maxCount, () => Math.min(", + " this.deviceBudget.count, this.deviceBudget.mapCount))],", " ]);", " this.shadowCountByKind = new Map([", - " ['point', makeDeviceCappedCapacity(shadowCount, deviceBudget.shadowCount)],", - " ['spot', makeDeviceCappedCapacity(shadowCount, deviceBudget.shadowCount)],", - " ['spotWithMap', makeDeviceCappedCapacity(shadowCount, deviceBudget.shadowCount)],", + " ['point', makeDeviceCappedCapacity(", + " shadowCount, () => this.deviceBudget.shadowCount)],", + " ['spot', makeDeviceCappedCapacity(", + " shadowCount, () => this.deviceBudget.shadowCount)],", + " ['spotWithMap', makeDeviceCappedCapacity(", + " shadowCount, () => this.deviceBudget.shadowCount)],", " ]);", " }", "", + " /**", + " * Take note that the scene uses lights of this kind, sharing the device", + " * budget between one more kind if it had not been seen before.", + " *", + " * A kind can only ever be added, never dropped: a light that goes away for", + " * a while must not give the others a wider budget, or the numbers three.js", + " * compiles into its shaders would move again.", + " *", + " * @param kind {string}", + " */", + " _useKind(kind) {", + " if (this.usedKinds.has(kind)) {", + " return;", + " }", + " this.usedKinds.add(kind);", + " this.deviceBudget = this._computeDeviceBudget(this.usedKinds.size);", + "", + " if (this.deviceBudget.count < this._requestedCount.value", + " || this.deviceBudget.shadowCount", + " < this._requestedShadowCount.value) {", + " logger.info(", + " 'This device limits 3D lights to ' + this.deviceBudget.count", + " + ' per kind (' + this.deviceBudget.shadowCount", + " + ' casting a shadow, ' + this.deviceBudget.mapCount", + " + ' projecting a texture), now that ' + this.usedKinds.size", + " + ' kind(s) of light share its budget. Going over what it '", + " + 'reports would stop the shaders from linking.'", + " );", + " }", + " }", + "", " clear() {", " for (const list of this.visibleLists.values()) {", " list.clear();", @@ -3811,6 +4500,7 @@ " applyVisibilityAndShadow(object, distance) {", " const light = object.__light3D;", " const kind = LightManager.getLightKind(light);", + " this._useKind(kind);", "", " if (object._getIsCastingShadow()) {", " LightManager._getListForKind(", @@ -7933,7 +8623,7 @@ }, { "name": "Mixed light kinds stay within a stable budget", - "description": "With more lights placed than one budget can hold, and of three kinds the shaders count separately (point, spot, spot with a projected texture), the number of each kind that is lit must stay the same while the lights move. A count that moves makes the renderer compile a new shader, which drops a frame.", + "description": "With more lights placed than one budget can hold, and of two kinds the shaders count separately (point lights and spot lights), the number of each kind that is lit must stay the same while the lights move. A count that moves makes the renderer compile a new shader, which drops a frame.", "type": "gameplay", "source": [ "// Look at the lights the 3D renderer can actually see, the way three.js does:", @@ -7997,6 +8687,80 @@ " seen.size + ': ' + [...seen].join(', ') + ').');", "await harness.takeScreenshot('budget held while the camera swept');" ] + }, + { + "name": "One light kind gets the whole device budget", + "description": "Eight shadow casting point lights, and nothing else: a scene that uses a single light kind must be given the device budget for that kind rather than a third of it, so it keeps as many shadow casters as the device really allows. The counts must also stay put while the camera moves, or the renderer compiles another shader and drops a frame.", + "type": "gameplay", + "source": [ + "// Look at the lights the 3D renderer can actually see, the way three.js does:", + "// hidden subtrees are skipped entirely, so this agrees with what the shaders", + "// were given rather than with what is in the scene graph.", + "const readLights = () => {", + " const threeScene = harness.getRuntimeLayer('').getRenderer().getThreeScene();", + " const lights = { point: 0, spot: 0, spotWithMap: 0, directional: 0, shadows: 0 };", + " if (!threeScene) return lights;", + " const walk = (object) => {", + " if (!object.visible) return;", + " if (object.isLight) {", + " if (object.isSpotLight) lights[object.map ? 'spotWithMap' : 'spot']++;", + " else if (object.isPointLight) lights.point++;", + " else if (object.isDirectionalLight) lights.directional++;", + " if (object.castShadow) lights.shadows++;", + " }", + " for (const child of object.children) walk(child);", + " };", + " walk(threeScene);", + " return lights;", + "};", + "", + "await harness.goToScene('Crowded Point Lights');", + "await harness.stepFrames(10);", + "", + "const placed = harness.getObjects('CrowdedPointShadow');", + "harness.assert(placed.length === 8,", + " '8 shadow casting point lights are placed (' + placed.length + ').');", + "", + "const lights = readLights();", + "console.log('lights=' + JSON.stringify(lights));", + "", + "// Every light here is a point light, so three.js counts them all under the", + "// same defines and the whole budget for that kind is theirs. A budget shared", + "// between kinds that the scene never uses would hold most of it back.", + "harness.assert(lights.spot === 0 && lights.spotWithMap === 0 &&", + " lights.directional === 0,", + " 'Only point lights are lit, so a single kind holds the budget (' +", + " JSON.stringify(lights) + ').');", + "harness.assert(lights.point === 8,", + " 'All 8 point lights are lit (' + lights.point + ').');", + "", + "// Three is the floor across every conforming WebGL2 device: 15 varyings", + "// guaranteed, less what the geometry spends, all of it for this one kind.", + "// Splitting the same budget three ways - once per kind the engine knows of,", + "// used or not - would leave only one or two here.", + "harness.assert(lights.shadows >= 3,", + " 'At least 3 of them cast a shadow, which needs the budget to go to the kind actually in use (' + lights.shadows + ').');", + "await harness.takeScreenshot('eight point lights, shadows up to the budget');", + "", + "// The same stability property the mixed scene checks, on one kind: moving the", + "// camera reshuffles which lights are nearest, and the counts compiled into", + "// the shaders must not follow.", + "const scene = harness.getCurrentRuntimeScene();", + "const seen = new Set();", + "for (let step = 0; step < 8; step++) {", + " const t = step / 7;", + " gdjs.evtTools.camera.setCameraX(scene, -900 + t * 1800, '', 0);", + " gdjs.evtTools.camera.setCameraY(scene, 1450 - t * 700, '', 0);", + " await harness.stepFrames(3);", + " const l = readLights();", + " seen.add(l.point + '/' + l.shadows);", + "}", + "console.log('combinations=' + JSON.stringify([...seen]));", + "harness.assert(seen.size === 1,", + " 'The number of lit and shadow casting point lights stays the same as the ' +", + " 'camera moves (saw ' + seen.size + ': ' + [...seen].join(', ') + ').');", + "await harness.takeScreenshot('one kind held steady while the camera swept');" + ] } ] } From 0160e06ebefbfc28eeae5eec2f7b9646460d88e0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 11:48:13 +0000 Subject: [PATCH 4/6] Sync the light playground to Light3D 1.0.3 The previous commit's message says 1.3.0; the extension stays on 1.0.3, since the budget change is a fix to what that version already does rather than a new feature series. Only the version string differs, so the five gameplay tests pass unchanged. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011chSAxDN9jSEwWkowwzDBm --- examples/3d-light-playground/3d-light-playground.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/3d-light-playground/3d-light-playground.json b/examples/3d-light-playground/3d-light-playground.json index ba563d211..8c9b9a07f 100644 --- a/examples/3d-light-playground/3d-light-playground.json +++ b/examples/3d-light-playground/3d-light-playground.json @@ -3490,7 +3490,7 @@ "name": "Light3D", "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/f237ae4e3b857c556846c7b2c0b132556fd4bcdeff217034b4d9c97dc1aab1d6_lightbulb-on-outline.svg", "shortDescription": "A collection of light object for 3D.", - "version": "1.3.0", + "version": "1.0.3", "description": "A collection of light object for 3D.", "origin": { "identifier": "Light3D", From 03170de925d8337b18fda405350aab4573d5d703 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Aug 2026 15:33:08 +0000 Subject: [PATCH 5/6] Sync the light playground to the fixed Light3D 1.0.3 Picks up the extension fixes: the three.js renderer is read anew on every budget recomputation instead of being captured once, lists are trimmed when a newly seen light kind shrinks the budget mid frame, and the two "max lights" expressions now say that their limit is per light kind and can be lowered by the device. All five gameplay tests still pass, with the same counts as before: 14 point and 12 spot lights lit with 4 casting a shadow in the mixed scene, and 8 point lights with 4 casting a shadow in the single kind scene. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011chSAxDN9jSEwWkowwzDBm --- .../3d-light-playground.json | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/examples/3d-light-playground/3d-light-playground.json b/examples/3d-light-playground/3d-light-playground.json index 8c9b9a07f..4db9148f4 100644 --- a/examples/3d-light-playground/3d-light-playground.json +++ b/examples/3d-light-playground/3d-light-playground.json @@ -4254,18 +4254,18 @@ "function getLightManager(runtimeScene) {", " if (!runtimeScene.__lightManager) {", " // Create the shared manager if necessary.", - " const threeRenderer = runtimeScene", - " .getGame()", - " .getRenderer()", - " .getThreeRenderer();", " const requestedCount = isInGameEdition", " ? editorLightCountMax : lightCountMax;", " const requestedShadowCount = isInGameEdition", " ? editorLightShadowCountMax : lightShadowCountMax;", "", + " // The renderer is read anew on every recomputation: it may not", + " // exist yet when the manager is created on the first frame.", " runtimeScene.__lightManager = new LightManager(", " requestedCount, requestedShadowCount,", - " kindCount => computeDeviceLightBudget(threeRenderer, kindCount));", + " kindCount => computeDeviceLightBudget(", + " runtimeScene.getGame().getRenderer().getThreeRenderer(),", + " kindCount));", " }", " return runtimeScene.__lightManager;", "}", @@ -4305,10 +4305,16 @@ " this.objects.length = 0;", " }", "", + " /** Drop the farthest lights until the list fits its capacity again. */", + " trimToCapacity() {", + " while (this.objects.length > this.capacity.value) {", + " this.onDeletion(this.objects.pop().object);", + " }", + " }", + "", " /**", " * @param object {LightRuntimeObject}", - " * @param sortDistance {number} The squared distance to the camera, which", - " * more expensive lights can inflate to lose ties against cheaper ones.", + " * @param sortDistance {number} The squared distance to the camera.", " */", " insertByDistance(object, sortDistance) {", " let insertionIndex = 0;", @@ -4435,6 +4441,15 @@ " this.usedKinds.add(kind);", " this.deviceBudget = this._computeDeviceBudget(this.usedKinds.size);", "", + " // The budget just shrank: evict the lights that no longer fit, so", + " // that even this frame stays within what the device can take.", + " for (const list of this.visibleLists.values()) {", + " list.trimToCapacity();", + " }", + " for (const list of this.shadowLists.values()) {", + " list.trimToCapacity();", + " }", + "", " if (this.deviceBudget.count < this._requestedCount.value", " || this.deviceBudget.shadowCount", " < this._requestedShadowCount.value) {", @@ -4531,7 +4546,7 @@ "objectGroups": [] }, { - "description": "the maximum number of nearest lights displayed simultaneously.", + "description": "the maximum number of nearest lights displayed simultaneously, counted separately for each kind of light (point lights, spot lights, spot lights projecting a texture). Devices that can't take that many lights enforce a lower limit.", "fullName": "Max lights count", "functionType": "ExpressionAndCondition", "name": "LightCountMax", @@ -4604,7 +4619,7 @@ "objectGroups": [] }, { - "description": "the maximum number of nearest lights displayed with shadow simultaneously.", + "description": "the maximum number of nearest lights displayed with shadow simultaneously, counted separately for each kind of light (point lights, spot lights, spot lights projecting a texture). Devices that can't take that many shadows enforce a lower limit.", "fullName": "Max lights with shadow count", "functionType": "ExpressionAndCondition", "name": "LightShadowCountMax", From 839f0d0151d52c00280b72b490212be72f87920b Mon Sep 17 00:00:00 2001 From: Florian Rival Date: Sun, 23 Aug 2026 18:44:50 +0200 Subject: [PATCH 6/6] Update README.md --- examples/3d-light-playground/README.md | 32 -------------------------- 1 file changed, 32 deletions(-) diff --git a/examples/3d-light-playground/README.md b/examples/3d-light-playground/README.md index fae287817..0cfb40317 100644 --- a/examples/3d-light-playground/README.md +++ b/examples/3d-light-playground/README.md @@ -11,35 +11,3 @@ be looked at on its own. falloff towards its edge, and the shadows underneath. - **Mixed Lights** — twenty-six lights of two kinds at once (point and spot), more than one shared budget can hold, to see how the budget behaves. - -Point and spot lights come from the **3D lights** extension. The scene's -directional and ambient lights are effects on the layer, which is where to find -them in the editor. - -Two things worth knowing if you build on this: - -- A light's colour, intensity and range belong to the **object**, not to the - instance. GDevelop reads only the animation from an instance's properties for - an events-based object, so placing two lights of different colours means two - objects, not one object placed twice. -- A spot light shines along its own **+X** axis. Point it at the ground by giving - the instance a Y rotation of 90. - -The gameplay tests of this project check that each kind of light reaches the -renderer, keeps the colour it was given, and casts shadows — and take screenshots -of every scene, so a change in how lighting looks shows up as a change in the -pictures. - -The **Mixed Lights** test is the one that guards a real regression. It sweeps the -camera across the field, which changes which lights are nearest, and asserts that -the number of lights *of each kind* that are lit does not change with it. three.js -compiles those counts into its shaders, so a count that moves costs a shader -compilation and a dropped frame. Budgeting all kinds from one shared pool keeps -the total pinned while letting the split drift, and this test fails when it does: -with the 3D lights extension at v1.0.2 it sees three different splits -(`11/9`, `10/10`, `11/9` point/spot with different shadow counts); from v1.1.0 on -it sees one. - -Note that the crowded scene has to mix *kinds* for this to bite. A scene of -twenty-four point lights passes either way, because a single kind is pinned to the -cap by any budget — which is why this scene has both point and spot lights.