This commit is contained in:
Norman Köhring 2023-01-22 19:17:14 +01:00
commit fd2d98e76d
13 changed files with 1684 additions and 0 deletions

23
.eslintrc.cjs Normal file
View file

@ -0,0 +1,23 @@
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution");
module.exports = {
root: true,
env: {
browser: true,
node: true,
},
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
rules: {
// see https://eslint.org/docs/latest/rules/no-prototype-builtins#when-not-to-use-it
"no-prototype-builtins": "off",
// as long as it is explicit, it is fine to use any
"@typescript-eslint/no-explicit-any": "off",
},
};

24
.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

5
.prettierrc Normal file
View file

@ -0,0 +1,5 @@
{
"semi": false,
"singleQuote": true,
"printWidth": 100
}

31
index.html Normal file
View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Solar Neighbourhood</title>
<style>
body {
margin: 0;
display: flex;
place-items: center;
min-height: 100vh;
font: 16/1.5 sans-serif normal;
color-scheme: light dark;
color: #EEE;
background-color: #222;
}
#info {
position: absolute;
top: 1em;
right: 2em;
text-align: right;
}
</style>
</head>
<body>
<div id="info">...</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

28
package.json Normal file
View file

@ -0,0 +1,28 @@
{
"name": "threejs-test",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.2.0",
"@types/node": "^18.11.18",
"@types/three": "^0.148.0",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
"eslint": "^8.32.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-prettier": "^4.2.1",
"prettier": "^2.8.3",
"typescript": "^4.9.4",
"vite": "^4.0.0"
},
"dependencies": {
"three": "^0.148.0"
}
}

View file

@ -0,0 +1,153 @@
<!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>

73
src/main.ts Normal file
View file

@ -0,0 +1,73 @@
import {
WebGLRenderer,
Scene,
PerspectiveCamera,
Vector2,
Raycaster,
Intersection,
Object3D,
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { planeGeometry } from './plane'
import { renderStars } from './stars'
function init() {
const w = window.innerWidth
const h = window.innerHeight
const radius = 50
const renderer = new WebGLRenderer({ antialias: true })
renderer.setSize(w, h)
const scene = new Scene()
const camera = new PerspectiveCamera(30, w / h, 0.01, 1001)
camera.position.x = 0
camera.position.y = radius
camera.position.z = radius * 2
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableZoom = true
controls.enableRotate = true
controls.enablePan = false
controls.maxDistance = radius * 2.5
controls.minDistance = 0.1
const plane = planeGeometry(radius)
const stars = renderStars(radius)
scene.add(stars)
scene.add(plane)
const pointer = new Vector2()
const raycaster = new Raycaster()
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
})
document.addEventListener('pointermove', (event) => {
pointer.x = (event.clientX / window.innerWidth) * 2 - 1
pointer.y = -(event.clientY / window.innerHeight) * 2 + 1
})
const intersections: Intersection<Object3D<Event>>[] = []
renderer.setAnimationLoop(() => {
raycaster.setFromCamera(pointer, camera)
intersections.length = 0
raycaster.intersectObject(stars, false, intersections)
for (let intersection of intersections) {
intersection
}
renderer.render(scene, camera)
})
document.body.appendChild(renderer.domElement)
}
init()

40
src/plane.ts Normal file
View file

@ -0,0 +1,40 @@
import { BufferGeometry, Group, Line, LineBasicMaterial, Shape, Vector3 } from 'three'
export function planeGeometry(radius: number, n = 5) {
const material = new LineBasicMaterial({
color: 0xa0a0a0,
})
const plane = new Group()
const xLine = new BufferGeometry().setFromPoints([
new Vector3(-radius, 0, 0),
new Vector3(radius, 0, 0),
])
const yLine = new BufferGeometry().setFromPoints([
new Vector3(0, -radius, 0),
new Vector3(0, radius, 0),
])
const zLine = new BufferGeometry().setFromPoints([
new Vector3(0, 0, -radius),
new Vector3(0, 0, radius),
])
plane.add(new Line(xLine, material))
plane.add(new Line(yLine, material))
plane.add(new Line(zLine, material))
const step = Math.round(radius / n)
for (let r = step; r <= radius; r += step) {
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)
line.rotateX(Math.PI / 2)
plane.add(line)
}
return plane
}

69
src/stars.ts Normal file
View file

@ -0,0 +1,69 @@
import {
BufferGeometry,
Float32BufferAttribute,
PointsMaterial,
Points,
Group,
Line,
LineBasicMaterial,
Scene,
Vector3,
Spherical,
} from 'three'
import data from './testdata.json'
export interface StarData {
name: string
type: 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()
private isHighlighted = false
private lineMaterial = new LineBasicMaterial({ color: 0xffffff })
private pointMaterial = new PointsMaterial({ size: 1, vertexColors: true })
private poleLine = new Line(new BufferGeometry(), this.lineMaterial)
constructor(star: StarData) {
super()
const { radius, phi, theta } = star
this.sphericalCoords.set(radius, phi, theta)
this.cartesianCoords.setFromSpherical(this.sphericalCoords)
const { x, z } = this.cartesianCoords
this.tangentialCoords.set(x, 0, z)
this.poleLine.geometry.setFromPoints([this.cartesianCoords, this.tangentialCoords])
this.add(this.poleLine)
const geometry = new BufferGeometry()
const coords = [this.cartesianCoords.x, this.cartesianCoords.y, this.cartesianCoords.z]
geometry.setAttribute('position', new Float32BufferAttribute(coords, 3))
geometry.setAttribute('color', new Float32BufferAttribute([255, 255, 255], 3))
geometry.computeBoundingSphere()
const point = new Points(geometry, this.pointMaterial)
this.add(point)
}
}
export function renderStars(maxRadius: number) {
const group = new Group()
data.forEach((star) => {
if (star.radius > maxRadius) return
group.add(new Star(star))
})
return group
}

42
src/testdata.json Normal file
View file

@ -0,0 +1,42 @@
[
{
"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
}
]

1
src/vite-env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="vite/client" />

19
tsconfig.json Normal file
View file

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ESNext", "DOM"],
"moduleResolution": "Node",
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["src"]
}

1176
yarn.lock Normal file

File diff suppressed because it is too large Load diff