/* Copyright (C) 2024 Sebastián Santisi , CSC-CONICET */ import * as THREE from 'three'; import { STLLoader } from 'https://cdn.jsdelivr.net/npm/three@0.136.0/examples/jsm/loaders/STLLoader.js'; import { WindMill } from './windmill.js'; import { Screen } from './screen.js'; import { ArgentinianFlag } from './argentinianflag.js'; import { checkBoard } from './checkboard.js'; window.three = THREE; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000); const renderer = new THREE.WebGLRenderer({antialias: true}); document.body.appendChild(renderer.domElement); //renderer.setPixelRatio( window.devicePixelRatio ); class WindSimulation { constructor(width, height, mills) { this.width = width; this.height = height; this.mills = mills; this.wms = []; this.init_windmills(); this.wm = null; this.simulation = null; this.simulationPot = null; this.xhr = null; this.lastFrame = Date.now(); this.rotation = Math.PI; this.targetRotation = Math.PI; this.dir = 0; this.checkBoard = checkBoard(width, height); scene.add(this.checkBoard); const loader = new STLLoader(); loader.load('assets/MonumentoBandera.stl', (geometry) => { var material = new THREE.MeshStandardMaterial({color: 0xffffff}); var mesh = new THREE.Mesh(geometry, material); mesh.castShadow = true; mesh.receiveShadow = true; var scale = 0.0093; mesh.scale.setScalar(scale); mesh.rotation.z = -Math.PI / 2; mesh.position.x = 9.5; mesh.position.y = 0.3; scene.add(mesh); this.monument = mesh; }); this.flag = new ArgentinianFlag(0.3); this.flag.position.set(9.5, 0.3, 0.3); this.flag.rotation.z = Math.PI; scene.add(this.flag); } init_windmills() { if(!WindMill.isReady()) { setTimeout(() => { this.init_windmills() }, 100); return; } for(var i = 0; i < this.mills; i++) { var wm = new WindMill(); wm.position.set(4 + 0.5, i + 5 - Math.ceil(this.mills / 2) + 0.5, 0); wm.rotation.z = this.targetRotation; scene.add(wm); this.wms.push(wm); } this.wm = this.wms[0]; } move(pos, ended=false, count=2) { if(this.simulation) this.resetSimulation(); if(! count) return false; var wm = this.wm; if(ended) { if(this.wm != null) this.wm.select(false); this.wm = null; } if(wm == null) return false; if(pos.x > this.width - 0.5 || pos.x < 0.5 || pos.y > this.height - 0.5 || pos.y < 0.5) { if(pos.x > this.width - 0.5) pos.x = this.width - 0.5; if(pos.x < 0.5) pos.x = 0.5; if(pos.y > this.height - 0.5) pos.y = this.height - 0.5; if(pos.y < 0.5) pos.y = 0.5; return this.move(pos, ended, count - 1); } for(var i = 0; i < this.wms.length; i++) { if(this.wms[i] == wm) continue; if(pos.clone().sub(this.wms[i].position).length() < 1) { return this.move(this.wms[i].position.clone().add(pos.clone().sub(this.wms[i].position).normalize()), ended, count - 1); } } wm.position.x = pos.x; wm.position.y = pos.y; return true; } animate() { requestAnimationFrame(() => { this.animate(); }); var now = Date.now(); var step = (now - this.lastFrame) / 1000; if(!Screen.isMobile || (Screen.isMobile && screen.clicked)) { const intersects = screen.raycaster.intersectObjects(this.wms); var wm = null; for(var i = 0; i < intersects.length; i++) { if(intersects[i].object.constructor.name != "Spring") { wm = intersects[i].object.parent; break; } } if(!screen.clicked || Screen.isMobile) { if(this.wm != wm) { if(this.wm != null) { this.wm.select(false); document.getElementById("follower").style.display = "none"; } if(wm != null) { wm.select(true); if(this.simulation) { var i = this.wms.indexOf(wm); var follower = document.getElementById("follower"); follower.innerHTML = "Aerogenerador Nº" + (i + 1) + "
" + (Math.round(this.simulationPot[i] * 100) / 100) + " GWh (" + Math.round(this.simulationPot[i] * 100 / 14.95) + "%)"; follower.style.display = "block"; } } } this.wm = wm; } } for(var i = 0; i < this.wms.length; i++) this.wms[i].animate(step); var speed = 2 * step; if(Math.abs(this.targetRotation - this.rotation) > speed) { var step = speed * Math.sign(this.targetRotation - this.rotation); this.rotation += step; for(var i = 0; i < this.wms.length; i++) { this.wms[i].rotation.z += step; } this.flag.rotation.z += step; } this.flag.animate(); if(window.exclamation) window.exclamation.rotation.y += 0.02; this.lastFrame = now; renderer.render(scene, camera); } resetSimulation() { scene.remove(this.simulation); this.simulation = null; this.simulationPot = null; for(var i = 0; i < this.mills; i++) this.wms[i].lowPower(false); document.getElementById("follower").style.display = "none"; document.getElementById("results").style.display = "none"; } setRotation(dir) { if(this.simulation) this.resetSimulation(); var rotations = {}; rotations[0] = Math.PI; rotations[270] = Math.PI / 2; rotations[270 + 45] = Math.PI * 3 / 4; rotations[270 - 45] = Math.PI / 4; if(! (dir in rotations)) return false; this.dir = dir; this.targetRotation = rotations[dir]; } addWindMill() { if(this.mills >= 10) return; this.mills++; this.reset(); } removeWindMill() { if(this.mills <= 1) return; this.mills--; this.reset(); } reset() { for(var i = 0; i < this.wms.length; i++) scene.remove(this.wms[i]); this.wms = []; this.rotation = this.targetRotation; this.init_windmills(); } simulate() { if(this.xhr != null) this.xhr.abort(); var xhr = new XMLHttpRequest(); this.xhr = xhr; xhr.open("POST", "/solver/", true); xhr.setRequestHeader("Content-Type", "application/json"); xhr.onreadystatechange = () => { if(this.xhr != xhr) return; if (xhr.readyState === 4 && xhr.status === 200) { var json = JSON.parse(xhr.responseText); console.log(json.pot) this.add_simulation(json.ws, json.pot); this.xhr = null; } }; var pos = []; for(var i = 0; i < this.wms.length; i++) pos.push([this.wms[i].position.y, this.wms[i].position.x]); var data = JSON.stringify({"pos": pos, "dir": this.dir}); xhr.send(data); } add_simulation(ws, pot) { var sum = 0; for(let i = 0; i < pot.length; i++) { this.wms[i].lowPower(pot[i] < 14.95 * 0.5); sum += pot[i]; } var pasa = Math.round(sum / 14.95 / pot.length * 100) >= 80; var results = document.getElementById("results") var innerHTML = '

