< html >
< head >
< script src = "https://aframe.io/releases/1.3.0/aframe.min.js" > < / script >
< / head >
< body >
< script >
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
}
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 = 500
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 = [
// [50.8354400, 4.3582900],
{ position:[50.8365354, 4.3581719], hotspots: []},
{ shortname: "baquets", worldposition:[83.03582, -346.4272], position:[49.3516400, 0.5461600],
hotspots: [
// done via the AFrame inspector then pasting here, then regex
[-126.65372, -0.46424], [-126.65372, -0.46424], [-126.29815, -26.94149], [-125.92851, -42.42632], [-122.30186, -52.18768], [-105.27281, -51.22971], [-104.02136, -36.467], [-102.82045, -18.61413], [-102.20349, -3.76295], [-118.85539, 2.56914],
],
hotspotoffsetPosition: new THREE.Vector3(175.80343,0,-286.43518),
//hotspotoffsetRotation: "0 8.047765190407544 0"
},
{ shortname: "ruelle", worldposition:[-126, -40], position:[49.3605700, 0.5031000],
hotspots: [
[-134.62038, -13.29107], [-125.89544, -30.36393], [-119.72245, -49.78609], [-96.91944, -95.00968], [-90.17903, -109.1628],
, [-142.76086, 11.1124]
],
hotspotoffsetPosition: new THREE.Vector3(),
//hotspotoffsetPosition: "0 0 0",
//hotspotoffsetRotation: "0 0 0",
},
{ position:[50.8440239, 4.3952614], hotspots: []}, // brussels for tests
]
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] + " 1.6 " + positionsCurated[selection].worldposition[1] )
}
})
var ignoreCache = true // should be joined as 1 or better be location dependent
const disableWaysForGLTFExportInHubs = false
var selection = 2 // 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.map( i => [ i[0]+positionsCurated[selection].hotspotoffsetPosition.x ,i[1]+positionsCurated[selection].hotspotoffsetPosition.z ] )
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) )
.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){
// < a-gltf-model position = "-2 0 -6" scale = "2 2 2" src = "arbre.gltf" > < / a-gltf-model >
var tree = document.createElement("a-entity")
tree.setAttribute("position", AFRAME.utils.coordinates.stringify(
pointFromCoordinates( {lat:treedata.lat, lon:treedata.lon} )
) )
tree.setAttribute("gltf-model", "Visuals/EXPORTS/EXPORTS_gltf/ModelsSolo/AA_Ruelle_Lampadaire.glb" )
el.appendChild( tree )
}
function makeTree( treedata, el){
// < a-gltf-model position = "-2 0 -6" scale = "2 2 2" src = "arbre.gltf" > < / a-gltf-model >
var tree = document.createElement("a-entity")
tree.setAttribute("position", AFRAME.utils.coordinates.stringify(
pointFromCoordinates( {lat:treedata.lat, lon:treedata.lon} )
) )
tree.setAttribute("gltf-model", "Visuals/EXPORTS/EXPORTS_gltf/AA_Ruelle_Arbre.glb" )
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( 0x8F8FFF ).offsetHSL(0,0,-Math.random());
make3DRoof( poly, building, height, color )
building.setAttribute("shadow", "")// somehow doesn't seem to receive it, only casts
/*
var inbuilding = document.createElement("a-entity")
inbuilding.className = "inbuilding"
building.appendChild( inbuilding )
makeWalls( poly, 0.2, inbuilding )
make3DFloor( poly, inbuilding )
inbuilding.setAttribute("scale", "1.1 1 1.1") // adds an horizontal offset instead of being centered on building
inbuilding.setAttribute("position", "-.2 0 -.2")
*/
}
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('hotspots', {
init: function () {
var el = this.el
hotspots.map( h => {
//var pointEl = document.createElement("a-sphere")
//pointEl.setAttribute("geometry", "segmentsHeight: 4; segmentsWidth: 8")
var pointEl = document.createElement("a-gltf-model")
pointEl.setAttribute("gltf-model", "Visuals/ASSETS_3D/AA_Hotspot.glb")
//pointEl.setAttribute("opacity", .3)
pointEl.setAttribute("model-opacity", .3)
pointEl.setAttribute("scale", "50 50 50")
pointEl.setAttribute("position", h[0] + " 3 " + h[1])
pointEl.setAttribute("teleport-via-cursor", true)
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")
cam.setAttribute("position", pos.x + " 1.6 " + 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")
pointEl.setAttribute("value", "0.0")
pointEl.setAttribute("position", "0.3 0 -0.9")
el.appendChild( pointEl )
this.ring = ringEl
this.hud = pointEl
},
tick: function (time, timeDelta) {
animationTime++
if (animationTime > 20000) {
this.start = 0
animationTime = 0
}
this.hud.setAttribute("value", animationTime)
this.ring.setAttribute("theta-length", animationTime/20000*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(param){
var entity = document.querySelector('[sound]');
entity.components.sound.playSound();
document.querySelector("a-entity[sound]").components.sound.playSound()
param.style.display = "none";
resetAnimations()
}
< / script >
< button id = mainbutton style = "z-index: 1; position: absolute; width:50%; margin: auto; text-align:center; top:45%; left:30%; height:30%;" onclick = "playSound(this)" > Demarrer l'experience< / button >
< a-scene displaymodels forcestats customdebug >
< a-entity id = "hotspots" hotspots > < / 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-entity camera look-controls hud >
< 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 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 >
<!-- 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 = "6 6 9" scale = "100 100 100" rotation = "29 -160 -10" gltf-model = "Visuals/ASSETS_3D/AA_Reset.glb" opacity = "1" > < / a-gltf-model >
< a-gltf-model position = "-2.2864 1.02726 -1.41235" scale = "30 30 30" rotation = "0 70 0" gltf-model = "Visuals/ASSETS_3D/AA_Crue.glb" opacity = "1" > < / a-gltf-model >
< / a-entity >
< a-plane id = "ground" position = "0 -.2 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 = "-70 0 -42" osm id = "osm" > < / a-entity >
<!-- offset on test data for easier vr testing -->
< a-gltf-model class = "models_ruelle" visible = "" position = "-140.71021 0.73 -28.54369" 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 = "95.71286 -1.33294 -307.27021" scale = "1.2 1.2 1.2" rotation = "0 179.42402537639782 0" gltf-model = "Visuals/EXPORTS/EXPORTS_gltf/AA_Citedesbaquets.glb" listosmways = "" > < / a-gltf-model >
< a-plane id = "landscape" position = "0 -20 0" scale = "10 10 10" rotation = "-90 0 -90" width = "100" height = "100" geometry = "segmentsHeight: 128; segmentsWidth: 128"
material="src: ign_terrain_cached.jpg; displacementScale: 20; displacementMap: ign_elevation_cached.jpg;">< / a-plane >
< a-plane id = "watersim" cerema = "" position = "0 -50.25 0" rotation = "-90 90 0" src = "Visuals/EAU/Eau_01.jpg"
material="displacementScale: 0.02; 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 >