feat(ui): add interactive 3D LoRa devices and OpenWrt router

This commit is contained in:
yaya
2026-10-09 13:15:59 +01:00
parent 5812f53c7c
commit 5d64991203
8 changed files with 705 additions and 37 deletions
+58
View File
@@ -0,0 +1,58 @@
import * as THREE from 'three'
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js'
import { createDeviceModel } from './deviceModels'
export function createDeviceScene(host: HTMLElement, id: string) {
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, powerPreference: 'low-power' })
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 1.75))
renderer.setClearColor(0x000000, 0)
renderer.outputColorSpace = THREE.SRGBColorSpace
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMappingExposure = 1
renderer.domElement.setAttribute('aria-hidden', 'true')
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(33, 1, .1, 100)
let model = createDeviceModel(id)
scene.add(model.pivot)
model.pivot.rotation.set(id === 'beryl-ax' ? .27 : -.08, -.4, 0)
const studio = new RoomEnvironment()
const pmrem = new THREE.PMREMGenerator(renderer)
const environment = pmrem.fromScene(studio, .04, .1, 100, { size: 128 })
scene.environment = environment.texture; scene.environmentIntensity = .8
studio.dispose(); pmrem.dispose()
scene.add(new THREE.HemisphereLight(0xe4eeff, 0x202b3d, 1.4))
for (const [color, intensity, x, y, z] of [
[0xffffff, 2.2, 3, 5, 6], [0x91c6e8, 1.8, -4, 1, -3], [0xe8c5ac, 1.4, 4, 0, -4],
]) {
const light = new THREE.DirectionalLight(color, intensity)
light.position.set(x!, y!, z!); scene.add(light)
}
host.append(renderer.domElement)
let disposed = false
function render() { if (!disposed) renderer.render(scene, camera) }
function resize() {
if (disposed) return
const { width, height } = host.getBoundingClientRect()
if (!width || !height) return
renderer.setSize(width, height, false)
camera.aspect = width / height
const halfFov = Math.atan(Math.tan(THREE.MathUtils.degToRad(camera.fov / 2)) * Math.min(1, camera.aspect))
camera.position.set(0, 0, model.radius / Math.sin(halfFov) * 1.09)
camera.lookAt(0, 0, 0); camera.updateProjectionMatrix(); render()
}
resize()
return {
canvas: renderer.domElement, get rotation() { return model.pivot.rotation }, render, resize,
setModel(next: string) {
const replacement = createDeviceModel(next)
scene.remove(model.pivot); model.dispose(); model = replacement
model.pivot.rotation.set(next === 'beryl-ax' ? .27 : -.08, -.4, 0)
scene.add(model.pivot); resize()
},
dispose() {
if (disposed) return
disposed = true; model.dispose(); environment.dispose(); renderer.dispose()
renderer.forceContextLoss(); renderer.domElement.remove()
},
}
}