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