Resultados de la simulación

' + (pasa ? '

🥳🥳🥳 ¡Tu simulación está bien! ¿Probaste otros vientos? Si sí agregá más aerogeneradores.

' : '

⚠️⚠️⚠️ ¡Tu simulación no funciona! Probá otras configuraciones.

') + ''; for(let i = 0; i < pot.length; i++) { var perc = Math.round(pot[i] / 14.95 * 100); innerHTML += '' } var perc = Math.round(sum / 14.95 / pot.length * 100); innerHTML += '
Nº' + (i + 1) + ':' + Math.round(pot[i] * 100) / 100 + ' GWh
' + perc + '% ' + (perc < 50 ? '⚠️' : '') + '
TOTAL:' + Math.round(sum * 100) / 100 + ' GWh
' + perc + '% ' + (perc < 80 ? '⚠️' : '') + '
'; results.innerHTML = innerHTML; results.style.display = "block"; var geometry = new THREE.PlaneGeometry(this.width, this.height, 10 * this.width, 10 * this.height); var colors = new three.BufferAttribute(new Float32Array(geometry.attributes.position.count * 4), 4); geometry.setAttribute('color', colors); const material = new THREE.MeshBasicMaterial({vertexColors: true, transparent: true,}); var mesh = new THREE.Mesh(geometry, material); mesh.position.set(this.width / 2, this.height / 2, 90/126); for(var i = 0; i < colors.count; i++) { var j = Math.trunc(i / 101); colors.setXYZW(i, 1 - ws[i % 101][100 - j] / 8, 0, 0, 1 - ws[i % 101][100 - j] / 8); } if(this.simulation) this.resetSimulation(); this.simulation = mesh; this.simulationPot = pot; scene.add(mesh); } } var width = 10; var height = 10; var ws = new WindSimulation(width, height, 5); var screen = new Screen(camera, renderer, new THREE.Vector3(width / 2 + 0.5, height / 2, -0.3)); screen.setMoveCallBack((pos, end) => { ws.move(pos, end); }); screen.onResize(); /*const light = new THREE.DirectionalLight("#FFFFFF"); light.position.set(5,5,100); //scene.add(light);*/ const ambientLight = new THREE.AmbientLight("#999999", 0.6); scene.add(ambientLight); const light = new THREE.DirectionalLight( 0xfff8ee, 1 ); light.position.set(6, -10, 10); //default; light shining from top //light.target.position.set(5, 5, 0); light.castShadow = true; // default false scene.add( light ); light.shadow.mapSize.width = 1024; // default 512 light.shadow.mapSize.height = 1024; // default 512 light.shadow.camera.near = 5; light.shadow.camera.far = 25; light.shadow.camera.right = 3.5 + 5; light.shadow.camera.left = 3.5 - 5; light.shadow.camera.top = 6 + 6; light.shadow.camera.bottom = 6 - 6; renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap window.light = light; //const helper = new THREE.CameraHelper( light.shadow.camera ); //scene.add( helper); //window.helper = helper; window.windmill = WindMill; window.ws = ws; window.screen = screen; window.scene = scene; ws.animate();