|
|
|
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()
|
|
|
|
|
|
|
|
|