{"id":134,"date":"2024-06-03T10:49:01","date_gmt":"2024-06-03T08:49:01","guid":{"rendered":"https:\/\/stage.centrica.it\/?page_id=134"},"modified":"2024-06-05T16:28:46","modified_gmt":"2024-06-05T14:28:46","slug":"ricerca-su-three-js","status":"publish","type":"page","link":"https:\/\/stage.centrica.it\/index.php\/ricerca-su-three-js\/","title":{"rendered":"Ricerca su Three.js"},"content":{"rendered":"\n<p>Three.js \u00e8 un API utilizzata per creare ed esporre animazioni e modelli 3D su un browser.<\/p>\n\n\n\n<p>Uno dei formati file supportati pi\u00f9 usati per gli oggetti 3d in Three.js \u00e8 glTF(GL Transmission Format), un buon authoring tool per farne uno \u00e8 Blender.<\/p>\n\n\n\n<p>WebGL 3D Model Viewer \u00e8 il tool utilizzato per vedere i modelli 3D sul browser usando Three.js.<\/p>\n\n\n\n<p>Il mio compito era quello di fare in modo che su un modello 3D si potessero vedere degli hotspot interagibili, in modo tale da far si che ogni hotspot possa avere funzioni diverse, come mandare ad un&#8217;link, far apparire un pop-up oppure avviare un audio.<\/p>\n\n\n\n<p>Per fare questa task ho deciso di usare CSS2DRenderer che permette di combinare oggetti 3D con labels 2D fatti in HTML, CSS2DRenderer per\u00f2 \u00e8 un addon, quindi da solo non funziona perci\u00f2 ho dovuto installare prima il resto delle cose.<\/p>\n\n\n\n<p>Ogni progetto in Three.js deve avere almeno una pagina HTML per definire la webpage ed un file javascipt per il codice del progetto che faranno da struttura base per il progetto.<\/p>\n\n\n\n<p>Per iniziare ho installato Node cosi da poter fare un&#8217;applicazione server-side e per poter utilizzare il build tool, dopo ho installato Three.js ed il build tool Vite utilizzando il terminale con i seguenti comandi:<\/p>\n\n\n\n<p>npm install &#8211;save three <\/p>\n\n\n\n<p>npm install &#8211;save-dev vite<\/p>\n\n\n\n<p>Poi ho usato il comando &#8220;npx vite&#8221; per ricevere un link che porta alla pagina con il progetto, che per ora \u00e8 vuoto.<\/p>\n\n\n\n<p>Il prossimo passaggio per poter visualizzare qualcosa con Three.js \u00e8 creare una scena, una camera ed un renderer, in questo caso si far\u00e0 un cubo che rotea, il codice qui sotto presenta come crearli:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-c89c286671ca65b0f2d2e554ee59e70e\"><code>import * as THREE from 'three';\nconst scene = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera( 75, window.innerWidth \/ window.innerHeight, 0.1, 1000 );\n\nconst renderer = new THREE.WebGLRenderer();\nrenderer.setSize( window.innerWidth, window.innerHeight );\ndocument.body.appendChild( renderer.domElement );<\/code><\/pre>\n\n\n\n<p>Ci sono diversi tipi di camera, in questo caso si utilizza una Perspecive Camera, il primo attributo \u00e8 il Field Of View che indica quanto si veda della scene sul display, il secondo attributo \u00e8 aspect ratio che sar\u00e0 sempre la width divisa per l&#8217;height, gli ultimi due attributi sono near e far che servono a limitare il rendering dell&#8217;oggetto, tutte le cose oltre far non vengono renderizzate e tutte le cose prima di near neanche.<\/p>\n\n\n\n<p>Per il renderer oltre a crearne l&#8217;istanza bisogna anche configurarne la dimensione a cui deve renderizzare l&#8217;app, in questo caso prende le dimensione della pagina web, dopo ho aggiunto il renderer all&#8217; file HTML cos\u00ec da poter vedere la scene.<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-fc6f903416d8037f20188f151647d452\"><code>const geometry = new THREE.BoxGeometry( 1, 1, 1 );\nconst material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );\nconst cube = new THREE.Mesh( geometry, material );\nscene.add( cube );\n\ncamera.position.z = 5;<\/code><\/pre>\n\n\n\n<p>Per creare il cubo ho usato BoxGeometry, un Object che contiene tutti i vertici e facce del cubo, poi serve il materiale del cubo per dargli un colore, Three.js ha parecchi materiali, io ho utilizzato MashBasicMaterial egli ho dato l&#8217;attributo del colore verde,  bisogna poi aggiungere un Mesh, un oggetto che prende una forma geometrica e ci applica il materiale, cos\u00ec da visualizzarlo e spostarlo a volere, infine si aggiunge il cubo alla scena e si sposta la camera cos\u00ec da non avere camera e cubo compenetrati insieme.<\/p>\n\n\n\n<p>Per renderizzare la scena bisogna fare un render animation loop che ricarica la scena ogni volta che lo schermo si refresha, questa \u00e8 la funzione usata:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-26511b5db679cecd8acaafa78a2ce1e1\"><code>function animate() {\n\trenderer.render( scene, camera );\n}\nrenderer.setAnimationLoop( animate );<\/code><\/pre>\n\n\n\n<p>Per animare il cubo basta usare le seguenti linee di codice:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-9f1094cc49aa40535f9f1713678979a0\"><code>cube.rotation.x += 0.01;\ncube.rotation.y += 0.01;<\/code><\/pre>\n\n\n\n<p>Questo fa si che il cubo ruoti sia nell&#8217;asse x che nel asse y.<\/p>\n\n\n\n<p>Dopo aver fatto ci\u00f2 ho cambiato il cubo in un Icosaedro, quindi al posto di <\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-bf6a9f8d5f9e58d0f6755103417f3d31\"><code>const geometry = new THREE.BoxGeometry( 1, 1, 1 );<\/code><\/pre>\n\n\n\n<p>ho fatto<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-ea96cb23ab8daac74677e4162157f856\"><code>const geometry = new THREE.IcosahedronGeometry( 1, 2); <\/code><\/pre>\n\n\n\n<p>ed ho cambiato il nome del cubo in &#8220;mesh&#8221; per darli un nome usabile con qualunque forma.<\/p>\n\n\n\n<p>Dopo ho cambiato il materiale del oggetto con uno che funziona con le luci, quindi ho sostituito<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-3900bd48c993ef7a03b78c143423ae02\"><code>const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );<\/code><\/pre>\n\n\n\n<p>con<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-f8d17828a3b0289657463ae63d5a9a66\"><code>const material = new THREE.MeshStandardMaterial( { \n    color: 0xffffff, \n    flatShading: true\n  } );<\/code><\/pre>\n\n\n\n<p>date che non si \u00e8 ancora aggiunta un&#8217;illuminazione l&#8217;oggetto non sar\u00e0 visibile su schermo, per aggiungere una luce ho usato il seguente codice:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-4917ddf6250b3ba1ac5d500a7e61e0fc\"><code>const hemiLight= new THREE.HemisphereLight(0x228B22, 0xCCCC00);\nscene.add(hemiLight);<\/code><\/pre>\n\n\n\n<p>I due attributi sono il colore della luce, il primo \u00e8 la luce sopra ed il secondo quella sotto, dopodich\u00e9 l&#8217;ho aggiunto alla scena.<\/p>\n\n\n\n<p>Ho aggiunto anche un &#8220;Wire&#8221; in modo tale da evidenziare i vari lati dell&#8217;oggetto per poterlo vedere in modo pi\u00f9 chiaro, per farlo ho usato il seguente codice:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-56e6773f5f186013429a8a98f4c82057\"><code>const wireMat= new THREE.MeshBasicMaterial({\n    color: 0xffffff,\n    wireframe: true\n  })\n  const wireMesh= new THREE.Mesh(geometry, wireMat);\n  wireMesh.scale.setScalar(1.001);\n  mesh.add(wireMesh);<\/code><\/pre>\n\n\n\n<p>Ho creato un material ed un mesh di colore bianco, e l&#8217;ho leggermente scalato rendendolo pi\u00f9 grande per  non avere compenetrazioni e per vederlo in modo pi\u00f9 chiaro, dopo l&#8217;ho aggiunto come figlio del mesh.<\/p>\n\n\n\n<p>Dopo ho aggiunto un modo per poter controllare la forma in modo da girarla con il mouse, per farlo ho aggiunto l&#8217;addon di OrbitControls, importandolo con questo codice:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-d6e6e96349a62a72fc4fa0dbdf1ce32e\"><code>import { OrbitControls } from 'three\/addons\/controls\/OrbitControls.js';<\/code><\/pre>\n\n\n\n<p>Per implementarlo ho usato il seguente codice:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-90d16328a8864317beb09a8f7f43797a\"><code>const controls= new OrbitControls(camera, renderer.domElement);\ncontrols.enableDamping=true;\ncontrols.dampingFactor=0.03;<\/code><\/pre>\n\n\n\n<p>il damping serve a dare un senso di peso al movimento dell&#8217;oggetto cos\u00ec che sembri pi\u00f9 fluido\/naturale.<\/p>\n\n\n\n<p>Infine ho cambiato l&#8217;animazione dell&#8217;oggetto in questo modo:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-b34c44788f6ca3c7a162bdd15514b7e3\"><code>function animate() {\n    requestAnimationFrame(animate);\n    mesh.rotation.y += 0.001;\n    renderer.render( scene, camera );\n    controls.update();\n  }\n\n  animate();<\/code><\/pre>\n\n\n\n<p>Ho prima di tutto fatto in modo che l&#8217;animazione si ripetesse dentro alla funzione, poi ho rimosso la rotazione dell&#8217;asse x e ho reso la rotazione dell&#8217;asse y leggermente pi\u00f9 lenta, ed infino ho fatto in modo che i controlli si aggiornino ogni volta.<\/p>\n\n\n\n<p>Dopo dovevo aggiungerci degli hotspot sulla forma che facessero diversi tipi di azioni, per fare ci\u00f2 come ho detto in precedenza bisogna importare gli addon di CSS2DRenderer cos\u00ec da poter visualizzare testi in modo dinamico, qui sotto \u00e8 indicato il setup:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-512d865379ae04301bf01eec7d377393\"><code>import {CSS2DRenderer, CSS2DObject} from 'three\/addons\/renderers\/CSS2DRenderer';\n\/\/setup\nconst labelRenderer= new CSS2DRenderer();\nlabelRenderer.setSize(window.innerWidth, window.innerHeight);         labelRenderer.domElement.style.position='absolute';\nlabelRenderer.domElement.style.top='0px';      labelRenderer.domElement.style.pointerEvents='none';          document.body.appendChild(labelRenderer.domElement);\n\nconst group= new THREE.Group();<\/code><\/pre>\n\n\n\n<p>PointerEvents=&#8217;none&#8217; serve a far in modo che il container non catturi eventi del mouse cos\u00ec da funzionare con Orbit controls.<\/p>\n\n\n\n<p>Group servir\u00e0 a raggruppare insieme tutti i label creati, per crearli per\u00f2 serve una funzione:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-7e00c775587e48f960841bdee8e9ce10\"><code>function createCpointMesh(name, x, y , z){\n    const geo= new THREE.SphereGeometry(0.1);\n    const mat= new THREE.MeshBasicMaterial({color:0xFF0000});\n    const mesh= new THREE.Mesh(geo, mat);\n    mesh.position.set(x,y,z);\n    mesh.name=name;\n    return mesh;\n  }\n\n  const sphereMesh1 =createCpointMesh('sphereMesh1', 1, 0.6, 1.646);\n  const sphereMesh2 =createCpointMesh('sphereMesh2', -1.4, 0.6, -1.3);\n  group.add(sphereMesh1);\n  group.add(sphereMesh2);\n\nmesh.add(group);<\/code><\/pre>\n\n\n\n<p>Nella funzione bisogna inserire il nome e la posizione(coordinate) del label, dopo aver creato la funzione l&#8217;ho usata per creare due labels che poi ho aggiunto a group per poi aggiungere group come figlio di mesh.<\/p>\n\n\n\n<p>Nella funzione animate bisogna anche aggiungere la seguente linea di codice per aggiornare il render dei label:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-2eb91427ce71a9731b793969e60c99b3\"><code>labelRenderer.setSize(this.window.innerWidth, this.window.innerHeight);<\/code><\/pre>\n\n\n\n<p>Il seguente event listener aggiorna la dimensione dello schermo per adattarsi alla dimensione della pagina:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-4d05b63dd0f3320ec68c1a6f5d4a9bf8\"><code>window.addEventListener('resize', function(){\n    camera.aspect=this.window.innerWidth\/this.window.innerHeight;\n    camera.updateProjectionMatrix();\n    renderer.setSize(window.innerWidth, window.innerHeight);\n    labelRenderer.setSize(this.window.innerWidth, this.window.innerHeight);\n  })<\/code><\/pre>\n\n\n\n<p>Ora dovevo far si che i labels fossero visibili come dei pop-up, ho scelto i pop-up ma si possono fare diversi eventi non solo questo, per renderlo visibile ho fatto cos\u00ec:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-8409ced606c246e13175f9b9511105d0\"><code>const p=document.createElement('p');\n  p.className='tooltip';\n  const pContainer=document.createElement('div')\n  pContainer.appendChild(p);\n  const cPointLabel= new CSS2DObject(pContainer);\n  mesh.add(cPointLabel);\n\n  const mousePos= new THREE.Vector2();\n  const raycaster= new THREE.Raycaster();\n  \n  window.addEventListener('mousemove', function(e){\n    mousePos.x=(e.clientX\/this.window.innerWidth)*2-1;\n    mousePos.y=-(e.clientY\/this.window.innerWidth)*2+1;\n\n    raycaster.setFromCamera(mousePos, camera);\n    const intersects=raycaster.intersectObject(group);\n\n    if (intersects.length&gt;0) {\n      switch (intersects&#91;0].object.name) {\n        case 'sphereMesh1':\n          p.className= 'tooltip show';\n          cPointLabel.position.set(1, 0.6, 1.646)\n          p.textContent= 'Hotspot 1 (1, 0.6, 1.646)'\n          break;\n        case 'sphereMesh2':\n          p.className= 'tooltip show';\n          cPointLabel.position.set(-1.4, 0.6, -1.3)\n          p.textContent= 'Hotspot 2 (-1.4, 0.6, -1.3)'\n          break;\n        default:\n          break;\n      }\n    }\n    else{\n      p.className = 'tooltip hide'\n    }\n  })<\/code><\/pre>\n\n\n\n<p>Ci\u00f2 permette di conoscere sempre la posizione del mouse e di usare le funzione tooltip show quando il mouse \u00e8 sopra alle coordinate prestabilite.<\/p>\n\n\n\n<p>Poi per rendere i label pi\u00f9 belli da vedere e per dargli un effetto di &#8220;fading&#8221; ho creato un file CSS (che poi ho linkato all&#8217;HTML) in cui ho messo i seguenti comandi:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-21553b625e56a2e01ca9c8c476cb6b71\"><code>body {\n    margin: 0;\n}\n\n.tooltip{\n    background-color: white;\n    color: black;\n    padding: 10px;\n    position: relative;\n    transform: translateY(-10px);\n    opacity: 0;\n    transition-duration: 2s;\n    transition-property: opacity, transform;\n}\n.tooltip::after{\n    position: absolute;\n    content: '';\n    width: 20px;\n    height: 20px;\n    background-color: white;\n    top: 90%;\n    left: 50%;\n    transform: rotateZ(45deg) translateX(-50%);\n    z-index: -1;\n}\n\n.hide{\n    opacity: 0;\n    transform: translateY(-10px);\n}\n\n.show{\n    opacity: 1;\n    transform: translateY(0px);\n}<\/code><\/pre>\n\n\n\n<p>Per aggiungere una texture alla forma basta installare un immagine e metterlo in una cartella, dopo bisogna scrivere:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-68614e0c348254453cf9c32bca8c5d99\"><code>const texture = new THREE.TextureLoader().load(\"src\/Img\/terra.jpg\")<\/code><\/pre>\n\n\n\n<p>(&#8220;src\/Img\/terra.jpg&#8221;) \u00e8 il path dell&#8217;immagine che poi verr\u00e0 applicata all&#8217;oggetto cambiando il material cos\u00ec:<\/p>\n\n\n\n<pre class=\"wp-block-code has-contrast-color has-text-color has-link-color wp-elements-d95cbfd0db2cda26ad163ac133073be7\"><code>const material = new THREE.MeshStandardMaterial( { \n    map: texture,\n    side: THREE.DoubleSide,\n    \/\/flatShading: true\n  } );<\/code><\/pre>\n\n\n\n<p>Ho commentato flatShading per dar l&#8217;impressione che l&#8217;oggetto sia sferico, ho anche aumentato il numero di triangoli utilizzati a 10 nell&#8217;icosahedron, poi ho cambiato i colori della luce a bianco e nero per avere una luce pi\u00f9 naturale.<\/p>\n\n\n\n<div style=\"height:130px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Three.js \u00e8 un API utilizzata per creare ed esporre animazioni e modelli 3D su un browser. Uno dei formati file supportati pi\u00f9 usati per gli oggetti 3d in Three.js \u00e8 glTF(GL Transmission Format), un buon authoring tool per farne uno \u00e8 Blender. WebGL 3D Model Viewer \u00e8 il tool utilizzato per vedere i modelli 3D [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-134","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/stage.centrica.it\/index.php\/wp-json\/wp\/v2\/pages\/134","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/stage.centrica.it\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/stage.centrica.it\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/stage.centrica.it\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/stage.centrica.it\/index.php\/wp-json\/wp\/v2\/comments?post=134"}],"version-history":[{"count":30,"href":"https:\/\/stage.centrica.it\/index.php\/wp-json\/wp\/v2\/pages\/134\/revisions"}],"predecessor-version":[{"id":177,"href":"https:\/\/stage.centrica.it\/index.php\/wp-json\/wp\/v2\/pages\/134\/revisions\/177"}],"wp:attachment":[{"href":"https:\/\/stage.centrica.it\/index.php\/wp-json\/wp\/v2\/media?parent=134"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}