Compare commits
10 commits
d2b31f895a
...
bc569f1c16
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bc569f1c16 | ||
![]() |
30d64a2942 | ||
![]() |
4549aeaf16 | ||
![]() |
38f9c76c9d | ||
![]() |
748a2839c1 | ||
![]() |
f0928994a5 | ||
![]() |
bf1b43cf4c | ||
![]() |
890c86e654 | ||
![]() |
4a671e8019 | ||
![]() |
3bac2bd865 |
13 changed files with 4112 additions and 259 deletions
129
index.html
129
index.html
|
@ -11,29 +11,136 @@
|
|||
display: flex;
|
||||
place-items: center;
|
||||
min-height: 100vh;
|
||||
font: 16/1.5 sans-serif normal;
|
||||
font: 16px monospace;
|
||||
color-scheme: light dark;
|
||||
color: #EEE;
|
||||
background-color: #222;
|
||||
}
|
||||
#info {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
text-align: right;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 22rem;
|
||||
height: 100vh;
|
||||
background: #3368;
|
||||
backdrop-filter: blur(4px);
|
||||
border-right: 2px solid #336;
|
||||
transform: translate(0, 0);
|
||||
transition: transform .2s ease-out;
|
||||
}
|
||||
#info > button {
|
||||
margin: 0 .1em;
|
||||
border: 1px solid #DDD;
|
||||
border-radius: .5em;
|
||||
#info.hidden {
|
||||
transform: translate(-22rem, 0);
|
||||
}
|
||||
#info > button.highlighted {
|
||||
border-color: yellow;
|
||||
#info > header {
|
||||
position: relative;
|
||||
height: 8rem;
|
||||
margin: 1em 0 0 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
#info > header h1 {
|
||||
font-size: 1.5rem;
|
||||
margin: 6rem 0 0 0;
|
||||
text-align: center;
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
#info > header > .title-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -50%;
|
||||
width: 44rem;
|
||||
height: 44rem;
|
||||
border-radius: 100%;
|
||||
}
|
||||
#info > p {
|
||||
margin: 1em;
|
||||
}
|
||||
label {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin-top: -.5em;
|
||||
margin-left: .5em;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
transform: translate(0, 0);
|
||||
background: #0008;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition: opacity .2s ease;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
label.highlighted {
|
||||
color: yellow;
|
||||
z-index: 10000;
|
||||
}
|
||||
label.dimmed {
|
||||
opacity: .3;
|
||||
z-index: 0;
|
||||
}
|
||||
.spectral-class-o {
|
||||
background: rgb(222,221,237);
|
||||
background: radial-gradient(circle, #dedded 50%, #3774c1 90%);
|
||||
}
|
||||
.spectral-class-b {
|
||||
background: rgb(222,221,237);
|
||||
background: radial-gradient(circle, #dedded 50%, #91b3df 90%);
|
||||
}
|
||||
.spectral-class-a {
|
||||
background: rgb(222,221,237);
|
||||
background: radial-gradient(circle, #dedded 50%, #c7d2e6 90%);
|
||||
}
|
||||
.spectral-class-f {
|
||||
background: rgb(222,221,237);
|
||||
background: radial-gradient(circle, #dedded 50%, #edecf4 90%);
|
||||
}
|
||||
.spectral-class-g, .spectral-class-d {
|
||||
background: rgb(222,221,237);
|
||||
background: radial-gradient(circle, #dedded 40%, #fbebaf 90%);
|
||||
}
|
||||
.spectral-class-k {
|
||||
background: rgb(222,221,237);
|
||||
background: radial-gradient(circle, #dedded 40%, #f9cf97 90%);
|
||||
}
|
||||
.spectral-class-m {
|
||||
background: rgb(222,221,237);
|
||||
background: radial-gradient(circle, #dedded 40%, #f6936b 90%);
|
||||
}
|
||||
.spectral-class-l, .spectral-class-t, .spectral-class-y {
|
||||
background: rgb(222,221,237);
|
||||
background: radial-gradient(circle, #f6936b 40%, #532d1e 90%);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="info"></div>
|
||||
<div id="info">
|
||||
<header>
|
||||
<div class="title-bg spectral-class-{{ spectral-class }}">
|
||||
<h1>{{ name }}</h1>
|
||||
</div>
|
||||
</header>
|
||||
<p>
|
||||
<span>Star Type:<span>
|
||||
<strong>{{ type }}</strong>
|
||||
</p>
|
||||
<p>
|
||||
<span>Spectral Type:<span>
|
||||
<strong>{{ spectral }}</strong>
|
||||
</p>
|
||||
<p>
|
||||
<span>Additional Types:<span>
|
||||
<ul>
|
||||
{{ all-types }}
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
<span>Distance:<span>
|
||||
<strong>{{ distance }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div id="labels"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
11
input.example.csv
Normal file
11
input.example.csv
Normal file
|
@ -0,0 +1,11 @@
|
|||
1 ;* 61 Cyg B;Eruptive Variable ;Rotating Variable,Eruptive Variable,High Proper Motion Star,Variable Star,High Proper Motion Star,Eruptive Variable,Double or Multiple Star,Star,Star,Infra-Red Source,X-ray Source ;082.3171310715589 -05.8262372280102;K7V ;3.4964
|
||||
2 ;* 61 Cyg A;BY Dra Variable ;Rotating Variable,BY Dra Variable,High Proper Motion Star,Variable Star,High Proper Motion Star,Eruptive Variable,Double or Multiple Star,Star,Star,Infra-Red Source,UV-emission Source,X-ray Source,X-ray Source ;082.3197403091576 -05.8181041502094;K5V ;3.4965
|
||||
3 ;GAT 1383 ;High Proper Motion Star;High Proper Motion Star,High Proper Motion Star,High Proper Motion Star,Star,Infra-Red Source ;085.6450087237446 -07.3769487234860;M5.5V ;14.5094
|
||||
4 ;BD+40 4631;High Proper Motion Star;High Proper Motion Star,High Proper Motion Star,Star,Star,Infra-Red Source ;089.4905214280362 -08.8259946066449;K8V ;19.837
|
||||
5 ;V* EV Lac ;Eruptive Variable ;High Proper Motion Star,High Proper Motion Star,Eruptive Variable,High Proper Motion Star,Double or Multiple Star,Variable Star,High Proper Motion Star,Star,Star,Infra-Red Source,Radio Source,UV-emission Source,X-ray Source,X-ray Source,X-ray Source;100.6067176270883 -13.0693645782006;M4.0Ve;5.0515
|
||||
6 ;G 216-43 ;High Proper Motion Star;High Proper Motion Star,High Proper Motion Star,Double or Multiple Star,Star,Infra-Red Source ;107.6831520259098 -14.9929080409418;M4.5 ;35.6304
|
||||
7 ;BD+45 4378;Spectroscopic Binary ;High Proper Motion Star,High Proper Motion Star,High Proper Motion Star,Spectroscopic Binary,Variable Star,High Proper Motion Star,Spectroscopic Binary,Double or Multiple Star,Star,Infra-Red Source ;113.6133003656408 -15.1866691700906;K7V ;17.1213
|
||||
8 ;LP 149-14 ;High Proper Motion Star;High Proper Motion Star,High Proper Motion Star,High Proper Motion Star,Star,Infra-Red Source ;114.0239531748840 -14.2146886796646;M5.0V ;19.189
|
||||
9 ;HD 38B ;High Proper Motion Star;High Proper Motion Star,Double or Multiple Star,High Proper Motion Star,Star,Infra-Red Source ;114.6501340218375 -16.3243260935338;M0.5V ;11.518
|
||||
10;HD 38A ;Spectroscopic Binary ;High Proper Motion Star,Double or Multiple Star,Spectroscopic Binary,High Proper Motion Star,Star,Infra-Red Source ;114.6505214717521 -16.3226459980874;K6V ;11.5207
|
||||
11;BD+44 4548;High Proper Motion Star;High Proper Motion Star,High Proper Motion Star,High Proper Motion Star,High Proper Motion Star,Double or Multiple Star,Star,Infra-Red Source,UV-emission Source,UV-emission Source ;114.5559641502924 -16.3315501979801;M2Ve ;11.5035
|
|
|
@ -15,6 +15,7 @@
|
|||
"@types/three": "^0.148.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.2",
|
||||
"@typescript-eslint/parser": "^5.48.2",
|
||||
"csv-parse": "^5.3.3",
|
||||
"eslint": "^8.32.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
|
|
41
simbad4csv2json.js
Normal file
41
simbad4csv2json.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { parse } from 'csv-parse'
|
||||
import { readFile, writeFile } from 'fs/promises'
|
||||
;(async () => {
|
||||
const content = await readFile('input.csv')
|
||||
const records = []
|
||||
const columns = ['id', 'name', 'type', 'allTypes', 'coords', 'spectral', 'distance']
|
||||
const parser = parse(content, {
|
||||
bom: true,
|
||||
delimiter: ';',
|
||||
trim: true,
|
||||
columns,
|
||||
})
|
||||
|
||||
parser.on('readable', () => {
|
||||
let record
|
||||
while ((record = parser.read()) !== null) {
|
||||
// objects without spectral class are probably not stars
|
||||
if (record.spectral === '~') continue
|
||||
|
||||
const [phi, theta] = record.coords.split(' ').map((n) => parseFloat(n))
|
||||
// lots of duplicates in the allTypes field...
|
||||
const allTypes = [...new Set(record.allTypes.split(','))]
|
||||
|
||||
records.push({
|
||||
id: parseInt(record.id),
|
||||
name: record.name,
|
||||
type: record.type,
|
||||
spectral: record.spectral,
|
||||
radius: parseFloat(record.distance),
|
||||
phi,
|
||||
theta,
|
||||
allTypes,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
parser.on('end', async () => {
|
||||
await writeFile('output.json', JSON.stringify(records))
|
||||
console.log('wrote', records.length, 'records to output.json')
|
||||
})
|
||||
})()
|
|
@ -1,153 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- https://discourse.threejs.org/t/line-segment-coordinates/4358/3 -->
|
||||
<!-- http://jsfiddle.net/prisoner849/go0dfwo5/ -->
|
||||
<head>
|
||||
<title> Interaction with Points </title>
|
||||
<meta charset="utf-8" />
|
||||
<style>
|
||||
body {
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
<script src="../js/three.min.97.js"></script>
|
||||
<script src="../js/OrbitControls.js"></script>
|
||||
|
||||
<script> // @author prisoner849
|
||||
Object.assign(THREE.PlaneBufferGeometry.prototype, {
|
||||
toGrid: function() {
|
||||
let segmentsX = this.parameters.widthSegments || 1;
|
||||
let segmentsY = this.parameters.heightSegments || 1;
|
||||
let indices = [];
|
||||
for (let i = 0; i < segmentsY + 1; i++) {
|
||||
let index11 = 0;
|
||||
let index12 = 0;
|
||||
for (let j = 0; j < segmentsX; j++) {
|
||||
index11 = (segmentsX + 1) * i + j;
|
||||
index12 = index11 + 1;
|
||||
let index21 = index11;
|
||||
let index22 = index11 + (segmentsX + 1);
|
||||
indices.push(index11, index12);
|
||||
if (index22 < ((segmentsX + 1) * (segmentsY + 1) - 1)) {
|
||||
indices.push(index21, index22);
|
||||
}
|
||||
}
|
||||
if ((index12 + segmentsX + 1) <= ((segmentsX + 1) * (segmentsY + 1) - 1)) {
|
||||
indices.push(index12, index12 + segmentsX + 1);
|
||||
}
|
||||
}
|
||||
this.setIndex(indices);
|
||||
return this;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
//@author prisoner849
|
||||
|
||||
var scene = new THREE.Scene();
|
||||
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
|
||||
camera.position.set(1.25, 7, 7);
|
||||
camera.lookAt(scene.position);
|
||||
var renderer = new THREE.WebGLRenderer({antialias: true});
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
document.body.appendChild(renderer.domElement);
|
||||
|
||||
var controls = new THREE.OrbitControls(camera, renderer.domElement);
|
||||
|
||||
var geometry = new THREE.PlaneBufferGeometry(10, 10, 10, 10).toGrid();
|
||||
geometry.rotateX(-Math.PI * 0.5);
|
||||
|
||||
var plane = new THREE.LineSegments(geometry, new THREE.MeshBasicMaterial({
|
||||
color: "red"
|
||||
}));
|
||||
scene.add(plane);
|
||||
|
||||
var points = new THREE.Points(geometry, new THREE.PointsMaterial({
|
||||
size: 0.25,
|
||||
color: "yellow"
|
||||
}));
|
||||
scene.add(points);
|
||||
|
||||
var raycaster = new THREE.Raycaster();
|
||||
raycaster.params.Points.threshold = 0.25;
|
||||
var mouse = new THREE.Vector2();
|
||||
var intersects = null;
|
||||
var plane = new THREE.Plane();
|
||||
var planeNormal = new THREE.Vector3();
|
||||
var currentIndex = null;
|
||||
var planePoint = new THREE.Vector3();
|
||||
var dragging = false;
|
||||
|
||||
window.addEventListener("mousedown", mouseDown, false);
|
||||
window.addEventListener("mousemove", mouseMove, false);
|
||||
window.addEventListener("mouseup", mouseUp, false);
|
||||
|
||||
function mouseDown(event) {
|
||||
setRaycaster(event);
|
||||
getIndex();
|
||||
dragging = true;
|
||||
}
|
||||
|
||||
function mouseMove(event) {
|
||||
if (dragging && currentIndex !== null) {
|
||||
setRaycaster(event);
|
||||
raycaster.ray.intersectPlane(plane, planePoint);
|
||||
geometry.attributes.position.setXYZ(currentIndex, planePoint.x, planePoint.y, planePoint.z);
|
||||
geometry.attributes.position.needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
function mouseUp(event) {
|
||||
dragging = false;
|
||||
currentIndex = null;
|
||||
controlsEnabled(true);
|
||||
}
|
||||
|
||||
function getIndex() {
|
||||
intersects = raycaster.intersectObject(points);
|
||||
if (intersects.length === 0) {
|
||||
currentIndex = null;
|
||||
return;
|
||||
}
|
||||
controlsEnabled(false);
|
||||
currentIndex = intersects[0].index;
|
||||
setPlane(intersects[0].point);
|
||||
}
|
||||
|
||||
function setPlane(point) {
|
||||
planeNormal.subVectors(camera.position, point).normalize();
|
||||
plane.setFromNormalAndCoplanarPoint(planeNormal, point);
|
||||
}
|
||||
|
||||
function setRaycaster(event) {
|
||||
getMouse(event);
|
||||
raycaster.setFromCamera(mouse, camera);
|
||||
}
|
||||
|
||||
function getMouse(event) {
|
||||
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
||||
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
||||
}
|
||||
|
||||
function controlsEnabled(state){
|
||||
controls.enableZoom = state;
|
||||
controls.enableRotate = state;
|
||||
controls.enablePan = state;
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
function render() {
|
||||
requestAnimationFrame(render);
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
</script>
|
||||
</html>
|
33
src/info-display.ts
Normal file
33
src/info-display.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import type { StarData } from './stars'
|
||||
|
||||
export class InfoDisplay {
|
||||
private container = document.getElementById('info')!
|
||||
private template = this.container.innerHTML
|
||||
|
||||
constructor() {}
|
||||
|
||||
render(data: StarData) {
|
||||
const name = data.name.replace(/^NAME /, '')
|
||||
const ly = Math.round(data.radius * 3.2615637 * 100) / 100
|
||||
const distance = `${data.radius} pc / ${ly} ly`
|
||||
const allTypes = '<li>' + data.allTypes.join('</li><li>') + '</li>'
|
||||
|
||||
const html = this.template
|
||||
.replace('{{ name }}', name)
|
||||
.replace('{{ type }}', data.type)
|
||||
.replace('{{ spectral }}', data.spectral)
|
||||
.replace('{{ spectral-class }}', data.spectral[0].toLowerCase())
|
||||
.replace('{{ all-types }}', allTypes)
|
||||
.replace('{{ distance }}', distance)
|
||||
|
||||
this.container.innerHTML = html
|
||||
}
|
||||
|
||||
show() {
|
||||
this.container.classList.remove('hidden')
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.container.classList.add('hidden')
|
||||
}
|
||||
}
|
77
src/main.ts
77
src/main.ts
|
@ -9,13 +9,15 @@ import {
|
|||
} from 'three'
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
|
||||
import { planeGeometry } from './plane'
|
||||
import { renderStars } from './stars'
|
||||
import { renderStars, Star } from './stars'
|
||||
import { InfoDisplay } from './info-display'
|
||||
|
||||
function init() {
|
||||
async function init() {
|
||||
const w = window.innerWidth
|
||||
const h = window.innerHeight
|
||||
|
||||
const radius = 50
|
||||
const radius = 5
|
||||
const infoDisplay = new InfoDisplay()
|
||||
infoDisplay.hide()
|
||||
|
||||
const renderer = new WebGLRenderer({ antialias: true })
|
||||
renderer.setSize(w, h)
|
||||
|
@ -24,23 +26,25 @@ function init() {
|
|||
const camera = new PerspectiveCamera(30, w / h, 0.01, 1001)
|
||||
camera.position.x = 0
|
||||
camera.position.y = radius
|
||||
camera.position.z = radius * 2
|
||||
camera.position.z = radius * 5
|
||||
|
||||
const controls = new OrbitControls(camera, renderer.domElement)
|
||||
controls.enableZoom = true
|
||||
controls.enableRotate = true
|
||||
controls.enablePan = false
|
||||
controls.maxDistance = radius * 2.5
|
||||
controls.enablePan = true
|
||||
controls.maxDistance = radius * 5
|
||||
controls.minDistance = 0.1
|
||||
controls.listenToKeyEvents(window)
|
||||
|
||||
const plane = planeGeometry(radius)
|
||||
const stars = renderStars(radius)
|
||||
const stars = await renderStars(radius)
|
||||
|
||||
scene.add(stars)
|
||||
scene.add(plane)
|
||||
|
||||
const pointer = new Vector2()
|
||||
const raycaster = new Raycaster()
|
||||
const intersections: Intersection<Object3D<Event>>[] = []
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
camera.aspect = window.innerWidth / window.innerHeight
|
||||
|
@ -52,18 +56,67 @@ function init() {
|
|||
pointer.x = (event.clientX / window.innerWidth) * 2 - 1
|
||||
pointer.y = -(event.clientY / window.innerHeight) * 2 + 1
|
||||
})
|
||||
document.addEventListener('click', () => {
|
||||
for (let star of stars.children as Star[]) {
|
||||
star.highlighted = false
|
||||
}
|
||||
|
||||
const intersections: Intersection<Object3D<Event>>[] = []
|
||||
let closest: Intersection<Object3D<Event>> | null = null
|
||||
|
||||
for (let i of intersections) {
|
||||
if (i.distanceToRay === undefined) continue // ignore Lines
|
||||
if (closest === null || i.distanceToRay < (closest.distanceToRay ?? 0)) {
|
||||
closest = i
|
||||
}
|
||||
}
|
||||
|
||||
if (closest === null) {
|
||||
infoDisplay.hide()
|
||||
return
|
||||
}
|
||||
|
||||
const star = closest.object.parent as Star
|
||||
if (star.isStar) {
|
||||
star.highlighted = true
|
||||
infoDisplay.render(star.starData)
|
||||
infoDisplay.show()
|
||||
}
|
||||
})
|
||||
|
||||
for (let child of stars.children as Star[]) {
|
||||
child.labelEl.addEventListener('click', (event) => {
|
||||
event.stopPropagation()
|
||||
|
||||
for (let star of stars.children as Star[]) {
|
||||
star.highlighted = false
|
||||
}
|
||||
|
||||
const star = child as Star
|
||||
star.highlighted = true
|
||||
infoDisplay.render(star.starData)
|
||||
infoDisplay.show()
|
||||
})
|
||||
}
|
||||
|
||||
renderer.setAnimationLoop(() => {
|
||||
raycaster.setFromCamera(pointer, camera)
|
||||
|
||||
intersections.length = 0
|
||||
raycaster.intersectObject(stars, false, intersections)
|
||||
if (intersections.length) console.log(intersections)
|
||||
raycaster.intersectObject(stars, true, intersections)
|
||||
|
||||
renderer.render(scene, camera)
|
||||
|
||||
const distanceToPlane = camera.position.distanceTo(plane.children[0].position)
|
||||
|
||||
// updating HTML space and the stars' color
|
||||
// Attention: This has to happen after the render call, to avoid flickering
|
||||
for (let star of stars.children as Star[]) {
|
||||
star.setLabelPos(camera, w, h)
|
||||
star.dimmed = camera.position.distanceTo(star.coords) > distanceToPlane
|
||||
}
|
||||
})
|
||||
document.body.appendChild(renderer.domElement)
|
||||
|
||||
document.body.prepend(renderer.domElement)
|
||||
}
|
||||
|
||||
init()
|
||||
|
|
35
src/plane.ts
35
src/plane.ts
|
@ -1,11 +1,32 @@
|
|||
import { BufferGeometry, Group, Line, LineBasicMaterial, Shape, Vector3 } from 'three'
|
||||
import {
|
||||
BufferGeometry,
|
||||
Group,
|
||||
Line,
|
||||
LineBasicMaterial,
|
||||
MeshBasicMaterial,
|
||||
Shape,
|
||||
ShapeGeometry,
|
||||
Mesh,
|
||||
Vector3,
|
||||
DoubleSide,
|
||||
} from 'three'
|
||||
|
||||
export function planeGeometry(radius: number, n = 5) {
|
||||
const material = new LineBasicMaterial({
|
||||
color: 0xa0a0a0,
|
||||
const lineMaterial = new LineBasicMaterial({ color: 0x205020 })
|
||||
const shapeMaterial = new MeshBasicMaterial({
|
||||
color: 0x0,
|
||||
transparent: true,
|
||||
opacity: 0.8,
|
||||
side: DoubleSide,
|
||||
})
|
||||
const plane = new Group()
|
||||
|
||||
const shape = new Shape()
|
||||
shape.moveTo(0, 0).absarc(0, 0, radius, 0, Math.PI * 2, false)
|
||||
const shapeGeometry = new ShapeGeometry(shape)
|
||||
shapeGeometry.rotateX(Math.PI / 2)
|
||||
plane.add(new Mesh(shapeGeometry, shapeMaterial))
|
||||
|
||||
const xLine = new BufferGeometry().setFromPoints([
|
||||
new Vector3(-radius, 0, 0),
|
||||
new Vector3(radius, 0, 0),
|
||||
|
@ -21,9 +42,9 @@ export function planeGeometry(radius: number, n = 5) {
|
|||
new Vector3(0, 0, radius),
|
||||
])
|
||||
|
||||
plane.add(new Line(xLine, material))
|
||||
plane.add(new Line(yLine, material))
|
||||
plane.add(new Line(zLine, material))
|
||||
plane.add(new Line(xLine, lineMaterial))
|
||||
plane.add(new Line(yLine, lineMaterial))
|
||||
plane.add(new Line(zLine, lineMaterial))
|
||||
|
||||
const step = Math.round(radius / n)
|
||||
|
||||
|
@ -31,7 +52,7 @@ export function planeGeometry(radius: number, n = 5) {
|
|||
const shape = new Shape().moveTo(0, r).absarc(0, 0, r, 0, Math.PI * 2, false)
|
||||
shape.autoClose = true
|
||||
const geometry = new BufferGeometry().setFromPoints(shape.getPoints())
|
||||
const line = new Line(geometry, material)
|
||||
const line = new Line(geometry, lineMaterial)
|
||||
line.rotateX(Math.PI / 2)
|
||||
plane.add(line)
|
||||
}
|
||||
|
|
1
src/stars.json
Normal file
1
src/stars.json
Normal file
File diff suppressed because one or more lines are too long
150
src/stars.ts
150
src/stars.ts
|
@ -8,80 +8,162 @@ import {
|
|||
LineBasicMaterial,
|
||||
Vector3,
|
||||
Spherical,
|
||||
Camera,
|
||||
} from 'three'
|
||||
|
||||
import data from './testdata.json'
|
||||
|
||||
export interface StarData {
|
||||
id: number
|
||||
name: string
|
||||
type: string
|
||||
allTypes: string[]
|
||||
spectral: string
|
||||
radius: number
|
||||
phi: number
|
||||
theta: number
|
||||
}
|
||||
|
||||
class Star extends Group {
|
||||
private tangentialCoords = new Vector3()
|
||||
private cartesianCoords = new Vector3()
|
||||
private sphericalCoords = new Spherical()
|
||||
export class Star extends Group {
|
||||
public isStar = true
|
||||
public starData: StarData
|
||||
public labelEl = document.createElement('label')
|
||||
|
||||
public coords = new Vector3()
|
||||
|
||||
private isHighlighted = false
|
||||
private isDimmed = false
|
||||
private normalPointSize: number
|
||||
private highlightedPointSize: number
|
||||
|
||||
private lineMaterial = new LineBasicMaterial({ color: 0xffffff })
|
||||
private pointMaterial = new PointsMaterial({ size: 1, vertexColors: true })
|
||||
private pointGeometry = new BufferGeometry()
|
||||
|
||||
private whiteColor = new Float32BufferAttribute([255, 255, 255], 3)
|
||||
private grayColor = new Float32BufferAttribute([51, 51, 51], 3)
|
||||
private yellowColor = new Float32BufferAttribute([255, 255, 0], 3)
|
||||
|
||||
private poleLine = new Line(new BufferGeometry(), this.lineMaterial)
|
||||
private point: Points<BufferGeometry, PointsMaterial>
|
||||
private pole: Line<BufferGeometry, LineBasicMaterial>
|
||||
|
||||
constructor(star: StarData) {
|
||||
constructor(starData: StarData, maxRadius: number) {
|
||||
super()
|
||||
|
||||
const { radius, phi, theta } = star
|
||||
this.sphericalCoords.set(radius, phi, theta)
|
||||
this.cartesianCoords.setFromSpherical(this.sphericalCoords)
|
||||
this.starData = starData
|
||||
this.normalPointSize = maxRadius / 25
|
||||
this.highlightedPointSize = this.normalPointSize * 1.5
|
||||
this.pointMaterial.setValues({ size: this.normalPointSize })
|
||||
|
||||
const { x, z } = this.cartesianCoords
|
||||
this.tangentialCoords.set(x, 0, z)
|
||||
const { radius, phi, theta } = starData
|
||||
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
|
||||
this.pole = new Line(new BufferGeometry(), this.lineMaterial)
|
||||
this.pole.geometry.setFromPoints([this.coords, tangentialCoords])
|
||||
this.add(this.pole)
|
||||
|
||||
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('color', this.whiteColor)
|
||||
this.pointGeometry.computeBoundingSphere()
|
||||
|
||||
const point = new Points(this.pointGeometry, this.pointMaterial)
|
||||
this.add(point)
|
||||
this.point = new Points(this.pointGeometry, this.pointMaterial)
|
||||
this.add(this.point)
|
||||
|
||||
// label
|
||||
const container = document.getElementById('labels')!
|
||||
// stars with a proper name start with NAME, so lets strip that away
|
||||
this.labelEl.innerText = starData.name.replace(/^NAME /, '')
|
||||
container.appendChild(this.labelEl)
|
||||
}
|
||||
|
||||
setHighlight(isHighlight = true) {
|
||||
this.pointGeometry.setAttribute('color', isHighlight ? this.yellowColor : this.whiteColor)
|
||||
private setAttributes() {
|
||||
if (this.isDimmed && !this.isHighlighted) {
|
||||
this.pointMaterial.setValues({ size: this.normalPointSize })
|
||||
this.pointGeometry.setAttribute('color', this.grayColor)
|
||||
this.lineMaterial.setValues({ color: 0x333333 })
|
||||
this.labelEl.classList.remove('highlighted')
|
||||
this.labelEl.classList.add('dimmed')
|
||||
this.labelEl.style.zIndex = '0' // dimmed always in the back
|
||||
} else if (this.isHighlighted) {
|
||||
this.pointGeometry.setAttribute('color', this.yellowColor)
|
||||
this.pointMaterial.setValues({ size: this.highlightedPointSize })
|
||||
this.lineMaterial.setValues({ color: 0xffff00 })
|
||||
this.labelEl.classList.remove('dimmed')
|
||||
this.labelEl.classList.add('highlighted')
|
||||
this.labelEl.style.zIndex = '10000' // highlights always on top
|
||||
} else {
|
||||
this.pointGeometry.setAttribute('color', this.whiteColor)
|
||||
this.pointMaterial.setValues({ size: this.normalPointSize })
|
||||
this.lineMaterial.setValues({ color: 0xffffff })
|
||||
this.labelEl.classList.remove('highlighted')
|
||||
this.labelEl.classList.remove('dimmed')
|
||||
}
|
||||
}
|
||||
|
||||
public get highlighted() {
|
||||
return this.isHighlighted
|
||||
}
|
||||
|
||||
public set highlighted(isHighlighted) {
|
||||
this.isHighlighted = isHighlighted
|
||||
this.setAttributes()
|
||||
}
|
||||
|
||||
public get dimmed() {
|
||||
return this.isDimmed
|
||||
}
|
||||
|
||||
public set dimmed(isDimmed) {
|
||||
this.isDimmed = isDimmed
|
||||
this.setAttributes()
|
||||
}
|
||||
|
||||
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)`
|
||||
|
||||
if (!this.isDimmed && !this.isHighlighted) {
|
||||
const zIndex = `${10000000 - Math.round(pos.z * 10000000)}` // ridiculous
|
||||
this.labelEl.style.zIndex = zIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function renderStars(maxRadius: number) {
|
||||
export async function renderStars(maxRadius: number) {
|
||||
const group = new Group()
|
||||
const infoEl = document.getElementById('info')!
|
||||
const data: StarData[] = (await import('./stars.json')).default
|
||||
|
||||
const sol = new Star(
|
||||
{
|
||||
id: 0,
|
||||
name: 'Sol',
|
||||
type: 'White Dwarf',
|
||||
allTypes: ['White Dwarf', 'Star'],
|
||||
spectral: 'G2V',
|
||||
radius: 0.0,
|
||||
phi: 0.0,
|
||||
theta: 0.0,
|
||||
},
|
||||
maxRadius
|
||||
)
|
||||
|
||||
group.add(sol) // lets not forget our beloved sun
|
||||
|
||||
data.forEach((starData) => {
|
||||
if (starData.radius > maxRadius) return
|
||||
const star = new Star(starData)
|
||||
const star = new Star(starData, maxRadius)
|
||||
group.add(star)
|
||||
|
||||
let highlighted = false
|
||||
|
||||
const btnEl = document.createElement('button')
|
||||
btnEl.addEventListener('click', () => {
|
||||
highlighted = !highlighted
|
||||
star.setHighlight(highlighted)
|
||||
btnEl.classList.toggle('highlighted', highlighted)
|
||||
})
|
||||
btnEl.innerText = starData.name
|
||||
infoEl.appendChild(btnEl)
|
||||
})
|
||||
|
||||
return group
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
[
|
||||
{
|
||||
"name": "* 61 Cyg B",
|
||||
"type": "Eruptive Variable",
|
||||
"theta": 82.3171310715589,
|
||||
"phi": -5.8262372280102,
|
||||
"spectral": "K7V",
|
||||
"radius": 3.4964
|
||||
},
|
||||
{
|
||||
"name": "* 61 Cyg A",
|
||||
"type": "BY Dra Variable",
|
||||
"theta": 82.3197403091576,
|
||||
"phi": -5.8181041502094,
|
||||
"spectral": "K5V",
|
||||
"radius": 3.4965
|
||||
},
|
||||
{
|
||||
"name": "GAT 1383",
|
||||
"type": "High Proper Motion Star",
|
||||
"theta": 85.6450087237446,
|
||||
"phi": -7.3769487234860,
|
||||
"spectral": "M5.5V",
|
||||
"radius": 14.5094
|
||||
},
|
||||
{
|
||||
"name": "BD+40 4631",
|
||||
"type": "High Proper Motion Star",
|
||||
"theta": 89.4905214280362,
|
||||
"phi": -8.8259946066449,
|
||||
"spectral": "K8V",
|
||||
"radius": 19.837
|
||||
},
|
||||
{
|
||||
"name": "V* EV Lac",
|
||||
"type": "Eruptive Variable",
|
||||
"theta": 100.6067176270883,
|
||||
"phi": -13.0693645782006,
|
||||
"spectral": "M4.0Ve",
|
||||
"radius": 5.0515
|
||||
}
|
||||
]
|
|
@ -383,6 +383,11 @@ cross-spawn@^7.0.2:
|
|||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
csv-parse@^5.3.3:
|
||||
version "5.3.3"
|
||||
resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-5.3.3.tgz#3b75d2279e2edb550cbc54c65b25cbbf3d0033ad"
|
||||
integrity sha512-kEWkAPleNEdhFNkHQpFHu9RYPogsFj3dx6bCxL847fsiLgidzWg0z/O0B1kVWMJUc5ky64zGp18LX2T3DQrOfw==
|
||||
|
||||
debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
|
|
Loading…
Add table
Reference in a new issue