diff --git a/building-content/index.md b/building-content/index.md index 45ac1b5..02d080e 100644 --- a/building-content/index.md +++ b/building-content/index.md @@ -33,4 +33,5 @@ renderablestars satellites solar-video-overlay space-weather +vector-field ::: diff --git a/building-content/point-data/data-formats.md b/building-content/point-data/data-formats.md index 9d31615..8cf9190 100644 --- a/building-content/point-data/data-formats.md +++ b/building-content/point-data/data-formats.md @@ -71,7 +71,7 @@ It is also possible to add an identifier for each label, that can be utilized to 41.1 -544.2 50.8 id Label3 text And Point 3 ... ``` --> - +(colormaps_cmap_id)= ## Color Maps (.cmap) For color maps, we use a specific format that is similar to that of the .label and .speck files, but every line defines a color rather than a position. diff --git a/building-content/vector-field.md b/building-content/vector-field.md new file mode 100644 index 0000000..ce51077 --- /dev/null +++ b/building-content/vector-field.md @@ -0,0 +1,238 @@ +# Vector Field +`RenderableVectorField` renders a 3D vector field as a collection of arrows. Each arrow represents the direction and magnitude of a vector at a specific point in space. This page describes how to load a vector field dataset and the options for controlling the appearance of the vector arrows. + +:::{figure} vectorfield_example.png +:align: center +An example of a vector field rendered in OpenSpace using the sparse mode with direction-based coloring. +::: + +## Loading Vector Field Datasets +There are two ways to load vector field data into OpenSpace. The renderable supports two input modes: a regular volumetric grid and a sparse point-cloud dataset. Both modes provide several options for coloring, scaling, and filtering the result. + +The `Mode` parameter controls how the vector field data is loaded and interpreted. There are two options: [Volume](#volume-mode) and [Sparse](#sparse-mode). + +### Volume Mode +In `Volume` mode, the data is a regular 3D grid of velocity vectors stored in a raw binary file. The grid is mapped to a spatial domain defined by `MinDomain` and `MaxDomain`. Each voxel stores three floating-point values - the x, y, and z components of the velocity vector, packed tightly and written in little-endian format. + +The voxel order is x-y-z meaning x is the innermost loop, followed by y, and z is the outermost. The example Python script below shows how to write the binary data: + +```python +import struct + +# Volume dimensions +Nx = 32 +Ny = 32 +Nz = 32 + +with open("vectorfield.bin", "wb") as f: + for z in range(Nz): + for y in range(Ny): + for x in range(Nx): + f.write(struct.pack(" Red, -X -> Cyan, +Y -> Green, -Y -> Magenta, +Z -> Blue, -Z -> Yellow. This mode requires no additional settings and is useful for quickly identifying the dominant flow direction. + +```lua +Coloring = { + ColorMode = "Direction" +} +``` + +Putting it all together, a `Coloring` component added to a sparse vector field looks like this: + +```lua +... +Renderable = { + Type = "RenderableVectorField", + Mode = "Sparse", + Sparse = { + FilePath = asset.resource("data/vectors.csv") + }, + Coloring = { + ColorMode = "Magnitude", + ColorMap = asset.resource("colormaps/viridis.png"), + -- Optional: override the auto-detected magnitude range + ColorMappingDataRange = { 0.0, 500.0 } + } + ... +} +... +``` + +## Appearance Settings +There are additional settings that control the appearance of the vector field. These properties can also be adjusted interactively in the GUI under the scene graph node's properties panel at runtime. + +| Property | Default | Description | +| --- | --- | --- | +| `VectorFieldScale` | `1.0` | Scales the arrow lengths using an exponential scale, ranging from meter scale (1) to approximately 3 Mpc scale (100). | +| `LineWidth` | `1.0` | Width of the arrow lines in pixels. | +| `Stride` | `1` | Render only every *n*-th vector. A stride of 1 renders all vectors, 2 renders every other vector, 3 renders every third, and so on. This is useful for reducing visual clutter in dense datasets. | + +## Lua Filtering +For advanced use cases, it is possible to filter which vectors are displayed by providing a custom Lua script. This is useful for, for example, only showing vectors above a certain speed threshold or within a specific spatial region. + +To enable filtering, set `FilterByLua = true` and point `Script` to a `.lua` file. The script must define a function called `filter` that receives the position `(x, y, z)` and velocity `(vx, vy, vz)` of a vector and returns `true` to include it, or `false` to exclude it: + +```lua +-- example_filter.lua +function filter(x, y, z, vx, vy, vz) + local magnitude = math.sqrt(vx * vx + vy * vy + vz * vz) + -- Only show vectors with a speed greater than 100 + return magnitude > 100.0 +end +``` + +In the asset, enable filtering like this: + +```lua +... +Renderable = { + Type = "RenderableVectorField", + Mode = "Sparse", + Sparse = { + FilePath = asset.resource("data/vectors.csv") + }, + FilterByLua = true, + Script = asset.resource("scripts/example_filter.lua") +} +... +``` + +:::{note} +The filter script is reloaded automatically whenever the file changes on disk, so you can iterate on your filter function without restarting OpenSpace. +::: + +:::{warning} +If `FilterByLua` is set to `true` but the `Script` path is missing or the file cannot be found, filtering will be silently disabled. +::: + +## Full Asset Example +The following example shows a complete asset using sparse mode with magnitude coloring and a Lua filter: + +```lua +local Node = { + Identifier = "WindVectors", + Renderable = { + Type = "RenderableVectorField", + Mode = "Sparse", + Sparse = { + FilePath = asset.resource("data/wind.csv"), + Vx = "u", Vy = "v", Vz = "w" + }, + VectorFieldScale = 20.0, + LineWidth = 1.5, + Stride = 2, + Coloring = { + ColorMode = "Magnitude", + ColorMap = asset.resource("colormaps/plasma.png") + }, + FilterByLua = true, + Script = asset.resource("scripts/wind_filter.lua") + }, + GUI = { + Name = "Wind Vectors", + Path = "/Atmosphere" + } +} + +asset.onInitialize(function() + openspace.addSceneGraphNode(Node) +end) + +asset.onDeinitialize(function() + openspace.removeSceneGraphNode(Node) +end) +``` diff --git a/building-content/vectorfield_example.png b/building-content/vectorfield_example.png new file mode 100644 index 0000000..0991a7b Binary files /dev/null and b/building-content/vectorfield_example.png differ