floating labels

This commit is contained in:
Norman Köhring 2023-01-23 12:09:52 +01:00
parent 4a671e8019
commit 890c86e654
4 changed files with 62 additions and 21 deletions

View file

@ -11,7 +11,7 @@
display: flex; display: flex;
place-items: center; place-items: center;
min-height: 100vh; min-height: 100vh;
font: 16/1.5 sans-serif normal; font: 16px monospace;
color-scheme: light dark; color-scheme: light dark;
color: #EEE; color: #EEE;
background-color: #222; background-color: #222;
@ -30,10 +30,24 @@
#info > button.highlighted { #info > button.highlighted {
border-color: yellow; border-color: yellow;
} }
label {
position: fixed;
top: 0;
left: 0;
margin-top: -.5em;
margin-left: .5em;
font-weight: bold;
color: white;
pointer-events: none;
transform: translate(0, 0);
background: #0008;
line-height: 1;
}
</style> </style>
</head> </head>
<body> <body>
<div id="info">Click a star to get options.</div> <div id="info">Click a star to get options.</div>
<div id="labels"></div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

View file

@ -1,4 +1,3 @@
import { IncomingMessage } from 'http'
import { import {
WebGLRenderer, WebGLRenderer,
Scene, Scene,
@ -64,7 +63,7 @@ function init() {
let closest: Intersection<Object3D<Event>> | null = null let closest: Intersection<Object3D<Event>> | null = null
for (let i of intersections) { for (let i of intersections) {
if (i.distanceToRay === undefined) continue if (i.distanceToRay === undefined) continue // ignore Lines
if (closest === null || i.distanceToRay < (closest.distanceToRay ?? 0)) { if (closest === null || i.distanceToRay < (closest.distanceToRay ?? 0)) {
closest = i closest = i
} }
@ -86,8 +85,16 @@ function init() {
raycaster.intersectObject(stars, true, intersections) raycaster.intersectObject(stars, true, intersections)
renderer.render(scene, camera) renderer.render(scene, camera)
// update label positions in HTML space
// Attention: This has to happen after the render call, to avoid flickering
for (let star of stars.children) {
;(star as Star).setLabelPos(camera, w, h)
// set label z-index to distance to make them overlap intuitively
}
}) })
document.body.appendChild(renderer.domElement)
document.body.prepend(renderer.domElement)
} }
init() init()

View file

@ -1,9 +1,7 @@
import { BufferGeometry, Group, Line, LineBasicMaterial, Shape, Vector3 } from 'three' import { BufferGeometry, Group, Line, LineBasicMaterial, Shape, Vector3 } from 'three'
export function planeGeometry(radius: number, n = 5) { export function planeGeometry(radius: number, n = 5) {
const material = new LineBasicMaterial({ const material = new LineBasicMaterial({ color: 0x205020 })
color: 0x303030,
})
const plane = new Group() const plane = new Group()
const xLine = new BufferGeometry().setFromPoints([ const xLine = new BufferGeometry().setFromPoints([

View file

@ -8,6 +8,7 @@ import {
LineBasicMaterial, LineBasicMaterial,
Vector3, Vector3,
Spherical, Spherical,
Camera,
} from 'three' } from 'three'
import data from './testdata.json' import data from './testdata.json'
@ -25,9 +26,7 @@ export class Star extends Group {
public isStar = true public isStar = true
public starData: StarData public starData: StarData
private tangentialCoords = new Vector3() private coords = new Vector3()
private cartesianCoords = new Vector3()
private sphericalCoords = new Spherical()
private isHighlighted = false private isHighlighted = false
private normalPointSize = 2 private normalPointSize = 2
@ -40,30 +39,40 @@ export class Star extends Group {
private whiteColor = new Float32BufferAttribute([255, 255, 255], 3) private whiteColor = new Float32BufferAttribute([255, 255, 255], 3)
private yellowColor = new Float32BufferAttribute([255, 255, 0], 3) private yellowColor = new Float32BufferAttribute([255, 255, 0], 3)
private poleLine = new Line(new BufferGeometry(), this.lineMaterial) private labelEl = document.createElement('label')
private point: Points<BufferGeometry, PointsMaterial>
constructor(starData: StarData) { constructor(starData: StarData) {
super() super()
const { radius, phi, theta } = starData
this.starData = starData this.starData = starData
this.sphericalCoords.set(radius, phi, theta)
this.cartesianCoords.setFromSpherical(this.sphericalCoords)
const { x, z } = this.cartesianCoords const { radius, phi, theta } = starData
this.tangentialCoords.set(x, 0, z) const sphericalCoords = new Spherical(radius, phi, theta)
this.coords.setFromSpherical(sphericalCoords)
this.poleLine.geometry.setFromPoints([this.cartesianCoords, this.tangentialCoords]) const { x, z } = this.coords
const tangentialCoords = new Vector3(x, 0, z)
this.add(this.poleLine) // distance indicator / pole
const poleLine = new Line(new BufferGeometry(), this.lineMaterial)
poleLine.geometry.setFromPoints([this.coords, tangentialCoords])
this.add(poleLine)
const coords = [this.cartesianCoords.x, this.cartesianCoords.y, this.cartesianCoords.z] // the actual "star"
const coords = [this.coords.x, this.coords.y, this.coords.z]
this.pointGeometry.setAttribute('position', new Float32BufferAttribute(coords, 3)) this.pointGeometry.setAttribute('position', new Float32BufferAttribute(coords, 3))
this.pointGeometry.setAttribute('color', this.whiteColor) this.pointGeometry.setAttribute('color', this.whiteColor)
this.pointGeometry.computeBoundingSphere() this.pointGeometry.computeBoundingSphere()
const point = new Points(this.pointGeometry, this.pointMaterial) this.point = new Points(this.pointGeometry, this.pointMaterial)
this.add(point) this.add(this.point)
// label
const container = document.getElementById('labels')!
this.labelEl.innerText = starData.name
container.appendChild(this.labelEl)
} }
private setHighlight(isHighlight = true) { private setHighlight(isHighlight = true) {
@ -87,6 +96,19 @@ export class Star extends Group {
this.isHighlighted = !this.isHighlighted this.isHighlighted = !this.isHighlighted
this.setHighlight(this.isHighlighted) this.setHighlight(this.isHighlighted)
} }
public setLabelPos(camera: Camera, width: number, height: number) {
const dpr = window.devicePixelRatio
const pos = this.coords.clone()
pos.project(camera)
pos.x = Math.round((0.5 + pos.x / 2) * (width / dpr))
pos.y = Math.round((0.5 - pos.y / 2) * (height / dpr))
this.labelEl.style.transform = `translate(${pos.x}px, ${pos.y}px)`
const zIndex = `${10000000 - Math.round(pos.z * 10000000)}`
this.labelEl.style.zIndex = zIndex
}
} }
export function renderStars(maxRadius: number) { export function renderStars(maxRadius: number) {