You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

601 lines
17 KiB
HTML

<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.136/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.136/examples/jsm/"
}
}
</script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Encode+Sans:wdth,wght@87.5,100..900&display=swap" rel="stylesheet">
<style>
body {
margin: 0;
font-family: "Encode Sans", sans-serif;
font-weight: 500;
font-size: 4vh;
color: #fff;
}
#container {
position: absolute;
top: 0;
left: 0;
user-select: none;
width: 100%;
height: 100%;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
}
#popup {
z-index: 1000;
max-width: 70vw;
max-height: 70vh;
overflow-x: hidden;
overflow-y: scroll;
width: auto;
border: solid #fff 5px;
height: auto;
background: #00000088;
padding: 4vh;
text-align: center;
}
#popup p + p {
margin-top: -3vh;
}
#close {
font-weight: 900;
float: right;
font-size: 1.5em;
margin-top: -5vh;
margin-right: -3.5vh;
}
#close:hover {
color: #fff;
}
#button {
position: absolute;
right: 4vh;
top: 4vh;
padding: 4vh;
background: darkblue;
font-weight: 800;
}
#button:hover {
background: blue;
}
#copyright {
z-index: 2;
font-size: 0.4em;
position: absolute;
bottom: 5px;
}
#rotate {
display: none;
position: absolute;
top: 0;
left: 0;
margin: 1vh;
font-size: 0.5em;
width: 20vh;
}
#rotate img {
width: 7vh;
float: left;
padding-right: 1vh;
}
#logo {
z-index: 1;
position: absolute;
right: 1vh;
bottom: 1vh;
}
#logo img {
width: 15vh;
opacity: 50%;
}
#logo img:hover {
opacity: 100%;
}
</style>
<body>
<script type="module">
import * as THREE from 'three';
import { STLLoader } from 'https://cdn.jsdelivr.net/npm/three@0.136.0/examples/jsm/loaders/STLLoader.js';
import { Spring } from './spring.js';
const isMobile = 'ontouchstart' in document.documentElement;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({antialias: true});
//renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
window.camera = camera;
window.three = THREE;
window.renderer = renderer;
/*const axesHelper = new THREE.AxesHelper( 5 );
scene.add( axesHelper );*/
var width = 10;
var height = 10;
function plane(width, height) {
var geometry = new THREE.PlaneGeometry(width, height, width, height);
geometry = geometry.toNonIndexed();
const colors = new three.BufferAttribute(new Float32Array(geometry.attributes.position.array.length), 3);
for(var i = 0; i < colors.count; i++) {
var col = Math.trunc(i / 6) % width;
var row = Math.trunc(i / 6 / width);
if((col + row % 2) % 2)
colors.setXYZ(i, 0x44 / 0xff, 0xaa / 0xff, 0);
else
colors.setXYZ(i, 0x55 / 0xff, 0xd4 / 0xff, 0);
}
geometry.setAttribute('color', colors);
const material = new THREE.MeshBasicMaterial({vertexColors: THREE.VertexColors});
var mesh = new THREE.Mesh(geometry, material);
mesh.position.x = width / 2;
mesh.position.y = height / 2;
return mesh;
}
var cb = plane(width, height);
scene.add(cb);
window.cb = cb;
var center = new THREE.Vector3(width / 2, height / 2, 0);
var direction = new THREE.Vector3(-height / 2, -width / 2, 0);
window.r = 23;
window.phi = -0.5;
window.theta = 0.2;
window.center = center;
window.center.z = -0.95;
window.center.x += 0.5;
window.z = 15;
camera.setFocalLength(60);
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
var clicked = false;
window.point = new THREE.Vector3();
window.pos = new THREE.Vector3();
function log(s) {
document.getElementById("text").innerHTML = s;
document.getElementById("popup").style.display = "block";
}
function updatePointers(event) {
if(isMobile) {
var touch = event.touches[0] || event.changedTouches[0];
var x = touch.pageX;
var y = touch.pageY;
}
else {
var x = event.clientX
var y = event.clientY;
}
pointer.x = (x / window.innerWidth) * 2 - 1;
pointer.y = -(y / window.innerHeight) * 2 + 1;
window.point.set(( x / window.innerWidth) * 2 - 1, -(y / window.innerHeight) * 2 + 1, 0.5);
}
function onClick(event) {
clicked = true;
updatePointers(event);
}
function onMouseUp(event) {
clicked = false;
updatePointers(event);
//log(clicked);
ws.move(window.pos, true);
}
function onPointerMove(event) {
updatePointers(event);
window.point.unproject(camera);
window.point.sub(camera.position).normalize();
var distance = -camera.position.z / window.point.z;
window.pos.copy(camera.position).add(window.point.multiplyScalar(distance));
if(clicked) {
ws.move(window.pos);
}
}
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.fov = 15;
if(camera.aspect > window.aspect)
window.r = window.rad;
else {
window.r = window.rad * window.aspect / camera.aspect;
}
window.z = window.r / 2;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById("container").style.height = window.innerHeight;
document.getElementById("container").style.width = window.innerWidth;
document.getElementById("rotate").style.display = camera.aspect > 1 ? "none" : "block";
var theta = Math.PI / 2 - window.theta;
var phi = window.phi - Math.PI / 2;
var x = window.r * Math.sin(theta) * Math.cos(phi) + center.x;
var y = window.r * Math.sin(theta) * Math.sin(phi) + center.y;
var z = window.r * Math.cos(theta) + center.z;
camera.position.set(x, y, window.z);
camera.up = new THREE.Vector3(0,0,1);
camera.lookAt(center);
}
window.aspect = 2.0;
window.rad = 23;
window.resize = onResize;
if(isMobile) {
window.addEventListener('touchstart', onClick);
window.addEventListener('touchend', onMouseUp);
window.addEventListener('touchmove', onPointerMove);
}
else {
window.addEventListener('mouseup', onMouseUp);
window.addEventListener('mousedown', onClick);
window.addEventListener('pointermove', onPointerMove);
}
window.addEventListener('resize', onResize);
onResize();
class WindMill extends THREE.Group {
static geometryBody = null;
static geometryBlades = null;
static {
const loader = new STLLoader();
loader.load('./aspas_126m.stl', function(geometry) {
WindMill.geometryBlades = geometry;
});
loader.load('./cuerpo_126m.stl', function(geometry) {
WindMill.geometryBody = geometry;
});
}
static isReady() {
return WindMill.geometryBody != null && WindMill.geometryBlades != null;
}
constructor() {
super()
this.material = new THREE.MeshBasicMaterial({color: 0xffffff});
this.materialCircle = new THREE.MeshBasicMaterial({color: 0xaa0000});
//this.materialCircle.transparent = true;
//this.materialCircle.opacity = 0.5;
this.meshBlades = new THREE.Mesh(WindMill.geometryBlades, this.material);
this.meshBlades.scale.set(0.0075, 0.0075, 0.0075);
this.meshBlades.position.z = 90/126;
this.meshBody = new THREE.Mesh(WindMill.geometryBody, this.material);
this.meshBody.scale.set(0.0075, 0.0075, 0.0075);
this.meshBody.position.z = 90/126;
var geometry = new THREE.CircleGeometry(0.5, 20);
this.meshCircle = new THREE.Mesh(geometry, this.materialCircle);
this.meshCircle.position.z = 0.01;
this.meshCircle.visible = false;
this.add(this.meshBlades);
this.add(this.meshBody);
this.add(this.meshCircle);
this.spring1 = new Spring(0.5, 2, 20, 6, 1, new THREE.MeshBasicMaterial({color: 0xff0000}));
this.spring1.position.z = 90/126;
this.spring1.rotation.z = -Math.PI/2;
this.spring1.update();
this.add(this.spring1);
this.spring2 = new Spring(0.5, 2, 20, 6, 1, new THREE.MeshBasicMaterial({color: 0xff0000}));
this.spring2.position.z = 90/126;
this.spring2.rotation.z = -Math.PI/2;
this.spring2.rotation.x = Math.PI * 2 / 3;
this.spring2.update();
this.add(this.spring2);
this.spring3 = new Spring(0.5, 2, 20, 6, 1, new THREE.MeshBasicMaterial({color: 0xff0000}));
this.spring3.position.z = 90/126;
this.spring3.rotation.z = -Math.PI/2;
this.spring3.rotation.x = Math.PI * 4 / 3;
this.spring3.update();
this.add(this.spring3);
this.spring1.visible = this.spring2.visible = this.spring3.visible = false;
this.selected = false;
}
animate() {
this.meshBlades.rotation.x += 0.1;
this.spring1.rotation.x += 0.1;
this.spring2.rotation.x += 0.1;
this.spring3.rotation.x += 0.1;
}
select(selected) {
if(selected == this.selected) return;
this.selected = selected;
this.meshCircle.visible = selected;
if(selected)
this.material.color.set(0xff0000);
else
this.material.color.set(0xffffff);
}
}
window.windmill = WindMill;
window.scene = scene;
var wm = null;
var wms = [];
window.wms = wms;
class WindSimulation {
constructor(width, height, mills) {
this.width = width;
this.height = height;
this.mills = mills;
this.wms = [];
this.init();
this.wm = null;
this.simulation = null;
}
init() {
if(!WindMill.isReady()) {
setTimeout(() => { this.init() }, 100);
return;
}
for(var i = 0; i < this.mills; i++) {
var wm = new WindMill();
wm.position.set(i + 0.5, i + 0.5, 0);
wm.rotation.z = Math.PI;
scene.add(wm);
this.wms.push(wm);
}
this.wm = this.wms[0];
}
move(pos, ended=false) {
if(this.simulation) {
scene.remove(this.simulation);
this.simulation = null;
}
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) return false;
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 false;
}
wm.position.x = pos.x;
wm.position.y = pos.y;
return true;
}
animate() {
requestAnimationFrame(() => { this.animate(); });
raycaster.setFromCamera(pointer, camera);
if(!isMobile || (isMobile && clicked)) {
const intersects = raycaster.intersectObjects(this.wms);
var wm = null;
for(var i = 0; i < intersects.length; i++) {
if(intersects[i].object.geometry.constructor.name != "CylinderGeometry") {
wm = intersects[i].object.parent;
break;
}
}
if(!clicked || isMobile) {
if(this.wm != wm) {
if(this.wm != null) this.wm.select(false);
if(wm != null) wm.select(true);
}
this.wm = wm;
}
}
for(var i = 0; i < this.wms.length; i++) {
this.wms[i].animate();
//this.wms[i].rotation.z += 0.01;
}
flag.animate();
/*
cone.rotation.y = 0.1 * Math.sin(Date.now() / 1000);
cone.rotation.x = 0.08 * Math.sin(Date.now() / 5000);
*/
renderer.render(scene, camera);
}
simulate() {
var xhr = new XMLHttpRequest();
xhr.open("POST", "/solver/", true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
var json = JSON.parse(xhr.responseText);
this.add_simulation(json.ws);
}
};
var pos = [];
for(var i = 0; i < this.wms.length; i++)
pos.push([this.wms[i].position.y, this.wms[i].position.x]);
console.log(pos);
var data = JSON.stringify({"pos": pos});
xhr.send(data);
}
add_simulation(ws) {
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)
scene.remove(this.simulation);
this.simulation = mesh;
scene.add(mesh);
}
}
const loader = new STLLoader();
//loader.load('./casa_80.stl', function(geometry) {
loader.load('./MonumentoBandera.stl', function(geometry) {
var material = new THREE.MeshBasicMaterial({color: 0xffffff});
var mesh = new THREE.Mesh(geometry, material);
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);
window.casa = mesh;
});
// Based on https://codepen.io/okada-web/pen/OJydGzy?editors=0010
class ArgentinianFlag extends THREE.Group {
constructor(height) {
super();
var geometry = new THREE.PlaneGeometry(3,2,30,20);
var colors = new three.BufferAttribute(new Float32Array(geometry.attributes.position.array.length), 3);
geometry.setAttribute('color', colors);
for(var i = 0; i < colors.count; i++)
if(i < 31 * 7 || i >= 31 * 14)
colors.setXYZ(i, 117/255, 170/255, 219/255);
else
colors.setXYZ(i, 1, 1, 1);
var material = new THREE.MeshBasicMaterial({side:THREE.DoubleSide, vertexColors: true});
this.mesh = new THREE.Mesh(geometry,material);
this.mesh.rotateX(Math.PI/2);
var scale = height / 3;
this.mesh.position.x = 1.5 * scale;
this.mesh.position.z = 1 * scale;
this.mesh.scale.setScalar(scale);
this.add(this.mesh);
}
animate() {
var hor = 0.3;
var speed = 0.1;
var ver = 0.1;
var swing = 0.5;
var pos = this.mesh.geometry.attributes.position;
const time = Date.now() * speed / 50;
for (let y=0; y<20+1; y++) {
for (let x=0; x<30+1; x++) {
const index = x + y * (30+1);
const vertex = pos[index];
pos.setZ(index, Math.sin(hor * x + ver * y - time) * swing * x / 40);
}
}
pos.needsUpdate = true;
}
}
var flag = new ArgentinianFlag(0.3);
scene.add(flag);
window.flag = flag;
flag.position.set(9.5, 0.3, 0.3);
flag.rotation.z = Math.PI;
/*
var geometry = new THREE.CylinderGeometry(0.09, 0.03, 0.5, 10, 5, true);
geometry = geometry.toNonIndexed();
var colors = new three.BufferAttribute(new Float32Array(geometry.attributes.position.array.length), 3);
geometry.setAttribute('color', colors);
for(var i = 0; i < colors.count; i++) {
if((Math.trunc(i / 6) + Math.trunc(i / 30) % 2) % 2)
colors.setXYZ(i, 1, 1, 1);
else
colors.setXYZ(i, 1, 0.3, 0);
}
var material = new THREE.MeshBasicMaterial({vertexColors: true, side: THREE.DoubleSide});
var mesh = new THREE.Mesh(geometry, material);
mesh.position.z = 1;
mesh.position.x = 9.5;
mesh.position.y = 0.5;
mesh.rotation.z = -Math.PI / 2;
var cone = mesh;
scene.add(mesh);*/
var ws = new WindSimulation(10, 10, 5);
window.ws = ws;
window.scene = scene;
ws.animate();
</script>
<script>
function hide() {
document.getElementById("popup").style.display = "none";
}
</script>
<div id="container">
<div id="popup">
<div id="close" onclick="hide();"></div>
<div id="text">
<img src="logooscuro.png" style="width: 30vh;" /><br />
<p>Simulador de Aerogeneradores y Parques Eólicos (SAPE)</p>
<p>Centro de Simulación Computacional para Aplicaciones Tecnológicas - CONICET</p>
</div>
</div>
<div id="button" onclick="ws.simulate();">¡SIMULAR!</div>
<div id="copyright">&copy;2024 Centro de Simulación Computacional para Aplicaciones Tecnológicas (CSC) - CONICET</div>
<div id="rotate"><img src="rotar_telefono.png" /><p>Rotá tu<br />dispositivo</p></div>
<div id="logo"><a href="http://www.csc.gob.ar"><img src="logooscuro.png" /></a></div>
</div>
</body>