let haStateURL = "http://beacon:1880/ha_state" const scene = new THREE.Scene() const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ) let homeContainer = new THREE.Group() scene.add(homeContainer) let home = new THREE.Group() homeContainer.add(home) var areaNames = [ ] const renderer = new THREE.WebGLRenderer() renderer.setSize( window.innerWidth, window.innerHeight ) document.body.appendChild( renderer.domElement ) const light = new THREE.AmbientLight(0xFFAAAA, 1) // light.position.z = 100 scene.add(light) scene.background = new THREE.Color("white") // back the camera up camera.position.z = 25 // flip home on x axis home.rotation.x = Math.PI // Rotate container a bit for comfort homeContainer.rotation.x = 0.6 * (-Math.PI / 2) function animate() { requestAnimationFrame( animate ) homeContainer.rotation.z += 0.005 renderer.render( scene, camera ) } animate() function createTextMesh(text, colorName) { let canvas = document.createElement('canvas') let context = canvas.getContext('2d') let size = context.measureText(text) canvas.width = 100 canvas.height = 100 context.textAlign = "center" context.textBaseline = "middle" context.fillStyle = colorName context.font = '24pt sans-serif' context.fillText(text, 50, 50) let texture = new THREE.CanvasTexture(canvas) texture.needsUpdate = true let material = new THREE.MeshBasicMaterial({ map: texture }) material.transparent = true let plane = new THREE.PlaneGeometry(3, 3) let mesh = new THREE.Mesh(plane, material) return mesh } function updateWithHAData(data) { // `data` is an array of entities. loop through and group by area var areasToEntities = { } for (var area of areaNames) { areasToEntities[area] = new Array() } let sortedAreaNames = areaNames sortedAreaNames.sort(function(a, b) { return b.length - a.length; }) for (var entity of data) { let id = entity.entity_id let friendlyName = entity.attributes.friendly_name || entity.entity_id for (var area of sortedAreaNames) { let matchesUnderscored = id.includes(area.toLowerCase().replace(' ','_')) let matchesNoSpaces = id.includes(area.toLowerCase().replace(' ','')) let matchesFriendlyName = friendlyName.includes(area) let matches = matchesUnderscored || matchesNoSpaces || matchesFriendlyName if (matches) { let areaEntities = areasToEntities[area] areaEntities.push(entity) areasToEntities[area] = areaEntities break } } } } async function loadHAData() { let request = new Request(haStateURL) fetch(request) .then(response => { return response.json() }) .then(json => { updateWithHAData(json) }) } function setPosition(mesh, x, y, z, w, h, d) { // position sets the *center* position // our coordinates are top/left/bottom let meshX = x + w/2 let meshY = y + h/2 let meshZ = -z - d/2 mesh.position.x = meshX mesh.position.y = meshY mesh.position.z = meshZ } function configureScene(data) { var minX = 1000 var maxX = -1000 var minY = minX var maxY = maxX // Add geometry for the rooms for (var room of data.rooms) { areaNames.push(room.name) let roomContainer = new THREE.Group() roomContainer.userData = room.name let roomGeo = new THREE.BoxGeometry(room.w, room.h, room.d) let roomColor = new THREE.Color(room.color) let roomBoxMaterial = new THREE.MeshPhysicalMaterial({ color: roomColor }) roomBoxMaterial.transparent = true roomBoxMaterial.opacity = 0.3 let roomMesh = new THREE.Mesh(roomGeo, roomBoxMaterial) roomMesh.userData = "box" roomContainer.add(roomMesh) let roomEdgesGeo = new THREE.EdgesGeometry(roomGeo) let roomLinesMaterial = new THREE.LineBasicMaterial({ color: roomColor }) let roomLines = new THREE.LineSegments(roomEdgesGeo, roomLinesMaterial) roomLines.userData = "lines" roomContainer.add(roomLines) let roomLabel = createTextMesh("test", "red") roomLabel.userData = "label" roomContainer.add(roomLabel) setPosition(roomContainer, room.x, room.y, room.z || 0, room.w, room.h, room.d) home.add(roomContainer) minX = Math.min(minX, room.x) minY = Math.min(minY, room.y) maxX = Math.max(maxX, room.x + room.w) maxY = Math.max(maxY, room.y + room.h) } let xExtent = maxX - minX let yExtent = maxY - minY home.position.x = -1 * (xExtent / 2) home.position.y = +1 * (yExtent / 2) } async function loadHomeData() { let request = new Request('data.json') fetch(request) .then(response => { return response.json() }) .then(json => { configureScene(json) // Kick off first HA load loadHAData() }) } loadHomeData()