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