initial
This commit is contained in:
commit
fd2d98e76d
13 changed files with 1684 additions and 0 deletions
23
.eslintrc.cjs
Normal file
23
.eslintrc.cjs
Normal 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
24
.gitignore
vendored
Normal 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
5
.prettierrc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100
|
||||
}
|
31
index.html
Normal file
31
index.html
Normal 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
28
package.json
Normal 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"
|
||||
}
|
||||
}
|
153
src/Interaction with Points.html
Normal file
153
src/Interaction with Points.html
Normal 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
73
src/main.ts
Normal 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
40
src/plane.ts
Normal 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
69
src/stars.ts
Normal 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
42
src/testdata.json
Normal 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
1
src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference types="vite/client" />
|
19
tsconfig.json
Normal file
19
tsconfig.json
Normal 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"]
|
||||
}
|
Loading…
Add table
Reference in a new issue