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;
|
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;
|
||||||
}
|
}
|
||||||
#info {
|
#info {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1em;
|
top: 0;
|
||||||
right: 1em;
|
left: 0;
|
||||||
text-align: right;
|
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 {
|
#info.hidden {
|
||||||
margin: 0 .1em;
|
transform: translate(-22rem, 0);
|
||||||
border: 1px solid #DDD;
|
|
||||||
border-radius: .5em;
|
|
||||||
}
|
}
|
||||||
#info > button.highlighted {
|
#info > header {
|
||||||
border-color: yellow;
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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",
|
"@types/three": "^0.148.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.48.2",
|
"@typescript-eslint/eslint-plugin": "^5.48.2",
|
||||||
"@typescript-eslint/parser": "^5.48.2",
|
"@typescript-eslint/parser": "^5.48.2",
|
||||||
|
"csv-parse": "^5.3.3",
|
||||||
"eslint": "^8.32.0",
|
"eslint": "^8.32.0",
|
||||||
"eslint-config-prettier": "^8.6.0",
|
"eslint-config-prettier": "^8.6.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"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'
|
} from 'three'
|
||||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
|
||||||
import { planeGeometry } from './plane'
|
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 w = window.innerWidth
|
||||||
const h = window.innerHeight
|
const h = window.innerHeight
|
||||||
|
const radius = 5
|
||||||
const radius = 50
|
const infoDisplay = new InfoDisplay()
|
||||||
|
infoDisplay.hide()
|
||||||
|
|
||||||
const renderer = new WebGLRenderer({ antialias: true })
|
const renderer = new WebGLRenderer({ antialias: true })
|
||||||
renderer.setSize(w, h)
|
renderer.setSize(w, h)
|
||||||
|
@ -24,23 +26,25 @@ function init() {
|
||||||
const camera = new PerspectiveCamera(30, w / h, 0.01, 1001)
|
const camera = new PerspectiveCamera(30, w / h, 0.01, 1001)
|
||||||
camera.position.x = 0
|
camera.position.x = 0
|
||||||
camera.position.y = radius
|
camera.position.y = radius
|
||||||
camera.position.z = radius * 2
|
camera.position.z = radius * 5
|
||||||
|
|
||||||
const controls = new OrbitControls(camera, renderer.domElement)
|
const controls = new OrbitControls(camera, renderer.domElement)
|
||||||
controls.enableZoom = true
|
controls.enableZoom = true
|
||||||
controls.enableRotate = true
|
controls.enableRotate = true
|
||||||
controls.enablePan = false
|
controls.enablePan = true
|
||||||
controls.maxDistance = radius * 2.5
|
controls.maxDistance = radius * 5
|
||||||
controls.minDistance = 0.1
|
controls.minDistance = 0.1
|
||||||
|
controls.listenToKeyEvents(window)
|
||||||
|
|
||||||
const plane = planeGeometry(radius)
|
const plane = planeGeometry(radius)
|
||||||
const stars = renderStars(radius)
|
const stars = await renderStars(radius)
|
||||||
|
|
||||||
scene.add(stars)
|
scene.add(stars)
|
||||||
scene.add(plane)
|
scene.add(plane)
|
||||||
|
|
||||||
const pointer = new Vector2()
|
const pointer = new Vector2()
|
||||||
const raycaster = new Raycaster()
|
const raycaster = new Raycaster()
|
||||||
|
const intersections: Intersection<Object3D<Event>>[] = []
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
camera.aspect = window.innerWidth / window.innerHeight
|
camera.aspect = window.innerWidth / window.innerHeight
|
||||||
|
@ -52,18 +56,67 @@ function init() {
|
||||||
pointer.x = (event.clientX / window.innerWidth) * 2 - 1
|
pointer.x = (event.clientX / window.innerWidth) * 2 - 1
|
||||||
pointer.y = -(event.clientY / window.innerHeight) * 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(() => {
|
renderer.setAnimationLoop(() => {
|
||||||
raycaster.setFromCamera(pointer, camera)
|
raycaster.setFromCamera(pointer, camera)
|
||||||
|
|
||||||
intersections.length = 0
|
intersections.length = 0
|
||||||
raycaster.intersectObject(stars, false, intersections)
|
raycaster.intersectObject(stars, true, intersections)
|
||||||
if (intersections.length) console.log(intersections)
|
|
||||||
renderer.render(scene, camera)
|
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()
|
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) {
|
export function planeGeometry(radius: number, n = 5) {
|
||||||
const material = new LineBasicMaterial({
|
const lineMaterial = new LineBasicMaterial({ color: 0x205020 })
|
||||||
color: 0xa0a0a0,
|
const shapeMaterial = new MeshBasicMaterial({
|
||||||
|
color: 0x0,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.8,
|
||||||
|
side: DoubleSide,
|
||||||
})
|
})
|
||||||
const plane = new Group()
|
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([
|
const xLine = new BufferGeometry().setFromPoints([
|
||||||
new Vector3(-radius, 0, 0),
|
new Vector3(-radius, 0, 0),
|
||||||
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),
|
new Vector3(0, 0, radius),
|
||||||
])
|
])
|
||||||
|
|
||||||
plane.add(new Line(xLine, material))
|
plane.add(new Line(xLine, lineMaterial))
|
||||||
plane.add(new Line(yLine, material))
|
plane.add(new Line(yLine, lineMaterial))
|
||||||
plane.add(new Line(zLine, material))
|
plane.add(new Line(zLine, lineMaterial))
|
||||||
|
|
||||||
const step = Math.round(radius / n)
|
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)
|
const shape = new Shape().moveTo(0, r).absarc(0, 0, r, 0, Math.PI * 2, false)
|
||||||
shape.autoClose = true
|
shape.autoClose = true
|
||||||
const geometry = new BufferGeometry().setFromPoints(shape.getPoints())
|
const geometry = new BufferGeometry().setFromPoints(shape.getPoints())
|
||||||
const line = new Line(geometry, material)
|
const line = new Line(geometry, lineMaterial)
|
||||||
line.rotateX(Math.PI / 2)
|
line.rotateX(Math.PI / 2)
|
||||||
plane.add(line)
|
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,
|
LineBasicMaterial,
|
||||||
Vector3,
|
Vector3,
|
||||||
Spherical,
|
Spherical,
|
||||||
|
Camera,
|
||||||
} from 'three'
|
} from 'three'
|
||||||
|
|
||||||
import data from './testdata.json'
|
|
||||||
|
|
||||||
export interface StarData {
|
export interface StarData {
|
||||||
|
id: number
|
||||||
name: string
|
name: string
|
||||||
type: string
|
type: string
|
||||||
|
allTypes: string[]
|
||||||
spectral: string
|
spectral: string
|
||||||
radius: number
|
radius: number
|
||||||
phi: number
|
phi: number
|
||||||
theta: number
|
theta: number
|
||||||
}
|
}
|
||||||
|
|
||||||
class Star extends Group {
|
export class Star extends Group {
|
||||||
private tangentialCoords = new Vector3()
|
public isStar = true
|
||||||
private cartesianCoords = new Vector3()
|
public starData: StarData
|
||||||
private sphericalCoords = new Spherical()
|
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 lineMaterial = new LineBasicMaterial({ color: 0xffffff })
|
||||||
private pointMaterial = new PointsMaterial({ size: 1, vertexColors: true })
|
private pointMaterial = new PointsMaterial({ size: 1, vertexColors: true })
|
||||||
private pointGeometry = new BufferGeometry()
|
private pointGeometry = new BufferGeometry()
|
||||||
|
|
||||||
private whiteColor = new Float32BufferAttribute([255, 255, 255], 3)
|
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 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()
|
super()
|
||||||
|
|
||||||
const { radius, phi, theta } = star
|
this.starData = starData
|
||||||
this.sphericalCoords.set(radius, phi, theta)
|
this.normalPointSize = maxRadius / 25
|
||||||
this.cartesianCoords.setFromSpherical(this.sphericalCoords)
|
this.highlightedPointSize = this.normalPointSize * 1.5
|
||||||
|
this.pointMaterial.setValues({ size: this.normalPointSize })
|
||||||
|
|
||||||
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
|
||||||
|
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('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')!
|
||||||
|
// 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) {
|
private setAttributes() {
|
||||||
this.pointGeometry.setAttribute('color', isHighlight ? this.yellowColor : this.whiteColor)
|
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')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderStars(maxRadius: number) {
|
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 async function renderStars(maxRadius: number) {
|
||||||
const group = new Group()
|
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) => {
|
data.forEach((starData) => {
|
||||||
if (starData.radius > maxRadius) return
|
if (starData.radius > maxRadius) return
|
||||||
const star = new Star(starData)
|
const star = new Star(starData, maxRadius)
|
||||||
group.add(star)
|
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
|
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"
|
shebang-command "^2.0.0"
|
||||||
which "^2.0.1"
|
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:
|
debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
||||||
version "4.3.4"
|
version "4.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||||
|
|
Loading…
Add table
Reference in a new issue