diff --git a/.storybook/manager.ts b/.storybook/manager.ts
new file mode 100644
index 0000000..f6f2fc3
--- /dev/null
+++ b/.storybook/manager.ts
@@ -0,0 +1,6 @@
+import { addons } from '@storybook/manager-api';
+import customTheme from './theme';
+
+addons.setConfig({
+ theme: customTheme,
+});
\ No newline at end of file
diff --git a/.storybook/preview.ts b/.storybook/preview.ts
index 3bf6ee8..350fc88 100644
--- a/.storybook/preview.ts
+++ b/.storybook/preview.ts
@@ -9,7 +9,7 @@ const preview: Preview = {
},
}, options: {
storySort: {
- order: ['nbody', 'Installation', 'Quick Start', 'Integration', 'Contribute', 'Define', ['Intro', 'Force', 'Simulate Function', 'Transformation'], 'Visualize', ['Intro', 'Dimension', 'Multiverse', 'Record', 'Controller', 'Trails', 'Debug Info'], 'Showcase', ['Intro', 'Analemma', ['Intro', 'Sun-Earth', 'Sun-Mars'],'HorseshoeOrbit', ['Intro', '54509 YORP'], 'SolarSystem']],
+ order: ['nbody', 'Installation', 'Quick Start', 'Integration', 'Contribute', 'Define', ['Intro', 'Force', 'Simulate Function', 'Transformation'], 'Visualize', ['Intro', 'Dimension', 'Multiverse', 'Record', 'Controller', 'Trails', 'Debug Info'], 'Showcase', ['Intro', 'Analemma', ['Intro', 'Sun-Earth', 'Sun-Mars'],'Horseshoe Orbit', ['Intro', '2010 SO16', '54509 YORP'], 'Solar System', ['Simulate', 'Recorded']]],
},
}
},
diff --git a/.storybook/stories/Contribute.mdx b/.storybook/stories/Contribute.mdx
index a00ac21..214188a 100644
--- a/.storybook/stories/Contribute.mdx
+++ b/.storybook/stories/Contribute.mdx
@@ -83,3 +83,5 @@ Development setup of the project
Contributors are free to pick up any of the following tasks or suggest new features.
- Setup a bundled version of the project for CDN deployment
+- `enhancement` Provide more predefined simulate functions
+- `enhancement` Allow users to use custom shapes/textures for bodies
\ No newline at end of file
diff --git a/.storybook/stories/Define/Force.mdx b/.storybook/stories/Define/Force.mdx
index 3112369..7e8f230 100644
--- a/.storybook/stories/Define/Force.mdx
+++ b/.storybook/stories/Define/Force.mdx
@@ -4,7 +4,7 @@ import { Meta, Story } from '@storybook/blocks';
# Force
-A force object encapsulates logic for calculating forces acting on celestial bodies due to other objects or environment. It has a getForces method that takes in an array of celestial bodies and returns an array of forces acting on each body. It is defined as the following Typescript interface.
+A **Force** object encapsulates logic for calculating forces acting on celestial bodies due to other objects or environment. It has a `getForces` method that takes in an array of celestial bodies and returns an array of forces acting on each body. It is defined as the following Typescript interface.
```typescript
interface Force {
diff --git a/.storybook/stories/Define/SimulateFunction.mdx b/.storybook/stories/Define/SimulateFunction.mdx
index 98bef1b..5ed8d01 100644
--- a/.storybook/stories/Define/SimulateFunction.mdx
+++ b/.storybook/stories/Define/SimulateFunction.mdx
@@ -4,7 +4,7 @@ import { Meta, Story } from '@storybook/blocks';
# Simulate Function
-A **Simulate Function** object encapsulates logic for advancing the state of the universe over time, usually using [numerical integration](https://en.wikipedia.org/wiki/Numerical_integration). It has a simulate method that takes in a time step, current state of the universe, (optionally the previous state of the universe) and returns the next state of the universe.
+A **Simulate Function** object encapsulates logic for advancing the state of the universe over time, usually using [numerical integration](https://en.wikipedia.org/wiki/Numerical_integration). It has a `simulate` method that takes in a time step, current state of the universe, (optionally the previous state of the universe) and returns the next state of the universe.
```typescript
interface SimulateFunction {
@@ -22,7 +22,7 @@ Full API reference can be found [here](https://source-academy.github.io/nbody/ap
### Velocity Verlet
-Create a [velocity verlet](https://en.wikipedia.org/wiki/Verlet_integration#Velocity_Verlet) integrator. Uses newtonian gravity by default, or the provided force object.
+A [velocity verlet](https://en.wikipedia.org/wiki/Verlet_integration#Velocity_Verlet) integrator implementation. Uses newtonian gravity by default, or the provided force object.
```javascript
new VelocityVerletSim();
@@ -31,7 +31,7 @@ new VelocityVerletSim(customForce);
### Explicit Euler
-Create a [explicit euler](https://en.wikipedia.org/wiki/Explicit_and_implicit_methods) integrator. Uses newtonian gravity by default, or the provided force object.
+An [explicit euler](https://en.wikipedia.org/wiki/Explicit_and_implicit_methods) integrator implementation. Uses newtonian gravity by default, or the provided force object.
```javascript
new ExplicitEulerSim();
@@ -40,7 +40,7 @@ new ExplicitEulerSim(customForce);
### Semi Implicit Euler
-Create a [semi-implicit euler](https://en.wikipedia.org/wiki/Explicit_and_implicit_methods) integrator. Uses newtonian gravity by default, or the provided force object.
+A [semi-implicit euler](https://en.wikipedia.org/wiki/Explicit_and_implicit_methods) integrator implementation. Uses newtonian gravity by default, or the provided force object.
```javascript
new SemiImplicitEulerSim();
@@ -49,7 +49,7 @@ new SemiImplicitEulerSim(customForce);
### Runge-Kutta Order 4
-Create a [runge-kutta order 4](https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods) integrator. Uses newtonian gravity by default, or the provided force object. Optionally, you can provide the weight coefficients for the averaging step.
+A [runge-kutta order 4](https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods) integrator implementation. Uses newtonian gravity by default, or the provided force object. Optionally, you can provide the weight coefficients for the averaging step.
```javascript
new RungeKutta4Sim();
@@ -59,7 +59,7 @@ new RungeKutta4Sim(customForce, [1, 2, 2, 1]);
### Lambda integrator
-Create a simulate function from a lambda function.
+A simulate function based the given lambda/arrow/anonymous function.
```javascript
new LambdaSim((deltaT, currState, prevState) => {
diff --git a/.storybook/stories/Define/Transformation.mdx b/.storybook/stories/Define/Transformation.mdx
index 932d3a2..df1a64c 100644
--- a/.storybook/stories/Define/Transformation.mdx
+++ b/.storybook/stories/Define/Transformation.mdx
@@ -4,7 +4,7 @@ import { Meta, Story } from '@storybook/blocks';
# Transformation
-TODO
+A **Transformation** object can be used to modify/transform the [frame of reference](https://en.wikipedia.org/wiki/Frame_of_reference) of the nbody system. It has a `transform` method that takes in a state and returns a new state with the updated frame of reference by modifying the position, velocity and acceleration of the bodies as necessary.
```typescript
export interface Transformation {
@@ -22,15 +22,16 @@ Full API reference can be found [here](https://source-academy.github.io/nbody/ap
### Body Center Transformation
-TODO
+A frame of reference transformation that uses the position of the ith body as the origin.
```javascript
new BodyCenterTransformation();
+new BodyCenterTransformation(index);
```
### Center of Mass Transformation
-TODO
+A frame of reference transformation that uses the position of the center of mass of the system as the origin.
```javascript
new CoMTransformation();
@@ -38,7 +39,7 @@ new CoMTransformation();
### Rotate Transformation
-TODO
+A frame of reference transformation that rotates the frames around the provided axis by the provided angle.
```javascript
new RotateTransformation(new Vector3(0, 1, 0), Math.PI / 2);
@@ -46,7 +47,7 @@ new RotateTransformation(new Vector3(0, 1, 0), Math.PI / 2);
### Lambda Transformation
-TODO
+A frame of reference transformation that uses the given lambda/arrow/anonymous function to transform states.
```javascript
new LambdaTransformation((state, deltaT) => {
@@ -60,7 +61,15 @@ new LambdaTransformation((state, deltaT) => {
You can define and configure your own transformation object in javascript with a transform method as follows
```javascript
-TODO
+const customTransform = {
+ transform(state) {
+ const offset = state.bodies[0].position.clone();
+ state.bodies.forEach((b) => {
+ b.position.sub(offset);
+ });
+ return state;
+ }
+}
```
## Typescript
@@ -68,5 +77,13 @@ TODO
You can define and configure your own transformation object in typescript by implementing the Transformation interface as follows
```typescript
-TODO
+class CustomTransformation implements Transformation {
+ transform(state: State): State {
+ const offset = state.bodies[0].position.clone();
+ state.bodies.forEach((b) => {
+ b.position.sub(offset);
+ });
+ return state;
+ }
+}
```
\ No newline at end of file
diff --git a/.storybook/stories/Showcase/Analemma/Analemma.tsx b/.storybook/stories/Showcase/Analemma/Analemma.tsx
index b1414fa..8504a40 100644
--- a/.storybook/stories/Showcase/Analemma/Analemma.tsx
+++ b/.storybook/stories/Showcase/Analemma/Analemma.tsx
@@ -23,7 +23,8 @@ const SUN = new CelestialBody(
const EARTH = new CelestialBody(
"Earth",
5.97219e24,
- 6371e3,
+ // 6371e3,
+ 2e8,
// new Vector3(-2.48109932596539e10, 1.449948612736719e11, -8.215203670851886e6),
// new Vector3(-2.984146365518679e4, -5.126262286859617e3, 1.184224839788195),
// new Vector3(0, 0, 0)
@@ -36,7 +37,8 @@ const EARTH = new CelestialBody(
const MARS = new CelestialBody(
"Mars",
6.41e23,
- 3389.5e3,
+ // 3389.5e3,
+ 2.5e8,
// new Vector3(
// -4.388577457378983e10,
// -2.170849264747524e11,
@@ -88,6 +90,7 @@ export const sunEarth: AnalemmaSetup = {
366.24 * 86164.0905
),
],
+ radiusScale: 10,
});
newUniverse.currState = new RotateTransformation(
new Vector3(1, 0, 0),
@@ -126,6 +129,7 @@ export const sunMars: AnalemmaSetup = {
668.5991 * 88775.244
),
],
+ radiusScale: 20,
});
newUniverse.currState = new RotateTransformation(
new Vector3(1, 0, 0),
diff --git a/.storybook/stories/Showcase/Analemma/Intro.mdx b/.storybook/stories/Showcase/Analemma/Intro.mdx
index 067f81c..f0327b7 100644
--- a/.storybook/stories/Showcase/Analemma/Intro.mdx
+++ b/.storybook/stories/Showcase/Analemma/Intro.mdx
@@ -2,4 +2,8 @@ import { Meta, Story } from '@storybook/blocks';
-# Analemma
\ No newline at end of file
+# Analemma
+
+An analemma is a curve representing the changing position of the Sun in the sky, as viewed from a fixed location on the Earth at the same clock time over the course of an year. Analemma shapes range from figure-8s to infinity symbols, depending on the observer's latitude and the time of day. The analemma is a result of the Earth's tilt and elliptical orbit around the Sun. Analemmas can also be seen on other planets, with shapes that are different from Earth's due to different axial tilts and orbital eccentricities. The analemma on Mars, for example, is a teardrop shape. [[Wikipedia](https://en.wikipedia.org/wiki/Analemma)]
+
+Analemmas are a good example of what is possible with *nbody*, as they involve the simulation of the Earth's orbit around the Sun, along with frame of reference transformations.
\ No newline at end of file
diff --git a/.storybook/stories/Showcase/Analemma/Sun-Earth.mdx b/.storybook/stories/Showcase/Analemma/Sun-Earth.mdx
index a34b5bb..6319b7e 100644
--- a/.storybook/stories/Showcase/Analemma/Sun-Earth.mdx
+++ b/.storybook/stories/Showcase/Analemma/Sun-Earth.mdx
@@ -6,5 +6,7 @@ import * as Stories from "./Analemma.stories";
# Sun-Earth Analemma
-
+Analemma as seen from the Earth on the equator mid-day. Frame of reference transformations have been setup to replicate the axial tilt of the earth. Additionally, the frame is rotated smoothly (as opposed to every 24 hours) to smooth out the analemma curve. Use the controls below to change the axial tilt of the Earth and its effects on the shape of the analemma.
+
+
\ No newline at end of file
diff --git a/.storybook/stories/Showcase/Analemma/Sun-Mars.mdx b/.storybook/stories/Showcase/Analemma/Sun-Mars.mdx
index b46c158..65c0830 100644
--- a/.storybook/stories/Showcase/Analemma/Sun-Mars.mdx
+++ b/.storybook/stories/Showcase/Analemma/Sun-Mars.mdx
@@ -6,5 +6,7 @@ import * as Stories from "./Analemma.stories";
# Sun-Mars Analemma
-
+Analemma as seen from Mars at the equator mid-day. Frame of reference transformations have been setup to replicate the axial tilt of Mars. Additionally, the frame is rotated smoothly to smooth out the analemma curve. Use the controls below to change the axial tilt of Mars and its effects on the shape of the analemma.
+
+
\ No newline at end of file
diff --git a/.storybook/stories/Showcase/HorseshoeOrbit/2010 SO16.mdx b/.storybook/stories/Showcase/HorseshoeOrbit/2010 SO16.mdx
new file mode 100644
index 0000000..1d74765
--- /dev/null
+++ b/.storybook/stories/Showcase/HorseshoeOrbit/2010 SO16.mdx
@@ -0,0 +1,22 @@
+import { Meta, Story, Controls } from '@storybook/blocks';
+
+import * as Stories from "./HorseshoeOrbit.stories";
+
+
+
+# 2010 SO16
+
+[2010 SO16](https://en.wikipedia.org/wiki/(419624)_2010_SO16) is a sub-kilometer asteriod in a co-orbital configuration with Earth that looks like a horseshoe in a non-rotating frame of reference.
+
+
+
+The initial configurations of Sun, Earth and the asteriod have been setup, recorded at 20000000x speed for about 13.5 minutes of playback. Use the speed control below to change the playback speed. You can also try changing the frame of reference to see the horseshoe orbit from different perspectives.
+
+
+
\ No newline at end of file
diff --git a/.storybook/stories/Showcase/HorseshoeOrbit/54509 YORP.mdx b/.storybook/stories/Showcase/HorseshoeOrbit/54509 YORP.mdx
index b5841aa..d5e1ae9 100644
--- a/.storybook/stories/Showcase/HorseshoeOrbit/54509 YORP.mdx
+++ b/.storybook/stories/Showcase/HorseshoeOrbit/54509 YORP.mdx
@@ -1,13 +1,22 @@
-import { Meta, Story } from '@storybook/blocks';
+import { Meta, Story, Controls } from '@storybook/blocks';
import * as Stories from "./HorseshoeOrbit.stories";
-
+
# 54509 YORP
-54509 YORP is an Earth co-orbital asteroid that follows a horse shoe orbit
+[54509 YORP](https://en.wikipedia.org/wiki/54509_YORP) is an Earth co-orbital asteroid that follows a flattened horse shoe orbit that looks like the following in frames of refernce snapped to sun-earth and sun respectively:
+
-
\ No newline at end of file
+The initial configurations of Sun, Earth and the asteriod have been setup, recorded at 20000000x speed for about 5.5 minutes of playback. Use the speed control below to change the playback speed. You can also try changing the frame of reference to see the horseshoe orbit from different perspectives.
+
+
+
+ );
+};
diff --git a/.storybook/stories/Showcase/HorseshoeOrbit/Intro.mdx b/.storybook/stories/Showcase/HorseshoeOrbit/Intro.mdx
new file mode 100644
index 0000000..013e693
--- /dev/null
+++ b/.storybook/stories/Showcase/HorseshoeOrbit/Intro.mdx
@@ -0,0 +1,14 @@
+import { Meta, Story } from '@storybook/blocks';
+
+import * as Stories from "./HorseshoeOrbit.stories";
+
+
+
+
+# Horseshoe Orbits
+
+Horseshoe orbits are a type of co-orbital motion of a small body around a larger body. The orbital period of the smaller body is very close to that of the larger body, and the two bodies are in a 1:1 orbital resonance. The smaller body appears to move in a horseshoe shape relative to the larger body as seen from a non-rotating frame of reference. Read on to find out more about and see horse shoe orbits in action.
+
+- [2010 SO16](?path=/docs/showcase-horseshoeorbit-2010-so16--docs)
+- [54509 YORP](?path=/docs/showcase-horseshoeorbit-54509-yorp--docs)
+
diff --git a/.storybook/stories/Showcase/Intro.mdx b/.storybook/stories/Showcase/Intro.mdx
index 430e0a1..929db97 100644
--- a/.storybook/stories/Showcase/Intro.mdx
+++ b/.storybook/stories/Showcase/Intro.mdx
@@ -2,7 +2,10 @@ import { Meta, Story } from '@storybook/blocks';
+# Showcase
-## Horseshoe Orbits
-## Solar system
-## Analemma
+In this section, we showcase some of the interesting simulations that can be created using the n-body simulator, including some of the most famous celestial phenomena. Read on to learn more about each of these simulations.
+
+- [Analemma](?path=/docs/showcase-analemma-intro--docs)
+- [Horseshoe Orbits](?path=/docs/showcase-horseshoeorbit-intro--docs)
+- [Solar System](?path=/docs/showcase-solarsystem-intro--docs)
diff --git a/.storybook/stories/Showcase/SolarSystem/Recorded.mdx b/.storybook/stories/Showcase/SolarSystem/Recorded.mdx
new file mode 100644
index 0000000..dcdb6fa
--- /dev/null
+++ b/.storybook/stories/Showcase/SolarSystem/Recorded.mdx
@@ -0,0 +1,11 @@
+import { Meta, Story } from '@storybook/blocks';
+
+import * as Stories from "./SolarSystem.stories";
+
+
+
+# Recorded
+
+Recorded ~247 years of simulated time, which is the orbital period of Pluto, and played on loop.
+
+
\ No newline at end of file
diff --git a/.storybook/stories/Showcase/SolarSystem/Simulate.mdx b/.storybook/stories/Showcase/SolarSystem/Simulate.mdx
new file mode 100644
index 0000000..14bd37c
--- /dev/null
+++ b/.storybook/stories/Showcase/SolarSystem/Simulate.mdx
@@ -0,0 +1,11 @@
+import { Meta, Story } from '@storybook/blocks';
+
+import * as Stories from "./SolarSystem.stories";
+
+
+
+# Simulate
+
+The Solar system has been setup based on positions and velocities of the planets on 1st January 2024. You can simulate the system at a high speed to oberve the traced orbits. However, keep in mind that the faster you simulate, the less accurate the orbits will be (due to the numerical integration method used).
+
+
\ No newline at end of file
diff --git a/.storybook/stories/Showcase/SolarSystem/SolarSystem.mdx b/.storybook/stories/Showcase/SolarSystem/SolarSystem.mdx
deleted file mode 100644
index 2b3d5ef..0000000
--- a/.storybook/stories/Showcase/SolarSystem/SolarSystem.mdx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Meta, Story } from '@storybook/blocks';
-
-import * as Stories from "./SolarSystem.stories";
-
-
-
-##
-```js
-```
-
+ );
+};
diff --git a/.storybook/stories/Universe.ts b/.storybook/stories/Universe.ts
index 0a9e2a7..6cecdd1 100644
--- a/.storybook/stories/Universe.ts
+++ b/.storybook/stories/Universe.ts
@@ -17,7 +17,7 @@ export const fig8 = new Universe({
new CelestialBody(
"Body 1",
1,
- 1,
+ 0.1,
new Vector3(-0.97000436, 0.24308753, 0),
new Vector3(0.466203685, 0.43236573, 0),
new Vector3(0, 0, 0)
@@ -25,7 +25,7 @@ export const fig8 = new Universe({
new CelestialBody(
"Body 2",
1,
- 1,
+ 0.1,
new Vector3(0.97000436, -0.24308753, 0),
new Vector3(0.466203685, 0.43236573, 0),
new Vector3(0, 0, 0)
@@ -33,7 +33,7 @@ export const fig8 = new Universe({
new CelestialBody(
"Body 3",
1,
- 1,
+ 0.1,
new Vector3(0, 0, 0),
new Vector3(-2 * 0.466203685, -2 * 0.43236573, 0),
new Vector3(0, 0, 0)
@@ -50,7 +50,7 @@ export const multiFig8 = [
new CelestialBody(
"Body 1",
1,
- 1,
+ 0.1,
new Vector3(-0.97000436, 0.24308753, 0),
new Vector3(0.466203685, 0.43236573, 0),
new Vector3(0, 0, 0)
@@ -58,7 +58,7 @@ export const multiFig8 = [
new CelestialBody(
"Body 2",
1,
- 1,
+ 0.1,
new Vector3(0.97000436, -0.24308753, 0),
new Vector3(0.466203685, 0.43236573, 0),
new Vector3(0, 0, 0)
@@ -66,7 +66,7 @@ export const multiFig8 = [
new CelestialBody(
"Body 3",
1,
- 1,
+ 0.1,
new Vector3(0, 0, 0),
new Vector3(-2 * 0.466203685, -2 * 0.43236573, 0),
new Vector3(0, 0, 0)
diff --git a/.storybook/stories/Visualize/Controller/Controller.mdx b/.storybook/stories/Visualize/Controller/Controller.mdx
index 0cf1aff..7a16376 100644
--- a/.storybook/stories/Visualize/Controller/Controller.mdx
+++ b/.storybook/stories/Visualize/Controller/Controller.mdx
@@ -4,7 +4,20 @@ import * as ControllerStories from "./Controller.stories";
+# Controller
+
+**Simulation** objects can be controlled in three ways - through the UI, through code, or not at all. You can set the `controller` field when creating a **Simulation** object in the following way. Controls include play, pause, speed, show/hide trails and show/hide each universe. Available in both 2D and 3D views.
+
+```js
+new Simulation(universe, {
+ controller: 'none' | 'ui' | 'code',
+})
+```
+
## None (default)
+
+When set to none, simulation cannot be controlled through the UI or code.
+
```js
new Simulation(universe, {
controller: 'none'
@@ -13,6 +26,9 @@ new Simulation(universe, {
## Ui
+
+Displays UI controls to change speed, show/hide trails and show/hide each universe.
+
```js
new Simulation(universe, {
controller: 'ui'
@@ -21,9 +37,15 @@ new Simulation(universe, {
## Code
+
+Allows you to control the simulation through code via methods on the **Simulation** object. Full API reference available [here](https://source-academy.github.io/nbody/api/classes/Simulation.html).
+
```js
new Simulation(universe, {
controller: 'code'
+ showTrails: true,
})
+
+// toggle show/hide universe every 500 ms
```
\ No newline at end of file
diff --git a/.storybook/stories/Visualize/Debug Info/Debug.mdx b/.storybook/stories/Visualize/Debug Info/Debug.mdx
index 32b1d17..d6e9d7f 100644
--- a/.storybook/stories/Visualize/Debug Info/Debug.mdx
+++ b/.storybook/stories/Visualize/Debug Info/Debug.mdx
@@ -6,7 +6,9 @@ import * as DebugStories from "./Debug.stories";
# Debug Info
-Passed as the `showDebugInfo` property in the Simulation config. This will display debug information in the corner of the simulation canvas. Click on the pop-up to toggle between frames showing the frame rate, time taken for each frame and the total memory usage.
+Passed as the `showDebugInfo` property in the Simulation config, this will display debug information in the corner of the simulation canvas. Click on the pop-up to toggle between frames showing the frame rate, time taken for each frame and the total memory usage. Available in both 2D and 3D views.
+
+```js
## Off (default)
```js
diff --git a/.storybook/stories/Visualize/Debug Info/Debug.stories.tsx b/.storybook/stories/Visualize/Debug Info/Debug.stories.tsx
index a94165e..844d3b0 100644
--- a/.storybook/stories/Visualize/Debug Info/Debug.stories.tsx
+++ b/.storybook/stories/Visualize/Debug Info/Debug.stories.tsx
@@ -25,6 +25,7 @@ export const DebugInfoOn: Story = {
storyName: 'DebugInfoOn',
universe: [fig8],
showDebugInfo: true,
+ visType: '3D',
},
};
@@ -33,13 +34,6 @@ export const DebugInfoOff: Story = {
storyName: 'DebugInfoOff',
universe: [fig8],
showDebugInfo: false,
- },
-};
-
-export const Ui: Story = {
- args: {
- storyName: 'Ui',
- universe: [fig8],
- controller: 'ui'
+ visType: '3D',
},
};
\ No newline at end of file
diff --git a/.storybook/stories/Visualize/Dimension/Dimension.mdx b/.storybook/stories/Visualize/Dimension/Dimension.mdx
index 823a747..eb93634 100644
--- a/.storybook/stories/Visualize/Dimension/Dimension.mdx
+++ b/.storybook/stories/Visualize/Dimension/Dimension.mdx
@@ -6,20 +6,37 @@ import * as DimensionStories from "./Dimension.stories";
# Dimension
-TODO
+You can choose a 2 dimensional or 3 dimensional visualization system for your simulation. Simply set the `visType` field when creating a **Simulation** object in the following way.
+
+```js
+new Simulation(universe, {
+ visType: '2D' | '3D',
+})
+```
## 2D (default)
+
+The 2D visualization system uses [Plotly.js](https://plotly.com/javascript/). It provides default controls for panning, zooming, and scaling the view, along with responsive axes indicating the scale of the universe(s) being visualized. Additionally, you can hover on bodies to see their details like label and position.
+
```js
new Simulation(universe, {
visType: '2D',
})
```
+
## 3D
+
+The 3D visualization system uses [Three.js](https://threejs.org/). There is a small orientation helper at the bottom right provided for easier navigation along with coloured axes. You can control the visualization as follows:
+- Orbit around the center of the frame by holding down the left click and moving the mouse in direction of choice
+- Scroll up to zoom in and down to zoom out
+- Pan around by holding down the right click and moving the mouse in direction of choice.
+
```js
new Simulation(universe, {
visType: '3D',
})
```
+
\ No newline at end of file
diff --git a/.storybook/stories/Visualize/Intro.mdx b/.storybook/stories/Visualize/Intro.mdx
index de802be..2082898 100644
--- a/.storybook/stories/Visualize/Intro.mdx
+++ b/.storybook/stories/Visualize/Intro.mdx
@@ -4,4 +4,11 @@ import { Meta, Story } from '@storybook/blocks';
# Visualize
-This section shall explore the various setups and configurations of the visualization system.
+This section shall explore the various setups and configurations of the visualization system. Read on to find out more about how to precisely configure the visualization system to your needs.
+
+- [Dimension](?path=/docs/visualize-dimension--docs)
+- [Multiverse](?path=/docs/visualize-multiverse--docs)
+- [Record](?path=/docs/visualize-record--docs)
+- [Controller](?path=/docs/visualize-controller--docs)
+- [Trails](?path=/docs/visualize-trails--docs)
+- [Debug Info](?path=/docs/visualize-debug-info--docs)
\ No newline at end of file
diff --git a/.storybook/stories/Visualize/Multiverse/Multiverse.mdx b/.storybook/stories/Visualize/Multiverse/Multiverse.mdx
index 1f564eb..9258924 100644
--- a/.storybook/stories/Visualize/Multiverse/Multiverse.mdx
+++ b/.storybook/stories/Visualize/Multiverse/Multiverse.mdx
@@ -1,4 +1,4 @@
-import { Meta, Story } from '@storybook/blocks';
+import { Meta, Story } from "@storybook/blocks";
import * as MultiverseStories from "./Multiverse.stories";
@@ -6,22 +6,38 @@ import * as MultiverseStories from "./Multiverse.stories";
# Multiverse
-TODO
+You can setup multiple universes in a single simulation. This is useful for simulating multiple n-body systems in parallel with varying initial configurations, forces, simuation functions and/or transformations. Simply create each universe as you would for a single universe simulation and pass them as an array to the **Simulation** object in the following way. Available for both 2D and 3D visualizations, UI controls (when enabled) are provided to hide/show each universe separately (see [Controller](?path=/docs/visualize-controller--docs)).
## Single Universe - 2D
-```js
+```js
+new Simulation(universe);
+new Simulation([universe]);
```
-
diff --git a/.storybook/stories/Visualize/Multiverse/Multiverse.stories.tsx b/.storybook/stories/Visualize/Multiverse/Multiverse.stories.tsx
index eaf8801..4786b97 100644
--- a/.storybook/stories/Visualize/Multiverse/Multiverse.stories.tsx
+++ b/.storybook/stories/Visualize/Multiverse/Multiverse.stories.tsx
@@ -24,6 +24,7 @@ export const SingleUniverse: Story = {
args: {
storyName: 'SingleUniverse',
universe: [fig8],
+ width: 600,
},
};
@@ -31,7 +32,7 @@ export const Multiverse: Story = {
args: {
storyName: 'Multiverse',
universe: multiFig8,
- // showTrails: true,
+ width: 600,
},
};
@@ -40,7 +41,7 @@ export const Multiverse3D: Story = {
storyName: 'Multiverse3D',
universe: multiFig8,
visType: '3D',
- showTrails: true,
- showDebugInfo: true,
+ controller: 'ui',
+ width: 600,
},
};
\ No newline at end of file
diff --git a/.storybook/stories/Visualize/Record/Record.mdx b/.storybook/stories/Visualize/Record/Record.mdx
index 8e834fa..19ab8e5 100644
--- a/.storybook/stories/Visualize/Record/Record.mdx
+++ b/.storybook/stories/Visualize/Record/Record.mdx
@@ -4,6 +4,52 @@ import * as RecordStories from "./Record.stories";
-
-
-
\ No newline at end of file
+# Record
+
+You can simulate and visualize the n-body system in real-time or record the simulation and play it back later. Simply set the `record` field when creating a **Simulation** object in the following way. Available in both 2D and 3D views.
+
+```js
+new Simulation(universe, {
+ record: true,
+})
+```
+
+## Real-Time (Default)
+
+The universe(s) are simulated and visualized in real-time.
+```js
+new Simulation(universe, {})
+new Simulation(universe, {
+ record: false,
+})
+```
+
+
+
+
+## Recorded
+
+The universe(s) are simulated at `recordSpeed` and recorded completely for `recordFor` seconds worth of playback. The recording can then be played back at any specified speed include -ve values for reverse playback.
+
+```js
+const sim = new Simulation(universe, {
+ record: true,
+})
+// divId, width, height, playSpeed, startPaused, recordFor, recordSpeed
+sim.start(, , 1, false, 60, 2)
+```
+
+
+
+## Recorded and Looped
+
+The recorded simulation can also be looped indefinitely with the `looped` field set to `true` in the **Simulation** object - upon reaching the end of the recording, loops back to the start and loops back to the end in case of reverse playback. The recorded playback is not looped by default.
+
+```js
+new Simulation(universe, {
+ record: true,
+ looped: true,
+})
+```
+
+
\ No newline at end of file
diff --git a/.storybook/stories/Visualize/Trails/Trails.mdx b/.storybook/stories/Visualize/Trails/Trails.mdx
index cb39e02..21d40cd 100644
--- a/.storybook/stories/Visualize/Trails/Trails.mdx
+++ b/.storybook/stories/Visualize/Trails/Trails.mdx
@@ -4,5 +4,38 @@ import * as TrailsStories from "./Trails.stories";
-
-
\ No newline at end of file
+# Trails
+
+Trails are the paths that objects take as they move through space. They can be shown or hidden using the `showTrails` field when creating a **Simulation** object in the following way. Once simulation is running, trails can be toggled on or off using the UI controls or through code, when controller is set to `ui` or `code` respectively. Maximum trail length can also be set using the `maxTrailLength` field, and this is the maximum number of points that will be tracked for all objects combined. Available in both 2D and 3D views.
+
+```js
+new Simulation(universe, {
+ showTrails: true | false,
+ maxTrailLength: number,
+})
+```
+
+## Off (default)
+
+When set to false, trails are not shown.
+
+```js
+new Simulation(universe, {
+ showTrails: false
+})
+```
+
+
+
+## On
+
+When set to true, trails are shown.
+
+```js
+new Simulation(universe, {
+ showTrails: true
+})
+```
+
+
+
\ No newline at end of file
diff --git a/.storybook/theme.ts b/.storybook/theme.ts
new file mode 100644
index 0000000..28559c3
--- /dev/null
+++ b/.storybook/theme.ts
@@ -0,0 +1,22 @@
+import { create } from '@storybook/theming/create';
+
+const COLORS = {
+ bg: '#272525',
+ fg: '#ffedcf',
+ highlight: '#f79e31',
+}
+
+export default create({
+ base: 'dark',
+ brandTitle: 'nbody.js',
+ brandUrl: 'https://source-academy.github.io/nbody/',
+ brandImage: 'https://i.imgur.com/b7Nb1M5.png',
+ brandTarget: '_self',
+
+ appBg: COLORS.bg,
+ barBg: COLORS.bg,
+ barHoverColor: COLORS.fg,
+ barSelectedColor: COLORS.highlight,
+ colorPrimary: COLORS.fg,
+ colorSecondary: COLORS.highlight,
+});
\ No newline at end of file
diff --git a/README.md b/README.md
index 83036e6..5b4487b 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# nbody
-N-body simulations as a Source Academy module.
+JS/TS library to run n-body simulations on the browser. Documentation available [here](https://source-academy.github.io/nbody/api)
## Installation
@@ -18,6 +18,6 @@ yarn add nbody three @types/three plotly.js-dist @types/plotly.js
## Usage
-Full API Documentation available [here](https://source-academy.github.io/nbody/).
+Full API Documentation available [here](https://source-academy.github.io/nbody/api).
## For developers
diff --git a/dist/src/Universe.js b/dist/src/Universe.js
index 4232db3..38bbbde 100644
--- a/dist/src/Universe.js
+++ b/dist/src/Universe.js
@@ -23,6 +23,7 @@ export class Universe {
= config.transformations === undefined
? []
: config.transformations;
+ this.radiusScale = config.radiusScale === undefined ? 1 : config.radiusScale;
}
/**
* Simulate a step in the Universe using the SimulateFunction and Transformations.
@@ -46,6 +47,7 @@ export class Universe {
prevState: this.prevState.clone(),
currState: this.currState.clone(),
color: this.color,
+ radiusScale: this.radiusScale,
label: this.label,
simFunc: this.simFunc,
transformations: this.transformations,
diff --git a/dist/src/library/Visualizer.js b/dist/src/library/Visualizer.js
index a917a21..304f69b 100644
--- a/dist/src/library/Visualizer.js
+++ b/dist/src/library/Visualizer.js
@@ -4,20 +4,6 @@ import Plotly from 'plotly.js-dist';
import Stats from 'stats.js';
import * as THREE from 'three';
import { OrbitControls, ViewHelper } from 'three/examples/jsm/Addons.js';
-/**
- * Extrapolates a value from a range to another range.
- * @param val value to extrapolate.
- * @param minR minimum value of the input range.
- * @param maxR maximum value of the input range.
- * @param low minimum value of the output range.
- * @param high maximum value of the output range.
- * @returns extrapolated value.
- */
-function extrapolateRadius(val, minR, maxR, low, high) {
- if (minR === maxR)
- return (low + high) / 2;
- return low + ((val - minR) / (maxR - minR)) * (high - low);
-}
/**
* Container object for body trails in a 2D universe based in Plotly.
*/
@@ -146,17 +132,11 @@ export class RealTimeVisualizer {
// const height = element.clientHeight;
let maxWidth = 0;
let maxHeight = 0;
- let maxRadius = 0;
- let minRadius = Infinity;
this.simulation.universes.forEach((u) => u.currState.bodies.forEach((b) => {
maxWidth = Math.max(maxWidth, Math.abs(b.position.x));
maxHeight = Math.max(maxHeight, Math.abs(b.position.y));
- minRadius = Math.min(minRadius, b.radius);
- maxRadius = Math.max(maxRadius, b.radius);
}));
const scale = 0.5 * Math.min(height / maxHeight, width / maxWidth);
- const minShowRadius = 0.01 * Math.min(height, width);
- const maxShowRadius = 0.05 * Math.min(height, width);
const layout = {
paper_bgcolor: '#000000',
plot_bgcolor: '#000000',
@@ -196,8 +176,8 @@ export class RealTimeVisualizer {
mode: 'markers',
marker: {
color: uni.color,
- sizemin: minShowRadius,
- size: uni.currState.bodies.map((body) => extrapolateRadius(body.radius, minRadius, maxRadius, minShowRadius, maxShowRadius)),
+ sizemin: 5,
+ size: uni.currState.bodies.map((body) => body.radius * uni.radiusScale),
},
};
if (this.simulation.getShowTrails()) {
@@ -269,8 +249,8 @@ export class RealTimeVisualizer {
hovertext: uni.currState.bodies.map((body) => body.label),
marker: {
color: uni.color,
- sizemin: minShowRadius,
- size: uni.currState.bodies.map((body) => extrapolateRadius(body.radius, minRadius, maxRadius, minShowRadius, maxShowRadius)),
+ size: uni.currState.bodies.map((body) => body.radius * uni.radiusScale),
+ sizemin: 5,
},
mode: 'markers',
};
@@ -455,18 +435,12 @@ export class RealTimeVisualizer3D {
}
element.style.position = 'relative';
let maxLength = 0;
- let maxRadius = 0;
- let minRadius = Infinity;
this.simulation.universes.forEach((u) => u.currState.bodies.forEach((b) => {
for (let i = 0; i < 3; i++) {
maxLength = Math.max(maxLength, Math.abs(b.position.getComponent(i)));
}
- minRadius = Math.min(minRadius, b.radius);
- maxRadius = Math.max(maxRadius, b.radius);
}));
const scale = 0.5 * Math.min(height, width) / maxLength;
- const minShowRadius = 0.015 * Math.min(height, width);
- const maxShowRadius = 0.04 * Math.min(height, width);
this.scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 0, 10000000000);
camera.position.set(0, 0, Math.max(width, height));
@@ -512,7 +486,7 @@ export class RealTimeVisualizer3D {
this.simulation.universes.forEach((u) => {
this.universeTrails.push(new ThreeUniverseTrail(this.simulation.maxTrailLength, typeof u.color === 'string' ? u.color : u.color[0], this.scene, scale));
u.currState.bodies.forEach((b, i) => {
- const sph = new THREE.SphereGeometry(extrapolateRadius(b.radius, minRadius, maxRadius, minShowRadius, maxShowRadius), 12, 12);
+ const sph = new THREE.SphereGeometry(b.radius * scale * u.radiusScale, 12, 12);
const group = new THREE.Group();
group.add(new THREE.LineSegments(new THREE.WireframeGeometry(sph), new THREE.LineBasicMaterial({
color: new THREE.Color(typeof u.color === 'string' ? u.color : u.color[i]),
@@ -733,17 +707,11 @@ export class RecordingVisualizer {
}
let maxWidth = 0;
let maxHeight = 0;
- let maxRadius = 0;
- let minRadius = Infinity;
this.simulation.universes.forEach((u) => u.currState.bodies.forEach((b) => {
maxWidth = Math.max(maxWidth, Math.abs(b.position.x));
maxHeight = Math.max(maxHeight, Math.abs(b.position.y));
- minRadius = Math.min(minRadius, b.radius);
- maxRadius = Math.max(maxRadius, b.radius);
}));
const scale = 0.5 * Math.min(height / maxHeight, width / maxWidth);
- const minShowRadius = 0.01 * Math.min(height, width);
- const maxShowRadius = 0.05 * Math.min(height, width);
const recordedFrames = [];
const totalFrames = this.simulation.maxFrameRate * recordFor;
let playInd = 1;
@@ -795,8 +763,8 @@ export class RecordingVisualizer {
mode: 'markers',
marker: {
color: uni.color,
- sizemin: minShowRadius,
- size: uni.currState.bodies.map((body) => extrapolateRadius(body.radius, minRadius, maxRadius, minShowRadius, maxShowRadius)),
+ sizemin: 5,
+ size: uni.currState.bodies.map((body) => body.radius * uni.radiusScale),
},
};
if (this.simulation.getShowTrails()) {
@@ -851,8 +819,8 @@ export class RecordingVisualizer {
hovertext: currState.bodies.map((body) => body.label),
marker: {
color: uni.color,
- sizemin: minShowRadius,
- size: uni.currState.bodies.map((body) => extrapolateRadius(body.radius, minRadius, maxRadius, minShowRadius, maxShowRadius)),
+ sizemin: 5,
+ size: uni.currState.bodies.map((body) => body.radius * uni.radiusScale),
},
mode: 'markers',
};
@@ -1003,18 +971,12 @@ export class RecordingVisualizer3D {
return;
}
let maxLength = 0;
- let maxRadius = 0;
- let minRadius = Infinity;
this.simulation.universes.forEach((u) => u.currState.bodies.forEach((b) => {
for (let i = 0; i < 3; i++) {
maxLength = Math.max(maxLength, Math.abs(b.position.getComponent(i)));
}
- minRadius = Math.min(minRadius, b.radius);
- maxRadius = Math.max(maxRadius, b.radius);
}));
const scale = 0.5 * Math.min(height, width) / maxLength;
- const minShowRadius = 0.015 * Math.min(height, width);
- const maxShowRadius = 0.04 * Math.min(height, width);
this.scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 0, 10000000000);
camera.position.set(0, 0, Math.max(width, height));
@@ -1050,7 +1012,7 @@ export class RecordingVisualizer3D {
this.simulation.universes.forEach((u) => {
this.universeTrails.push(new ThreeUniverseTrail(this.simulation.maxTrailLength, typeof u.color === 'string' ? u.color : u.color[0], this.scene, scale));
u.currState.bodies.forEach((b, i) => {
- const sph = new THREE.SphereGeometry(extrapolateRadius(b.radius, minRadius, maxRadius, minShowRadius, maxShowRadius), 12, 12);
+ const sph = new THREE.SphereGeometry(b.radius * scale * u.radiusScale, 12, 12);
const group = new THREE.Group();
group.add(new THREE.LineSegments(new THREE.WireframeGeometry(sph), new THREE.LineBasicMaterial({
color: new THREE.Color(typeof u.color === 'string' ? u.color : u.color[i]),
@@ -1170,6 +1132,7 @@ export class RecordingVisualizer3D {
c.material.dispose();
});
});
+ renderer.domElement.remove();
};
this.animationId = requestAnimationFrame(paint);
}
diff --git a/dist/types/src/Universe.d.ts b/dist/types/src/Universe.d.ts
index b05de54..b6cd140 100644
--- a/dist/types/src/Universe.d.ts
+++ b/dist/types/src/Universe.d.ts
@@ -18,6 +18,10 @@ export type UniverseConfig = {
* Color of the bodies in the Universe. A single color applied to all bodies or an array of colors applied to each body respectively. Length of the array should match the number of bodies in the state.
*/
color: string | string[];
+ /**
+ * Scale the radius of the bodies in the Universe. Default is 1.
+ */
+ radiusScale: number;
/**
* Label of the Universe.
*/
@@ -42,6 +46,10 @@ export declare class Universe {
* Color of the bodies in the Universe. A single color applied to all bodies or an array of colors applied to each body respectively. Incase of array, length should match the number of bodies in the state.
*/
color: string | string[];
+ /**
+ * Scale the radius of the bodies in the Universe. Default is 1.
+ */
+ radiusScale: number;
/**
* Label of the Universe.
*/
diff --git a/docs/api/assets/search.js b/docs/api/assets/search.js
index 5e983cd..5a3fb9b 100644
--- a/docs/api/assets/search.js
+++ b/docs/api/assets/search.js
@@ -1 +1 @@
-window.searchData = "data:application/octet-stream;base64,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";
\ No newline at end of file
+window.searchData = "data:application/octet-stream;base64,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";
\ No newline at end of file
diff --git a/docs/api/classes/BodyCenterTransformation.html b/docs/api/classes/BodyCenterTransformation.html
index a82c153..e578a5c 100644
--- a/docs/api/classes/BodyCenterTransformation.html
+++ b/docs/api/classes/BodyCenterTransformation.html
@@ -1,10 +1,10 @@
BodyCenterTransformation | nbody
Frame of reference transformation to the center of body i in the system.
-