parent
b11cedc3f9
commit
3a007231c7
@ -0,0 +1,721 @@ |
|||||||
|
<html> |
||||||
|
<head> |
||||||
|
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script> |
||||||
|
<script src="https://cdn.jsdelivr.net/gh/diarmidmackenzie/instanced-mesh@v0.6.0/src/instanced-mesh.min.js"></script> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<script> |
||||||
|
if(window.location.protocol != 'https:') { location.href = location.href.replace("http://", "https://"); } |
||||||
|
|
||||||
|
var simsrc = "simulations/initial_simulation_data/crop_outpy_" |
||||||
|
var forceSimulationSource = AFRAME.utils.getUrlParameter('simsrc'); |
||||||
|
if ( forceSimulationSource ) { |
||||||
|
simsrc = forceSimulationSource |
||||||
|
console.log(simsrc,"switched from default simulation to this one") |
||||||
|
// sould also update the src of id="debug_sim_cerema" with a specific step, e.g 50 |
||||||
|
} |
||||||
|
var simscale = 0.035 |
||||||
|
var forceDisplacementScale = AFRAME.utils.getUrlParameter('simscale'); |
||||||
|
if ( forceDisplacementScale ) { |
||||||
|
simscale = forceDisplacementScale |
||||||
|
console.log(simsrc,"switched from default simulation scale to this one", simscale) |
||||||
|
} |
||||||
|
const vrHeight = 0.8 |
||||||
|
const desktopHeight = 2.5 |
||||||
|
const simext = ".jpg" |
||||||
|
const simstart = 1 |
||||||
|
const simend = 95 // mostly correct as new simulations got to 96 steps |
||||||
|
const sim_filename_padding = true |
||||||
|
const animationSpeedStep = 900 |
||||||
|
var animationCurrentStep = simstart |
||||||
|
|
||||||
|
var wsimanim |
||||||
|
|
||||||
|
function animationStep(){ |
||||||
|
wsim.setAttribute("material", { |
||||||
|
// offset: {x: animationCurrentStep/1000, y:0}, // impacts also the displacement map, not just the texture |
||||||
|
displacementMap: simsrc+(animationCurrentStep++)+simext, |
||||||
|
}) |
||||||
|
//wsim.setAttribute("material", "displacementMap", simsrc+(animationCurrentStep++)+simext) |
||||||
|
//wsim.object3D.children[0].material.needsUpdate = true //no need to do this for every step... |
||||||
|
//wsim.object3D.children[0].material.map.offset.setX(animationCurrentStep/1000) // same problem, that offset is not just for the texture but also for the displacement map |
||||||
|
// should find a way to decouple |
||||||
|
// otherwise could try a custom shader but might still encounter the same problem |
||||||
|
if (animationCurrentStep >= simend) resetAnimations() |
||||||
|
} |
||||||
|
|
||||||
|
var currentGeoPose = null; // https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition |
||||||
|
// navigator.geolocation.getCurrentPosition( geolocalized ) // should handle error, including being ignored |
||||||
|
// disable for faster testing |
||||||
|
|
||||||
|
// to consider https://github.com/mourner/suncalc |
||||||
|
|
||||||
|
// otherwise have 2 fallbacks via menu |
||||||
|
var positionsCurated = [ |
||||||
|
{ position:[50.8365354, 4.3581719], hotspots: []}, |
||||||
|
{ shortname: "baquets", worldposition:[-38, 17], position:[49.3516400, 0.5461600], // way heavier! |
||||||
|
offsetLandscapePosition: "-124.32254 0.78377 -46.41836", // TODO use, might prefer something easier to compute to add e.g Vector3, or do pose (position/rotation/scale) per id |
||||||
|
// alternatively might show/hide per classname based on shortname |
||||||
|
offsetWatersimPosition: "-124.32254 0.78377 -46.41836", // TODO use |
||||||
|
hotspots: [ |
||||||
|
// done via the AFrame inspector then pasting here, then regex |
||||||
|
"-36.50124 3 18.52317", "-15.31684 3 26.46878", "-42.59553 3 32.46203", "12.26538 3 38.90027", "-0.69236 3 67.76877", "-49.01323 3 48.21272", "-79.56723 3 34.81311", "-94.0 3 25.0", "-19.2 3 60", "-86.58562 3 -5.73462", "-58.70357 3 8.36794", |
||||||
|
], |
||||||
|
visualMarkerPositions:[ |
||||||
|
// {rotation:"0 -90 0" , position:"54.31842 -0.21946 -335.17472"}, |
||||||
|
{rotation:"0 -22.49241 0", position:"-30.15801 0.05 14.66674"}, |
||||||
|
{rotation:"0 66.80401412327834 0" , position:"-41.94021 0.05 19.43"} |
||||||
|
], |
||||||
|
}, |
||||||
|
{ shortname: "ruelle", worldposition:[-126, -40], position:[49.3605700, 0.5031000], |
||||||
|
offsetLandscapePosition: "-124.32254 0.78377 -46.41836", // TODO use |
||||||
|
offsetWatersimPosition: "-124.32254 0.78377 -46.41836", // TODO use |
||||||
|
hotspots: [ "-134.62038 3 -13.29107", "-125.89544 3 -30.36393", "-119.72245 3 -49.78609", "-96.91944 3 -95.00968", "-90.17903 3 -109.1628", "-142.76086 3 11.1124"], |
||||||
|
visualMarkerPositions:[ |
||||||
|
{position:"-125.67578 0.78377 -46.72093"}, |
||||||
|
{position:"-132.39466 0.78377 -30.06373"}, |
||||||
|
{position:"-138.95986 0.78377 -13.43454"}, |
||||||
|
], |
||||||
|
}, |
||||||
|
] |
||||||
|
|
||||||
|
var textureOffsetStart = 0 |
||||||
|
var textureOffsetEnd = 1 |
||||||
|
AFRAME.registerComponent('texture-drift', { |
||||||
|
// should be able to animate instead... but somehow didn't manage. |
||||||
|
// animation_texture="property: components.material.material.map.offset.x; from: 0; to: 1; dur: 2000; easing: linear; loop: true; dir: alternate;" |
||||||
|
init: function () { |
||||||
|
var el = this.el |
||||||
|
el.addEventListener('materialtextureloaded', function () { |
||||||
|
console.log(el,"text loaded") |
||||||
|
setTimeout(function () { // setTimeout for good measure. |
||||||
|
var x = el.components.material.material.map.offset.x |
||||||
|
setInterval( _ => { |
||||||
|
(x >= textureOffsetEnd) ? x = textureOffsetStart : x += .0001 |
||||||
|
}, 100) |
||||||
|
}, 200); |
||||||
|
}); |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
AFRAME.registerComponent('randomfx', { |
||||||
|
init: function () { |
||||||
|
var FXsounds = Array.from( document.querySelectorAll(".randomfx") ) |
||||||
|
setInterval( _ => { FXsounds[Math.floor(Math.random()*FXsounds.length)].components.sound.playSound() }, 9000 ) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
AFRAME.registerComponent('set-initial-camera-from-data', { |
||||||
|
init: function () { |
||||||
|
this.el.setAttribute("position", positionsCurated[selection].worldposition[0] + " "+vrHeight+" " + positionsCurated[selection].worldposition[1] ) |
||||||
|
} |
||||||
|
}) |
||||||
|
var ignoreCache = true // should be joined as 1 or better be location dependent |
||||||
|
const disableWaysForGLTFExportInHubs = false |
||||||
|
|
||||||
|
var selection = 1 // default position fallback to faciliate tests |
||||||
|
|
||||||
|
// initially done as a different page at first with query parameter |
||||||
|
var forceSelection = AFRAME.utils.getUrlParameter('position'); |
||||||
|
if ( forceSelection ) selection = forceSelection |
||||||
|
|
||||||
|
var position = positionsCurated[selection].position |
||||||
|
var hotspots = positionsCurated[selection].hotspots |
||||||
|
var visualmarkers = positionsCurated[selection].visualMarkerPositions |
||||||
|
|
||||||
|
var precision = 4 |
||||||
|
var bbox_limit = 10/10**precision |
||||||
|
bbox_limit = 3*10/10**precision |
||||||
|
|
||||||
|
var elevation_res = 10 |
||||||
|
|
||||||
|
// generate 3D model from the target location from OSM data |
||||||
|
var bbox = "" + (position[0]-bbox_limit/2) |
||||||
|
+ "," + (position[1]-bbox_limit/2) |
||||||
|
+ "," + (position[0]+bbox_limit-bbox_limit/2) |
||||||
|
+ "," + (position[1]+bbox_limit-bbox_limit/2) |
||||||
|
|
||||||
|
var queryOSMgeom = `https://overpass-api.de/api/interpreter?data=[out:json];nwr(${bbox});out geom;` |
||||||
|
|
||||||
|
// fetch flood data of target location (should be only 2 available) as timeseries |
||||||
|
|
||||||
|
var locations = [] |
||||||
|
for (var i=0;i<elevation_res;i++) |
||||||
|
for (var j=0;j<elevation_res;j++) |
||||||
|
locations.push({ |
||||||
|
latitude:Number( (position[0]+(bbox_limit/elevation_res)*i).toFixed(precision) ), |
||||||
|
longitude:Number( (position[1]+(bbox_limit/elevation_res)*j).toFixed(precision) ), |
||||||
|
}) |
||||||
|
|
||||||
|
var queryElevation = 'https://api.open-elevation.com/api/v1/lookup' |
||||||
|
// doesn't seem to have sufficient resolution, might use IGN instead |
||||||
|
|
||||||
|
var buildingHeight = 6 |
||||||
|
var forceHeight = AFRAME.utils.getUrlParameter('height'); |
||||||
|
if ( forceHeight ) buildingHeight = Number( forceHeight ) |
||||||
|
var wayHeight = .5 |
||||||
|
|
||||||
|
var globaljosm |
||||||
|
var offsets = { // used to re-center the map |
||||||
|
"x": Number( position[0].toFixed(precision-1) ), |
||||||
|
"y": 0.1, // try to avoid z-fighting |
||||||
|
"z": Number( position[1].toFixed(precision-1) )} |
||||||
|
var scale = 10**(precision+1) |
||||||
|
|
||||||
|
function pointFromCoordinates( p ){ |
||||||
|
return new THREE.Vector3( (p.lat-offsets.x)*scale, 0, (p.lon-offsets.z)*scale ) |
||||||
|
} |
||||||
|
|
||||||
|
function curvesTo3DElements( curves, el ){ |
||||||
|
curves.map( c => { |
||||||
|
makeLine( c.geometry.coordinates.map( p => pointFromCoordinates({lat:p[1],lon:p[0]}) ) , c.properties.ALTITUDE, el ) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
function elevationTo3DElements( elevations, el ){ |
||||||
|
localStorage.setItem('elevation', JSON.stringify( elevations ) ) |
||||||
|
var minElevation = elevations.sort( (a,b) => a.elevation - b.elevation )[0].elevation |
||||||
|
elevations.map( h => { |
||||||
|
var pointEl = document.createElement("a-sphere") |
||||||
|
pointEl.setAttribute("color", "blue") |
||||||
|
pointEl.setAttribute("radius", "0.1") |
||||||
|
// simplification, for a proper polygon see make3DFloor() but here shouldn't assume a centroid |
||||||
|
// could instead rpointEly on a grid |
||||||
|
var pos = pointFromCoordinates( {lat:h.latitude,lon:h.longitude} ) |
||||||
|
pos.y = h.elevation - minElevation |
||||||
|
pointEl.setAttribute("position", AFRAME.utils.coordinates.stringify( pos ) ) |
||||||
|
// see pointFromCoordinates |
||||||
|
// could find the minimum altitude then remove it from every other values |
||||||
|
el.appendChild( pointEl ) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
function fromOSMTo3DElements( josm, el ){ |
||||||
|
localStorage.setItem('osm', JSON.stringify( josm ) ) |
||||||
|
globaljosm = josm // used for debug in console |
||||||
|
|
||||||
|
// right click on elements on https://overpass-turbo.eu/s/1jbc allows to see type visually |
||||||
|
//var houses = josm.elements.filter( e => (e.type == "way" && e.tags && e.tags["add:housenumber"]) ).map( e => e.geometry) |
||||||
|
var houses = josm.elements.filter( e => (e.type == "way" && e.tags && e.tags["building"]) ) |
||||||
|
.filter( i => !loadedosmways.includes(i.id) ) |
||||||
|
.filter( i => (i.id < 561974951 || i.id > 561974997) ) // does not seem present in the model (assuming sequential OSM list, as of now) |
||||||
|
.map( e => e.geometry) |
||||||
|
houses.map( e => { makeBuilding( e, buildingHeight*(1+Math.random()), el ) }) |
||||||
|
idfound = josm.elements.filter( e => (e.type == "way" && e.tags && e.tags["building"]) ) |
||||||
|
idfoundfilter = josm.elements.filter( e => (e.type == "way" && e.tags && e.tags["building"]) ) |
||||||
|
.filter( i => !loadedosmways.includes(i.id) ) |
||||||
|
console.log("way IDs found", idfound) |
||||||
|
console.log("way IDs filtered", idfoundfilter) |
||||||
|
|
||||||
|
// to add trees |
||||||
|
var trees = josm.elements.filter( i => i.type == "node" && i.tags && i.tags.natural == "tree") |
||||||
|
var lamp = josm.elements.filter( i => i.type == "node" && i.tags && i.tags.highway == "street_lamp") |
||||||
|
// https://www.openstreetmap.org/node/9923232483 |
||||||
|
|
||||||
|
trees.map( e => { makeTree( e, el ) } ) |
||||||
|
lamp.map( e => { makeLamp( e, el ) } ) |
||||||
|
|
||||||
|
/* consider also |
||||||
|
landuse forest e.g https://www.openstreetmap.org/way/42198660 |
||||||
|
waterway river e.g https://www.openstreetmap.org/way/112050282 |
||||||
|
or rather with type multipolygon https://www.openstreetmap.org/relation/8029633 |
||||||
|
*/ |
||||||
|
|
||||||
|
// console.log('comparing ways from OSM to glTF') |
||||||
|
// existing_houses = josm.elements.filter( e => (e.type == "way" && e.tags && e.tags["building"]) ) |
||||||
|
// .filter( i => loadedosmways.includes(i.id) ) |
||||||
|
// .map( e => e.geometry) |
||||||
|
// existing_houses.map( e => { makeBuilding( e, buildingHeight*(.5), el ) }) |
||||||
|
// used for alignment |
||||||
|
|
||||||
|
//var highways = josm.elements.filter( e => (e.type == "way" && e.tags["highway"]) ).map( e => e.geometry) |
||||||
|
var highways = josm.elements.filter( e => (e.type == "way" && e.tags && e.tags["highway"]) ).map( e => e.geometry) |
||||||
|
// not so good, shows paths rather than a usable geometry |
||||||
|
if (!disableWaysForGLTFExportInHubs) |
||||||
|
highways.map( e => { makeWay( e, wayHeight, el, 0x120a06 ) }) |
||||||
|
} |
||||||
|
|
||||||
|
function geolocalized( pos ){ |
||||||
|
console.log( pos.coords ) |
||||||
|
} |
||||||
|
|
||||||
|
const loader = new THREE.TextureLoader(); |
||||||
|
|
||||||
|
function addWall(a, b, h, el, color=0x6d6a64){ |
||||||
|
var geometry = new THREE.BufferGeometry(); |
||||||
|
// create a simple rectangle shape. We duplicate the top left and bottom right |
||||||
|
// vertices because each vertex needs to appear once per triangle. |
||||||
|
var vertices = new Float32Array( [ |
||||||
|
a.x, a.y, a.z, |
||||||
|
a.x, a.y+h, a.z, |
||||||
|
b.x, b.y, b.z, |
||||||
|
|
||||||
|
a.x, a.y+h, a.z, |
||||||
|
b.x, b.y+h, b.z, |
||||||
|
b.x, b.y, b.z, |
||||||
|
] ); |
||||||
|
|
||||||
|
// itemSize = 3 because there are 3 values (components) per vertex |
||||||
|
geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); |
||||||
|
const material = new THREE.MeshBasicMaterial({ |
||||||
|
color : new THREE.Color( 0x6d6a64 ).offsetHSL(0,0,-Math.random()/10), |
||||||
|
side: THREE.DoubleSide, |
||||||
|
//map: loader.load('Visuals/TEXTURE_BAT/TEXTURE_BAT_Base_Color.jpg'), |
||||||
|
}); |
||||||
|
|
||||||
|
var mesh = new THREE.Mesh( geometry, material ); |
||||||
|
var entity = document.createElement("a-entity"); |
||||||
|
// not sure it's the best level of abstraction, maybe building instead |
||||||
|
entity.object3D.add( mesh ); |
||||||
|
el.appendChild( entity ); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function addFan(a, b, c, el, verticalOffset, color ){ |
||||||
|
var geometry = new THREE.BufferGeometry(); |
||||||
|
var vertices = new Float32Array( [ |
||||||
|
a.x, a.y+verticalOffset, a.z, |
||||||
|
b.x, b.y+verticalOffset, b.z, |
||||||
|
c.x, c.y+verticalOffset, c.z, |
||||||
|
] ); |
||||||
|
|
||||||
|
// itemSize = 3 because there are 3 values (components) per vertex |
||||||
|
geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); |
||||||
|
const material = new THREE.MeshBasicMaterial({ |
||||||
|
color : color, |
||||||
|
side: THREE.DoubleSide, |
||||||
|
}); |
||||||
|
var mesh = new THREE.Mesh( geometry, material ); |
||||||
|
var entity = document.createElement("a-entity"); |
||||||
|
entity.object3D.add( mesh ); |
||||||
|
el.appendChild( entity ); |
||||||
|
} |
||||||
|
|
||||||
|
function make3DFloor(points, el, color=0xedeceb ){ |
||||||
|
var entity = document.createElement("a-entity") |
||||||
|
var centroid = getCentroid( points ) |
||||||
|
entity.id = "roomcentroid" |
||||||
|
entity.setAttribute("position", centroid) |
||||||
|
el.appendChild( entity ) |
||||||
|
|
||||||
|
points.map( (e,i,arr) => { |
||||||
|
(i==arr.length-1) ? addFan(arr[0], e, centroid, el, 0, color) : addFan(e, arr[i+1], centroid, el, 0, color) |
||||||
|
}) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function make3DRoof(points, el , height, color=0x29465b){ |
||||||
|
var entity = document.createElement("a-entity") |
||||||
|
var centroid = getCentroid( points ) |
||||||
|
centroid.y += 3 // pointy roof |
||||||
|
entity.id = "roomcentroid" |
||||||
|
entity.setAttribute("position", centroid) |
||||||
|
el.appendChild( entity ) |
||||||
|
|
||||||
|
points.map( (e,i,arr) => { |
||||||
|
(i==arr.length-1) ? addFan(arr[0], e, centroid, el, height, color) : addFan(e, arr[i+1], centroid, el, height, color) |
||||||
|
}) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function getCentroid(points){ |
||||||
|
var centroid = new THREE.Vector3(0,0,0); |
||||||
|
// can probably be simplied now that we rely on threejs vectors rather than AFrame elements |
||||||
|
for (var e of points) { |
||||||
|
centroid.add( e ); |
||||||
|
} |
||||||
|
centroid.divideScalar(points.length); |
||||||
|
return centroid; |
||||||
|
} |
||||||
|
|
||||||
|
function makeWay( points, height, el, color=0xCCCCCC ){ |
||||||
|
var poly = points.map( p => pointFromCoordinates( p ) ) |
||||||
|
var way = document.createElement("a-entity") |
||||||
|
//way.setAttribute("position", "0 1 0") // vertical offset to avoid z-fighting on large horizontal planes |
||||||
|
way.className = "way" |
||||||
|
way.setAttribute("shadow", "") // somehow doesn't seem to receive, only casts |
||||||
|
el.appendChild( way ) |
||||||
|
make3DFloor( poly, way, color, "Visuals/TEXTURE_BAT/TEXTURE_SOL_Base_Color.jpg") |
||||||
|
} |
||||||
|
|
||||||
|
function makeLamp( treedata, el){ |
||||||
|
var tree = document.createElement("a-entity") |
||||||
|
tree.setAttribute("position", AFRAME.utils.coordinates.stringify( |
||||||
|
pointFromCoordinates( {lat:treedata.lat, lon:treedata.lon} ) |
||||||
|
) ) |
||||||
|
tree.setAttribute("instanced-mesh-member", "mesh:#meshlamp") |
||||||
|
el.appendChild( tree ) |
||||||
|
} |
||||||
|
|
||||||
|
function makeTree( treedata, el){ |
||||||
|
var tree = document.createElement("a-entity") |
||||||
|
tree.setAttribute("position", AFRAME.utils.coordinates.stringify( |
||||||
|
pointFromCoordinates( {lat:treedata.lat, lon:treedata.lon} ) |
||||||
|
) ) |
||||||
|
//tree.setAttribute("rotation", "0 "+ Math.random()*360 +" 0") // changing scale ever so slightly non uniformely could be interesting too |
||||||
|
// double check documentation, seems to move them too |
||||||
|
tree.setAttribute("instanced-mesh-member", "mesh:#meshtree") |
||||||
|
el.appendChild( tree ) |
||||||
|
} |
||||||
|
|
||||||
|
function makeBuilding( points, height, el ){ |
||||||
|
// somehow there is an angle, a slanting. Building usually have right angles at corners, not here. |
||||||
|
var poly = points.map( p => pointFromCoordinates( p ) ) |
||||||
|
var building = document.createElement("a-entity") |
||||||
|
building.className = "building" |
||||||
|
building.setAttribute("position", "0 "+ Math.random()/10 + " 0") // could rely on elevation instead. |
||||||
|
//Pavement would need to be high enough to make the connection between that and street. |
||||||
|
//which might make it look pointless |
||||||
|
el.appendChild( building ) |
||||||
|
makeWalls( poly, height, building ) |
||||||
|
// make3DFloor( poly, building ) // not visibile so might be removed |
||||||
|
var color = new THREE.Color( 0x262D7B ).offsetHSL(0,0,-Math.random()); |
||||||
|
make3DRoof( poly, building, height, color ) |
||||||
|
} |
||||||
|
|
||||||
|
function makeLine( poly, h, el ){ |
||||||
|
var color = 0x6d6a64 //+Math.floor( 100*Math.random() ) |
||||||
|
poly.map( (e,i,arr) => { if (i<arr.length-1) addWall(e, arr[i+1], h, el, color) }) |
||||||
|
} |
||||||
|
|
||||||
|
function makeWalls( poly, h, el ){ |
||||||
|
var color = new THREE.Color( 0x808080 ).offsetHSL(0,0,-Math.random()/10); |
||||||
|
poly.map( (e,i,arr) => { (i==arr.length-1) ? addWall(arr[0], e, h, el, color) : addWall(e, arr[i+1], h, el, color) }) |
||||||
|
} |
||||||
|
|
||||||
|
var elevationDataFromCache = localStorage.getItem('elevation'); |
||||||
|
// includes request date so might prefer to invalidate after a while |
||||||
|
var osmDataFromCache = localStorage.getItem('osm'); |
||||||
|
|
||||||
|
if (ignoreCache){ |
||||||
|
elevationDataFromCache = null |
||||||
|
osmDataFromCache = null |
||||||
|
} |
||||||
|
|
||||||
|
AFRAME.registerComponent('elevation', { |
||||||
|
init: function () { |
||||||
|
var el = this.el |
||||||
|
if (!elevationDataFromCache){ |
||||||
|
console.log("no elevation data from cache, querying") |
||||||
|
|
||||||
|
fetch(queryElevation, { |
||||||
|
method: 'POST', |
||||||
|
headers: { |
||||||
|
'Accept': 'application/json', |
||||||
|
'Content-Type': 'application/json', |
||||||
|
}, |
||||||
|
body: JSON.stringify({locations:locations}) |
||||||
|
}).then(response => response.json()).then(data => elevationTo3DElements(data.results, el ) ) |
||||||
|
} else { |
||||||
|
console.log("elevation data from cache") |
||||||
|
elevationTo3DElements( JSON.parse( elevationDataFromCache ), el ) |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
AFRAME.registerComponent('osm', { |
||||||
|
init: function () { |
||||||
|
var el = this.el |
||||||
|
if (!osmDataFromCache){ |
||||||
|
console.log("no osm data from cache, querying") |
||||||
|
fetch(queryOSMgeom).then(response => response.json()).then(data => fromOSMTo3DElements( data, el ) ) |
||||||
|
//console.log("no osm data from cache, querying locally for demo") |
||||||
|
//fetch("cached_osm_request.json").then(response => response.json()).then(data => fromOSMTo3DElements( data, el ) ) |
||||||
|
// for demo safety, cached locally on 6/1/2023 as cached_osm_request.json |
||||||
|
} else { |
||||||
|
console.log("osm data from cache") |
||||||
|
fromOSMTo3DElements( JSON.parse( osmDataFromCache ), el ) |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
// includes request date so might prefer to invalidate after a while |
||||||
|
|
||||||
|
AFRAME.registerComponent('cerema', { |
||||||
|
init: function () { |
||||||
|
var el = this.el |
||||||
|
wsim = el |
||||||
|
animationCurrentStep = simstart |
||||||
|
wsimanim = setInterval(animationStep, animationSpeedStep) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
AFRAME.registerComponent('watervisuals', { |
||||||
|
init: function () { |
||||||
|
var el = this.el |
||||||
|
visualmarkers.map( h => { |
||||||
|
var pointEl = document.createElement("a-gltf-model") |
||||||
|
pointEl.setAttribute("gltf-model", "Visuals/ASSETS_3D/AA_Crue.glb") |
||||||
|
pointEl.setAttribute("scale", "60 60 60") |
||||||
|
pointEl.setAttribute("rotation", "0 70 0") |
||||||
|
pointEl.setAttribute("position", h.position) |
||||||
|
if (h.rotation) pointEl.setAttribute("rotation", h.rotation) |
||||||
|
el.appendChild( pointEl ) |
||||||
|
}) |
||||||
|
|
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
AFRAME.registerComponent('hotspots', { |
||||||
|
init: function () { |
||||||
|
var el = this.el |
||||||
|
hotspots.map( h => { |
||||||
|
var pointEl = document.createElement("a-gltf-model") |
||||||
|
pointEl.setAttribute("gltf-model", "Visuals/ASSETS_3D/AA_Hotspot.glb") |
||||||
|
pointEl.setAttribute("model-opacity", .3) |
||||||
|
pointEl.setAttribute("scale", "50 50 50") |
||||||
|
pointEl.setAttribute("teleport-via-cursor", true) |
||||||
|
pointEl.setAttribute("position", h) |
||||||
|
el.appendChild( pointEl ) |
||||||
|
}) |
||||||
|
|
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
// from https://stackoverflow.com/questions/43914818/alpha-animation-in-aframe-for-a-object-model |
||||||
|
AFRAME.registerComponent('model-opacity', { |
||||||
|
schema: {default: 1.0}, |
||||||
|
init: function () { |
||||||
|
this.el.addEventListener('model-loaded', this.update.bind(this)); |
||||||
|
}, |
||||||
|
update: function () { |
||||||
|
var mesh = this.el.getObject3D('mesh'); |
||||||
|
var data = this.data; |
||||||
|
if (!mesh) { return; } |
||||||
|
mesh.traverse(function (node) { |
||||||
|
if (node.isMesh) { |
||||||
|
node.material.opacity = data; |
||||||
|
node.material.transparent = data < 1.0; |
||||||
|
node.material.needsUpdate = true; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
AFRAME.registerComponent('teleport-via-cursor', { |
||||||
|
init: function () { |
||||||
|
var el = this.el |
||||||
|
var cam = document.querySelector("#camera-rig") |
||||||
|
this.el.addEventListener('click', function (evt) { |
||||||
|
var pos = el.getAttribute("position") |
||||||
|
if (!AFRAME.utils.device.checkHeadsetConnected()) |
||||||
|
cam.setAttribute("position", pos.x + " " + desktopHeight + " " + pos.z) |
||||||
|
else |
||||||
|
cam.setAttribute("position", pos.x + " " + vrHeight + " " + pos.z) |
||||||
|
// might want to blink too, e.g black sphere around the camera for .5s |
||||||
|
}); |
||||||
|
this.el.addEventListener('fusing', function (evt) { |
||||||
|
//el.setAttribute("opacity", 1) |
||||||
|
el.setAttribute("model-opacity", 1) |
||||||
|
}); |
||||||
|
this.el.addEventListener('mouseleave', function (evt) { |
||||||
|
//el.setAttribute("opacity", 0.3) |
||||||
|
el.setAttribute("model-opacity", .3) |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
AFRAME.registerComponent('hud', { |
||||||
|
init: function () { |
||||||
|
var el = this.el |
||||||
|
var ringEl = document.createElement("a-ring") |
||||||
|
ringEl.setAttribute("theta-length", "0") |
||||||
|
ringEl.setAttribute("position", "1.5 0 -3") |
||||||
|
el.appendChild( ringEl ) |
||||||
|
var pointEl = document.createElement("a-text") |
||||||
|
// consider a-text instead https://github.com/lojjic/aframe-troika-text |
||||||
|
// CEREMA/Visuals/ASSETS_3D/TypoTimer/Abel/Abel-Regular.ttf |
||||||
|
pointEl.setAttribute("value", "0") |
||||||
|
pointEl.setAttribute("color", "white") |
||||||
|
pointEl.setAttribute("negate", "false") |
||||||
|
pointEl.setAttribute("text", "align", "center") |
||||||
|
pointEl.setAttribute("text", "font", "Visuals/Abel-Regular-msdf.json") |
||||||
|
pointEl.setAttribute("position", "0.4 0 -0.9") |
||||||
|
el.appendChild( pointEl ) |
||||||
|
var backgroundModelEl = document.createElement("a-gltf-model") |
||||||
|
backgroundModelEl.setAttribute("scale", "40 50 50") |
||||||
|
backgroundModelEl.setAttribute("position", "0.5 -0.4 -1.1") |
||||||
|
backgroundModelEl.setAttribute("gltf-model", "Visuals/ASSETS_3D/AA_Timer.glb") |
||||||
|
el.appendChild( backgroundModelEl ) |
||||||
|
this.ring = ringEl |
||||||
|
this.hud = pointEl |
||||||
|
}, |
||||||
|
|
||||||
|
tick: function (time, timeDelta) { |
||||||
|
animationTime++ |
||||||
|
if (animationTime > 20000) { |
||||||
|
this.start = 0 |
||||||
|
animationTime = 0 |
||||||
|
} |
||||||
|
this.hud.setAttribute("value", animationCurrentStep) |
||||||
|
this.ring.setAttribute("theta-length", animationCurrentStep/simend*360) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
AFRAME.registerComponent('listosmways', { |
||||||
|
init: function () { |
||||||
|
var el = this.el |
||||||
|
this.el.addEventListener('model-loaded', function (evt) { |
||||||
|
//el.object3D.traverse( i => { if (i.name && i.name !="Scene" && i.type=="Group") loadedosmways.push(Number( i.name )) }) |
||||||
|
el.object3D.traverse( i => { if (i.name && i.name !="Scene" && i.type=="Mesh") loadedosmways.push(Number( i.name )) }) |
||||||
|
}); |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
AFRAME.registerComponent('resetanimations', { |
||||||
|
init: function () { |
||||||
|
var el = this.el |
||||||
|
this.el.addEventListener('click', function (evt) { |
||||||
|
resetAnimations() |
||||||
|
}); |
||||||
|
this.el.addEventListener('fusing', function (evt) { |
||||||
|
el.setAttribute("opacity", 0.3) |
||||||
|
}); |
||||||
|
this.el.addEventListener('mouseleave', function (evt) { |
||||||
|
el.setAttribute("opacity", 1) |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
AFRAME.registerComponent('displaymodels', { |
||||||
|
init: function () { |
||||||
|
// sufficient, removed the others otherwise causes raycaster bugs |
||||||
|
Array.from( document.querySelectorAll('[class*="models_"]') ).map( m => { |
||||||
|
m.setAttribute( "scale", ".01 .01 .01" ) |
||||||
|
}) |
||||||
|
Array.from( document.querySelectorAll(".models_"+positionsCurated[selection].shortname) ).map( m => { |
||||||
|
m.setAttribute( "scale", "1.2 1.2 1.2") |
||||||
|
m.setAttribute( "visible", "true" ) |
||||||
|
m.setAttribute( "listosmways", "" ) |
||||||
|
}) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
AFRAME.registerComponent('forcestats', { |
||||||
|
init: function () { |
||||||
|
var forcestats = AFRAME.utils.getUrlParameter('forcestats'); |
||||||
|
if (forcestats == "true") this.el.setAttribute("stats", true) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
AFRAME.registerComponent('customdebug', { |
||||||
|
init: function () { |
||||||
|
var el = this.el |
||||||
|
var debugmode = AFRAME.utils.getUrlParameter('debugmode'); |
||||||
|
if (debugmode && debugmode == "true") { |
||||||
|
Array.from( document.querySelectorAll(".debug") ).map( e => e.setAttribute("visible", true) ) |
||||||
|
document.querySelector("#mainbutton").style.display = "none"; |
||||||
|
el.addEventListener('loaded', function () { |
||||||
|
el.components.inspector.openInspector() |
||||||
|
setTimeout(function () { // setTimeout for good measure. |
||||||
|
AFRAME.scenes[0].camera.position.set(400,600,-30) |
||||||
|
AFRAME.scenes[0].camera.rotation.set(-1.6,0.65,1.6); |
||||||
|
}, 1000) |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
function resetAnimations(){ |
||||||
|
Array.from( document.querySelectorAll("[animation]") ) |
||||||
|
.map( a => Object.keys( a.components ) |
||||||
|
.filter( k => k.match("animation") ) |
||||||
|
.map( key => a.components[key].time = 0 ) |
||||||
|
// could also use play() or pause() |
||||||
|
) |
||||||
|
animationTime = 0 |
||||||
|
animationCurrentStep = simstart |
||||||
|
clearInterval(wsimanim) |
||||||
|
wsimanim = setInterval(animationStep, animationSpeedStep) |
||||||
|
} |
||||||
|
|
||||||
|
var animationTime = 0 // this isn't exactly aligned with animations |
||||||
|
// because virtual and real time hasn't be decided yet (waiting on simulation results) |
||||||
|
// virtual time would amount to 2 to 3min total. |
||||||
|
|
||||||
|
loadedosmways = []; |
||||||
|
|
||||||
|
function playSound(){ |
||||||
|
var entity = document.querySelector('[sound]'); |
||||||
|
entity.components.sound.playSound(); |
||||||
|
document.querySelector("a-entity[sound]").components.sound.playSound() |
||||||
|
document.querySelector("#mainbutton").style.display = "none"; |
||||||
|
} |
||||||
|
|
||||||
|
function startExperience(){ |
||||||
|
document.querySelector("#watersim").setAttribute("material", "displacementScale", simscale) |
||||||
|
AFRAME.scenes[0].enterVR(); |
||||||
|
playSound(); |
||||||
|
resetAnimations() |
||||||
|
if (!AFRAME.utils.device.checkHeadsetConnected()){ |
||||||
|
document.querySelector("#camera-rig").object3D.position.y = desktopHeight |
||||||
|
// works only at the beginning, not after teleporting |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
<button id=mainbutton style="z-index: 1; position: absolute; width:50%; margin: auto; text-align:center; top:45%; left:30%; height:30%;" onclick="startExperience()">Demarrer l'experience</button> |
||||||
|
<a-scene displaymodels forcestats customdebug > |
||||||
|
<a-entity id="hotspots" hotspots ></a-entity> |
||||||
|
<a-entity id="watervisuals" watervisuals ></a-entity> |
||||||
|
<!-- Quest available only during event, otherwise cardboard --> |
||||||
|
<a-entity id="leftHand" hand-tracking-controls="hand: left;"></a-entity> |
||||||
|
<a-entity id="rightHand" hand-tracking-controls="hand: right;"></a-entity> |
||||||
|
<a-entity id="camera-rig" set-initial-camera-from-data> |
||||||
|
<a-sound autoplay="false" loop="true" src="src: url(Sounds/AMBIANCES/CEREMA_AMB_VILLE.mp3)"></a-sound> |
||||||
|
<a-sound class="randomfx" autoplay="false" loop="false" src="src: url(Sounds/FX/CEREMA_CRACK_MAISON_01.mp3)"></a-sound> |
||||||
|
<a-sound class="randomfx" autoplay="false" loop="false" src="src: url(Sounds/FX/CEREMA_CRACK_MAISON_02.mp3)"></a-sound> |
||||||
|
<a-sound class="randomfx" autoplay="false" loop="false" src="src: url(Sounds/FX/CEREMA_CRACK_MAISON_03.mp3)"></a-sound> |
||||||
|
<a-sound class="randomfx" autoplay="false" loop="false" src="src: url(Sounds/FX/CEREMA_CRACK_MAISON_04.mp3)"></a-sound> |
||||||
|
<a-sound class="randomfx" autoplay="false" loop="false" src="src: url(Sounds/FX/CEREMA_SIRENNES_POMPIER.mp3)"></a-sound> |
||||||
|
<a-sound class="randomfx" autoplay="false" loop="false" src="src: url(Sounds/FX/CEREMA_SIRENNE_COMMUNALE.mp3)"></a-sound> |
||||||
|
<a-entity position="0 -1.2 0" sound="src: url(Sounds/FX/CEREMA_BOUCLE_EAU.mp3); loop:true; autoplay:false; volume:0;" |
||||||
|
animation="property: sound.volume; from:0; to: 1; dur: 9000; easing: linear; loop: false;" |
||||||
|
></a-entity> |
||||||
|
<a-entity camera look-controls hud> |
||||||
|
<!-- consider time as HUD text display--> |
||||||
|
<a-entity cursor="fuse: true; fuseTimeout: 500" |
||||||
|
position="0 0 -3" |
||||||
|
geometry="primitive: ring; radiusInner: 0.04; radiusOuter: 0.05" |
||||||
|
material="color: orange; shader: flat"> |
||||||
|
</a-entity> |
||||||
|
</a-entity> |
||||||
|
<a-gltf-model resetanimations="" position="2 2 4" scale="50 50 50" rotation="29 -160 -10" gltf-model="Visuals/ASSETS_3D/AA_Reset.glb" opacity="1"></a-gltf-model> |
||||||
|
</a-entity> |
||||||
|
<a-entity id="meshlamp" gltf-model="Visuals/EXPORTS/EXPORTS_gltf/ModelsSolo/AA_Ruelle_Lampadaire.glb" instanced-mesh ></a-entity> |
||||||
|
<a-entity id="meshtree" position="-25 0 9" gltf-model="Visuals/EXPORTS/EXPORTS_gltf/AA_Ruelle_Arbre.glb" instanced-mesh ></a-entity> |
||||||
|
|
||||||
|
<a-plane id="ground" visible=false position="0 -.5 0" rotation="-90 0 0" width="1000" height="1000" color="lightgrey"></a-plane> |
||||||
|
<a-entity light="type: ambient; color: #BBB"></a-entity> |
||||||
|
<a-entity id="sun" light="type: directional; color: #FFF; castShadow:true;" position="-5 5 0" |
||||||
|
animation="property: light.intensity; from:0.1; to: 0.6; dur: 20000; easing: linear; loop: true; dir:alternate;" |
||||||
|
animation__pos="property: position; from:-50 50 100; to: 50 50 100; dur: 20000; easing: linear; loop: true"> |
||||||
|
</a-entity> |
||||||
|
|
||||||
|
<a-entity position="0 -0.10 0" scale="1.3 1 1.3" osm id="osm"></a-entity> |
||||||
|
|
||||||
|
<!-- offset on test data for easier vr testing --> |
||||||
|
<a-gltf-model class="models_ruelle" visible="" position="-139.95628 0.73 -34.3154" scale="1.2 1.2 1.2" rotation="0 -111.8545396388247 0" gltf-model="Visuals/EXPORTS/EXPORTS_gltf/AA_Quaidelaruelle.glb" listosmways=""></a-gltf-model> |
||||||
|
<a-gltf-model class="models_baquets" visible="" position="-28.39341 0 10.05424" scale="" rotation="0 66.68655777527651 0" gltf-model="Visuals/EXPORTS/EXPORTS_gltf/AA_Citedesbaquets.glb" listosmways=""></a-gltf-model> |
||||||
|
<a-plane id="landscape" position="1135.38323 -36.66906 -1784.89792" scale="50 50 50" rotation="-90.00021045914971 -96.34743691360897 0" width="100" height="100" geometry="segmentsHeight: 128; segmentsWidth: 128" material="src: ign_terrain_cached.jpg; displacementScale: 5; displacementMap: ign_elevation_cached.jpg" visible=""></a-plane> |
||||||
|
|
||||||
|
<a-plane id="watersim" cerema="" position="0 -51.55 0" rotation="-90 90 0" src="Visuals/EAU/Eau_01.jpg" |
||||||
|
material="displacementScale: 0.04; wireframe: false; displacementMap: " |
||||||
|
geometry="segmentsHeight: 128; segmentsWidth: 128" scale="1000 1000 100"></a-plane> |
||||||
|
<!-- animation might fail due texture.neddUpdate unset --> |
||||||
|
|
||||||
|
<a-image id="debug_baquet_osm" src="baquet_osm.png" visible=false position="-235 1.1 145" rotation="-90 90 0" scale="200 200 200" material="opacity: 0.5" class=debug></a-image> |
||||||
|
<a-image id="debug_ruelle_osm" src="ruelle_osm.png" visible=false position="-40 1 -220" rotation="-90 90 0" material="opacity: 0.5" scale="200 200 200" class="debug"></a-image> |
||||||
|
<a-image id="debug_sim_cerema" src="" visible=false position="0 1.2 0" rotation="-90 90 0" material="opacity: 0.5" scale="1000 1000 1000" class="debug"></a-image> |
||||||
|
<a-sky src="cloudsky.jpg" |
||||||
|
animation="from: #000; to: #aaf; loop: true; dur: 20000; dir: alternate; loop:true; property: components.material.material.color; type: color;" |
||||||
|
animation_texture="property: material.offset; to: 1 1; dur: 200; easing: linear; loop: true; dir: alternate;" |
||||||
|
></a-sky> |
||||||
|
</a-scene> |
||||||
|
<!-- |
||||||
|
live layers (deactivated by default) |
||||||
|
|
||||||
|
material="src: https://wxs.ign.fr/ortho/geoportail/r/wms?LAYERS=ORTHOIMAGERY.ORTHOPHOTOS.BDORTHO&EXCEPTIONS=text/xml&FORMAT=image/jpeg&SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&STYLES=&CRS=CRS:84&BBOX=0.470286,49.338484,0.556117,49.381417&WIDTH=4096&HEIGHT=4096; displacementScale: 20; displacementMap: https://wxs.ign.fr/altimetrie/geoportail/r/wms?LAYERS=ELEVATION.ELEVATIONGRIDCOVERAGE.HIGHRES&EXCEPTIONS=text/xml&FORMAT=image/jpeg&SERVICE=WMS&VERSION=1.3.0&REQUEST=GETMAP&STYLES=&CRS=CRS:84&BBOX=0.470286,49.338484,0.556117,49.381417&WIDTH=1024&HEIGHT=1024;" ></a-plane> |
||||||
|
--> |
||||||
|
</body> |
||||||
|
</html> |
Loading…
Reference in new issue