Experience results from numerical simulations by the CEREMA in 2 locations in France. Build on OpenStreetMap, IGN data and AFrame for WebXR support. https://bric-vr.com/cerema/portail/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
CEREMA/index.html

722 lines
30 KiB

<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 queryOSM = `https://overpass-api.de/api/interpreter?data=[out:json];nwr(${bbox});out;`
// format https://overpass-api.de/output_formats.html#json
// bounding box https://dev.overpass-api.de/overpass-doc/en/full_data/bbox.html
// testing https://overpass-turbo.eu
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 ) )
} 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>