/* 🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑 */ /* 🛑 MAIN PLUGIN: MATTER FABRICATOR + V13.2 (SKYBOX FOG INTEGRATION) 🛑 */ /* 🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑🛑 */ (function() { window.matterManualInput = (el) => { let v = prompt("Enter exact value:", el.value); if (v !== null && !isNaN(v)) { el.value = v; el.dispatchEvent(new Event('input')); el.dispatchEvent(new Event('change')); } }; /* 🛑 V13.0 FLUID ENGINE MODULE 🛑 */ const G1_FLUID = { enabled: false, points: null, size: 192, params: { speed: 7.0, gravity: 0.11, repulsion: 2.8, floorY: 0, size: 1.3, opacity: 0.7, color: '#00ffff' }, rtPosA: null, rtPosB: null, rtVelA: null, rtVelB: null, scene: null, camera: null, mesh: null, dispose() { if (this.points) { window.G1_SCENE.remove(this.points); this.points.geometry.dispose(); this.points.material.dispose(); } if (this.rtPosA) { this.rtPosA.dispose(); this.rtPosB.dispose(); this.rtVelA.dispose(); this.rtVelB.dispose(); } if (this.mesh) { this.scene.remove(this.mesh); this.mesh.geometry.dispose(); } }, init(newSize = 128) { this.dispose(); this.size = Math.pow(2, Math.round(Math.log2(newSize))); try { const T = window.THREE; const ren = window.ELEMENT_APP?.renderer; if(!ren) return; const type = (/(iPad|iPhone|iPod)/g.test(navigator.userAgent)) ? T.HalfFloatType : T.FloatType; this.rtPosA = new T.WebGLRenderTarget(this.size, this.size, { type: type }); this.rtPosB = this.rtPosA.clone(); this.rtVelA = this.rtPosA.clone(); this.rtVelB = this.rtPosA.clone(); const posData = new Float32Array(this.size * this.size * 4); const velData = new Float32Array(this.size * this.size * 4); for (let i = 0; i < posData.length; i += 4) { posData[i] = (Math.random() - 0.5) * 600; posData[i + 1] = 100 + Math.random() * 100; posData[i + 2] = (Math.random() - 0.5) * 600; posData[i + 3] = 1.0; } const initialPos = new T.DataTexture(posData, this.size, this.size, T.RGBAFormat, T.FloatType); initialPos.needsUpdate = true; this.velMat = new T.ShaderMaterial({ uniforms: { tPos: { value: null }, tVel: { value: null }, mouse: { value: new T.Vector3() }, floorY: { value: 0 }, terrainH: { value: 50.0 }, terrainSpeed: { value: 0.5 }, gravity: { value: 0.11 }, repulsion: { value: 2.8 }, time: { value: 0 } }, vertexShader: `varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position, 1.0); }`, fragmentShader: ` uniform sampler2D tPos; uniform sampler2D tVel; uniform vec3 mouse; uniform float floorY; uniform float terrainH; uniform float terrainSpeed; uniform float gravity; uniform float repulsion; uniform float time; varying vec2 vUv; float getH(vec2 p) { float d1 = length(p - vec2(200.0, 200.0)); float d2 = length(p - vec2(-200.0, -200.0)); float d3 = length(p); float w = sin(d1 * 0.03 - time * terrainSpeed) * 0.5; w += cos(d2 * 0.04 - time * terrainSpeed * 1.2) * 0.5; w += sin(d3 * 0.02 + time * terrainSpeed * 0.8) * 0.5; float edge = 1.0 - smoothstep(400.0, 500.0, abs(p.x)); edge *= 1.0 - smoothstep(400.0, 500.0, abs(p.y)); return w * terrainH * edge; } void main() { vec3 pos = texture2D(tPos, vUv).xyz; vec3 vel = texture2D(tVel, vUv).xyz; vel.y -= gravity; float e = 1.5; float h = getH(pos.xz); float hX = getH(vec2(pos.x+e, pos.z)) - getH(vec2(pos.x-e, pos.z)); float hZ = getH(vec2(pos.x, pos.z+e)) - getH(vec2(pos.x, pos.z-e)); vec3 normal = normalize(vec3(-hX, e*2.0, -hZ)); float actualFloor = floorY + h; if (pos.y < actualFloor) { pos.y = actualFloor; vel = reflect(vel, normal) * 0.35; vel += normal * 0.1; } float mDist = distance(pos, mouse); if(mDist < 90.0) vel += normalize(pos - mouse) * (90.0 - mDist) * 0.03; for(int i=0; i<4; i++) { vec2 off = vec2(float(i)-1.5, 0.0) * 0.01; vec3 otherPos = texture2D(tPos, vUv + off).xyz; if(distance(pos, otherPos) < 15.0) vel += normalize(pos - otherPos) * repulsion * 0.15; } gl_FragColor = vec4(vel * 0.985, 1.0); } ` }); this.posMat = new T.ShaderMaterial({ uniforms: { tPos: { value: null }, tVel: { value: null }, speed: { value: 7.0 } }, vertexShader: `varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position, 1.0); }`, fragmentShader: ` uniform sampler2D tPos; uniform sampler2D tVel; uniform float speed; void main() { vec3 pos = texture2D(tPos, gl_FragCoord.xy / ${this.size.toFixed(1)}).xyz; vec3 vel = texture2D(tVel, gl_FragCoord.xy / ${this.size.toFixed(1)}).xyz; gl_FragColor = vec4(pos + (vel * speed), 1.0); } ` }); this.scene = new T.Scene(); this.camera = new T.Camera(); this.mesh = new T.Mesh(new T.PlaneGeometry(2, 2), this.posMat); this.scene.add(this.mesh); const geo = new T.BufferGeometry(); const gridIndices = new Float32Array(this.size * this.size * 2); for (let i = 0; i < this.size * this.size; i++) { gridIndices[i * 2] = (i % this.size) / this.size; gridIndices[i * 2 + 1] = Math.floor(i / this.size) / this.size; } geo.setAttribute('uv', new T.BufferAttribute(gridIndices, 2)); geo.setAttribute('position', new T.BufferAttribute(new Float32Array(this.size * this.size * 3), 3)); this.pointMat = new T.ShaderMaterial({ fog: true, transparent: true, blending: T.AdditiveBlending, depthWrite: false, uniforms: T.UniformsUtils.merge([ T.UniformsLib['fog'], { tPos: { value: null }, color: { value: new T.Color(this.params.color) }, opacity: { value: this.params.opacity }, pSize: { value: this.params.size }, tMap: { value: new T.Texture() }, useMap: { value: 0.0 } } ]), vertexShader: ` uniform sampler2D tPos; uniform float pSize; varying float vFogDepth; void main() { vec4 p = texture2D(tPos, uv); vec4 mvPosition = modelViewMatrix * vec4(p.xyz, 1.0); gl_PointSize = pSize * (600.0 / -mvPosition.z); gl_Position = projectionMatrix * mvPosition; vFogDepth = -mvPosition.z; } `, fragmentShader: ` uniform vec3 color; uniform float opacity; uniform sampler2D tMap; uniform float useMap; ${T.ShaderChunk.fog_pars_fragment} void main() { vec4 tex = vec4(1.0); if(useMap > 0.5) tex = texture2D(tMap, gl_PointCoord); else if(distance(gl_PointCoord, vec2(0.5)) > 0.5) discard; gl_FragColor = vec4(color, opacity) * tex; ${T.ShaderChunk.fog_fragment} }` }); this.points = new T.Points(geo, this.pointMat); this.points.frustumCulled = false; this.points.visible = this.enabled; window.G1_SCENE.add(this.points); this.rtPosA.texture = initialPos; } catch(e) { console.error("FLUID INIT ERROR", e); } }, update(t) { if (!this.enabled || !window.ELEMENT_APP?.renderer || !this.rtPosA) return; const ren = window.ELEMENT_APP.renderer; const ms = window.settings.matter; this.mesh.material = this.velMat; this.velMat.uniforms.tPos.value = this.rtPosA.texture; this.velMat.uniforms.tVel.value = this.rtVelA.texture; this.velMat.uniforms.time.value = t; this.velMat.uniforms.floorY.value = this.params.floorY; this.velMat.uniforms.gravity.value = this.params.gravity; this.velMat.uniforms.repulsion.value = this.params.repulsion; this.velMat.uniforms.terrainH.value = ms.terrainH; this.velMat.uniforms.terrainSpeed.value = ms.terrainSpeed; if(window.G1_MOUSE) this.velMat.uniforms.mouse.value.copy(window.G1_MOUSE); ren.setRenderTarget(this.rtVelB); ren.render(this.scene, this.camera); this.mesh.material = this.posMat; this.posMat.uniforms.tPos.value = this.rtPosA.texture; this.posMat.uniforms.tVel.value = this.rtVelB.texture; this.posMat.uniforms.speed.value = this.params.speed; ren.setRenderTarget(this.rtPosB); ren.render(this.scene, this.camera); ren.setRenderTarget(null); let tmpP = this.rtPosA; this.rtPosA = this.rtPosB; this.rtPosB = tmpP; let tmpV = this.rtVelA; this.rtVelA = this.rtVelB; this.rtVelB = tmpV; this.points.material.uniforms.tPos.value = this.rtPosA.texture; this.points.material.uniforms.pSize.value = this.params.size; this.points.material.uniforms.color.value.set(this.params.color); this.points.material.uniforms.opacity.value = this.params.opacity; } }; /* 🛑 MAIN MATTER FABRICATOR OBJECT 🛑 */ const G1_MATTER = { loaderFBX: null, loaderGLTF: null, blobCache: new Map(), models: [], activeModel: null, env: { bgMesh: null, groundMesh: null, ocean: null, oceanSky: null, mirrorMesh: null, lights: { sun: null, sunFill: null }, cubeCamera: null, cubeRenderTarget: null, oceanSunVec: null }, init() { try { const isBackend = window.location.href.includes('wp-admin') || window.location.href.includes('elementor-preview') || window.location.href.includes('action=elementor') || (typeof window.elementor !== 'undefined') || (typeof window.elementorFrontend !== 'undefined' && window.elementorFrontend.isEditMode()) || (window.parent !== window && typeof window.parent.elementor !== 'undefined') || (document.body && ( document.body.classList.contains('wp-admin') || document.body.classList.contains('elementor-editor-active') || document.body.classList.contains('elementor-editor-preview') )); if (isBackend) { console.log(">>> MATTER FABRICATOR: Backend environment detected. Execution Aborted."); return; } } catch (e) { console.log(">>> MATTER FABRICATOR: Cross-origin environment detected. Execution Aborted."); return; } const check = setInterval(() => { if (window.G1_SCENE && window.THREE && window.settings) { clearInterval(check); this.setupSettings(); this.setupDependencies().then(() => { this.setupEnvironment(); G1_FLUID.init(128); // Boot Fluid Engine this.injectUI(); this.startLoop(); const oldRebuild = window.rebuildScene; window.rebuildScene = () => { if (oldRebuild) oldRebuild(); if (this.restoreAssets) this.restoreAssets(); }; this.log("SYSTEM: Matter Fabricator Core Online."); if (!this._g1HistoryBound) { this._g1HistoryBound = true; window.addEventListener('G1_MASTER_RESET', () => { this.onG1MasterReset(); }); window.addEventListener('G1_UNDO_RESTORE', () => { this.onG1UndoRestore(); }); } setTimeout(() => { try { if (window.G1_HISTORY && typeof window.G1_HISTORY.save === 'function') { window.G1_HISTORY.save(); } } catch (e) { /* ignore */ } }, 0); }); } }, 500); }, log(msg) { const logEl = document.getElementById('m-debug'); if (logEl) { const time = new Date().toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }); logEl.innerHTML += `
[${time}] ${msg}
`; logEl.scrollTop = logEl.scrollHeight; } if(window.CUSCUS) window.CUSCUS.log(`MATTER: ${msg}`); }, setupSettings() { window.settings = window.settings || {}; const defaults = { brightness: 1.0, contrast: 1.0, skyVisible: true, skyBgVisible: true, skyRepeatX: false, skyRepeatY: false, skyFlipH: false, skyBlur: 0, skyTexScaleX: 1.0, skyTexScaleY: 1.0, skyTexRepeatX: 1.0, skyTexRepeatY: 1.0, skyBright: 1.0, skyContrast: 1.0, skyHue: 0, skySat: 0, skyReflect: 1.0, skyRot: { x: 0, y: 0, z: 0 }, skyNudge: 0, groundVisible: false, groundY: 0, groundOpacity: 1.0, normalScale: 1.0, groundRough: 0.5, groundBright: 0.47, groundHue: 0, groundSat: 0, terrainH: 50, terrainDet: 128, terrainScale: 1.0, terrainSpeed: 0.5, groundTexSize: 600, groundNoiseH: 0.0, groundNormalName: "", oceanVisible: false, oceanY: -10, oceanColor: '#001e0f', oceanSunColor: '#ffffff', oceanSize: 1.0, oceanDistortion: 3.7, oceanNormalName: "", oceanElevation: 2.0, oceanAzimuth: 180.0, oceanExposure: 1.0, oceanBloomStr: 0.1, oceanBloomRad: 0.0, oceanCloudCov: 0.4, oceanCloudDen: 0.5, oceanCloudElev: 0.5, shadowsEnabled: true, shadowInt: 1.0, shadowSpread: 500, sunTotal: 1.2, sunColor: '#ffffff', // Fog Defaults fogEnabled: false, fogDensity: 0.005, fogColor: '#cccccc', models: [], skyTexName: "", groundTexName: "" }; window.settings.matter = window.settings.matter || {}; window.settings.matter = { ...defaults, ...window.settings.matter }; window.settings.matter.skyRot = { ...defaults.skyRot, ...(window.settings.matter.skyRot || {}) }; }, disposeAllModels() { if (!window.G1_SCENE) return; const scene = window.G1_SCENE; this.models.forEach((m) => { m.parts.forEach((p) => { scene.remove(p); if (p.geometry) p.geometry.dispose(); const mats = Array.isArray(p.material) ? p.material : [p.material]; mats.forEach((mat) => { if (!mat) return; if (mat.map) mat.map.dispose(); mat.dispose(); }); }); }); this.models = []; this.activeModel = null; }, onG1MasterReset() { if (window.CUSCUS) window.CUSCUS.log('MATTER-MOD: Master reset — clearing models & re-syncing defaults...'); this.disposeAllModels(); window.settings = window.settings || {}; this.setupSettings(); if (this.restoreAssets) this.restoreAssets(); this.refreshList(); this.applySkyFilters(); this.applySunIntensity(); this.applyGroundFilters(); this.updateFog(); this.needsEnvironmentUpdate = true; }, onG1UndoRestore() { if (window.CUSCUS) window.CUSCUS.log('MATTER-MOD: Rebuilding from restored JSON...'); this.disposeAllModels(); window.settings = window.settings || {}; this.setupSettings(); if (this.restoreAssets) this.restoreAssets(); this.refreshList(); this.applySkyFilters(); this.applySunIntensity(); this.applyGroundFilters(); this.updateFog(); this.needsEnvironmentUpdate = true; }, async setupDependencies() { await this.injectScript('https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/objects/Reflector.js'); await this.injectScript('https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/objects/Water.js'); await this.injectScript('https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/objects/Sky.js'); return Promise.resolve(); }, injectScript(src) { return new Promise((resolve) => { if (document.querySelector(`script[src="${src}"]`)) return resolve(); const s = document.createElement('script'); s.src = src; s.onload = resolve; s.onerror = () => { this.log(`Dependency Error: Failed to load ${src}`); resolve(); }; document.head.appendChild(s); }); }, setupEnvironment() { const scene = window.G1_SCENE; const THREE = window.THREE; const ms = window.settings.matter; if (window.ELEMENT_APP && window.ELEMENT_APP.renderer) { window.ELEMENT_APP.renderer.shadowMap.enabled = true; window.ELEMENT_APP.renderer.shadowMap.type = THREE.PCFSoftShadowMap; window.ELEMENT_APP.renderer.shadowMap.autoUpdate = true; window.ELEMENT_APP.renderer.toneMapping = THREE.ACESFilmicToneMapping; window.ELEMENT_APP.renderer.toneMappingExposure = ms.oceanExposure; } this.env.lights.sun = new THREE.DirectionalLight(0xffffff, 1.2); this.env.lights.sun.position.set(500, 1000, 500); this.env.lights.sun.castShadow = ms.shadowsEnabled; this.env.lights.sun.shadow.mapSize.width = 2048; this.env.lights.sun.shadow.mapSize.height = 2048; this.env.lights.sun.shadow.camera.near = 0.5; this.env.lights.sun.shadow.camera.far = 5000; const d = ms.shadowSpread; this.env.lights.sun.shadow.camera.left = -d; this.env.lights.sun.shadow.camera.right = d; this.env.lights.sun.shadow.camera.top = d; this.env.lights.sun.shadow.camera.bottom = -d; this.env.lights.sun.shadow.bias = -0.0001; scene.add(this.env.lights.sun); this.env.lights.sunFill = new THREE.DirectionalLight(0xffffff, 0.0); this.env.lights.sunFill.position.set(500, 1000, 500); this.env.lights.sunFill.castShadow = false; scene.add(this.env.lights.sunFill); /* 🛑 FOG-INJECTED TEXTURE SKYBOX 🛑 */ const skyGeo = new THREE.SphereGeometry(500, 64, 32); skyGeo.scale(-1, 1, 1); const skyMat = new THREE.ShaderMaterial({ fog: true, // Let three.js know this material uses fog transparent: true, uniforms: THREE.UniformsUtils.merge([ THREE.UniformsLib['fog'], { tMap: { value: null }, blurAmount: { value: 0.0 }, colorMult: { value: new THREE.Vector3(1.0, 1.0, 1.0) }, uvScale: { value: new THREE.Vector2(1.0, 1.0) }, uvRepeat: { value: new THREE.Vector2(1.0, 1.0) }, doRepeat: { value: new THREE.Vector2(0.0, 0.0) } } ]), vertexShader: ` varying vec2 vUv; uniform vec2 uvScale; uniform vec2 uvRepeat; ${THREE.ShaderChunk.fog_pars_vertex} void main() { vUv = uv * uvScale * uvRepeat; vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); gl_Position = projectionMatrix * mvPosition; ${THREE.ShaderChunk.fog_vertex} } `, fragmentShader: ` uniform sampler2D tMap; uniform float blurAmount; uniform vec3 colorMult; uniform vec2 doRepeat; varying vec2 vUv; ${THREE.ShaderChunk.fog_pars_fragment} void main() { vec2 finalUv = vUv; if (doRepeat.x < 0.5 && (finalUv.x < 0.0 || finalUv.x > 1.0)) discard; if (doRepeat.y < 0.5 && (finalUv.y < 0.0 || finalUv.y > 1.0)) discard; vec4 texColor = vec4(0.0); if (blurAmount > 0.0) { float radius = blurAmount * 0.015; float aspect = 2.0; float taps = 1.0; texColor = texture2D(tMap, finalUv); for(float d = 0.0; d < 6.28318530718; d += 6.28318530718 / 16.0) { for(float i = 1.0 / 3.0; i <= 1.0; i += 1.0 / 3.0) { vec2 offset = vec2(cos(d), sin(d) * aspect) * radius * i; vec2 sampleUV = finalUv + offset; bool outX = (doRepeat.x < 0.5 && (sampleUV.x < 0.0 || sampleUV.x > 1.0)); bool outY = (doRepeat.y < 0.5 && (sampleUV.y < 0.0 || sampleUV.y > 1.0)); if(!outX && !outY) { texColor += texture2D(tMap, sampleUV); taps += 1.0; } } } texColor /= taps; } else { texColor = texture2D(tMap, finalUv); } texColor.rgb *= colorMult; gl_FragColor = texColor; ${THREE.ShaderChunk.fog_fragment} // Apply Fog Over Skybox } `, depthWrite: false }); this.env.bgMesh = new THREE.Mesh(skyGeo, skyMat); this.env.bgMesh.renderOrder = -99999; this.env.bgMesh.onBeforeRender = function(renderer, scene, camera) { this.position.copy(camera.position); }; this.env.bgMesh.visible = false; if(ms.skyFlipH) this.env.bgMesh.scale.x *= -1; scene.add(this.env.bgMesh); /* 🛑 Advanced Ground Mesh Setup 🛑 */ this.env.groundMesh = new THREE.Mesh( new THREE.PlaneGeometry(10000, 10000, ms.terrainDet, ms.terrainDet), new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: ms.groundRough, metalness: 1.0 - ms.groundRough, side: THREE.DoubleSide, transparent: true, opacity: ms.groundOpacity, normalScale: new THREE.Vector2(ms.normalScale, ms.normalScale) }) ); this.env.groundMesh.rotation.x = -Math.PI / 2; this.env.groundMesh.position.y = ms.groundY; this.env.groundMesh.visible = ms.groundVisible; this.env.groundMesh.receiveShadow = true; scene.add(this.env.groundMesh); /* Ocean Setup */ if (window.THREE.Water) { const waterGeometry = new THREE.PlaneGeometry(10000, 10000); this.env.oceanSunVec = new THREE.Vector3(); this.env.ocean = new window.THREE.Water( waterGeometry, { textureWidth: 512, textureHeight: 512, waterNormals: new THREE.TextureLoader().load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/waternormals.jpg', function ( texture ) { texture.wrapS = texture.wrapT = THREE.RepeatWrapping; }), sunDirection: new THREE.Vector3(), sunColor: new THREE.Color(ms.oceanSunColor), waterColor: new THREE.Color(ms.oceanColor), distortionScale: ms.oceanDistortion, fog: scene.fog !== undefined } ); this.env.ocean.rotation.x = -Math.PI / 2; this.env.ocean.position.y = ms.oceanY; this.env.ocean.visible = ms.oceanVisible; this.env.ocean.material.uniforms['size'].value = ms.oceanSize; scene.add(this.env.ocean); } if (window.THREE.Sky) { this.env.oceanSky = new window.THREE.Sky(); this.env.oceanSky.scale.setScalar(10000); this.env.oceanSky.visible = ms.oceanVisible; scene.add(this.env.oceanSky); const skyUniforms = this.env.oceanSky.material.uniforms; skyUniforms['turbidity'].value = 10; skyUniforms['rayleigh'].value = 2; skyUniforms['mieCoefficient'].value = 0.005; skyUniforms['mieDirectionalG'].value = 0.8; if(skyUniforms['cloudCoverage']) skyUniforms['cloudCoverage'].value = ms.oceanCloudCov; if(skyUniforms['cloudDensity']) skyUniforms['cloudDensity'].value = ms.oceanCloudDen; if(skyUniforms['cloudElevation']) skyUniforms['cloudElevation'].value = ms.oceanCloudElev; } if (window.ELEMENT_APP && window.ELEMENT_APP.renderer) { this.env.pmremGenerator = new THREE.PMREMGenerator(window.ELEMENT_APP.renderer); this.env.pmremGenerator.compileCubemapShader(); } this.updateOceanSun(); if (window.THREE.Reflector) { this.env.mirrorMesh = new window.THREE.Reflector( new window.THREE.PlaneGeometry(10000, 10000), { clipBias: 0.003, textureWidth: window.innerWidth * window.devicePixelRatio, textureHeight: window.innerHeight * window.devicePixelRatio, color: 0x888888 } ); this.env.mirrorMesh.rotation.x = -Math.PI / 2; this.env.mirrorMesh.position.y = ms.groundY + 0.1; this.env.mirrorMesh.visible = false; scene.add(this.env.mirrorMesh); } this.env.cubeRenderTarget = new THREE.WebGLCubeRenderTarget(512, { generateMipmaps: true, minFilter: THREE.LinearMipmapLinearFilter }); this.env.cubeCamera = new THREE.CubeCamera(1, 10000, this.env.cubeRenderTarget); scene.add(this.env.cubeCamera); this.applySkyFilters(); this.updateFog(); }, /* 🛑 FOG SYSTEM UPDATE 🛑 */ updateFog() { const ms = window.settings.matter; const scene = window.G1_SCENE; if (ms.fogEnabled) { if (!scene.fog) scene.fog = new window.THREE.FogExp2(ms.fogColor, ms.fogDensity); scene.fog.color.set(ms.fogColor); scene.fog.density = ms.fogDensity; scene.background = new window.THREE.Color(ms.fogColor); // Obscures everything naturally } else { scene.fog = null; scene.background = null; } }, updateOceanSun() { const ms = window.settings.matter; if (!this.env.oceanSunVec) this.env.oceanSunVec = new window.THREE.Vector3(); const phi = window.THREE.MathUtils.degToRad(90 - ms.oceanElevation); const theta = window.THREE.MathUtils.degToRad(ms.oceanAzimuth); this.env.oceanSunVec.setFromSphericalCoords(1, phi, theta); if (this.env.oceanSky) { this.env.oceanSky.material.uniforms['sunPosition'].value.copy(this.env.oceanSunVec); if (this.env.oceanSky.material.uniforms['cloudCoverage']) { this.env.oceanSky.material.uniforms['cloudCoverage'].value = ms.oceanCloudCov; this.env.oceanSky.material.uniforms['cloudDensity'].value = ms.oceanCloudDen; this.env.oceanSky.material.uniforms['cloudElevation'].value = ms.oceanCloudElev; } } if (this.env.ocean) this.env.ocean.material.uniforms['sunDirection'].value.copy(this.env.oceanSunVec).normalize(); if (this.env.lights.sun) this.env.lights.sun.position.copy(this.env.oceanSunVec).multiplyScalar(500); if (this.skyFilterTimeout) clearTimeout(this.skyFilterTimeout); this.skyFilterTimeout = setTimeout(() => { if (this.env.pmremGenerator && this.env.oceanSky && this.env.oceanSky.visible) { if (this.env.oceanRenderTarget) this.env.oceanRenderTarget.dispose(); const sceneEnv = new window.THREE.Scene(); sceneEnv.add(this.env.oceanSky); this.env.oceanRenderTarget = this.env.pmremGenerator.fromScene(sceneEnv); window.G1_SCENE.environment = this.env.oceanRenderTarget.texture; window.G1_SCENE.add(this.env.oceanSky); } }, 50); }, /* 🛑 SYNCED RADIAL INTERFERENCE MATH 🛑 */ updateTerrain(time) { if (!this.env.groundMesh) return; const ms = window.settings.matter; const pos = this.env.groundMesh.geometry.attributes.position; const smoothstep = (min, max, value) => { const x = Math.max(0, Math.min(1, (value - min) / (max - min))); return x * x * (3 - 2 * x); }; for (let i = 0; i < pos.count; i++) { const x = pos.getX(i); const y = pos.getY(i); const d1 = Math.sqrt((x - 200)**2 + (y - 200)**2); const d2 = Math.sqrt((x + 200)**2 + (y + 200)**2); const d3 = Math.sqrt(x**2 + y**2); let w = Math.sin(d1 * 0.03 - time * ms.terrainSpeed) * 0.5; w += Math.cos(d2 * 0.04 - time * ms.terrainSpeed * 1.2) * 0.5; w += Math.sin(d3 * 0.02 + time * ms.terrainSpeed * 0.8) * 0.5; let edge = 1.0 - smoothstep(400.0, 500.0, Math.abs(x)); edge *= 1.0 - smoothstep(400.0, 500.0, Math.abs(y)); pos.setZ(i, w * ms.terrainH * edge); } pos.needsUpdate = true; this.env.groundMesh.geometry.computeVertexNormals(); }, restoreAssets() { const ms = window.settings.matter; if (!ms) return; const blobMap = window.NEXUS_BLOB_MAP; const syncUI = (id, val) => { const el = document.getElementById(id); if (el) { el.value = val; const valDisp = document.getElementById(`val-${id}`); if (valDisp) valDisp.innerText = typeof val === 'number' ? val.toFixed(4) : val; } }; syncUI('m-sky-bright', ms.skyBright); syncUI('m-sky-contrast', ms.skyContrast); syncUI('m-sky-hue', ms.skyHue); syncUI('m-sky-sat', ms.skySat); syncUI('m-sky-reflect', ms.skyReflect); syncUI('m-sky-blur', ms.skyBlur); syncUI('m-sky-scale-x', ms.skyTexScaleX); syncUI('m-sky-scale-y', ms.skyTexScaleY); syncUI('m-sky-repeat-x', ms.skyTexRepeatX); syncUI('m-sky-repeat-y', ms.skyTexRepeatY); syncUI('m-sky-nudge', ms.skyNudge || 0); const sr = ms.skyRot || { x: 0, y: 0, z: 0 }; ['x', 'y', 'z'].forEach((axis) => { const rot = sr[axis] !== undefined && sr[axis] !== null ? sr[axis] : 0; syncUI(`m-sky-rot-${axis}`, rot); }); if (this.env.bgMesh) { this.env.bgMesh.rotation.set( sr.x || 0, (sr.y || 0) + (ms.skyNudge || 0), sr.z || 0 ); } syncUI('m-bright', ms.brightness); syncUI('m-contrast', ms.contrast); syncUI('m-ground-y', ms.groundY); syncUI('m-ground-rough', ms.groundRough); syncUI('m-ground-bright', ms.groundBright); syncUI('m-ground-hue', ms.groundHue); syncUI('m-ground-sat', ms.groundSat); syncUI('m-ground-tex-size', ms.groundTexSize); syncUI('m-terrain-h', ms.terrainH); syncUI('m-terrain-scale', ms.terrainScale); syncUI('m-terrain-det', ms.terrainDet); syncUI('m-ground-noise-h', ms.groundNoiseH); syncUI('m-ground-opacity', ms.groundOpacity); syncUI('m-norm-h', ms.normalScale); syncUI('m-ocean-y', ms.oceanY); syncUI('m-ocean-size', ms.oceanSize); syncUI('m-ocean-distortion', ms.oceanDistortion); syncUI('m-ocean-elevation', ms.oceanElevation); syncUI('m-ocean-azimuth', ms.oceanAzimuth); syncUI('m-ocean-exposure', ms.oceanExposure); syncUI('m-ocean-bloom-str', ms.oceanBloomStr); syncUI('m-ocean-bloom-rad', ms.oceanBloomRad); syncUI('m-ocean-cloud-cov', ms.oceanCloudCov); syncUI('m-ocean-cloud-den', ms.oceanCloudDen); syncUI('m-ocean-cloud-elev', ms.oceanCloudElev); // Restore Fog Settings syncUI('m-fog-den', ms.fogDensity); const fogColEl = document.getElementById('m-fog-color'); if (fogColEl) fogColEl.value = ms.fogColor; const fogTog = document.getElementById('m-fog-tog-btn'); if (fogTog) fogTog.classList.toggle('eye-off', !ms.fogEnabled); this.updateFog(); const oceanColEl = document.getElementById('m-ocean-color'); if (oceanColEl) oceanColEl.value = ms.oceanColor; const oceanSunColEl = document.getElementById('m-ocean-sun-color'); if (oceanSunColEl) oceanSunColEl.value = ms.oceanSunColor; if (document.getElementById('m-sky-bg-vis')) document.getElementById('m-sky-bg-vis').classList.toggle('eye-off', !ms.skyBgVisible); if (document.getElementById('m-sky-rep-x')) document.getElementById('m-sky-rep-x').classList.toggle('eye-off', !ms.skyRepeatX); if (document.getElementById('m-sky-rep-y')) document.getElementById('m-sky-rep-y').classList.toggle('eye-off', !ms.skyRepeatY); if (document.getElementById('m-sky-flip-h')) document.getElementById('m-sky-flip-h').classList.toggle('eye-off', !ms.skyFlipH); if (document.getElementById('m-ground')) document.getElementById('m-ground').classList.toggle('eye-off', !ms.groundVisible); if (document.getElementById('m-ocean')) document.getElementById('m-ocean').classList.toggle('eye-off', !ms.oceanVisible); if (document.getElementById('m-shadows')) document.getElementById('m-shadows').classList.toggle('eye-off', !ms.shadowsEnabled); if (this.env.groundMesh) { this.env.groundMesh.position.y = ms.groundY; this.env.groundMesh.visible = ms.groundVisible; this.env.groundMesh.material.roughness = ms.groundRough; this.env.groundMesh.material.metalness = 1.0 - ms.groundRough; this.env.groundMesh.material.color.setHSL(ms.groundHue, ms.groundSat, ms.groundBright); this.env.groundMesh.geometry.dispose(); this.env.groundMesh.geometry = new window.THREE.PlaneGeometry(10000, 10000, ms.terrainDet, ms.terrainDet); } if (this.env.ocean && this.env.ocean.material.uniforms) { this.env.ocean.position.y = ms.oceanY; this.env.ocean.visible = ms.oceanVisible; if(this.env.oceanSky) this.env.oceanSky.visible = ms.oceanVisible; this.env.ocean.material.uniforms['waterColor'].value.set(ms.oceanColor); this.env.ocean.material.uniforms['sunColor'].value.set(ms.oceanSunColor); this.env.ocean.material.uniforms['size'].value = ms.oceanSize; this.env.ocean.material.uniforms['distortionScale'].value = ms.oceanDistortion; } this.updateOceanSun(); if (!ms.skyTexName && this.env.bgMesh && this.env.bgMesh.material.uniforms && this.env.bgMesh.material.uniforms.tMap) { this.env.bgMesh.material.uniforms.tMap.value = null; } if (blobMap && ms.skyTexName && blobMap[ms.skyTexName]) { this.log(`ZIP: Restoring Skybox (${ms.skyTexName})`); const url = blobMap[ms.skyTexName]; const isVideo = ms.skyTexName.toLowerCase().endsWith('.mp4') || ms.skyTexName.toLowerCase().endsWith('.webm'); const applySkyTexture = (t) => { t.mapping = window.THREE.UVMapping; t.wrapS = window.THREE.RepeatWrapping; t.wrapT = window.THREE.RepeatWrapping; if (window.THREE.sRGBEncoding) t.encoding = window.THREE.sRGBEncoding; if (window.THREE.SRGBColorSpace) t.colorSpace = window.THREE.SRGBColorSpace; this.env.bgMesh.material.uniforms.tMap.value = t; this.env.bgMesh.material.needsUpdate = true; this.env.bgMesh.visible = ms.skyBgVisible; window.G1_SCENE.background = null; this.applySkyFilters(); }; if (isVideo) { const vid = document.createElement('video'); vid.src = url; vid.loop = true; vid.muted = true; vid.playsInline = true; const playPromise = vid.play(); if (playPromise !== undefined) { playPromise.catch(e => { this.log("ZIP Restore: Video blocked. Click anywhere to resume."); document.body.addEventListener('click', () => vid.play(), { once: true }); }); } applySkyTexture(new window.THREE.VideoTexture(vid)); } else { new window.THREE.TextureLoader().load(url, applySkyTexture); } } if (blobMap && ms.groundTexName && blobMap[ms.groundTexName]) { const url = blobMap[ms.groundTexName]; new window.THREE.TextureLoader().load(url, (tex) => { tex.wrapS = window.THREE.RepeatWrapping; tex.wrapT = window.THREE.RepeatWrapping; tex.repeat.set(ms.groundTexSize, ms.groundTexSize); this.env.groundMesh.material.map = tex; this.env.groundMesh.material.needsUpdate = true; }); } if (blobMap && ms.groundNormalName && blobMap[ms.groundNormalName]) { const url = blobMap[ms.groundNormalName]; new window.THREE.TextureLoader().load(url, (tex) => { tex.wrapS = window.THREE.RepeatWrapping; tex.wrapT = window.THREE.RepeatWrapping; tex.repeat.set(ms.groundTexSize, ms.groundTexSize); this.env.groundMesh.material.normalMap = tex; this.env.groundMesh.material.needsUpdate = true; }); } if (blobMap && ms.oceanNormalName && blobMap[ms.oceanNormalName]) { const url = blobMap[ms.oceanNormalName]; new window.THREE.TextureLoader().load(url, (tex) => { tex.wrapS = window.THREE.RepeatWrapping; tex.wrapT = window.THREE.RepeatWrapping; if(this.env.ocean) { this.env.ocean.material.uniforms['normalSampler'].value = tex; } }); } if (ms.models && ms.models.length > 0 && blobMap) { ms.models.forEach(async (mDef) => { if (blobMap[mDef.name]) { this.log(`ZIP: Restoring Model (${mDef.name})`); const url = blobMap[mDef.name]; const ext = mDef.name.split('.').pop().toLowerCase(); await this.loadModelFromUrl(url, ext, mDef.name, true, mDef); } }); } }, /* Applies sky colors and scales */ applySkyFilters() { const ms = window.settings.matter; if (this.env.bgMesh && (!this.env.bgMesh.material.uniforms.tMap.value)) { this.env.bgMesh.visible = false; return; } if (this.env.bgMesh && this.env.bgMesh.material.uniforms) { const c = new window.THREE.Color(); c.setHSL(ms.skyHue, ms.skySat, 0.5); const bMap = ms.skyBright * 2.0; const cMap = ms.skyContrast; this.env.bgMesh.material.uniforms.colorMult.value.set(c.r * bMap * cMap, c.g * bMap * cMap, c.b * bMap * cMap); this.env.bgMesh.material.uniforms.blurAmount.value = ms.skyBlur; this.env.bgMesh.material.uniforms.uvScale.value.set(ms.skyTexScaleX || 1.0, ms.skyTexScaleY || 1.0); this.env.bgMesh.material.uniforms.uvRepeat.value.set(ms.skyRepeatX ? (ms.skyTexRepeatX || 1.0) : 1.0, ms.skyRepeatY ? (ms.skyTexRepeatY || 1.0) : 1.0); this.env.bgMesh.material.uniforms.doRepeat.value.set(ms.skyRepeatX ? 1.0 : 0.0, ms.skyRepeatY ? 1.0 : 0.0); this.env.bgMesh.visible = ms.skyBgVisible; } this.models.forEach(m => { m.parts.forEach(p => { let mats = Array.isArray(p.material) ? p.material : [p.material]; mats.forEach(mat => { mat.envMapIntensity = ms.skyReflect; mat.needsUpdate = true; }); }); }); if (this.env.groundMesh && this.env.groundMesh.material) { this.env.groundMesh.material.envMapIntensity = ms.skyReflect; this.env.groundMesh.material.needsUpdate = true; } if (this.skyFilterTimeout) clearTimeout(this.skyFilterTimeout); this.skyFilterTimeout = setTimeout(() => { this.needsEnvironmentUpdate = true; }, 100); }, applySunIntensity() { const ms = window.settings.matter; if(this.env.lights.sun && this.env.lights.sunFill) { this.env.lights.sun.intensity = ms.sunTotal * (1.0 + (ms.shadowInt * 2.0)); this.env.lights.sunFill.intensity = ms.sunTotal * (1.0 - ms.shadowInt); } }, applyGroundFilters() { const ms = window.settings.matter; if (this.env.groundMesh && this.env.groundMesh.material) { this.env.groundMesh.material.color.setHSL(ms.groundHue, ms.groundSat, ms.groundBright); } }, applyActiveMaterial() { if(!this.activeModel) return; const s = this.activeModel.settings; this.activeModel.parts.forEach(p => { let mats = Array.isArray(p.material) ? p.material : [p.material]; mats.forEach(mat => { if (s.isEmissive) { mat.metalness = 0; mat.roughness = 1.0; mat.emissive.copy(mat.color); mat.emissiveIntensity = s.reflectiveness * 2.0; } else { mat.emissive.setHex(0x000000); mat.emissiveIntensity = 0; mat.metalness = Math.min(1.0, s.reflectiveness); mat.roughness = 1.0 - Math.min(1.0, s.reflectiveness); } if (s.hue !== 0 || s.saturation !== 1.0 || s.brightness !== 1.0) { if (!mat.userData.origColor) mat.userData.origColor = mat.color.clone(); mat.color.copy(mat.userData.origColor); mat.color.offsetHSL(s.hue, s.saturation - 1.0, s.brightness - 1.0); if (s.isEmissive) mat.emissive.copy(mat.color); } else if (mat.userData.origColor) { mat.color.copy(mat.userData.origColor); if (s.isEmissive) mat.emissive.copy(mat.color); } mat.needsUpdate = true; }); }); }, async loadModelFromUrl(url, ext, name, isRestoring, mDef = null, manager = null) { if ((ext === 'glb' || ext === 'gltf') && !window.THREE.GLTFLoader) { await this.injectScript('https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js'); } if (ext === 'fbx' && !window.THREE.FBXLoader) { if(!window.fflate) await this.injectScript('https://cdn.jsdelivr.net/npm/fflate@0.8.2/umd/index.js'); await this.injectScript('https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/FBXLoader.js'); } const onLoad = (res) => { const obj = (ext === 'fbx') ? res : res.scene; this.processModel(obj, name, isRestoring); if (isRestoring && this.activeModel && mDef) { Object.assign(this.activeModel.settings, mDef.settings); this.activeModel.instances = (mDef.instances || []).map(inst => { const p = inst.pos || {}; const r = inst.rot || {}; return { pos: new window.THREE.Vector3(p.x || 0, p.y || 0, p.z || 0), rot: new window.THREE.Euler(r._x || r.x || 0, r._y || r.y || 0, r._z || r.z || 0) }; }); mDef.settings = this.activeModel.settings; mDef.instances = this.activeModel.instances; this.applyActiveMaterial(); this.refreshList(); if (mDef.settings.texName && window.NEXUS_BLOB_MAP && window.NEXUS_BLOB_MAP[mDef.settings.texName]) { const texUrl = window.NEXUS_BLOB_MAP[mDef.settings.texName]; new window.THREE.TextureLoader().load(texUrl, (tex) => { this.activeModel.parts.forEach(p => { let mats = Array.isArray(p.material) ? p.material : [p.material]; mats.forEach(m => { m.map = tex; m.needsUpdate = true; }); }); }); } } }; if (ext === 'fbx') { const loader = manager ? new window.THREE.FBXLoader(manager) : new window.THREE.FBXLoader(); loader.load(url, onLoad, undefined, (e) => this.log(`Error: ${e}`)); } else { const loader = manager ? new window.THREE.GLTFLoader(manager) : new window.THREE.GLTFLoader(); loader.load(url, onLoad, undefined, (e) => this.log(`Error: ${e}`)); } }, processModel(obj, name, isRestoring) { isRestoring = isRestoring || false; const THREE = window.THREE; obj.updateMatrixWorld(true); const box = new THREE.Box3().setFromObject(obj); const center = box.getCenter(new THREE.Vector3()); const scale = 100 / (Math.max(box.getSize(new THREE.Vector3()).x, 50)); const newModel = { id: Math.random().toString(36).substr(2,9), name, dummy: new THREE.Object3D(), parts: [], instances: [], settings: { scale: 1.0, reflectiveness: 0.5, brightness: 1.0, hue: 0, saturation: 1.0, isEmissive: false } }; obj.traverse(c => { if (c.isMesh) { const g = c.geometry.clone(); g.applyMatrix4(c.matrixWorld); g.translate(-center.x, -center.y, -center.z); g.scale(scale, scale, scale); let mat; if (c.material) { const convertToStandard = (m) => { if (m.type === 'MeshStandardMaterial' || m.type === 'MeshPhysicalMaterial') return m.clone(); const newM = new window.THREE.MeshStandardMaterial({ color: m.color ? m.color.clone() : new window.THREE.Color(0xffffff), map: m.map || null, normalMap: m.normalMap || null, roughnessMap: m.specularMap || m.roughnessMap || null, transparent: m.transparent || false, opacity: m.opacity !== undefined ? m.opacity : 1.0, side: m.side !== undefined ? m.side : window.THREE.FrontSide, roughness: 0.5, metalness: 0.5 }); if (m.userData) newM.userData = JSON.parse(JSON.stringify(m.userData)); return newM; }; if (Array.isArray(c.material)) { mat = c.material.map(m => convertToStandard(m)); } else { mat = convertToStandard(c.material); } } else { mat = new THREE.MeshStandardMaterial({color:0xffffff, roughness: 0.5, metalness: 0.5}); } const inst = new THREE.InstancedMesh(g, mat, 1); inst.castShadow = true; inst.receiveShadow = true; inst.frustumCulled = false; window.G1_SCENE.add(inst); newModel.parts.push(inst); } }); this.models.push(newModel); this.activeModel = newModel; this.rebuild(newModel); if (!isRestoring) { if (!window.settings.matter.models) window.settings.matter.models = []; window.settings.matter.models.push({ name: name, settings: newModel.settings, instances: newModel.instances }); } this.refreshList(); this.log(`Model Active: ${name}`); this.applySkyFilters(); }, rebuild(m) { m.instances = [{ pos: new window.THREE.Vector3(0, 0, 0), rot: new window.THREE.Euler(0, 0, 0) }]; }, refreshList() { const selectEl = document.getElementById('m-model-select'); if (selectEl) { selectEl.innerHTML = this.models.map((m, i) => ``).join(''); } const instUI = document.getElementById('m-inst-ui'); if (instUI) instUI.style.display = this.activeModel ? 'block' : 'none'; if(this.activeModel) { const s = this.activeModel.settings; const inst = this.activeModel.instances[0]; ['x','y','z'].forEach(a => { const pEl = document.getElementById(`m-pos-${a}`); const rEl = document.getElementById(`m-rot-${a}`); if (pEl) pEl.value = inst.pos[a]; if (rEl) rEl.value = inst.rot[a]; }); const setV = (id, v) => { const el = document.getElementById(`val-${id}`); if(el) el.innerText = typeof v === 'number' ? v.toFixed(2) : v; }; const setEl = (id, v) => { const el = document.getElementById(id); if(el) el.value = v; }; setEl('m-scale', s.scale); setEl('m-active-reflect', s.reflectiveness); setEl('m-active-bright', s.brightness); setEl('m-active-hue', s.hue); setEl('m-active-sat', s.saturation); setV('m-scale', s.scale); setV('m-active-reflect', s.reflectiveness); setV('m-active-bright', s.brightness); setV('m-active-hue', s.hue); setV('m-active-sat', s.saturation); const emissiveEye = document.getElementById('m-eye-emissive'); if (emissiveEye) emissiveEye.classList.toggle('eye-off', !s.isEmissive); } }, selectModel(index) { this.activeModel = this.models[index]; this.log(`Selected: ${this.activeModel.name}`); this.refreshList(); }, /* 🛑 MASSIVE UI INJECTION 🛑 */ injectUI() { const root = document.getElementById('nexus-elementor-root') || document.body; if (document.getElementById('matter-sidebar')) return; const ms = window.settings.matter; const style = document.createElement('style'); style.id = 'matter-sky-xyz-css'; style.innerHTML = ` #matter-sidebar { z-index: 100000; border-color: rgba(255,255,0,0.2); color: #fff; display: flex; flex-direction: column; height: 100vh; } #matter-tab { position:fixed; left:20px; top:125px; width:42px; height:42px; background:transparent !important; border:1px solid rgba(255,255,0,0.4); color: rgba(255,255,0,0.4); border-radius:4px; cursor:pointer; display:flex; align-items:center; justify-content:center; font-size:18px; z-index:100002; transition: left 0.3s ease, border-color 0.3s ease, transform 0.3s ease; } #matter-tab:hover { border-color: rgba(255,255,0,1.0); color: rgba(255,255,0,1.0); box-shadow:0 0 10px rgba(255,255,0,0.4); transform: scale(1.05); } #aether-sidebar:not(.collapsed) ~ #matter-tab, #matter-sidebar:not(.collapsed) ~ #matter-tab { left: 370px; } #matter-sidebar input[type=range]::-webkit-slider-thumb { background: #ffff00 !important; } .eye-btn { font-size: 14px; transition: all 0.2s ease; cursor: pointer; display: inline-block; filter: brightness(0) invert(1); opacity: 1; } .eye-btn.eye-off { filter: brightness(0) invert(0.5); opacity: 0.5; } .xyz-row { display:flex; gap:4px; margin-top:5px; } .xyz-col { flex:1; } .xyz-label { font-size:7px; color:#ccc; text-align:center; font-weight:bold; } #m-debug { background: rgba(0,0,0,0.5); border: 1px solid #333; height: 90px; overflow-y: auto; font-family: monospace; font-size: 9px; padding: 8px; margin-top: 15px; border-radius: 4px; flex-shrink: 0; } .ui-scroll-area { flex-grow: 1; overflow-y: auto; padding-right: 5px; } #matter-sidebar ::-webkit-scrollbar { width: 5px; height: 5px; } #matter-sidebar ::-webkit-scrollbar-track { background: rgba(10, 10, 10, 0.8); border-radius: 3px; } #matter-sidebar ::-webkit-scrollbar-thumb { background: #444; border-radius: 3px; transition: background 0.3s; } #matter-sidebar ::-webkit-scrollbar-thumb:hover { background: #ffff00; } .ui-sub-section { border: 1px solid #333; margin-top: 8px; background: rgba(0,0,0,0.3); border-radius: 3px; } .sub-header { font-size: 9px; font-weight: bold; color: #ffff00; padding: 8px; cursor: pointer; display: flex; justify-content: space-between; background: rgba(255,255,0,0.05); transition: background 0.2s; } .sub-header:hover { background: rgba(255,255,0,0.1); } .sub-content { padding: 8px; } .ui-sub-section.collapsed .sub-content { display: none; } .ui-sub-section.collapsed .sub-header span:last-child { transform: rotate(-90deg); } .sub-header span:last-child { transition: transform 0.2s; font-size: 8px; color: #888; } .ui-section.collapsed .section-content { display: none; } #m-model-select { width: 100%; background: rgba(0,0,0,0.5); color: #fff; border: 1px solid #333; padding: 6px; font-size: 10px; border-radius: 3px; outline: none; margin-bottom: 10px; cursor: pointer; } #m-model-select:focus { border-color: #ffff00; } .c-checkbox-row { display: flex; justify-content: space-between; align-items: center; font-size: 9px; color: #aaa; margin-bottom: 5px; font-weight:bold; text-transform: uppercase; } .c-toggle { accent-color: #ffff00; cursor: pointer; } .orb-btn { display:block; border:1px dashed #ffff00; padding:8px; text-align:center; font-size:9px; cursor:pointer; color:#ffff00; background:rgba(255,255,0,0.05); } .orb-btn:hover { background:rgba(255,255,0,0.2); } `; document.head.appendChild(style); const xyzTemplate = (idPrefix, label, min, max, step) => `
${label}
RESET
X
Y
Z
`; const sidebar = document.createElement('div'); sidebar.id = 'matter-sidebar'; sidebar.className = 'ui-sidebar collapsed'; sidebar.innerHTML = `

MATTER.FAB

`; const tab = document.createElement('button'); tab.id = 'matter-tab'; tab.innerHTML = '⌬'; root.appendChild(sidebar); root.appendChild(tab); tab.onclick = () => { const sidebar = document.getElementById('matter-sidebar'); sidebar.classList.toggle('collapsed'); if(!sidebar.classList.contains('collapsed')) document.getElementById('aether-sidebar')?.classList.add('collapsed'); }; this.bindUI(sidebar); }, range(id, label, min, max, step, val) { return `
${label} ${val}
`; }, bindUI(s) { const setV = (id, v) => { const el = document.getElementById(`val-${id}`); if(el) el.innerText = typeof v === 'number' ? v.toFixed(4) : v; }; const historySave = () => { if (window.G1_HISTORY) window.G1_HISTORY.save(); }; const bindClick = (id, handler) => { const el = s.querySelector(id); if (el) el.onclick = (e) => { handler(e); historySave(); }; }; const bindInput = (id, handler) => { const el = s.querySelector(id); if (el) { el.addEventListener('change', historySave); el.oninput = handler; } }; const bindChange = (id, handler) => { const el = s.querySelector(id); if (el) el.onchange = (e) => { handler(e); historySave(); }; }; bindClick('#m-eye-emissive', (e) => { e.stopPropagation(); if (!this.activeModel) return; this.activeModel.settings.isEmissive = !this.activeModel.settings.isEmissive; e.target.classList.toggle('eye-off', !this.activeModel.settings.isEmissive); this.applyActiveMaterial(); }); bindClick('#m-exp-mirror', (e) => { e.stopPropagation(); const isOff = e.target.classList.contains('eye-off'); e.target.classList.toggle('eye-off', !isOff); if (isOff) { if (this.env.mirrorMesh) { this.env.mirrorMesh.visible = true; this.env.mirrorMesh.position.y = window.settings.matter.groundY; } if (this.env.ocean) this.env.ocean.visible = false; if (this.env.oceanSky) this.env.oceanSky.visible = false; if (this.env.groundMesh) this.env.groundMesh.visible = false; this.log("Experimental Planar Mirror: ON"); } else { if (this.env.mirrorMesh) this.env.mirrorMesh.visible = false; if (this.env.ocean) this.env.ocean.visible = window.settings.matter.oceanVisible; if (this.env.oceanSky) this.env.oceanSky.visible = window.settings.matter.oceanVisible; if (this.env.groundMesh) this.env.groundMesh.visible = window.settings.matter.groundVisible; this.log("Experimental Planar Mirror: OFF"); } }); bindClick('#m-fog-tog-btn', (e) => { e.stopPropagation(); window.settings.matter.fogEnabled = !window.settings.matter.fogEnabled; e.target.classList.toggle('eye-off', !window.settings.matter.fogEnabled); this.updateFog(); }); bindInput('#m-fog-den', (e) => { window.settings.matter.fogDensity = parseFloat(e.target.value); setV('m-fog-den', window.settings.matter.fogDensity); this.updateFog(); }); bindInput('#m-fog-color', (e) => { window.settings.matter.fogColor = e.target.value; this.updateFog(); }); bindClick('#m-sky-bg-vis', (e) => { e.stopPropagation(); window.settings.matter.skyBgVisible = !window.settings.matter.skyBgVisible; e.target.classList.toggle('eye-off', !window.settings.matter.skyBgVisible); if (this.env.bgMesh) this.env.bgMesh.visible = window.settings.matter.skyBgVisible; }); bindClick('#m-sky-rep-x', (e) => { e.stopPropagation(); window.settings.matter.skyRepeatX = !window.settings.matter.skyRepeatX; e.target.classList.toggle('eye-off', !window.settings.matter.skyRepeatX); this.applySkyFilters(); }); bindClick('#m-sky-rep-y', (e) => { e.stopPropagation(); window.settings.matter.skyRepeatY = !window.settings.matter.skyRepeatY; e.target.classList.toggle('eye-off', !window.settings.matter.skyRepeatY); this.applySkyFilters(); }); bindClick('#m-sky-flip-h', (e) => { e.stopPropagation(); window.settings.matter.skyFlipH = !window.settings.matter.skyFlipH; e.target.classList.toggle('eye-off', !window.settings.matter.skyFlipH); if (this.env.bgMesh) this.env.bgMesh.scale.x *= -1; }); bindClick('#m-eye-active', (e) => { e.stopPropagation(); if(this.activeModel) { const on = !this.activeModel.parts[0].visible; e.target.classList.toggle('eye-off', !on); this.activeModel.parts.forEach(p => p.visible = on); } }); bindChange('#m-model-select', (e) => { this.selectModel(parseInt(e.target.value)); }); bindChange('#m-upload', async (e) => { try { const files = Array.from(e.target.files); if (files.length === 0) return; if (window.NEXUS_ASSETS) files.forEach(file => window.NEXUS_ASSETS.set(file.name, file)); const rootFile = files.find(f => { const ext = f.name.split('.').pop().toLowerCase(); return ['fbx', 'gltf', 'glb'].includes(ext); }); if (!rootFile) throw new Error("No .fbx, .gltf, or .glb root file found."); this.log(`Loading: ${rootFile.name}`); const url = URL.createObjectURL(rootFile); const ext = rootFile.name.split('.').pop().toLowerCase(); await this.loadModelFromUrl(url, ext, rootFile.name, false); } catch (error) { this.log(`[ERROR] Model Upload: ${error.message}`); } }); bindChange('#m-tex-upload', (e) => { try { const file = e.target.files[0]; if(!file || !this.activeModel) return; if (window.NEXUS_ASSETS) window.NEXUS_ASSETS.set(file.name, file); this.activeModel.settings.texName = file.name; this.log(`Applying texture to ${this.activeModel.name}`); new window.THREE.TextureLoader().load(URL.createObjectURL(file), (tex) => { this.activeModel.parts.forEach(p => { let mats = Array.isArray(p.material) ? p.material : [p.material]; mats.forEach(m => { m.map = tex; m.needsUpdate = true; }); }); }, undefined, () => { throw new Error("Failed to load texture."); }); } catch (error) { this.log(`[ERROR] Texture Upload: ${error.message}`); } }); bindInput('#m-bright', (e) => { const val = parseFloat(e.target.value); setV('m-bright', val); window.settings.matter.brightness = val; if(window.ELEMENT_APP?.renderer) window.ELEMENT_APP.renderer.domElement.style.filter = `brightness(${window.settings.matter.brightness}) contrast(${window.settings.matter.contrast})`; }); bindInput('#m-contrast', (e) => { const val = parseFloat(e.target.value); setV('m-contrast', val); window.settings.matter.contrast = val; if(window.ELEMENT_APP?.renderer) window.ELEMENT_APP.renderer.domElement.style.filter = `brightness(${window.settings.matter.brightness}) contrast(${window.settings.matter.contrast})`; }); bindChange('#m-sky-up', (e) => { try { const file = e.target.files[0]; if(!file) return; if (window.NEXUS_ASSETS) { window.NEXUS_ASSETS.set(file.name, file); window.settings.matter.skyTexName = file.name; } this.log(`Applying Sky Dome: ${file.name}`); const applySkyTexture = (t) => { t.mapping = window.THREE.UVMapping; t.wrapS = window.THREE.RepeatWrapping; t.wrapT = window.THREE.RepeatWrapping; if (window.THREE.sRGBEncoding) t.encoding = window.THREE.sRGBEncoding; if (window.THREE.SRGBColorSpace) t.colorSpace = window.THREE.SRGBColorSpace; this.env.bgMesh.material.uniforms.tMap.value = t; this.env.bgMesh.material.needsUpdate = true; this.env.bgMesh.visible = window.settings.matter.skyBgVisible; window.G1_SCENE.background = null; this.applySkyFilters(); }; if (file.type.startsWith('video')) { const vid = document.createElement('video'); vid.src = URL.createObjectURL(file); vid.loop = true; vid.muted = true; vid.playsInline = true; const playPromise = vid.play(); if (playPromise !== undefined) { playPromise.catch(err => { this.log(`Skybox video blocked. Click screen to play.`); document.body.addEventListener('click', () => vid.play(), { once: true }); }); } applySkyTexture(new window.THREE.VideoTexture(vid)); } else { new window.THREE.TextureLoader().load(URL.createObjectURL(file), applySkyTexture, undefined, () => { throw new Error("Failed to load skybox image."); }); } } catch (error) { this.log(`[ERROR] Skybox Upload: ${error.message}`); } }); bindInput('#m-sky-blur', (e) => { setV('m-sky-blur', parseFloat(e.target.value)); window.settings.matter.skyBlur = parseFloat(e.target.value); this.applySkyFilters(); }); bindInput('#m-sky-scale-x', (e) => { const val = parseFloat(e.target.value); setV('m-sky-scale-x', val); window.settings.matter.skyTexScaleX = val; this.applySkyFilters(); }); bindInput('#m-sky-scale-y', (e) => { const val = parseFloat(e.target.value); setV('m-sky-scale-y', val); window.settings.matter.skyTexScaleY = val; this.applySkyFilters(); }); bindInput('#m-sky-repeat-x', (e) => { const val = parseFloat(e.target.value); setV('m-sky-repeat-x', val); window.settings.matter.skyTexRepeatX = val; this.applySkyFilters(); }); bindInput('#m-sky-repeat-y', (e) => { const val = parseFloat(e.target.value); setV('m-sky-repeat-y', val); window.settings.matter.skyTexRepeatY = val; this.applySkyFilters(); }); bindInput('#m-sky-bright', (e) => { window.settings.matter.skyBright = parseFloat(e.target.value); setV('m-sky-bright', window.settings.matter.skyBright); this.applySkyFilters(); }); bindInput('#m-sky-contrast', (e) => { window.settings.matter.skyContrast = parseFloat(e.target.value); setV('m-sky-contrast', window.settings.matter.skyContrast); this.applySkyFilters(); }); bindInput('#m-sky-hue', (e) => { window.settings.matter.skyHue = parseFloat(e.target.value); setV('m-sky-hue', window.settings.matter.skyHue); this.applySkyFilters(); }); bindInput('#m-sky-sat', (e) => { window.settings.matter.skySat = parseFloat(e.target.value); setV('m-sky-sat', window.settings.matter.skySat); this.applySkyFilters(); }); bindInput('#m-sky-reflect', (e) => { window.settings.matter.skyReflect = parseFloat(e.target.value); setV('m-sky-reflect', window.settings.matter.skyReflect); this.applySkyFilters(); }); ['x', 'y', 'z'].forEach(axis => { bindInput(`#m-sky-rot-${axis}`, (e) => { window.settings.matter.skyRot[axis] = parseFloat(e.target.value); if (this.env.bgMesh) { let r = window.settings.matter.skyRot[axis]; if (axis === 'y') r += (window.settings.matter.skyNudge || 0); this.env.bgMesh.rotation[axis] = r; this.needsEnvironmentUpdate = true; } }); bindInput(`#m-pos-${axis}`, (e) => { if(this.activeModel) this.activeModel.instances[0].pos[axis] = parseFloat(e.target.value); }); bindInput(`#m-rot-${axis}`, (e) => { if(this.activeModel) this.activeModel.instances[0].rot[axis] = parseFloat(e.target.value); }); }); bindInput('#m-sky-nudge', (e) => { const val = parseFloat(e.target.value); window.settings.matter.skyNudge = val; setV('m-sky-nudge', val); if (this.env.bgMesh) this.env.bgMesh.rotation.y = (window.settings.matter.skyRot.y || 0) + val; }); bindInput('#m-active-reflect', (e) => { if(this.activeModel) { this.activeModel.settings.reflectiveness = parseFloat(e.target.value); setV('m-active-reflect', this.activeModel.settings.reflectiveness); this.applyActiveMaterial(); } }); bindInput('#m-active-bright', (e) => { if(this.activeModel) { this.activeModel.settings.brightness = parseFloat(e.target.value); setV('m-active-bright', this.activeModel.settings.brightness); this.applyActiveMaterial(); } }); bindInput('#m-active-hue', (e) => { if(this.activeModel) { this.activeModel.settings.hue = parseFloat(e.target.value); setV('m-active-hue', this.activeModel.settings.hue); this.applyActiveMaterial(); } }); bindInput('#m-active-sat', (e) => { if(this.activeModel) { this.activeModel.settings.saturation = parseFloat(e.target.value); setV('m-active-sat', this.activeModel.settings.saturation); this.applyActiveMaterial(); } }); /* 🛑 NEW GROUND BINDINGS 🛑 */ bindClick('#m-ground', (e) => { e.stopPropagation(); window.settings.matter.groundVisible = !window.settings.matter.groundVisible; e.target.classList.toggle('eye-off', !window.settings.matter.groundVisible); if(this.env.groundMesh) this.env.groundMesh.visible = window.settings.matter.groundVisible; }); bindInput('#m-ground-y', (e) => { window.settings.matter.groundY = parseFloat(e.target.value); setV('m-ground-y', window.settings.matter.groundY); if(this.env.groundMesh) this.env.groundMesh.position.y = window.settings.matter.groundY; }); bindInput('#m-ground-opacity', (e) => { window.settings.matter.groundOpacity = parseFloat(e.target.value); setV('m-ground-opacity', window.settings.matter.groundOpacity); if(this.env.groundMesh) this.env.groundMesh.material.opacity = window.settings.matter.groundOpacity; }); bindInput('#m-terrain-h', (e) => { window.settings.matter.terrainH = parseFloat(e.target.value); setV('m-terrain-h', window.settings.matter.terrainH); }); bindInput('#m-terrain-speed', (e) => { window.settings.matter.terrainSpeed = parseFloat(e.target.value); setV('m-terrain-speed', window.settings.matter.terrainSpeed); }); bindInput('#m-ground-reflect', (e) => { const v = parseFloat(e.target.value); window.settings.matter.groundReflect = v; setV('m-ground-reflect', v); if(this.env.groundMesh) { this.env.groundMesh.material.metalness = v; this.env.groundMesh.material.roughness = 1.0 - v; } }); bindInput('#m-norm-h', (e) => { const v = parseFloat(e.target.value); window.settings.matter.normalScale = v; setV('m-norm-h', v); if(this.env.groundMesh && this.env.groundMesh.material.normalScale) this.env.groundMesh.material.normalScale.setScalar(v); }); const loadTexMap = (id, propName, matProp) => { bindChange(id, (e) => { const file = e.target.files[0]; if(!file) return; if (window.NEXUS_ASSETS) { window.NEXUS_ASSETS.set(file.name, file); window.settings.matter[propName] = file.name; } this.log(`Applying ${matProp}: ${file.name}`); new T.TextureLoader().load(URL.createObjectURL(file), (tex) => { tex.wrapS = tex.wrapT = T.RepeatWrapping; tex.repeat.set(window.settings.matter.groundTexSize || 10, window.settings.matter.groundTexSize || 10); if(this.env.groundMesh) { this.env.groundMesh.material[matProp] = tex; this.env.groundMesh.material.needsUpdate = true; } }); }); }; loadTexMap('#m-ground-tex', 'groundTexName', 'map'); loadTexMap('#m-ground-normal', 'groundNormalName', 'normalMap'); loadTexMap('#m-ground-refl', 'groundReflectName', 'roughnessMap'); bindInput('#m-ground-tex-size', (e) => { window.settings.matter.groundTexSize = parseFloat(e.target.value); setV('m-ground-tex-size', window.settings.matter.groundTexSize); if(this.env.groundMesh && this.env.groundMesh.material.map) this.env.groundMesh.material.map.repeat.set(window.settings.matter.groundTexSize, window.settings.matter.groundTexSize); if(this.env.groundMesh && this.env.groundMesh.material.normalMap) this.env.groundMesh.material.normalMap.repeat.set(window.settings.matter.groundTexSize, window.settings.matter.groundTexSize); if(this.env.groundMesh && this.env.groundMesh.material.roughnessMap) this.env.groundMesh.material.roughnessMap.repeat.set(window.settings.matter.groundTexSize, window.settings.matter.groundTexSize); }); bindInput('#m-ground-bright', (e) => { window.settings.matter.groundBright = parseFloat(e.target.value); setV('m-ground-bright', window.settings.matter.groundBright); this.applyGroundFilters(); }); bindInput('#m-ground-hue', (e) => { window.settings.matter.groundHue = parseFloat(e.target.value); setV('m-ground-hue', window.settings.matter.groundHue); this.applyGroundFilters(); }); bindInput('#m-ground-sat', (e) => { window.settings.matter.groundSat = parseFloat(e.target.value); setV('m-ground-sat', window.settings.matter.groundSat); this.applyGroundFilters(); }); bindInput('#m-terrain-scale', (e) => { window.settings.matter.terrainScale = parseFloat(e.target.value); setV('m-terrain-scale', window.settings.matter.terrainScale); }); bindInput('#m-terrain-det', (e) => { window.settings.matter.terrainDet = parseInt(e.target.value); setV('m-terrain-det', window.settings.matter.terrainDet); if(this.env.groundMesh) { this.env.groundMesh.geometry.dispose(); this.env.groundMesh.geometry = new window.THREE.PlaneGeometry(10000, 10000, window.settings.matter.terrainDet, window.settings.matter.terrainDet); } }); /* 🛑 OCEAN BINDINGS 🛑 */ bindClick('#m-ocean', (e) => { e.stopPropagation(); window.settings.matter.oceanVisible = !window.settings.matter.oceanVisible; e.target.classList.toggle('eye-off', !window.settings.matter.oceanVisible); if(this.env.ocean) this.env.ocean.visible = window.settings.matter.oceanVisible; if(this.env.oceanSky) this.env.oceanSky.visible = window.settings.matter.oceanVisible; this.updateOceanSun(); }); bindChange('#m-ocean-normal', (e) => { try { const file = e.target.files[0]; if(!file) return; if (window.NEXUS_ASSETS) { window.NEXUS_ASSETS.set(file.name, file); window.settings.matter.oceanNormalName = file.name; } this.log(`Applying Ocean Normal Map: ${file.name}`); new window.THREE.TextureLoader().load(URL.createObjectURL(file), (tex) => { tex.wrapS = window.THREE.RepeatWrapping; tex.wrapT = window.THREE.RepeatWrapping; if(this.env.ocean) { this.env.ocean.material.uniforms['normalSampler'].value = tex; } }); } catch (error) { this.log(`[ERROR] Ocean Normal Upload: ${error.message}`); } }); bindInput('#m-ocean-color', (e) => { window.settings.matter.oceanColor = e.target.value; if(this.env.ocean) this.env.ocean.material.uniforms['waterColor'].value.set(e.target.value); }); bindInput('#m-ocean-sun-color', (e) => { window.settings.matter.oceanSunColor = e.target.value; if(this.env.ocean) this.env.ocean.material.uniforms['sunColor'].value.set(e.target.value); }); bindInput('#m-ocean-y', (e) => { window.settings.matter.oceanY = parseFloat(e.target.value); setV('m-ocean-y', window.settings.matter.oceanY); if(this.env.ocean) this.env.ocean.position.y = window.settings.matter.oceanY; }); bindInput('#m-ocean-size', (e) => { window.settings.matter.oceanSize = parseFloat(e.target.value); setV('m-ocean-size', window.settings.matter.oceanSize); if(this.env.ocean) this.env.ocean.material.uniforms['size'].value = window.settings.matter.oceanSize; }); bindInput('#m-ocean-distortion', (e) => { window.settings.matter.oceanDistortion = parseFloat(e.target.value); setV('m-ocean-distortion', window.settings.matter.oceanDistortion); if(this.env.ocean) this.env.ocean.material.uniforms['distortionScale'].value = window.settings.matter.oceanDistortion; }); bindInput('#m-ocean-elevation', (e) => { window.settings.matter.oceanElevation = parseFloat(e.target.value); setV('m-ocean-elevation', window.settings.matter.oceanElevation); this.updateOceanSun(); }); bindInput('#m-ocean-azimuth', (e) => { window.settings.matter.oceanAzimuth = parseFloat(e.target.value); setV('m-ocean-azimuth', window.settings.matter.oceanAzimuth); this.updateOceanSun(); }); bindInput('#m-ocean-exposure', (e) => { window.settings.matter.oceanExposure = parseFloat(e.target.value); setV('m-ocean-exposure', window.settings.matter.oceanExposure); if (window.ELEMENT_APP && window.ELEMENT_APP.renderer) window.ELEMENT_APP.renderer.toneMappingExposure = window.settings.matter.oceanExposure; }); bindInput('#m-ocean-cloud-cov', (e) => { window.settings.matter.oceanCloudCov = parseFloat(e.target.value); setV('m-ocean-cloud-cov', window.settings.matter.oceanCloudCov); this.updateOceanSun(); }); bindInput('#m-ocean-cloud-den', (e) => { window.settings.matter.oceanCloudDen = parseFloat(e.target.value); setV('m-ocean-cloud-den', window.settings.matter.oceanCloudDen); this.updateOceanSun(); }); bindInput('#m-ocean-cloud-elev', (e) => { window.settings.matter.oceanCloudElev = parseFloat(e.target.value); setV('m-ocean-cloud-elev', window.settings.matter.oceanCloudElev); this.updateOceanSun(); }); bindInput('#m-ocean-bloom-str', (e) => { window.settings.matter.oceanBloomStr = parseFloat(e.target.value); setV('m-ocean-bloom-str', window.settings.matter.oceanBloomStr); if(window.bloomPass) window.bloomPass.strength = window.settings.matter.oceanBloomStr; }); bindInput('#m-ocean-bloom-rad', (e) => { window.settings.matter.oceanBloomRad = parseFloat(e.target.value); setV('m-ocean-bloom-rad', window.settings.matter.oceanBloomRad); if(window.bloomPass) window.bloomPass.radius = window.settings.matter.oceanBloomRad; }); bindClick('#m-shadows', (e) => { e.stopPropagation(); window.settings.matter.shadowsEnabled = !window.settings.matter.shadowsEnabled; e.target.classList.toggle('eye-off', !window.settings.matter.shadowsEnabled); if(this.env.lights.sun) this.env.lights.sun.castShadow = window.settings.matter.shadowsEnabled; if (window.ELEMENT_APP?.renderer) { window.ELEMENT_APP.renderer.shadowMap.enabled = true; window.ELEMENT_APP.renderer.shadowMap.needsUpdate = true; } if(this.env.groundMesh) this.env.groundMesh.material.needsUpdate = true; this.models.forEach(m => m.parts.forEach(p => p.material.needsUpdate = true)); }); bindInput('#m-shadow-int', (e) => { window.settings.matter.shadowInt = parseFloat(e.target.value); setV('m-shadow-int', window.settings.matter.shadowInt); this.applySunIntensity(); }); bindInput('#m-shadow-spread', (e) => { window.settings.matter.shadowSpread = parseFloat(e.target.value); setV('m-shadow-spread', window.settings.matter.shadowSpread); if(this.env.lights.sun) { const d = window.settings.matter.shadowSpread; this.env.lights.sun.shadow.camera.left = -d; this.env.lights.sun.shadow.camera.right = d; this.env.lights.sun.shadow.camera.top = d; this.env.lights.sun.shadow.camera.bottom = -d; this.env.lights.sun.shadow.camera.updateProjectionMatrix(); this.env.lights.sun.shadow.map?.dispose(); this.env.lights.sun.shadow.map = null; } }); /* 🛑 FLUID BINDINGS 🛑 */ bindClick('#m-fluid-tog-btn', (e) => { e.stopPropagation(); G1_FLUID.enabled = !G1_FLUID.enabled; e.target.classList.toggle('eye-off', !G1_FLUID.enabled); if (G1_FLUID.points) G1_FLUID.points.visible = G1_FLUID.enabled; }); bindChange('#m-f-qty', (e) => { const v = parseInt(e.target.value); G1_FLUID.init(v); setV('m-f-qty', v); }); bindInput('#m-f-floor', (e) => { G1_FLUID.params.floorY = parseFloat(e.target.value); setV('m-f-floor', G1_FLUID.params.floorY); }); bindInput('#m-f-speed', (e) => { G1_FLUID.params.speed = parseFloat(e.target.value); setV('m-f-speed', G1_FLUID.params.speed); }); bindInput('#m-f-gravity', (e) => { G1_FLUID.params.gravity = parseFloat(e.target.value); setV('m-f-gravity', G1_FLUID.params.gravity); }); bindInput('#m-f-repel', (e) => { G1_FLUID.params.repulsion = parseFloat(e.target.value); setV('m-f-repel', G1_FLUID.params.repulsion); }); bindInput('#m-f-size', (e) => { G1_FLUID.params.size = parseFloat(e.target.value); setV('m-f-size', G1_FLUID.params.size); }); bindInput('#m-f-opacity', (e) => { G1_FLUID.params.opacity = parseFloat(e.target.value); setV('m-f-opacity', G1_FLUID.params.opacity); }); bindChange('#m-f-color', (e) => { G1_FLUID.params.color = e.target.value; }); bindChange('#m-f-map', (e) => { const file = e.target.files[0]; if(file) new T.TextureLoader().load(URL.createObjectURL(file), (tex) => { G1_FLUID.points.material.uniforms.tMap.value = tex; G1_FLUID.points.material.uniforms.useMap.value = 1.0; }); }); }, startLoop() { const loop = () => { requestAnimationFrame(loop); const time = performance.now() * 0.001; if (this.env.bgMesh && this.env.bgMesh.material.uniforms.tMap.value) { const tex = this.env.bgMesh.material.uniforms.tMap.value; if (tex.isVideoTexture || (tex.image instanceof HTMLVideoElement)) { tex.needsUpdate = true; this.needsEnvironmentUpdate = true; } } if (this.env.ocean && window.settings.matter.oceanVisible) { this.env.ocean.material.uniforms['time'].value += 1.0 / 60.0; if (this.env.lights.sun) { const sunDir = this.env.lights.sun.position.clone().normalize(); this.env.ocean.material.uniforms['sunDirection'].value.copy(sunDir); } } if (this.needsEnvironmentUpdate && window.ELEMENT_APP?.renderer && this.env.cubeCamera) { const visCache = new Map(); const envCache = new Map(); window.G1_SCENE.children.forEach(c => { visCache.set(c, c.visible); if (c !== this.env.bgMesh && c !== this.env.oceanSky && !c.isLight && !c.isCamera) c.visible = false; }); this.models.forEach(m => m.parts.forEach(p => p.visible = true)); if (this.env.bgMesh) this.env.bgMesh.visible = window.settings.matter.skyBgVisible; if (this.env.oceanSky) this.env.oceanSky.visible = window.settings.matter.oceanVisible; window.G1_SCENE.traverse(c => { if (c.material) { const mats = Array.isArray(c.material) ? c.material : [c.material]; mats.forEach(m => { if (m.envMapIntensity !== undefined) { envCache.set(m, m.envMapIntensity); m.envMapIntensity = 0; } }); } }); window.G1_SCENE.environment = null; this.env.cubeCamera.position.set(0, window.settings.matter.groundY + 10, 0); const reflectNudge = 0.05; let originalScaleX = 1; let originalRotY = 0; if (this.env.bgMesh) { originalScaleX = this.env.bgMesh.scale.x; originalRotY = this.env.bgMesh.rotation.y; this.env.bgMesh.scale.x = -1; this.env.bgMesh.rotation.y = (window.settings.matter.skyRot.y || 0) + reflectNudge; } this.env.cubeCamera.update(window.ELEMENT_APP.renderer, window.G1_SCENE); if (this.env.bgMesh) { this.env.bgMesh.scale.x = originalScaleX; this.env.bgMesh.rotation.y = originalRotY; } if (this.env.pmremGenerator) { if (this.env.pmremRT) this.env.pmremRT.dispose(); this.env.pmremRT = this.env.pmremGenerator.fromCubemap(this.env.cubeRenderTarget.texture); window.G1_SCENE.environment = this.env.pmremRT.texture; } else { window.G1_SCENE.environment = this.env.cubeRenderTarget.texture; } envCache.forEach((intensity, mat) => { mat.envMapIntensity = intensity; }); window.G1_SCENE.children.forEach(c => { if (visCache.has(c)) c.visible = visCache.get(c); }); if (this.env.groundMesh) { this.env.groundMesh.material.envMap = this.env.cubeRenderTarget.texture; this.env.groundMesh.material.needsUpdate = true; } this.needsEnvironmentUpdate = false; } this.models.forEach(m => { m.instances.forEach((inst, i) => { m.dummy.position.copy(inst.pos); m.dummy.rotation.copy(inst.rot); m.dummy.scale.setScalar(m.settings.scale); m.dummy.updateMatrix(); m.parts.forEach(p => p.setMatrixAt(i, m.dummy.matrix)); }); m.parts.forEach(p => { p.count = 1; p.instanceMatrix.needsUpdate = true; }); }); this.updateTerrain(time); G1_FLUID.update(time); }; loop(); } }; window.G1_MATTER = G1_MATTER; G1_MATTER.init(); })();