diff --git a/extensions/community/Procedural2DClouds.json b/extensions/community/Procedural2DClouds.json new file mode 100644 index 000000000..dc608c093 --- /dev/null +++ b/extensions/community/Procedural2DClouds.json @@ -0,0 +1,525 @@ +{ + "author": "", + "category": "", + "extensionNamespace": "", + "fullName": "Procedural 2D Clouds", + "gdevelopVersion": "", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Zz4NCgk8cGF0aCBkPSJNMjIuNCwyOUgxNmMtMi44LDAtNS0yLjItNS01YzAtMi41LDEuOC00LjUsNC4yLTQuOWMxLTMsMy45LTUuMSw3LjItNS4xYzQuMiwwLDcuNiwzLjQsNy42LDcuNVMyNi42LDI5LDIyLjQsMjl6Ii8+DQo8L2c+DQo8cGF0aCBkPSJNMTMuOCwxNy40YzEuNi0zLjMsNC45LTUuNCw4LjctNS40YzIuMSwwLDQsMC43LDUuNSwxLjhjMC0wLjMsMC4xLTAuNSwwLjEtMC44YzAtNS41LTQuNS0xMC0xMC0xMGMtNC40LDAtOC4yLDIuOC05LjUsNw0KCUM0LjksMTAsMiwxMi45LDIsMTYuNUMyLDIwLjEsNC45LDIzLDguNSwyM2gwLjZDOS41LDIwLjQsMTEuMywxOC4zLDEzLjgsMTcuNHoiLz4NCjwvc3ZnPg0K", + "name": "Procedural2DClouds", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Glyphster Pack/Master/SVG/Weather/36689a4ced833069ff3d8bc4bf3068b454dd596b69d69fe22507dcf9bba509ce_Weather_forecast_weather_clouds_cloudy.svg", + "shortDescription": "Generates procedural cloud like 2D shapes and patterns.", + "version": "0.0.1", + "description": [ + "### Procedural 2D Clouds Extension", + "Generate and control dynamic, procedural 2D cloud layers in your GDevelop 3D scenes.", + "", + "#### **Features:**", + "* **Procedural Generation:** Unique, non-repeating cloud patterns using advanced noise.", + "* **Customizable:**", + " * **Visuals:** Adjust color, transparency, density, sharpness, and edge softness.", + " * **Animation:** Set independent horizontal and vertical movement speeds.", + " * **Pattern:** Control unique cloud shapes with seed, tiling, and FBM (Octaves, Persistence, Lacunarity) parameters.", + "* **Multiple Layers:** Create distinct cloud layers with unique IDs and properties.", + "", + "#### Limitations:", + "* Clouds doesn’t cast shadows.", + "* Suitable for the game that camera stays at ground level.", + "", + "For further information and Cloud Builder App visit: [Itchio Page](https://eldarduil.itch.io/procedural-2d-clouds-extension-for-gdevelop)." + ], + "tags": [ + "cloud", + "sky", + "procedural", + "3D" + ], + "authorIds": [ + "m8kleQHonagHWsvILDhyJhgVhuF2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "description": "Create Cloud Plane. (This action should be used on every frame).", + "fullName": "Create Cloud Plane", + "functionType": "Action", + "name": "CreateCloudPlane", + "sentence": "Create Cloud Plane Named:_PARAM1_ Seed:_PARAM2_ CloudColor: _PARAM3_ | Plane Width: _PARAM4_ Plane Height: _PARAM5_ X Position: _PARAM6_ Y Positon: _PARAM7_ Z Position: _PARAM8_ | Cloud Speed on X axis: _PARAM9_ Cloud Speed on Y axis: _PARAM10_ | Tiling Factor: _PARAM11_ Octaves: _PARAM12_ Persistence: _PARAM13_ Lacunarity: _PARAM14_ Density Power: _PARAM15_ Alpha Threshold Min: _PARAM16_ Alpha Threshold Max: _PARAM17_ Global Alpha Multiplier: _PARAM18_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "DepartScene" + }, + "parameters": [ + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const cloudId = eventsFunctionContext.getArgument(\"CloudID\");\r", + "const cloudColorString = eventsFunctionContext.getArgument(\"CloudColor\");\r", + "const planeWidth = eventsFunctionContext.getArgument(\"PlaneWidth\");\r", + "const planeHeight = eventsFunctionContext.getArgument(\"PlaneHeight\");\r", + "const planeX = eventsFunctionContext.getArgument(\"PlaneX\");\r", + "const planeY = eventsFunctionContext.getArgument(\"PlaneY\");\r", + "const planeZ = eventsFunctionContext.getArgument(\"PlaneZ\");\r", + "const cloudSeed = eventsFunctionContext.getArgument(\"CloudSeed\");\r", + "const tilingFactor = eventsFunctionContext.getArgument(\"TilingFactor\");\r", + "const movementSpeedX = eventsFunctionContext.getArgument(\"MovementSpeedX\");\r", + "const movementSpeedY = eventsFunctionContext.getArgument(\"MovementSpeedY\");\r", + "const octaves = eventsFunctionContext.getArgument(\"Octaves\");\r", + "const persistence = eventsFunctionContext.getArgument(\"Persistence\");\r", + "const lacunarity = eventsFunctionContext.getArgument(\"Lacunarity\");\r", + "const densityPower = eventsFunctionContext.getArgument(\"DensityPower\");\r", + "const alphaThresholdMin = eventsFunctionContext.getArgument(\"AlphaThresholdMin\");\r", + "const alphaThresholdMax = eventsFunctionContext.getArgument(\"AlphaThresholdMax\");\r", + "const globalAlphaMultiplier = eventsFunctionContext.getArgument(\"GlobalAlphaMultiplier\");\r", + "\r", + "\r", + "// Initialize Global Namespace and Storage for Multiple Clouds\r", + "if (typeof gdjs._Procedural2DClouds === 'undefined') {\r", + " gdjs._Procedural2DClouds = {};\r", + " gdjs._Procedural2DClouds.activeClouds = new Map();\r", + "}\r", + "\r", + "// Check if a cloud with this ID already exists. If so, this block should not re-create it.\r", + "if (gdjs._Procedural2DClouds.activeClouds.has(cloudId)) {\r", + " return;\r", + "}\r", + "\r", + "// Define the Shader function (only once, if not already defined)\r", + "if (typeof gdjs._Procedural2DClouds.getShader === 'undefined') {\r", + " gdjs._Procedural2DClouds.getShader = function() {\r", + " const vertexShader = `\r", + " varying vec2 vUv;\r", + " void main() {\r", + " vUv = uv;\r", + " gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\r", + " }\r", + " `;\r", + "\r", + " const fragmentShader = `\r", + " precision highp float;\r", + " precision highp int;\r", + "\r", + " uniform float time;\r", + " uniform vec3 cloudColor;\r", + " uniform float seed;\r", + " uniform float tilingFactor;\r", + " uniform vec2 cloudMovementSpeed;\r", + " uniform int octaves;\r", + " uniform float persistence;\r", + " uniform float lacunarity;\r", + " uniform float densityPower;\r", + " uniform float alphaThresholdMin;\r", + " uniform float alphaThresholdMax;\r", + " uniform float globalAlphaMultiplier;\r", + "\r", + " varying vec2 vUv;\r", + " vec2 hash2D(vec2 p) {\r", + " p += seed; // Re-added seed to hash2D\r", + " p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));\r", + " return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);\r", + " }\r", + "\r", + " float perlinNoise(vec2 p) {\r", + " vec2 i = floor(p);\r", + " vec2 f = fract(p);\r", + " vec2 u = f * f * (3.0 - 2.0 * f);\r", + "\r", + " float a = dot(hash2D(i + vec2(0.0, 0.0)), f - vec2(0.0, 0.0));\r", + " float b = dot(hash2D(i + vec2(1.0, 0.0)), f - vec2(1.0, 0.0));\r", + " float c = dot(hash2D(i + vec2(0.0, 1.0)), f - vec2(0.0, 1.0));\r", + " float d = dot(hash2D(i + vec2(1.0, 1.0)), f - vec2(1.0, 1.0));\r", + "\r", + " return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\r", + " }\r", + "\r", + " float fbmPerlin(vec2 p, int numOctaves, float pers, float lac) {\r", + " float value = 0.0;\r", + " float amplitude = 0.5;\r", + " float frequency = 1.0;\r", + " for (int i = 0; i < numOctaves; i++) {\r", + " value += amplitude * perlinNoise(p * frequency);\r", + " frequency *= lac;\r", + " amplitude *= pers;\r", + " }\r", + " return value;\r", + " }\r", + "\r", + " void main() {\r", + " vec2 movementOffset = (cloudMovementSpeed * time) / 100.0; // To not struggle with very small float numbers there is that hardcoded divide by 100.0\r", + " \r", + " vec2 patternInput = vUv * tilingFactor + movementOffset;\r", + "\r", + " float rawCloudDensity = fbmPerlin(patternInput, octaves, persistence, lacunarity);\r", + " \r", + " float normalizedCloudDensity = (rawCloudDensity + 1.0) * 0.5;\r", + " normalizedCloudDensity = pow(normalizedCloudDensity, densityPower);\r", + " \r", + " float alpha = smoothstep(alphaThresholdMin, alphaThresholdMax, normalizedCloudDensity);\r", + " alpha = clamp(alpha * globalAlphaMultiplier, 0.0, 1.0);\r", + "\r", + " float distFromCenter = length(vUv - vec2(0.5, 0.5));\r", + " float innerRadius = 0.4;\r", + " float outerRadius = 0.5;\r", + " float fadeFactor = 1.0 - smoothstep(innerRadius, outerRadius, distFromCenter);\r", + " gl_FragColor = vec4(cloudColor, alpha * fadeFactor);\r", + " }\r", + " `;\r", + " return { vertex: vertexShader, fragment: fragmentShader };\r", + " };\r", + "}\r", + "\r", + "\r", + "// Access Three.js Scene\r", + "const threeScene = runtimeScene.getLayer(\"\").getRenderer().getThreeScene();\r", + "\r", + "// Parse Initial Cloud Color\r", + "let r = 255, g = 255, b = 255; // Default to white\r", + "if (cloudColorString) {\r", + " const colorParts = cloudColorString.split(';').map(Number);\r", + " if (colorParts.length === 3) {\r", + " r = Math.max(0, Math.min(255, colorParts[0])) / 255;\r", + " g = Math.max(0, Math.min(255, colorParts[1])) / 255;\r", + " b = Math.max(0, Math.min(255, colorParts[2])) / 255;\r", + " } else {\r", + " console.warn(\"Invalid cloud color string format:\", cloudColorString, \"Expected 'R;G;B'. Defaulting to white.\");\r", + " }\r", + "}\r", + "\r", + "const threeColor = new THREE.Color(r, g, b);\r", + "\r", + "// Shader Material Setup\r", + "const shaders = gdjs._Procedural2DClouds.getShader();\r", + "const vertexShader = shaders.vertex;\r", + "const fragmentShader = shaders.fragment;\r", + "\r", + "// Create uniforms specific to this cloud instance\r", + "const cloudUniforms = {\r", + " time: { value: 0.0 },\r", + " cloudColor: { value: threeColor },\r", + " seed: { value: cloudSeed },\r", + " tilingFactor: { value: tilingFactor },\r", + " cloudMovementSpeed: { value: new THREE.Vector2(movementSpeedX, movementSpeedY) },\r", + " octaves: { value: Math.floor(octaves) },\r", + " persistence: { value: persistence },\r", + " lacunarity: { value: lacunarity },\r", + " densityPower: { value: densityPower },\r", + " alphaThresholdMin: { value: alphaThresholdMin },\r", + " alphaThresholdMax: { value: alphaThresholdMax },\r", + " globalAlphaMultiplier: { value: globalAlphaMultiplier }\r", + "};\r", + "\r", + "const cloudMaterial = new THREE.ShaderMaterial({\r", + " uniforms: cloudUniforms,\r", + " vertexShader: vertexShader,\r", + " fragmentShader: fragmentShader,\r", + " transparent: true,\r", + " blending: THREE.NormalBlending,\r", + " depthWrite: false,\r", + " side: THREE.DoubleSide\r", + "});\r", + "\r", + "//Plane Geometry Setup\r", + "const planeGeometry = new THREE.PlaneGeometry(planeWidth, planeHeight, 1, 1);\r", + "const cloudMesh = new THREE.Mesh(planeGeometry, cloudMaterial);\r", + "\r", + "// Set the plane's initial position\r", + "cloudMesh.position.set(planeX, planeY, planeZ);\r", + "\r", + "// Add the cloud plane to the Three.js scene\r", + "threeScene.add(cloudMesh);\r", + "\r", + "// Initialize the Three.js Clock for this instance\r", + "const cloudClock = new THREE.Clock(); \r", + "\r", + "// Store all instance-specific data in the global map\r", + "gdjs._Procedural2DClouds.activeClouds.set(cloudId, {\r", + " mesh: cloudMesh,\r", + " uniforms: cloudUniforms,\r", + " clock: cloudClock\r", + "});" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const cloudId = eventsFunctionContext.getArgument(\"CloudID\");", + "const cloudColorString = eventsFunctionContext.getArgument(\"CloudColor\");", + "const planeWidth = eventsFunctionContext.getArgument(\"PlaneWidth\");", + "const planeHeight = eventsFunctionContext.getArgument(\"PlaneHeight\");", + "const planeX = eventsFunctionContext.getArgument(\"PlaneX\");", + "const planeY = eventsFunctionContext.getArgument(\"PlaneY\");", + "const planeZ = eventsFunctionContext.getArgument(\"PlaneZ\");", + "const cloudSeed = eventsFunctionContext.getArgument(\"CloudSeed\");", + "const tilingFactor = eventsFunctionContext.getArgument(\"TilingFactor\");", + "const movementSpeedX = eventsFunctionContext.getArgument(\"MovementSpeedX\");", + "const movementSpeedY = eventsFunctionContext.getArgument(\"MovementSpeedY\");", + "const octaves = eventsFunctionContext.getArgument(\"Octaves\");", + "const persistence = eventsFunctionContext.getArgument(\"Persistence\");", + "const lacunarity = eventsFunctionContext.getArgument(\"Lacunarity\");", + "const densityPower = eventsFunctionContext.getArgument(\"DensityPower\");", + "const alphaThresholdMin = eventsFunctionContext.getArgument(\"AlphaThresholdMin\");", + "const alphaThresholdMax = eventsFunctionContext.getArgument(\"AlphaThresholdMax\");", + "const globalAlphaMultiplier = eventsFunctionContext.getArgument(\"GlobalAlphaMultiplier\");", + "", + "", + "// Ensure the cloud map is initialized and the specific cloud instance exists before attempting to update.", + "if (typeof gdjs._Procedural2DClouds === 'undefined' || !gdjs._Procedural2DClouds.activeClouds || !gdjs._Procedural2DClouds.activeClouds.has(cloudId)) {", + " console.warn(`Cloud plane with ID \"${cloudId}\" not found or not initialized, skipping update.`);", + " return;", + "}", + "", + "// Retrieve the specific cloud instance's data from the map", + "const cloudInstance = gdjs._Procedural2DClouds.activeClouds.get(cloudId);", + "const cloudMesh = cloudInstance.mesh;", + "const cloudUniforms = cloudInstance.uniforms;", + "const cloudClock = cloudInstance.clock;", + "", + "// Update 'time' Uniform for Animation", + "cloudUniforms.time.value = cloudClock.getElapsedTime();", + "", + "const colorParts = cloudColorString.split(';').map(Number);", + "const currentCloudColor = cloudUniforms.cloudColor.value;", + "if (currentCloudColor.r * 255 !== colorParts[0] ||", + " currentCloudColor.g * 255 !== colorParts[1] ||", + " currentCloudColor.b * 255 !== colorParts[2]) {", + " currentCloudColor.setRGB(", + " Math.max(0, Math.min(255, colorParts[0])) / 255,", + " Math.max(0, Math.min(255, colorParts[1])) / 255,", + " Math.max(0, Math.min(255, colorParts[2])) / 255", + " );", + "}", + "", + "if (cloudUniforms.seed.value !== cloudSeed) {", + " cloudUniforms.seed.value = cloudSeed;", + "}", + "", + "if (cloudUniforms.tilingFactor.value !== tilingFactor) {", + " cloudUniforms.tilingFactor.value = tilingFactor;", + "}", + "", + "const currentMovementSpeed = cloudUniforms.cloudMovementSpeed.value;", + "if (currentMovementSpeed.x !== movementSpeedX || currentMovementSpeed.y !== movementSpeedY) {", + " currentMovementSpeed.set(movementSpeedX, movementSpeedY);", + "}", + "", + "if (cloudUniforms.octaves.value !== Math.floor(octaves)) {", + " cloudUniforms.octaves.value = Math.floor(octaves);", + "}", + "if (cloudUniforms.persistence.value !== persistence) {", + " cloudUniforms.persistence.value = persistence;", + "}", + "if (cloudUniforms.lacunarity.value !== lacunarity) {", + " cloudUniforms.lacunarity.value = lacunarity;", + "}", + "if (cloudUniforms.densityPower.value !== densityPower) {", + " cloudUniforms.densityPower.value = densityPower;", + "}", + "if (cloudUniforms.alphaThresholdMin.value !== alphaThresholdMin) {", + " cloudUniforms.alphaThresholdMin.value = alphaThresholdMin;", + "}", + "if (cloudUniforms.alphaThresholdMax.value !== alphaThresholdMax) {", + " cloudUniforms.alphaThresholdMax.value = alphaThresholdMax;", + "}", + "if (cloudUniforms.globalAlphaMultiplier.value !== globalAlphaMultiplier) {", + " cloudUniforms.globalAlphaMultiplier.value = globalAlphaMultiplier;", + "}", + "", + "const currentGeometry = cloudMesh.geometry;", + "if (currentGeometry.parameters.width !== planeWidth ||", + " currentGeometry.parameters.height !== planeHeight) {", + " currentGeometry.dispose();", + " cloudMesh.geometry = new THREE.PlaneGeometry(planeWidth, planeHeight, 1, 1);", + "}", + "", + "const currentPosition = cloudMesh.position;", + "if (currentPosition.x !== planeX ||", + " currentPosition.y !== planeY ||", + " currentPosition.z !== planeZ) {", + " currentPosition.set(planeX, planeY, planeZ);", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Unique Name of the cloud plane", + "longDescription": "A unique name or identifier for this specific cloud layer.", + "name": "CloudID", + "type": "string" + }, + { + "description": "Seed number", + "name": "CloudSeed", + "type": "expression" + }, + { + "description": "Color of the clouds", + "name": "CloudColor", + "type": "color" + }, + { + "description": "Cloud plane's width", + "name": "PlaneWidth", + "type": "expression" + }, + { + "description": "Cloud plane's height", + "name": "PlaneHeight", + "type": "expression" + }, + { + "description": "Cloud Plane's X position", + "name": "PlaneX", + "type": "expression" + }, + { + "description": "Cloud Plane's Y position", + "name": "PlaneY", + "type": "expression" + }, + { + "description": "Cloud Plane's Z elevation", + "name": "PlaneZ", + "type": "expression" + }, + { + "description": "Cloud Movement Speed on X - Default: 1", + "name": "MovementSpeedX", + "type": "expression" + }, + { + "description": "Cloud Movement Speed on Y - Default 1", + "name": "MovementSpeedY", + "type": "expression" + }, + { + "description": "Tiling Factor - Default: 6.0", + "longDescription": "How many times the cloud pattern repeats", + "name": "TilingFactor", + "type": "expression" + }, + { + "description": "Octaves - Default: 8", + "longDescription": "Number of layers of noise used to create the cloud's detail. More octaves add finer details and complexity. Higher the number it is more computationally intensive.", + "name": "Octaves", + "type": "expression" + }, + { + "description": "Persistence - Default: 0.5", + "longDescription": "Controls how much each successive octave (layer of noise) contributes to the overall cloud shape. A lower value means higher-frequency details are less prominent, making clouds smoother. A higher value makes details more pronounced.", + "name": "Persistence", + "type": "expression" + }, + { + "description": "Lacunarity - Default: 2.0", + "longDescription": "Controls the frequency increase for each successive octave. A higher value means the details in subsequent layers are much smaller and denser, leading to more \"jagged\" or \"fractal\" cloud shapes.", + "name": "Lacunarity", + "type": "expression" + }, + { + "description": "Density Power - Default: 1.2", + "longDescription": "Affects the sharpness and overall coverage of the clouds. Higher values makes clouds sparse, Lower values more widespread.", + "name": "DensityPower", + "type": "expression" + }, + { + "description": "Alpha Threshold Min - Default: 0.3", + "longDescription": "The minimum density value (from 0 to 1) at which the cloud starts to become visible.", + "name": "AlphaThresholdMin", + "type": "expression" + }, + { + "description": "Alpha Threshold Max - Default: 0.5", + "longDescription": "The density value (from 0 to 1) at which the cloud becomes fully opaque.", + "name": "AlphaThresholdMax", + "type": "expression" + }, + { + "description": "Global Alpha Multiplier - Default: 1.0", + "longDescription": "Overall multiplier for the cloud's transparency. Values less than 1.0 make the entire cloud layer more transparent.", + "name": "GlobalAlphaMultiplier", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Destroy A Cloud Plane. When the cloud planes are not needed (such as changing the scene) it's good to destroy them to release the resources they take.", + "fullName": "Destroy Cloud Plane", + "functionType": "Action", + "name": "DestroyCloudPlane", + "sentence": "Destroy Cloud Plane with Unique ID: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const cloudId = eventsFunctionContext.getArgument(\"CloudID\");", + "", + "// Ensure the cloud map is initialized and the specific cloud instance exists before attempting to destroy.", + "if (typeof gdjs._Procedural2DClouds === 'undefined' || !gdjs._Procedural2DClouds.activeClouds || !gdjs._Procedural2DClouds.activeClouds.has(cloudId)) {", + " return;", + "}", + "", + "// Retrieve the specific cloud instance's data from the map", + "const cloudInstance = gdjs._Procedural2DClouds.activeClouds.get(cloudId);", + "const cloudMesh = cloudInstance.mesh;", + "const cloudUniforms = cloudInstance.uniforms;", + "const cloudClock = cloudInstance.clock;", + "", + "// If the cloud mesh exists, remove it from the scene and dispose of its resources", + "if (cloudMesh) {", + " const threeScene = runtimeScene.getLayer(\"\").getRenderer().getThreeScene();", + " if (threeScene) {", + " threeScene.remove(cloudMesh);", + " }", + " if (cloudMesh.geometry) {", + " cloudMesh.geometry.dispose(); // Release geometry memory", + " }", + " if (cloudMesh.material && cloudMesh.material.dispose) {", + " cloudMesh.material.dispose(); // Release material memory", + " }", + " // Remove the instance from our global map", + " gdjs._Procedural2DClouds.activeClouds.delete(cloudId);", + "", + "} else {", + " console.warn(`Attempted to destroy non-existent cloud plane mesh for ID \"${cloudId}\". Nothing to do.`);", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Unique ID of Cloud Plane", + "name": "CloudID", + "type": "string" + } + ], + "objectGroups": [] + } + ], + "eventsBasedBehaviors": [], + "eventsBasedObjects": [] +} \ No newline at end of file