Sonification/Telemetry (#3461)

* Add sonification module

* WIP: Extract planet positions in own thread

* WIP Extract camera position and angle

* Extract distance to camera and fix angle

* WIP: Extract focus node

* Optimize and clean up

* Fix signed angle for surround sound

* WIP add solar system view sonification

* Extract time
Extract the speed of time in OpenSpace and send it to supercollider.
Needed to scale the sonification time in sync with OpenSpace

* Extract moon angles
Extract the angle from the planet to the major moons. Used in
sonification to position the moons in surround.

* Add easy change of angles
Easier to switch what angle is calculated and sent to the sonification.
Preperation for test in Dome

* Fix shutdown error and nullptr error
Add deinitialize for sonification module. Make sure pointers are
initialized to nullptrs at start. Check in sonification main in the
start if the scene is initialized

* Add properties to contol sonification
* Add property to turn on/off sonification for each planet

* Change so the data only send for those planets that are enabled with the property

* WIP: Improve GUI for sonification control
* Add GUI for every sonification parameter per planet

* However, only for Earth and Mars so far

* Make data send more often and not just when the planet is enabled

* Add sonification GUI for Mercury and Venus
* GUI for each parameter for Marcury and Venus

* Send a shutdown message to SuperCollider during deinitialization

* Group properties together
* Gather the planets properties under a planets property owner

* Prepare to expand GUI

* Name changes

* Add Solar view in GUI
* Add GUI that controls the solar sonification

* Remove the all enabled slot in the settings arrays

* No longer sends the all enabled slot in teh settings arrays

* WIP Compare view

* Add compare view in GUI. Allows to compare two planets sonification.

* Finish compare view

* Add connection to sonification for compare view

* Improve responsiveness of GUI

* Expand GUI with the outer planets

* Add GUI for the outer planets

* Increase array sizes to compe with all the planets

* Add functionality for the outer planets GUI

* Add temperature option in GUI

* Fix so that compare cannot compare to itself

* Scale selected planets in compare view

* Fix reset of compare view

* Add Rings sonification to GUI

* Fix solar sonification GUI

* Bind onChange function for the outer planets in the solar GUI

* Add comment about where in the settings array the setting for rings
are

* Add master buttuns in GUI

* Add button to turn on/off all the planets

* Add button to turn on/off everything, solar when sun is in focus and
planetary otherwise.

* Add function to easier turn on/off all the parameters of solar and
planetary

* Only master start sonification module

* Add check that only the master start the sonification thread

* Clean up a few lines in the angle calculation

* Optimize the sending of time

* Time is not sent with every message but only when it changes instead

* Add GUI for choosing what parts to compare

* More GUI in compare view to control which sonification to compare

* Fix deinitialize turn off sounds

* Make sure the auto (enable all) now also work with the new compare GUI

* Fix socket problem and code clean up

* Only use one socket to send information to supercollider

* General code clean up

* Remove sending of focus node since supercollider does not use it

* Adjust to new master

* Fix oscpack linker issues

* Update to new master

* Add start of SonificationEngine

* Move osc code into SonificationEngine

* Add function for the planetary settings

* Add function to access SonificationEngine via the module

* Add base sonification class

* Start seperating into different sonification classes

* Add PlanetarySonification to the internal list

* Move osc to its own module

* Seperate planet sonifications into seperate sonification classes

* Make time sonification its own class

* Some clean up

* Update to new cmake version

* Add proper module dependency for the sonification module

* Fix thread issue

* Temporary fix with a sleep, works for now

* Fix log messages issue

* Add enabled property for the sonification module

* Some clean up

* More clean up

* More clean up

* Make it possible to send in node position directly for helper function

* Add stop function for sonifications

* Make possible to specify sonification destination ip and port in cfg file

* Use Parameters struct for the sonification module dictionary

* Make sonification module a singleton

* Minor code style fix

* Use module engine module function for singleton pattern instead

* Add angle calculations for the surround modes

* Use codegen map function for surround mode enum

* Improve data storage and sending for planet sonificaitons

* Add focus sonification

* Fix elevation angle calculation for circular surround mode

* Add Lua function to add entries for the PlanetsSonification

* Add sonification profile

* Add lua library file for planets sonification

* Fix gui osc messages sending

* Update to latest master

* Rename planets sonificaitons to reflect they only work for planets

* Add generic nodes sonification

* Clean up Time sonification

* plus a little in the nodes sonification

* Add current time information to the time sonification

* Add precision propertysubowner to structure Gui and make it flexible

* Some general clean up

* Rename solar sonification to planets overview sonification

* Add Camera sonification

* Fix some bugs with the camera sonificaiton

* Make sure the sonificaiton thread is synced with the main thread

* Fix camera and scene bug in sonificaiton

* Dont hold up the main thread while sonificaiton thread does its work

* Use the current frame time for speed calculaiton instead of average

* Fix osc::Blob memory bug

* Wait max 30 seconds in the sonification thread for the main thread

* Make sure deinitialize of sonificaiton thread does not halt

* Add the mode sonification as a proper sonification

* Add nodes sonification example with ISS and Tiangong

* Some renaming and restructuring

* Add more detailed documentation for the sonification math

* The util functions for different angles in the sonificaiton/telemetry module

* Major clean up and restructuring

* Rename module to telemetry to make it more clear that we do not
  produce the sonifications, we just spit out data used by a
  sonificaiton software

* Add the new moon of Neptune to sonification and add sonification files

* And add the test osc supercollider file

* Add the OpenSpace header to the supercollider example file

* Apply suggestions from code review

Co-authored-by: Alexander Bock <alexander.bock@liu.se>

* Adjust suggestions to fix errors

* Address more PR comments

* Address more PR comments

* More PR comments

* More PR comments

* More PR comments

* Even more PR comments

* Update to latest master

* More PR comments

* Add OSC as a submodule

* NOTE that due to path issues this commit will not build with the osc
  module, you will need to change the paths inside two osc files for it
  to build. Will fix very soon

* Fix issue with oscpack not setting correct include directory

* Address PR comments

* Address PR comments

* Update the SuperCollider files

---------

Co-authored-by: Alexander Bock <alexander.bock@liu.se>
Co-authored-by: Ivar Gorenko <ivago071@student.liu.se>
This commit is contained in:
Malin E
2025-05-19 15:02:48 +02:00
committed by GitHub
parent 327a0693c4
commit ac3e30c24e
42 changed files with 8043 additions and 1 deletions
+3 -1
View File
@@ -41,10 +41,12 @@
[submodule "support/doxygen/css"]
path = support/doxygen/css
url = https://github.com/jothepro/doxygen-awesome-css.git
[submodule "modules/audio/ext/soloud"]
path = modules/audio/ext/soloud
url = https://github.com/jarikomppa/soloud
[submodule "modules/opensoundcontrol/ext/oscpack"]
path = modules/opensoundcontrol/ext/oscpack
url = https://github.com/OpenSpace/oscpack.git
[submodule "ext/json"]
path = ext/json
url = https://github.com/nlohmann/json
@@ -0,0 +1,21 @@
-- Nodes sonification
-- This example adds the ISS and Tiangong space stations to the NodesTelemetry in the
-- Telemetry module. This should be used together with the nodesSonification.scd file in
-- SuperCollider to listen to an example sonification of the nodes.
--
-- This is a good foundation for creating your own sonification of any node(s) in
-- OpenSpace. If you want to use this to create your own sonification, you can copy this
-- file and the nodesSonification.scd file to your own asset folder and modify them to
-- your liking. In this file, that modification would be to replace the ISS and Tiangong
-- identifies with the identifiers of the node(s) you want to sonify instead.
--
-- For more information about sonification in OpenSpace and how to use it, see the
-- documentation:
-- https://docs.openspaceproject.com/latest/creating-data-assets/modules/telemetry/sonification.html
asset.require("scene/solarsystem/planets/earth/satellites/misc/iss")
asset.require("scene/solarsystem/planets/earth/satellites/misc/tiangong")
asset.onInitialize(function ()
openspace.telemetry.addNodes({ "ISS", "Tiangong" })
end)
@@ -0,0 +1,21 @@
-- Voyager sonification
-- This example adds the Voyager 1 and Voyager 2 probes to the NodesTelemetry in the
-- Telemetry module. This should be used together with the voyager-sonification.scd file
-- in SuperCollider to listen to an example sonification of the Voyager probes.
--
-- This is a good foundation for creating your own sonification of any node(s) in
-- OpenSpace. If you want to use this to create your own sonification, you can copy this
-- file and the voyager-sonification.scd file to your own asset folder and modify them to
-- your liking. In this file, that modification would be to replace the Voyager 1 and 2
-- identifiers with the identifiers of the node(s) you want to sonify instead.
--
-- For more information about sonification in OpenSpace and how to use it, see the
-- documentation:
-- https://docs.openspaceproject.com/latest/creating-data-assets/modules/telemetry/sonification.html
asset.require("scene/solarsystem/missions/voyager/voyager1")
asset.require("scene/solarsystem/missions/voyager/voyager2")
asset.onInitialize(function ()
openspace.telemetry.addNodes({ "Voyager_1", "Voyager_2" })
end)
@@ -0,0 +1,438 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
Platform.userExtensionDir
NetAddr.langPort;
NetAddr.localAddr;
/*****************************************************************************************
* This is a SuperCollider file that needs to be run in the SuperCollider application *
* that can be downloaded here: https://supercollider.github.io/ *
* *
* This is an example file that shows all the messages that the Telemetry module in *
* OpenSpace can send over Open Sound Control (OSC). SuperCollider can then listen to *
* these OSC messages and create sounds based on their content (a sonification). This *
* example only listens to the messages and displays their content in the log to the *
* right in the SuperCollider application. For a more extensive example that shows how *
* to create sounds in SuperCollider based on the data from OpenSpace, see the *
* planets-sonification.scd file located in data\assets\modules\telemetry\sonification. *
* *
* To run a SuperCollider file, click any line within the parentheses below, such as the *
* marked line. When you have selected a line, press the CTRL and ENTER keys on your *
* keyboard at the same time, and SuperCollider will run the code. You will see a *
* message appear in the log to the right, the message should be: *
* "-> OSCdef(Neptune, /Neptune, nil, nil, nil)". At this point, SuperCollider is ready *
* to receive messages from OpenSpace. For a guide on how to enable and use the *
* Telemetry module inside OpenSpace, see this documentation page: *
* https://docs.openspaceproject.com/latest/creating-data-assets/modules/telemetry/ *
* index.html *
****************************************************************************************/
(
// To run this example, click this line and press CTRL + ENTER on the keyboard
// When finished, press CTRL + . on you keyboard simultaneously to stop the sonification
// Camera sonification
OSCdef.new(
\Camera,
{
arg msg;
// msg[0] = OSC Label.
// msg[1] = The x position of the camera in the world.
// msg[2] = The y position of the camera in the world.
// msg[3] = The z position of the camera in the world.
// msg[4] = The w component of the quaternion rotation of the camera.
// msg[5] = The x component of the quaternion rotation of the camera.
// msg[6] = The y component of the quaternion rotation of the camera.
// msg[7] = The z component of the quaternion rotation of the camera.
// msg[8] = The movement speed of the camera, in the distance unit per second
// specified by the next item.
// msg[9] = The distance unit for the movement speed of the camera, as a string in
// singular form with the first letter capitalized. For example,
// "Kilometer".
//
// Note that the first seven items describe the position and orientation of the
// camera. The first three items specify the x, y, and z positions of the camera
// in the world, in relation to the solar system barycenter. The next four items
// are the quaternion rotation of the camera in the order of w, x, y, and z.
("Camera: " + msg).postln;
},
'/Camera'
);
// Focus sonification
OSCdef.new(
\Focus,
{
arg msg;
// msg[0] = OSC Label.
// msg[1] = The identifier of the new focus in OpenSpace, as a string.
("Focus: " + msg).postln;
},
'/Focus'
);
// Surround mode sonification
OSCdef.new(
\Mode,
{
arg msg;
// msg[0] = OSC Label.
// msg[1] = The first item is an integer value that specifies what method was
// used to calculate the angles. If the value is 0, then the method
// used was the Horizontal angle calculation mode. In the case where
// the value is 1, then the Circular angle calculation mode was used.
// msg[2] = The second value is an integer value of either 0 or 1 that
// determines if the additional elevation angle is used or not. If the
// value is 1, the additional elevation angle is calculated. Otherwise,
// if 0, the elevation angle is always set to 0.0.
("Mode: " + msg).postln;
},
'/Mode'
);
// Time sonification
OSCdef.new(
\Time,
{
arg msg;
// msg[0] = OSC Label.
// msg[1] = The speed of the simulation time, specified in the selected time unit
// in the simulation, per real-life second. For example, 10 simulated
// seconds per real-life second means that the simulation goes 10 times
// faster than real-life.
// msg[2] = The selected time unit for the speed of simulation time, as a string
// in singular form with the first letter capitalized. For example,
// "Day".
// msg[3] = The current simulation time in OpenSpace specified in J2000 seconds,
// that is, the number of seconds past the J2000 epoch
// (i.e. January 1, 2000 12:00:00 TT).
("Time: " + msg).postln;
},
'/Time'
);
// Nodes sonification, data map:
// msg[0] = OSC Label (The identifier for each node).
// msg[1] = The distance from the camera to the node, in the distance unit specified in
// the last item.
// msg[2] = The horizontal angle to the node, in radians, with the current angle
// calculation mode taken into account. For more information, see
// https://docs.openspaceproject.com/latest/creating-data-assets/modules/
// telemetry/angle-information.html.
// msg[3] = The elevation angle to the node, in radians, with the current angle
// calculation mode taken into account. Again, see
// https://docs.openspaceproject.com/latest/creating-data-assets/modules/
// telemetry/angle-information.html for details.
// msg[4] = The unit for the distance to the camera, as a string in singular form with
// the first letter capitalized. For example, "Meter".
// ISS node
OSCdef.new(
\ISS,
{
arg msg;
// Follow the nodes sonification data map above
("ISS: " + msg).postln;
},
'/ISS'
);
// Tiangong node
OSCdef.new(
\Tiangong,
{
arg msg;
// Follow the nodes sonification data map above
("Tiangong: " + msg).postln;
},
'/Tiangong'
);
// Planet related sonifications:
// Compare planets
OSCdef.new(
\Compare,
{
arg msg;
// msg[0] = OSC Label.
// msg[1] = The index of the first planet to be compared. See the list below on
// how to convert the index to a planet name:
// 0 -> None selected
// 1 -> Mercury
// 2 -> Venus
// 3 -> Earth
// 4 -> Mars
// 5 -> Jupiter
// 6 -> Saturn
// 7 -> Uranus
// 8 -> Neptune
// msg[2] = The index of the second planet to be compared (will never be the same
// as the first).
// msg[3] = List of user interface settings for the comparison, which determines
// which aspects of the sonification should be turned on or off. A value
// of 0 means that a setting is turned off, and 1 means that it is turned
// on. The order of the settings can be seen in the list below:
// msg[3][0] = Size/day. 1 -> Size/day is turned on.
// 0 -> Size/day is turned off.
// msg[3][1] = Gravity
// msg[3][2] = Temperature
// msg[3][3] = Atmosphere
// msg[3][4] = Moons
// msg[3][5] = Rings
("Compare: " + msg).postln;
},
'/Compare'
);
// Planets overview
OSCdef.new(
\Overview,
{
arg msg;
// msg[0] = OSC Label.
// msg[1] = List of user interface settings for the planets overview. This
// determines which planets are part of the sonification or not. A value
// of 0 means that the planet is turned off, and a 1 means that it is
// turned on. The order of the settings can be seen in the list below:
// msg[1][0] = Mercury. 1 -> Mercury is turned on.
// 0 -> Mercury is turned off.
// msg[1][1] = Venus
// msg[1][2] = Earth
// msg[1][3] = Mars
// msg[1][4] = Jupiter
// msg[1][5] = Saturn
// msg[1][6] = Uranus
// msg[1][7] = Neptune
("Overview: " + msg).postln;
},
'/Overview'
);
// Planets sonifications, data map:
// msg[0] = OSC Label (The name of the planet).
// msg[1] = The distance from the camera to the planet in kilometers.
// msg[2] = The horizontal angle in radians to the planet, with the current angle
// calculation mode taken into account. For more information see
// https://docs.openspaceproject.com/latest/creating-data-assets/modules/
// telemetry/angle-information.html.
// msg[3] = The elevation angle in radians to the planet, with the current angle
// calculation mode taken into account. Again, see
// https://docs.openspaceproject.com/latest/creating-data-assets/modules/
// telemetry/angle-information.html for details.
// msg[4] = List of user interface settings for the planet sonification, which aspects
// of the sonification should be turned on or off. A value of 0 means that it
// is turned off, and a 1 means that it is turned on. The order of the
// settings can be seen in the table below. If the setting does not exist for
// a planet, the value is always 0.
// msg[4][0] = Size/day. 1 -> Size/day is turned on.
// 0 -> Size/day is turned off.
// msg[4][1] = Gravity
// msg[4][2] = Temperature
// msg[4][3] = Atmosphere
// msg[4][4] = Moons
// msg[4][5] = Rings
// msg[5] = (optional) The distance from the camera to the first moon in kilometers.
// msg[6] = (optional) The horizontal angle in radians to the first moon.
// msg[7] = (optional) The elevation angle in radians to the first moon.
// msg[8] = (optional) The distance from the camera to the second moon in kilometers.
// msg[9] = (optional) The horizontal angle in radians to the second moon.
// msg[10] = (optional) The elevation angle in radians to the second moon.
// msg[...] = The data then continues in the same pattern for each of the planet's
// moons, with three values per moon. The moons are given in order of
// distance from the planet (closest first, farthest last) as specified in
// the planets.asset file in the data\assets\modules\telemetry\sonification
// folder.
// Mercury
OSCdef.new(
\Mercury,
{
arg msg;
// Follow the planets sonification data map above
// Mercury does not have the GUI settings:
// - Atmosphere
// - Moons
// - Rings
// Mercury have no moon data since it does not have any moon
("Mercury: " + msg).postln;
},
'/Mercury'
);
// Venus
OSCdef.new(
\Venus,
{
arg msg;
// Follow the planets sonification data map above
// Venus does not have the GUI settings:
// - Moons
// - Rings
// Venus have no moon data since it does not have any moon
("Venus: " + msg).postln;
},
'/Venus'
);
// Earth
OSCdef.new(
\Earth,
{
arg msg;
// Follow the planets sonification data map above
// Earth does not have the GUI settings:
// - Rings
// Earth have moon data for one moon:
// - The Moon
("Earth: " + msg).postln;
},
'/Earth'
);
// Mars
OSCdef.new(
\Mars,
{
arg msg;
// Follow the planets sonification data map above
// Mars does not have the GUI settings:
// - Rings
// Mars have moon data for 2 moons in the following order:
// - Phobos
// - Deimos
("Mars: " + msg).postln;
},
'/Mars'
);
// Jupiter
OSCdef.new(
\Jupiter,
{
arg msg;
// Follow the planets sonification data map above
// Jupiter does not have the GUI settings:
// - Rings (there are rings in real-life, but not in OpenSpace)
// Jupiter have moon data for 4 moons in the following order:
// - Io
// - Europa
// - Ganymede
// - Callisto
("Jupiter: " + msg).postln;
},
'/Jupiter'
);
// Saturn
OSCdef.new(
\Saturn,
{
arg msg;
// Follow the planets sonification data map above
// Saturn have moon data for 8 moons in the following order:
// - Dione
// - Enceladus
// - Hyperion
// - Iapetus
// - Mimas
// - Rhea
// - Tethys
// - Titan
("Saturn: " + msg).postln;
},
'/Saturn'
);
// Uranus
OSCdef.new(
\Uranus,
{
arg msg;
// Follow the planets sonification data map above
// Uranus does not have the GUI settings:
// - Rings (there are rings in real-life, but not in OpenSpace)
// Uranus have moon data for 5 moons in the following order:
// - Ariel
// - Miranda
// - Oberon
// - Titania
// - Umbriel
("Uranus: " + msg).postln;
},
'/Uranus'
);
// Neptune
OSCdef.new(
\Neptune,
{
arg msg;
// Follow the planets sonification data map above
// Neptune does not have the GUI settings:
// - Rings (there are rings in real-life, but not in OpenSpace)
// Neptune have moon data for 2 moons in the following order:
// - Triton
// - Nereid
("Neptune: " + msg).postln;
},
'/Neptune'
);
)
@@ -0,0 +1,390 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
Platform.userExtensionDir
NetAddr.langPort;
NetAddr.localAddr;
/*****************************************************************************************
* This is a SuperCollider file that needs to be run in the SuperCollider application *
* that can be downloaded here: https://supercollider.github.io/ *
* *
* This is an example file for the ISS and Tiangong space stations. This sonification *
* will emit an impulse sound for both stations, with Tiangong having a higher pitch and *
* ISS a lower pitch when the camera is close to either of the space stations. For a *
* more extensive example that shows how to create sounds in SuperCollider based on the *
* data from OpenSpace, see the planets-sonification.scd file located in *
* data\assets\modules\telemetry\sonification. This sonification was made by Elias *
* Elmquist. *
* *
* To run a SuperCollider file, click any line within the parentheses below, such as the *
* marked line. When you have selected a line, press the CTRL and ENTER keys on your *
* keyboard at the same time, and SuperCollider will run the code. You will see several *
* messages appear in the log to the right, the last message should be: *
* "---------- Sonification is ready ----------". At this point, SuperCollider is ready *
* to receive messages from OpenSpace and produce the sonification. For a guide on how *
* to enable and use the Telemetry module inside OpenSpace, see this documentation page: *
* https://docs.openspaceproject.com/latest/creating-data-assets/modules/telemetry/ *
* index.html *
****************************************************************************************/
(
// To run this example, click this line and press CTRL + ENTER on the keyboard
// When finished, press CTRL + . on you keyboard simultaneously to stop the sonification
s.quit;
o = Server.default.options;
// TODO Update this to use the angle calculation setting in OpenSpace
// 0: Stereo (Binaurual)
// 1: VisC Dome
// 2: Hayden
~setup = 0;
if((~setup == 0),
{~numSpeakerChannels = 2;}
);
if((~setup == 1),
{o.outDevice_("ASIO : Focusrite USB ASIO");
~numSpeakerChannels = 8;
}
);
if((~setup == 2),
{o.outDevice_("ASIO : ASIO MADIface USB");
~numSpeakerChannels = 32;
}
);
o.numOutputBusChannels = ~numSpeakerChannels;
//3D
~ampMode = 1;
~focusType = 1;
~lockedFocus = 0;
~comparisonMode = 1;
// Setup which nodes to listen to in OpenSpace
~oscLabel0 = \ISS;
~oscLabel1 = \Tiangong;
s.reboot;
s.waitForBoot{
var loadSynths = {
// Create a sound function to play a poof sound
SynthDef(
\synth,
{
arg out=0, gate=1, mamp=5, amp=0, baseAmp=1, freq=400, buf, loop=0, rate=1, da=0, atk=1, dcay=0, suslvl=1, rel=1, mute=1, trig=0, phase=0;
var sig=0, env, t_gate=0;
t_gate = gate * LFPulse.kr(0.2,phase);
env = EnvGen.kr(
Env.perc(0.01,1/0.5),
t_gate,
doneAction:0
);
sig = BrownNoise.ar(1);
sig = (BPF.ar(sig, freq*2, 0.004) * 50);
sig = mamp * sig;
sig = mute.lag(5) * (baseAmp*amp) * sig * env;
Out.ar(out, sig);
}
).add;
// Setup Ambisonics
~order = 2; // set this to the order you want
~numChannels = ((~order + 1)**2).asInteger;
// binaural decoder (~numChannels -> 2) - reads from 'bus' and sums into 'out'
SynthDef.new(\binauralDecoder, { | bus, out = 0 |
Out.ar(out, VSTPlugin.ar(In.ar(bus, ~numChannels), 2));
}).add;
SynthDef.new(\allraDecoder, { | bus, out = 0 |
Out.ar(out, VSTPlugin.ar(In.ar(bus, ~numChannels), ~numSpeakerChannels));
}).add;
// stereo encoder (2 -> ~numChannels) - replaces stereo signal with ambisonics signal
SynthDef.new(\stereoEncoder, { | bus = 0 |
ReplaceOut.ar(bus, VSTPlugin.ar(In.ar(bus, 2), ~numChannels));
}).add;
// ambisonics insert FX Surround (replaces input with output)
SynthDef.new(\ambiFX, { | bus = 0, bypass |
ReplaceOut.ar(bus, VSTPlugin.ar(In.ar(bus, ~numChannels), ~numChannels, bypass));
}).add;
// helper Synth (throws audio from ambi bus to ambi master bus)
SynthDef.new(\ambiThrow, { | from, to |
Out.ar(to, In.ar(from, ~numChannels));
}).add;
};
var initiateAmbisonic = {
// bus + group
~ambiMasterBus = Bus.audio(s, ~numChannels);
~ambiMasterGroup = Group.new;
if((~setup == 0),
// binaural decoder (writes to master output)
{~decoder = VSTPluginController(Synth(\binauralDecoder, [\bus, ~ambiMasterBus, \out, 0], target: ~ambiMasterGroup, addAction: \addToTail)).open("BinauralDecoder");},
// AllRA decoder
{~decoder = VSTPluginController(Synth(\allraDecoder, [\bus, ~ambiMasterBus, \out, 0], target: ~ambiMasterGroup, addAction: \addToTail)).open("SimpleDecoder");}
);
// ambisonics insert FX (replaces input with output)
SynthDef.new(\ambiFX, { | bus = 0, bypass |
ReplaceOut.ar(bus, VSTPlugin.ar(In.ar(bus, 2), ~numChannels, bypass));
}).add;
// a group for ambisonic master effects
~ambiMasterFXGroup = Group.before(~decoder.synth);
};
var initiateSynths = {
// create ambisonic busses
~soundBus = Array.newClear(7);
~numBus = ~soundBus.size;
~ambiBus = Array.newClear(~numBus);
~ambiGroup = Array.newClear(~numBus);
~encoder = Array.newClear(~numBus);
~sounds = Array.newClear(~numBus);
// First Poof
~ambiBus[0] = Bus.audio(s, ~numChannels);
~ambiGroup[0] = Group.before(~ambiMasterGroup);
~sounds[0] = Synth.new(\synth, [\out, ~ambiBus[0], \freq, 200], ~ambiGroup[0], addAction: \addToHead);
~encoder[0] = VSTPluginController(Synth(\stereoEncoder, [\bus, ~ambiBus[0]], target: ~ambiGroup[0], addAction: \addToTail));
~encoder[0].open("StereoEncoder", action: { |self| self.set(6, 0.5) }); // 6 -> azimuth
Synth(\ambiThrow, [\from, ~ambiBus[0], \to, ~ambiMasterBus], target: ~ambiGroup[0], addAction: \addToTail);
// Second Poof
~ambiBus[1] = Bus.audio(s, ~numChannels);
~ambiGroup[1] = Group.before(~ambiMasterGroup);
~sounds[1] = Synth.new(\synth, [\out, ~ambiBus[1], \freq, 400, \phase, 0.3], ~ambiGroup[1], addAction: \addToHead);
~encoder[1] = VSTPluginController(Synth(\stereoEncoder, [\bus, ~ambiBus[1]], target: ~ambiGroup[1], addAction: \addToTail));
~encoder[1].open("StereoEncoder", action: { |self| self.set(6, 0.6) }); // 6 -> azimuth
Synth(\ambiThrow, [\from, ~ambiBus[1], \to, ~ambiMasterBus], target: ~ambiGroup[1], addAction: \addToTail);
//Audio mixing of the sounds (if needed)
~sounds[0].set(\baseAmp, 0.3);
~sounds[1].set(\baseAmp, 0.3);
// Debugging spatial position
//~encoder[0].editor;
// add an ambisonic master FX
~ambiReverb = VSTPluginController(Synth(\ambiFX, [\bus, ~ambiMasterBus, \out, 0],
target: ~ambiMasterFXGroup)).open("FdnReverb", action: { |self| self.set(1, 0.2) });
};
var loadDecoder = {
if((~setup == 1),
{
~decoder.iemPluginOSC("/SimpleDecoder/loadFile", thisProcess.nowExecutingPath.dirname +/+ "AmbiDecoders" +/+ "DomenVisC_5th.json");
0.1.wait;
~decoder.iemPluginOSC("/SimpleDecoder/swMode", 1);
0.1.wait;
~decoder.iemPluginOSC("/SimpleDecoder/swChannel", 4);
0.1.wait;
~decoder.iemPluginOSC("/SimpleDecoder/lowPassGain", -12);
}
);
if((~setup == 2),
{
~decoder.iemPluginOSC("/SimpleDecoder/loadFile", thisProcess.nowExecutingPath.dirname +/+ "AmbiDecoders" +/+ "HaydenIEM_5th_0deg_80top_rot180deg.json");
0.1.wait;
~decoder.iemPluginOSC("/SimpleDecoder/swMode", 1);
0.1.wait;
~decoder.iemPluginOSC("/SimpleDecoder/swChannel", 32);
0.1.wait;
}
);
};
// Set which OSC labels to listen to
var oscDef = {
//Horizontal mode
~modeElSign = 1;
~modeElPhase = 0.5;
//Circular mode (switch manually)
//~modeElSign = -1;
//~modeElPhase = 0.75;
//Size of hearable area
~distanceLimit = 10;
//Ambient amp
~ambientAmp = 1.0;
OSCdef.new(\Mode,
{
arg msg;
//msg[1] == 0 -> Horizontal
//msg[1] == 1 -> Circular
//msg[2] == 0 -> Elevation on
//msg[2] == 1 -> Elevation off
("SurroundMode:" + msg[1]).postln;
//Horizontal
if(((msg[1]==0)),
{~modeElSign = 1;
~modeElPhase = 0.5;},
{}
);
//Circular
if(((msg[1]==1)),
{~modeElSign = -1;
~modeElPhase = 0.75;},
{}
);
("~modeElSign:" + ~modeElSign).postln;
("~modeElPhase:" + ~modeElPhase).postln;
},'/Mode');
OSCdef.new(
~oscLabel0,
{
arg msg;
var name, distance, invDistance, azimuth, amp, value, elevation;
//msg[1]: label distance
//msg[2]: label azimuth
//msg[3]: label elevation
// DEBUG: Uncomment this line to get console output of all messages from OpenSpace
//(~oscLabel0 ++ ": " ++ msg).postln;
name = msg[0].asString;
distance = msg[1]/1000000;
//invDistance = ~distanceLimit-distance;
azimuth = (msg[2]/(2*pi))+0.5;
elevation = ~modeElSign*(msg[3]/(2*pi))+~modeElPhase;
if((~ampMode == 1),
{
if((distance < ~distanceLimit),
{amp = LinLin.kr(distance, 0, ~distanceLimit, 0, 1);
},
{amp = ~ambientAmp;}
);
}
);
(~oscLabel0 ++ "Amp: " ++ amp).postln;
~sounds[0].set(\amp, amp);
~encoder[0].do(_.set(6, azimuth));
~encoder[0].do(_.set(7, elevation));
},
~oscLabel0
);
OSCdef.new(
~oscLabel1,
{
arg msg;
var name, distance, invDistance, azimuth, amp, value, elevation;
//msg[1]: label distance
//msg[2]: label azimuth
//msg[3]: label elevation
// DEBUG: Uncomment this line to get console output of messages from OpenSpace
//(~oscLabel1 ++ ": " ++ msg).postln;
name = msg[0].asString;
distance = msg[1]/10000;
//invDistance = ~distanceLimit-distance;
azimuth = (msg[2]/(2*pi))+0.5;
elevation = ~modeElSign*(msg[3]/(2*pi))+~modeElPhase;
if((~ampMode == 1),
{
if((distance < ~distanceLimit),
{amp = LinLin.kr(distance, 0, ~distanceLimit, 0, 1);
},
{amp = ~ambientAmp;}
);
}
);
(~oscLabel1 ++ "Amp: " ++ amp).postln;
~sounds[1].set(\amp, amp);
~encoder[1].do(_.set(6, azimuth));
~encoder[1].do(_.set(7, elevation));
},
~oscLabel1
);
};
// Start routine
~startRoutine = Routine({
0.5.wait;
loadSynths.value;
"--Loaded the synths--".postln;
0.3.wait;
initiateAmbisonic.value;
"--Loaded the ambisonics--".postln;
0.3.wait;
initiateSynths.value;
"--Initiated the synths--".postln;
0.2.wait;
loadDecoder.value;
"--Loaded the decoder--".postln;
0.2.wait;
oscDef.value;
"--Loaded the OSCdefs--".postln;
"---------- Sonification is ready ----------".postln;
});
~startRoutine.play(AppClock);
}
)
@@ -0,0 +1,402 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
Platform.userExtensionDir
NetAddr.langPort;
NetAddr.localAddr;
/*****************************************************************************************
* This is a SuperCollider file that needs to be run in the SuperCollider application *
* that can be downloaded here: https://supercollider.github.io/ *
* *
* This is an example file for the Voyager 1 and 2 space probes. This sonification will *
* play and loop the Greetings to the Universe audio clip (one of the recordings *
* included on the Golden Records aboard both probes) when the camera is close to either *
* of the probes. For a more extensive example that shows how to create sounds in *
* SuperCollider based on the data from OpenSpace, see the planets-sonification.scd file *
* located in data\assets\modules\telemetry\sonification. This sonification was made by *
* Elias Elmquist. *
* *
* To run a SuperCollider file, click any line within the parentheses below, such as the *
* marked line. When you have selected a line, press the CTRL and ENTER keys on your *
* keyboard at the same time, and SuperCollider will run the code. You will see several *
* messages appear in the log to the right, the last message should be: *
* "---------- Sonification is ready ----------". At this point, SuperCollider is ready *
* to receive messages from OpenSpace and produce the sonification. For a guide on how *
* to enable and use the Telemetry module inside OpenSpace, see the documentation page: *
* https://docs.openspaceproject.com/latest/creating-data-assets/modules/telemetry/ *
* index.html *
****************************************************************************************/
(
// To run this example, click this line and press CTRL + ENTER on the keyboard
// When finished, press CTRL + . on you keyboard simultaneously to stop the sonification
s.quit;
o = Server.default.options;
// TODO Update this to use the angle calculation setting in OpenSpace
// Set the surround setting
// 0: Stereo (Binaurual)
// 1: VisC Dome
// 2: Hayden
~setup = 0;
if((~setup == 0),
{~numSpeakerChannels = 2;}
);
if((~setup == 1),
{o.outDevice_("ASIO : Focusrite USB ASIO");
~numSpeakerChannels = 8;
}
);
if((~setup == 2),
{o.outDevice_("ASIO : ASIO MADIface USB");
~numSpeakerChannels = 32;
}
);
o.numOutputBusChannels = ~numSpeakerChannels;
//3D
~ampMode = 1;
~focusType = 1;
~lockedFocus = 0;
~comparisonMode = 1;
// Setup which nodes to listen to in OpenSpace
~oscLabel0 = \Voyager_1;
~oscLabel1 = \Voyager_2;
s.reboot;
s.waitForBoot{
// Load the Greetings to the Universe audio clip (located next to this file)
var filePath = thisProcess.nowExecutingPath.dirname;
var loadSamples = {
~b1 = Buffer.read(s, filePath +/+ "Greetings_from_Earth_mono.wav");
// To load another audio clip, copy the line above, change the variable name
// and the path to the audio file
};
var loadSynths = {
// Create a sound function to play and loop the audio clip
SynthDef(
\audiofile,
{
arg out=0, gate=1, mamp=5, amp=0, baseAmp=1, buf, loop=0, rate=1, da=0, atk=1, dcay=0, suslvl=1, rel=1, mute=1, trig=0;
var sig=0, env;
env = EnvGen.kr(
Env.adsr(atk,dcay,suslvl,rel),
gate,
doneAction:da
);
sig = PlayBuf.ar(1, buf, rate:rate, loop:loop, trigger:trig);
sig = mamp * sig;
sig = mute.lag(5) * (baseAmp*amp) * sig * env;
Out.ar(out, sig);
}
).add;
// Setup Ambisonics
~order = 2; // set this to the order you want
~numChannels = ((~order + 1)**2).asInteger;
// binaural decoder (~numChannels -> 2) - reads from 'bus' and sums into 'out'
SynthDef.new(\binauralDecoder, { | bus, out = 0 |
Out.ar(out, VSTPlugin.ar(In.ar(bus, ~numChannels), 2));
}).add;
SynthDef.new(\allraDecoder, { | bus, out = 0 |
Out.ar(out, VSTPlugin.ar(In.ar(bus, ~numChannels), ~numSpeakerChannels));
}).add;
// stereo encoder (2 -> ~numChannels) - replaces stereo signal with ambisonics signal
SynthDef.new(\stereoEncoder, { | bus = 0 |
ReplaceOut.ar(bus, VSTPlugin.ar(In.ar(bus, 2), ~numChannels));
}).add;
// ambisonics insert FX Surround (replaces input with output)
SynthDef.new(\ambiFX, { | bus = 0, bypass |
ReplaceOut.ar(bus, VSTPlugin.ar(In.ar(bus, ~numChannels), ~numChannels, bypass));
}).add;
// helper Synth (throws audio from ambi bus to ambi master bus)
SynthDef.new(\ambiThrow, { | from, to |
Out.ar(to, In.ar(from, ~numChannels));
}).add;
};
var initiateAmbisonic = {
// bus + group
~ambiMasterBus = Bus.audio(s, ~numChannels);
~ambiMasterGroup = Group.new;
if((~setup == 0),
// binaural decoder (writes to master output)
{~decoder = VSTPluginController(Synth(\binauralDecoder, [\bus, ~ambiMasterBus, \out, 0], target: ~ambiMasterGroup, addAction: \addToTail)).open("BinauralDecoder");},
// AllRA decoder
{~decoder = VSTPluginController(Synth(\allraDecoder, [\bus, ~ambiMasterBus, \out, 0], target: ~ambiMasterGroup, addAction: \addToTail)).open("SimpleDecoder");}
);
// ambisonics insert FX (replaces input with output)
SynthDef.new(\ambiFX, { | bus = 0, bypass |
ReplaceOut.ar(bus, VSTPlugin.ar(In.ar(bus, 2), ~numChannels, bypass));
}).add;
// a group for ambisonic master effects
~ambiMasterFXGroup = Group.before(~decoder.synth);
};
var initiateSynths = {
// create ambisonic busses
~soundBus = Array.newClear(7);
~numBus = ~soundBus.size;
~ambiBus = Array.newClear(~numBus);
~ambiGroup = Array.newClear(~numBus);
~encoder = Array.newClear(~numBus);
~sounds = Array.newClear(~numBus);
// First Audio clip
~ambiBus[0] = Bus.audio(s, ~numChannels);
~ambiGroup[0] = Group.before(~ambiMasterGroup);
~sounds[0] = Synth.new(\audiofile, [\out, ~ambiBus[0], \buf, ~b1], ~ambiGroup[0], addAction: \addToHead);
~encoder[0] = VSTPluginController(Synth(\stereoEncoder, [\bus, ~ambiBus[0]], target: ~ambiGroup[0], addAction: \addToTail));
~encoder[0].open("StereoEncoder", action: { |self| self.set(6, 0.5) }); // 6 -> azimuth
Synth(\ambiThrow, [\from, ~ambiBus[0], \to, ~ambiMasterBus], target: ~ambiGroup[0], addAction: \addToTail);
// Second Audio clip (if needed)
/*~ambiBus[1] = Bus.audio(s, ~numChannels);
~ambiGroup[1] = Group.before(~ambiMasterGroup);
~sounds[1] = Synth.new(\audiofile, [\out, ~ambiBus[1], \buf, ~b2], ~ambiGroup[1], addAction: \addToHead);
~encoder[1] = VSTPluginController(Synth(\stereoEncoder, [\bus, ~ambiBus[1]], target: ~ambiGroup[1], addAction: \addToTail));
~encoder[1].open("StereoEncoder", action: { |self| self.set(6, 0.6) }); // 6 -> azimuth
Synth(\ambiThrow, [\from, ~ambiBus[1], \to, ~ambiMasterBus], target: ~ambiGroup[1], addAction: \addToTail);
//Audio mixing of the sounds (if needed)
~sounds[0].set(\baseAmp, 0.3);
~sounds[1].set(\baseAmp, 0.3);*/
// Debugging spatial position
//~encoder[0].editor;
~sounds[0].set(\baseAmp, 0.3);
// add an ambisonic master FX
~ambiReverb = VSTPluginController(Synth(\ambiFX, [\bus, ~ambiMasterBus, \out, 0],
target: ~ambiMasterFXGroup)).open("FdnReverb", action: { |self| self.set(1, 0.2) });
};
var loadDecoder = {
if((~setup == 1),
{
~decoder.iemPluginOSC("/SimpleDecoder/loadFile", thisProcess.nowExecutingPath.dirname +/+ "AmbiDecoders" +/+ "DomenVisC_5th.json");
0.1.wait;
~decoder.iemPluginOSC("/SimpleDecoder/swMode", 1);
0.1.wait;
~decoder.iemPluginOSC("/SimpleDecoder/swChannel", 4);
0.1.wait;
~decoder.iemPluginOSC("/SimpleDecoder/lowPassGain", -12);
}
);
if((~setup == 2),
{
~decoder.iemPluginOSC("/SimpleDecoder/loadFile", thisProcess.nowExecutingPath.dirname +/+ "AmbiDecoders" +/+ "HaydenIEM_5th_0deg_80top_rot180deg.json");
0.1.wait;
~decoder.iemPluginOSC("/SimpleDecoder/swMode", 1);
0.1.wait;
~decoder.iemPluginOSC("/SimpleDecoder/swChannel", 32);
0.1.wait;
}
);
};
// Set which OSC labels to listen to
var oscDef = {
//Horizontal mode
~modeElSign = 1;
~modeElPhase = 0.5;
//Circular mode (switch manually)
//~modeElSign = -1;
//~modeElPhase = 0.75;
//Size of hearable area
~distanceLimit = 0.5;
//Ambient amp
~ambientAmp = 1.0;
OSCdef.new(\Mode,
{
arg msg;
//msg[1] == 0 -> Horizontal
//msg[1] == 1 -> Circular
//msg[2] == 0 -> Elevation on
//msg[2] == 1 -> Elevation off
("SurroundMode:" + msg[1]).postln;
//Horizontal
if(((msg[1]==0)),
{~modeElSign = 1;
~modeElPhase = 0.5;},
{}
);
//Circular
if(((msg[1]==1)),
{~modeElSign = -1;
~modeElPhase = 0.75;},
{}
);
("~modeElSign:" + ~modeElSign).postln;
("~modeElPhase:" + ~modeElPhase).postln;
},'/Mode');
OSCdef.new(
~oscLabel0,
{
arg msg;
var name, distance, invDistance, azimuth, amp, value, elevation;
//msg[1]: label distance
//msg[2]: label azimuth
//msg[3]: label elevation
// DEBUG: Uncomment this line to get console output of all messages from OpenSpace
//(~oscLabel0 ++ ": " ++ msg).postln;
name = msg[0].asString;
distance = msg[1]/1000000;
//invDistance = ~distanceLimit-distance;
azimuth = (msg[2]/(2*pi))+0.5;
elevation = ~modeElSign*(msg[3]/(2*pi))+~modeElPhase;
if((~ampMode == 1),
{
if((distance < ~distanceLimit),
{amp = LinLin.kr(distance, 0, ~distanceLimit, 0, 1);
},
{amp = ~ambientAmp;}
);
}
);
(~oscLabel0 ++ "Amp: " ++ amp).postln;
~sounds[0].set(\amp, amp);
~encoder[0].do(_.set(6, azimuth));
~encoder[0].do(_.set(7, elevation));
},
~oscLabel0
);
OSCdef.new(
~oscLabel1,
{
arg msg;
var name, distance, invDistance, azimuth, amp, value, elevation;
//msg[1]: label distance
//msg[2]: label azimuth
//msg[3]: label elevation
// DEBUG: Uncomment this line to get console output of all messages from OpenSpace
//(~oscLabel1 ++ ": " ++ msg).postln;
name = msg[0].asString;
distance = msg[1]/1000000;
//invDistance = ~distanceLimit-distance;
azimuth = (msg[2]/(2*pi))+0.5;
elevation = ~modeElSign*(msg[3]/(2*pi))+~modeElPhase;
if((~ampMode == 1),
{
if((distance < ~distanceLimit),
{amp = LinLin.kr(distance, 0, ~distanceLimit, 0, 1);
},
{amp = ~ambientAmp;}
);
}
);
(~oscLabel1 ++ "Amp: " ++ amp).postln;
~sounds[0].set(\amp, amp);
~encoder[0].do(_.set(6, azimuth));
~encoder[0].do(_.set(7, elevation));
},
~oscLabel1
);
};
// Start routine
~startRoutine = Routine({
0.5.wait;
loadSamples.value;
"--Loaded the data--".postln;
0.2.wait;
loadSynths.value;
"--Loaded the synths--".postln;
0.3.wait;
initiateAmbisonic.value;
"--Loaded the ambisonics--".postln;
0.3.wait;
initiateSynths.value;
"--Initiated the synths--".postln;
0.2.wait;
loadDecoder.value;
"--Loaded the decoder--".postln;
0.2.wait;
oscDef.value;
"--Loaded the OSCdefs--".postln;
"---------- Sonification is ready ----------".postln;
});
~startRoutine.play(AppClock);
}
)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,81 @@
-- For more information about sonification in OpenSpace and how to use it, see the
-- documentation:
-- https://docs.openspaceproject.com/latest/creating-data-assets/modules/telemetry/sonification.html
asset.require("scene/solarsystem/planets/planets")
asset.onInitialize(function ()
-- Note that the planets need to be in this order
local planets = {
{
Name = "Mercury"
},
{
Name = "Venus"
},
{
Name = "Earth",
Moons = {
"Moon"
}
},
{
Name = "Mars",
Moons = {
"Phobos",
"Deimos"
}
},
{
Name = "Jupiter",
Moons = {
"Io",
"Europa",
"Ganymede",
"Callisto"
}
},
{
Name = "Saturn",
Moons = {
"Dione",
"Enceladus",
"Hyperion",
"Iapetus",
"Mimas",
"Rhea",
"Tethys",
"Titan"
}
},
{
Name = "Uranus",
Moons = {
"Ariel",
"Miranda",
"Oberon",
"Titania",
"Umbriel"
}
},
{
Name = "Neptune",
Moons = {
"Triton",
"Nereid"
}
}
}
openspace.sonification.addPlanets(planets)
end)
asset.meta = {
Name = "PlanetSonification planets",
Version = "1.0",
Description = "The list of planets in the PlanetSonification",
Author = "OpenSpace Team",
URL = "http://openspaceproject.com",
License = "MIT license"
}
+58
View File
@@ -0,0 +1,58 @@
##########################################################################################
# #
# OpenSpace #
# #
# Copyright (c) 2014-2025 #
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy of this #
# software and associated documentation files (the "Software"), to deal in the Software #
# without restriction, including without limitation the rights to use, copy, modify, #
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to #
# permit persons to whom the Software is furnished to do so, subject to the following #
# conditions: #
# #
# The above copyright notice and this permission notice shall be included in all copies #
# or substantial portions of the Software. #
# #
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, #
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A #
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT #
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF #
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE #
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #
##########################################################################################
include(${PROJECT_SOURCE_DIR}/support/cmake/module_definition.cmake)
set(HEADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/include/opensoundcontrolconnection.h
)
source_group("Header Files" FILES ${HEADER_FILES})
set(SOURCE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/src/opensoundcontrolconnection.cpp
)
source_group("Source Files" FILES ${SOURCE_FILES})
create_new_module(
"OpenSoundControl"
opensoundcontrol_module
STATIC
${HEADER_FILES}
${SOURCE_FILES}
)
begin_dependency("oscpack")
add_subdirectory(ext/oscpack SYSTEM)
set_target_properties(oscpack PROPERTIES FOLDER "External")
if (MSVC)
target_compile_options(oscpack PRIVATE "/W0")
target_compile_definitions(oscpack PRIVATE "_SCL_SECURE_NO_WARNINGS")
else ()
target_compile_options(oscpack PRIVATE "-w")
endif ()
target_include_directories(openspace-module-opensoundcontrol SYSTEM PRIVATE ext/oscpack)
target_link_libraries(openspace-module-opensoundcontrol PRIVATE oscpack)
end_dependency("oscpack")
@@ -0,0 +1,53 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_OPENSOUNDCONTROL___OPENSOUNDCONTROLCONNECTION___H__
#define __OPENSPACE_MODULE_OPENSOUNDCONTROL___OPENSOUNDCONTROLCONNECTION___H__
#include <modules/opensoundcontrol/ext/oscpack/ip/UdpSocket.h>
#include <modules/opensoundcontrol/ext/oscpack/osc/OscOutboundPacketStream.h>
#include <string>
#include <variant>
#include <vector>
namespace openspace {
using OpenSoundControlDataType = std::variant<osc::Blob, double, int, std::string>;
class OpenSoundControlConnection {
public:
OpenSoundControlConnection(const std::string& ip, int port);
~OpenSoundControlConnection();
void send(const std::string& label,
const std::vector<OpenSoundControlDataType>& data);
private:
UdpTransmitSocket _socket;
char* _buffer = nullptr;
osc::OutboundPacketStream _stream;
};
} // namespace openspace
#endif __OPENSPACE_MODULE_OPENSOUNDCONTROL___OPENSOUNDCONTROLCONNECTION___H__
@@ -0,0 +1,32 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/opensoundcontrol/opensoundcontrolmodule.h>
namespace openspace {
OpenSoundControlModule::OpenSoundControlModule() : OpenSpaceModule("OpenSoundControl") {}
} // namespace openspace
@@ -0,0 +1,42 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_OPENSOUNDCONTROL___OPENSOUNDCONTROLMODULE___H__
#define __OPENSPACE_MODULE_OPENSOUNDCONTROL___OPENSOUNDCONTROLMODULE___H__
#include "openspace/util/openspacemodule.h"
namespace openspace {
class OpenSoundControlModule : public OpenSpaceModule {
public:
constexpr static const char* Name = "OpenSoundControlModule";
OpenSoundControlModule();
~OpenSoundControlModule() = default;
};
} // namespace openspace
#endif __OPENSPACE_MODULE_OPENSOUNDCONTROL___OPENSOUNDCONTROLMODULE___H__
@@ -0,0 +1,82 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/opensoundcontrol/include/opensoundcontrolconnection.h>
#include <ghoul/logging/logmanager.h>
namespace {
constexpr std::string_view _loggerCat = "OpenSoundControlConnection";
constexpr int BufferSize = 1024;
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
} // namespace
namespace openspace {
OpenSoundControlConnection::OpenSoundControlConnection(const std::string& ip, int port)
: _socket(IpEndpointName(ip.c_str(), port))
, _buffer(new char[BufferSize])
, _stream(osc::OutboundPacketStream(_buffer, BufferSize))
{
}
OpenSoundControlConnection::~OpenSoundControlConnection() {
delete[] _buffer;
}
void OpenSoundControlConnection::send(const std::string& label,
const std::vector<OpenSoundControlDataType>& data)
{
if (label.empty()) {
LERROR("Cannot send Open Sound Control message without a label");
return;
}
_stream.Clear();
_stream << osc::BeginMessage(label.c_str());
for (const OpenSoundControlDataType& item : data) {
std::visit(overloaded {
[this](const osc::Blob& value) {
_stream << value;
},
[this](double value) {
_stream << value;
},
[this](int value) {
_stream << value;
},
[this](const std::string& value) {
_stream << value.c_str();
}
}, item);
}
_stream << osc::EndMessage;
_socket.Send(_stream.Data(), _stream.Size());
}
} // namespace openspace
+63
View File
@@ -0,0 +1,63 @@
##########################################################################################
# #
# OpenSpace #
# #
# Copyright (c) 2014-2025 #
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy of this #
# software and associated documentation files (the "Software"), to deal in the Software #
# without restriction, including without limitation the rights to use, copy, modify, #
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to #
# permit persons to whom the Software is furnished to do so, subject to the following #
# conditions: #
# #
# The above copyright notice and this permission notice shall be included in all copies #
# or substantial portions of the Software. #
# #
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, #
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A #
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT #
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF #
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE #
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #
##########################################################################################
include(${PROJECT_SOURCE_DIR}/support/cmake/module_definition.cmake)
set(HEADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/include/general/anglemodetelemetry.h
${CMAKE_CURRENT_SOURCE_DIR}/include/general/cameratelemetry.h
${CMAKE_CURRENT_SOURCE_DIR}/include/general/focustelemetry.h
${CMAKE_CURRENT_SOURCE_DIR}/include/general/nodestelemetry.h
${CMAKE_CURRENT_SOURCE_DIR}/include/general/timetelemetry.h
${CMAKE_CURRENT_SOURCE_DIR}/include/specific/planetscomparesonification.h
${CMAKE_CURRENT_SOURCE_DIR}/include/specific/planetsoverviewsonification.h
${CMAKE_CURRENT_SOURCE_DIR}/include/specific/planetssonification.h
${CMAKE_CURRENT_SOURCE_DIR}/include/telemetrybase.h
${CMAKE_CURRENT_SOURCE_DIR}/include/util.h
)
source_group("Header Files" FILES ${HEADER_FILES})
set(SOURCE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/src/general/anglemodetelemetry.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/general/cameratelemetry.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/general/focustelemetry.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/general/nodestelemetry.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/general/nodestelemetry_lua.inl
${CMAKE_CURRENT_SOURCE_DIR}/src/general/timetelemetry.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/specific/planetscomparesonification.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/specific/planetsoverviewsonification.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/specific/planetssonification.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/specific/planetssonification_lua.inl
${CMAKE_CURRENT_SOURCE_DIR}/src/telemetrybase.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/util.cpp
)
source_group("Source Files" FILES ${SOURCE_FILES})
create_new_module(
"Telemetry"
telemetry_module
STATIC
${HEADER_FILES}
${SOURCE_FILES}
)
+3
View File
@@ -0,0 +1,3 @@
set (OPENSPACE_DEPENDENCIES
opensoundcontrol
)
@@ -0,0 +1,64 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_TELEMETRY___ANGLEMODETELEMETRY___H__
#define __OPENSPACE_MODULE_TELEMETRY___ANGLEMODETELEMETRY___H__
#include <modules/telemetry/include/telemetrybase.h>
#include <modules/telemetry/telemetrymodule.h>
namespace openspace {
class AngleModeTelemetry : public TelemetryBase {
public:
AngleModeTelemetry(const std::string& ip, int port);
~AngleModeTelemetry() override = default;
private:
/**
* Gather angle settings telemetry information (Angle calculation mode, and whether
* the elevation angle is included).
*
* \param camera The camera in the scene (not used in this case)
* \return `true` if the data is new compared to before, otherwise `false`
*/
bool updateData(const Camera* camera) override;
/**
* Send the current angle settings telemetry information to the Open Sound Control
* receiver. The order of sent data is as follows: Angle calculation mode, and whether
* the elevation angle is included or not.
*/
void sendData() override;
TelemetryModule::AngleCalculationMode _angleMode =
TelemetryModule::AngleCalculationMode::Horizontal;
bool _includeElevation = false;
bool _isInitialized = false;
};
} // namespace openspace
#endif __OPENSPACE_MODULE_TELEMETRY___ANGLEMODETELEMETRY___H__
@@ -0,0 +1,74 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_TELEMETRY___CAMERATELEMETRY___H__
#define __OPENSPACE_MODULE_TELEMETRY___CAMERATELEMETRY___H__
#include <modules/telemetry/include/telemetrybase.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/scalar/doubleproperty.h>
namespace openspace {
class CameraTelemetry : public TelemetryBase {
public:
CameraTelemetry(const std::string& ip, int port);
~CameraTelemetry() override = default;
private:
/**
* Gather camera telemetry information (position, rotation, and speed).
*
* \param camera The camera in the scene
* \return `true` if the data is new compared to before, otherwise `false`
*/
bool updateData(const Camera* camera) override;
/**
* Send the current camera telemetry information to the Open Sound Control
* receiver. The order of sent data is as follows: Camera position (x, y, z), rotation
* quaternion (w, x, y z), speed, and speed distance unit.
*/
void sendData() override;
struct PrecisionProperties : properties::PropertyOwner {
PrecisionProperties(properties::PropertyOwner::PropertyOwnerInfo precisionInfo);
properties::DoubleProperty positionPrecision;
properties::DoubleProperty rotationPrecision;
properties::DoubleProperty speedPrecision;
};
properties::OptionProperty _cameraSpeedDistanceUnitOption;
PrecisionProperties _precisionProperties;
glm::dvec3 _cameraPosition = glm::dvec3(0.0);
glm::dquat _cameraRotation = glm::dquat(0.0, 0.0, 0.0, 0.0);
double _cameraSpeed = 0.0;
};
} // namespace openspace
#endif __OPENSPACE_MODULE_TELEMETRY___CAMERATELEMETRY___H__
@@ -0,0 +1,59 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_TELEMETRY___FOCUSTELEMETRY___H__
#define __OPENSPACE_MODULE_TELEMETRY___FOCUSTELEMETRY___H__
#include <modules/telemetry/include/telemetrybase.h>
#include <openspace/properties/misc/optionproperty.h>
namespace openspace {
class FocusTelemetry : public TelemetryBase {
public:
FocusTelemetry(const std::string& ip, int port);
~FocusTelemetry() override = default;
private:
/**
* Gather focus telemetry information (current focus node).
*
* \param camera The camera in the scene (not used in this case)
* \return `true` if the data is new compared to before, otherwise `false`
*/
bool updateData(const Camera* camera) override;
/**
* Send the current focus telemetry information to the Open Sound Control
* receiver. The order of sent data is as follows: Current focus node.
*/
void sendData() override;
std::string _currentFocus;
};
} // namespace openspace
#endif __OPENSPACE_MODULE_TELEMETRY___FOCUSTELEMETRY___H__
@@ -0,0 +1,147 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_TELEMETRY___NODESTELEMETRY___H__
#define __OPENSPACE_MODULE_TELEMETRY___NODESTELEMETRY___H__
#include <modules/telemetry/include/telemetrybase.h>
#include <modules/telemetry/telemetrymodule.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/scalar/doubleproperty.h>
namespace openspace {
namespace scripting { struct LuaLibrary; }
class NodesTelemetry : public TelemetryBase {
public:
NodesTelemetry(const std::string& ip, int port);
~NodesTelemetry() override = default;
/**
* Main update function to gather telemetry information from a list of scene graph
* nodes (distance, horizontal angle, vertical angle) and send it to the Open Sound
* Control receiver.
*
* \param camera The camera in the scene
*/
void update(const Camera* camera) override;
/**
* Add the given node to the list of nodes to gather telemetry data for.
*
* \param node The identifier of the node that should be added
*/
void addNode(std::string node);
/**
* Returns the Lua library that contains all Lua functions available for the
* nodes telemetry.
*
* \return The Lua library that contains all Lua functions available for the
* nodes telemetry
*/
static scripting::LuaLibrary luaLibrary();
private:
static constexpr int NumDataItems = 4;
// Struct to hold data for all the nodes
struct TelemetryNode {
TelemetryNode(std::string id);
std::string identifier;
// Distance, horizontal angle, vertical angle (do not store the distance unit
// here, the option property stores it instead)
std::vector<double> data = std::vector<double>(NumDataItems - 1, 0.0);
};
/**
* For this telemetry, a more advanced custom updateData function is needed with
* additional arguments. Therefore, this implementation is left empty and the update
* function is overriden to use the custom updateData function instead.
*
* \param camera The camera in the scene (not used in this case)
* \return Always return `false` (this function is empty)
*/
bool updateData(const Camera* camera) override;
/**
* For this telemetry, a more advanced custom sendData function is needed with
* additional arguments. Therefore, this implementation is left empty and the update
* function is overriden to use the custom updateData function instead.
*/
void sendData() override;
/**
* Update telemetry data (distance, horizontal angle, vertical angle) for the given
* node.
*
* \param camera The camera in the scene
* \param nodeIndex The index to the internally stored node data that should be
* updated
* \param angleCalculationMode The angle calculation mode to use. This determines
* which method to use when calculating the angle.
* \param includeElevation Whether the additional elevation angle should be calculated
*
* \return `true` if the data is new compared to before, otherwise `false`
*/
bool updateData(const Camera* camera, int nodeIndex,
TelemetryModule::AngleCalculationMode angleCalculationMode,
bool includeElevation);
/**
* Send current telemetry data for the indicated node to the Open Sound Control
* receiver. The order of sent data is as follows: distance, horizontal angle,
* vertical angle, and the unit used for the distance value
*/
void sendData(int nodeIndex);
struct PrecisionProperties : properties::PropertyOwner {
PrecisionProperties(properties::PropertyOwner::PropertyOwnerInfo precisionInfo);
// The low and high precision values are used in different situations. When the
// node is the current focus node, then the high precision value is used. This
// is due to the node being in the current focus and therfore needs better
// precision. If the node is not the current focus node, then the low precision
// value is used to save performance.
properties::DoubleProperty lowDistancePrecision;
properties::DoubleProperty highDistancePrecision;
properties::DoubleProperty lowAnglePrecision;
properties::DoubleProperty highAnglePrecision;
};
properties::OptionProperty _distanceUnitOption;
PrecisionProperties _precisionProperties;
std::vector<TelemetryNode> _nodes;
double _anglePrecision = 0.0;
double _distancePrecision = 0.0;
};
} // namespace openspace
#endif __OPENSPACE_MODULE_TELEMETRY___NODESTELEMETRY___H__
@@ -0,0 +1,71 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_TELEMETRY___TIMETELEMETRY___H__
#define __OPENSPACE_MODULE_TELEMETRY___TIMETELEMETRY___H__
#include <modules/telemetry/include/telemetrybase.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/scalar/doubleproperty.h>
namespace openspace {
class TimeTelemetry : public TelemetryBase {
public:
TimeTelemetry(const std::string& ip, int port);
~TimeTelemetry() override = default;
private:
/**
* Gather time telemetry information (speed, and current time).
*
* \param camera The camera in the scene (not used in this case)
* \return `true` if the data is new compared to before, otherwise `false`
*/
bool updateData(const Camera* camera) override;
/**
* Send the current time telemetry information to the Open Sound Control receiver. The
* order of sent data is as follows: Time speed, unit of time speed, and current
* simulation time in J2000 number of seconds.
*/
void sendData() override;
struct PrecisionProperties : properties::PropertyOwner {
PrecisionProperties(properties::PropertyOwner::PropertyOwnerInfo precisionInfo);
properties::DoubleProperty timePrecision;
};
properties::OptionProperty _timeUnitOption;
PrecisionProperties _precisionProperties;
double _timeSpeed = 0.0;
double _currentTime = 0.0;
};
} // namespace openspace
#endif __OPENSPACE_MODULE_TELEMETRY___TIMETELEMETRY___H__
@@ -0,0 +1,118 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_TELEMETRY___PLANETSCOMPARESONIFICATION___H__
#define __OPENSPACE_MODULE_TELEMETRY___PLANETSCOMPARESONIFICATION___H__
#include <modules/telemetry/include/telemetrybase.h>
#include <openspace/properties/scalar/doubleproperty.h>
#include <openspace/properties/misc/optionproperty.h>
namespace openspace {
class PlanetsCompareSonification : public TelemetryBase {
public:
PlanetsCompareSonification(const std::string& ip, int port);
~PlanetsCompareSonification() override = default;
/**
* Function to stop the sonification.
*/
void stop() override;
private:
/**
* Create an osc::Blob object with the current compare planets sonification settings.
* Order of settings: Size/day, gravity, temperature, atmosphere, moons, rings.
*
* \return An osc::Blob object with the current compare planets sonification settings
*/
osc::Blob createSettingsBlob() const;
/**
* For this sonification, this implementiation is empty since the GUI properties keep
* track of tha data with the `onChange` function.
*
* \param camera The camera in the scene (not used in this case)
* \return `true` if the data was updated, otherwise `false`
*/
bool updateData(const Camera* camera) override;
/**
* Send current compare planets sonification settings to the Open Sound Control
* receiver. The order of sent data is as follows: Name of the first selected planet,
* name of the second selected planet, and compare planets settings.
*/
void sendData() override;
/**
* Function that gets called when either the first or second planet selection
* was changed with the GUI.
*
* \param changedPlanet The planet selection that was recently changed
* \param otherPlanet The other planet selection that was NOT changed
* \param prevChangedPlanet The previous value of the planet that was changed
*/
void onPlanetSelectionChanged(properties::OptionProperty& changedPlanet,
properties::OptionProperty& otherPlanet, std::string& prevChangedPlanet);
/**
* Function that scales the given planet by the given amount over the given amount of
* seconds.
*
* \param planet The identifer of the planet that should be scaled
* \param scale The amount that the planet should be scaled with as a multiplier of
* the original size
* \param interpolationTime The amount of time in seconds to interpolate to the new
* scale for the planet
*/
void scalePlanet(const std::string& planet, double scale,
double interpolationTime);
void onUpscaleChanged();
void onFirstChanged();
void onSecondChanged();
void onToggleAllChanged();
properties::DoubleProperty _selectedUpscale;
properties::DoubleProperty _selectedScaleInterpolationTime;
properties::OptionProperty _firstPlanet;
properties::OptionProperty _secondPlanet;
properties::BoolProperty _toggleAll;
properties::BoolProperty _sizeDayEnabled;
properties::BoolProperty _gravityEnabled;
properties::BoolProperty _temperatureEnabled;
properties::BoolProperty _atmosphereEnabled;
properties::BoolProperty _moonsEnabled;
properties::BoolProperty _ringsEnabled;
std::string _oldFirst;
std::string _oldSecond;
};
} // namespace openspace
#endif __OPENSPACE_MODULE_SONIFICATION___PLANETSCOMPARESONIFICATION___H__
@@ -0,0 +1,81 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_TELEMETRY___PLANETSOVERVIEWSONIFICATION___H__
#define __OPENSPACE_MODULE_TELEMETRY___PLANETSOVERVIEWSONIFICATION___H__
#include <modules/telemetry/include/telemetrybase.h>
namespace openspace {
class PlanetsOverviewSonification : public TelemetryBase {
public:
PlanetsOverviewSonification(const std::string& ip, int port);
~PlanetsOverviewSonification() override = default;
/**
* Function to stop the sonification.
*/
void stop() override;
private:
/**
* Create an osc::Blob object with the current planets overview sonification settings.
* Order of settings: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune.
*
* \return An osc::Blob object with the current planets overview sonification settings
*/
osc::Blob createSettingsBlob() const;
/**
* For this sonification, this implementiation is empty since the GUI properties keep
* track of tha data with the `onChange` function.
*
* \param camera The camera in the scene (not used in this case)
* \return `true` if the data was updated, otherwise `false`
*/
bool updateData(const Camera* camera) override;
/**
* Send current planets overview sonification settings to the Open Sound Control
* receiver. The order of sent data is as follows: Planets overview settings.
*/
void sendData() override;
void onToggleAllChanged();
properties::BoolProperty _toggleAll;
properties::BoolProperty _mercuryEnabled;
properties::BoolProperty _venusEnabled;
properties::BoolProperty _earthEnabled;
properties::BoolProperty _marsEnabled;
properties::BoolProperty _jupiterEnabled;
properties::BoolProperty _saturnEnabled;
properties::BoolProperty _uranusEnabled;
properties::BoolProperty _neptuneEnabled;
};
} // namespace openspace
#endif __OPENSPACE_MODULE_TELEMETRY___PLANETSOVERVIEWSONIFICATION___H__
@@ -0,0 +1,211 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_TELEMETRY___PLANETSSONIFICATION___H__
#define __OPENSPACE_MODULE_TELEMETRY___PLANETSSONIFICATION___H__
#include <modules/telemetry/include/telemetrybase.h>
#include <modules/telemetry/telemetrymodule.h>
#include <openspace/properties/scalar/doubleproperty.h>
namespace openspace {
namespace scripting { struct LuaLibrary; }
class PlanetsSonification : public TelemetryBase {
public:
PlanetsSonification(const std::string& ip, int port);
~PlanetsSonification() override = default;
/**
* Main update function to gather planets telemetry information (distance, horizontal
* angle, vertical angle, distance to moons, horizontal angle to moons, and vertical
* angle to moons) for the planets sonification and send it to the Open Sound Control
* receiver.
*
* \param camera The camera in the scene
*/
void update(const Camera* camera) override;
/**
* Function to stop the sonification.
*/
void stop() override;
/**
* Add the given planet information to the list of planets and their moons.
*
* \param dict The planet that should be added
*/
void addPlanet(const ghoul::Dictionary& dict);
/**
* Returns the Lua library that contains all Lua functions available to change the
* planets sonification.
*
* \return The Lua library that contains all Lua functions available to change the
* planets sonification
*/
static scripting::LuaLibrary luaLibrary();
private:
// To hold all the data for a planet or a moon, including a list of data for the
// moons of the planet. However, in the case of a moon, then the list of moons
// is empty
struct DataBody {
DataBody(std::string inName);
std::string name;
double distance = 0.0;
double horizontalAngle = 0.0;
double verticalAngle = 0.0;
// List of moons that orbit this planet (in the case that this is a moon, then
// this list is empty)
std::vector<DataBody> moons;
};
/**
* Create an osc::Blob object with the current sonification settings for the indicated
* planet.
* Order of settings: Size/day, gravity, temperature, and optionally atmosphere, moons,
* and rings.
*
* \param planetIndex The index of the planet to create the settings blob for
* \return An osc::Blob object with current sonification settings for the indicated
* planet
*/
osc::Blob createSettingsBlob(int planetIndex) const;
/**
* For this sonification, a more advanced custom updateData function is needed with
* additional arguments. Therefore, this implementation is left empty and the update
* function is overriden to use the custom updateData function instead.
*
* \param camera The camera in the scene (not used in this case)
* \return Always return `false` (this function is empty)
*/
bool updateData(const Camera* camera) override;
/**
* For this sonification, a more advanced custom sendData function is needed with
* additional arguments. Therefore, this implementation is left empty and the update
* function is overriden to use the custom updateData function instead.
*/
void sendData() override;
/**
* Update the distance and angle information for the given planet.
*
* \param camera The camera in the scene
* \param planetIndex The index to the internally stored planet data that should be
* updated
* \param angleCalculationMode The angle calculation mode to use. This determines
* which method to use when calculating the angle.
* \param includeElevation Whether the additional elevation angle should be calculated
*
* \return `true` if the data is new compared to before, otherwise `false`
*/
bool updateData(const Camera* camera, int planetIndex,
TelemetryModule::AngleCalculationMode angleCalculationMode,
bool includeElevation);
/**
* Send the current sonification settings for the indicated planet to the Open Sound
* Control receiver. The order of sent data is as follows: distance, horizontal angle,
* vertical angle, settings, and data for each moon in order of distance to the planet
* (distance, horizontal angle, and vertical angle).
*/
void sendData(int planetIndex);
void onToggleAllChanged();
void onMercuryAllChanged();
void onMercurySettingChanged();
void onVenusAllChanged();
void onVenusSettingChanged();
void onEarthAllChanged();
void onEarthSettingChanged();
void onMarsAllChanged();
void onMarsSettingChanged();
void onJupiterAllChanged();
void onJupiterSettingChanged();
void onSaturnAllChanged();
void onSaturnSettingChanged();
void onUranusAllChanged();
void onUranusSettingChanged();
void onNeptuneAllChanged();
void onNeptuneSettingChanged();
struct PlanetProperties : properties::PropertyOwner {
PlanetProperties(properties::PropertyOwner::PropertyOwnerInfo planetInfo);
// All planets have these settings
properties::BoolProperty toggleAll;
properties::BoolProperty sizeDayEnabled;
properties::BoolProperty gravityEnabled;
properties::BoolProperty temperatureEnabled;
// Only some planets have some of these settings
properties::BoolProperty atmosphereEnabled;
properties::BoolProperty moonsEnabled;
properties::BoolProperty ringsEnabled;
};
struct PrecisionProperties : properties::PropertyOwner {
PrecisionProperties(properties::PropertyOwner::PropertyOwnerInfo precisionInfo);
// The low and high precision values are used in different situations. When the
// planet is the current focus node, then the high precision value is used. This
// is due to the sonification and planet being in the current focus and should
// therefore have better precision. If the planet is not the current focus node,
// then the low precision value is used to save performance, both on the
// OpenSpace side and the receiving side.
properties::DoubleProperty lowDistancePrecision;
properties::DoubleProperty highDistancePrecision;
properties::DoubleProperty lowAnglePrecision;
properties::DoubleProperty highAnglePrecision;
};
properties::BoolProperty _toggleAll;
PlanetProperties _mercuryProperty;
PlanetProperties _venusProperty;
PlanetProperties _earthProperty;
PlanetProperties _marsProperty;
PlanetProperties _jupiterProperty;
PlanetProperties _saturnProperty;
PlanetProperties _uranusProperty;
PlanetProperties _neptuneProperty;
PrecisionProperties _precisionProperties;
std::vector<DataBody> _planets;
double _anglePrecision;
double _distancePrecision;
bool _overviewEnabled = false;
};
} // namespace openspace
#endif __OPENSPACE_MODULE_TELEMETRY___PLANETSSONIFICATION___H__
+87
View File
@@ -0,0 +1,87 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_TELEMETRY___TELEMETRYBASE___H__
#define __OPENSPACE_MODULE_TELEMETRY___TELEMETRYBASE___H__
#include <openspace/properties/propertyowner.h>
#include <modules/opensoundcontrol/include/opensoundcontrolconnection.h>
#include <openspace/camera/camera.h>
#include <openspace/properties/scalar/boolproperty.h>
namespace openspace {
class TelemetryBase : public properties::PropertyOwner {
public:
TelemetryBase(properties::PropertyOwner::PropertyOwnerInfo info,
const std::string& ip, int port);
virtual ~TelemetryBase();
/**
* Main update function to gather telemetry data and send it to the Open Sound Control
* receiver.
*
* \param camera The camera in the scene
*/
virtual void update(const Camera* camera);
/**
* Function to stop the gathering of telemetry data.
*/
virtual void stop();
/**
* \return The identifier of the telemetry class, the type of data that is gathered
* and sent
*/
std::string identifier() const;
/**
* \return Whether the telemetry monitoring is enabled or not for this type of data
*/
bool enabled() const;
protected:
/**
* Gather telemetry information and update any internal data storage.
*
* \param camera The camera in the scene
* \return `true` if the data was updated, otherwise `false`
*/
virtual bool updateData(const Camera* camera) = 0;
/**
* Send the current telemetry information to the Open Sound Control receiver.
*/
virtual void sendData() = 0;
std::string _identifier;
properties::BoolProperty _enabled;
OpenSoundControlConnection* _connection = nullptr;
};
} // namespace openspace
#endif __OPENSPACE_MODULE_TELEMETRY___TELEMETRYBASE___H__
+206
View File
@@ -0,0 +1,206 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_TELEMETRY___UTIL___H__
#define __OPENSPACE_MODULE_TELEMETRY___UTIL___H__
#include <modules/telemetry/telemetrymodule.h>
#include <openspace/camera/camera.h>
#include <openspace/util/distanceconversion.h>
namespace openspace {
/**
* Calculate the distance from the camera to the node with the given identifier, in the
* given distance unit.
*
* \param camera Pointer to the camera in the scene that the distance should be calculated
* from
* \param nodeIdentifier The identifier of the node that the distance should be calculated
* to
* \param unit The distance unit that the answer should be in, the default is meters
*
* \return The distance from the camera to the node with the given identifier in the given
* distance unit
*/
double calculateDistanceTo(const Camera* camera, const std::string& nodeIdentifier,
DistanceUnit unit = DistanceUnit::Meter);
/**
* Calculate the distance from the camera to the node with the given position, in the given
* distance unit.
*
* \param camera Pointer to the camera in the scene that the distance should be calculated
* from
* \param nodePosition The world position of the node that the distance should be
* calculated to
* \param unit The distance unit that the answer should be in, the default is meters
*
* \return The distance from the camera to the node with the given position in the given
* distance unit
*/
double calculateDistanceTo(const Camera* camera, glm::dvec3 nodePosition,
DistanceUnit unit = DistanceUnit::Meter);
/**
* Calculate the angle in radians from the camera to the node with the given identifier.
*
* \param camera Pointer to the camera in the scene that the angle should be calculated
* from
* \param nodeIdentifier The identifier of the node, that the angle should be calculated to
* \param angleCalculationMode The angle calculation mode to use. This determines which
* method to use when calculating the angle.
*
* \return The angle in radians from the camera to the node with the given identifier
*/
double calculateAngleTo(const Camera* camera, const std::string& nodeIdentifier,
TelemetryModule::AngleCalculationMode angleCalculationMode);
/**
* Calculate the angle in radians from the camera to the node with the given position.
*
* \param camera Pointer to the camera in the scene that the angle should be calculated
* from
* \param nodePosition The position of the node, that the angle should be calculated to
* \param angleCalculationMode The angle calculation mode to use. This determines which
* method to use when calculating the angle.
*
* \return The angle in radians from the camera to the node with the given position
*/
double calculateAngleTo(const Camera* camera, glm::dvec3 nodePosition,
TelemetryModule::AngleCalculationMode angleCalculationMode);
/**
* Calculate the angle in radians from the first node (A) with the given identifier to the
* second node (B) with the given identifier.
*
* \param camera Pointer to the camera in the scene
* \param nodeIdentifierA The identifier of the first node (A) that the angle should be
* calculated from
* \param nodeIdentifierB The identifier of the second node (B) that the angle should be
* calculated to
* \param angleCalculationMode The angle calculation mode to use. This determines which
* method to use when calculating the angle.
*
* \return The angle in radians from the first node (A) with the given identifier to the
* second node (B) with the given identifier
*/
double calculateAngleFromAToB(const Camera* camera,
const std::string& nodeIdentifierA, const std::string& nodeIdentifierB,
TelemetryModule::AngleCalculationMode angleCalculationMode);
/**
* Calculate the angle in radians from the first node (A) with the given position to the
* second node (B) with the given position.
*
* \param camera Pointer to the camera in the scene
* \param nodePositionA The position of the first node (A) that the angle should be
* calculated from
* \param nodePositionB The position of the second node (B) that the angle should be
* calculated to
* \param angleCalculationMode The angle calculation mode to use. This determines which
* method to use when calculating the angle.
*
* \return The angle in radians from the first node (A) with the given position to the
* second node (B) with the given position
*/
double calculateAngleFromAToB(const Camera* camera, glm::dvec3 nodePositionA,
glm::dvec3 nodePositionB,
TelemetryModule::AngleCalculationMode angleCalculationMode);
/**
* Calculate the elevation angle in radians from the camera to the node with the given
* identifier.
*
* \param camera Pointer to the camera in the scene that the elevation angle should be
* calculated from
* \param nodeIdentifier The identifier of the node that the elevation angle should be
* calculated to
* \param angleCalculationMode The angle calculation mode to use. This determines which
* method to use when calculating the angle.
*
* \return The elevation angle in radians from the camera to the node with the given
* identifier
*/
double calculateElevationAngleTo(const Camera* camera,
const std::string& nodeIdentifier,
TelemetryModule::AngleCalculationMode angleCalculationMode);
/**
* Calculate the elevation angle in radians from the camera to the node with the given
* position.
*
* \param camera Pointer to the camera in the scene that the elevation angle should be
* calculated from
* \param nodePosition The position of the node that the elevation angle should be
* calculated to
* \param angleCalculationMode The angle calculation mode to use. This determines which
* method to use when calculating the angle.
*
* \return The elevation angle in radians from the camera to the node with the given
* position
*/
double calculateElevationAngleTo(const Camera* camera, glm::dvec3 nodePosition,
TelemetryModule::AngleCalculationMode angleCalculationMode);
/**
* Calculate the elevation angle in radians from the first node (A) with the given
* identifier to the second node (B) with the given identifier.
*
* \param camera Pointer to the camera in the scene
* \param nodeIdentifierA The identifier of the first node (A) that the elevation angle
* should be calculated from
* \param nodeIdentifierB The identifier of the second node (B) that the elevation angle
* should be calculated to
* \param angleCalculationMode The angle calculation mode to use. This determines which
* method to use when calculating the angle.
*
* \return The elevation angle in radians from the first node (A) with the given identifier
* to the second node (B) with the given identifier
*/
double calculateElevationAngleFromAToB(const Camera* camera,
const std::string& nodeIdentifierA, const std::string& nodeIdentifierB,
TelemetryModule::AngleCalculationMode angleCalculationMode);
/**
* Calculate the elevation angle in radians from the first node (A) with the given position
* to the second node (B) with the given position.
*
* \param camera Pointer to the camera in the scene
* \param nodePositionA The position of the first node (A) that the elevation angle should
* be calculated from
* \param nodePositionB The position of the second node (B) that the elevation angle should
* be calculated to
* \param angleCalculationMode The angle calculation mode to use. This determines which
* method to use when calculating the angle.
*
* \return The elevation angle in radians from the first node (A) with the given position
* to the second node (B) with the given position
*/
double calculateElevationAngleFromAToB(const Camera* camera,
glm::dvec3 nodePositionA, glm::dvec3 nodePositionB,
TelemetryModule::AngleCalculationMode angleCalculationMode);
} // namespace openspace
#endif __OPENSPACE_MODULE_TELEMETRY___UTIL___H__
@@ -0,0 +1,100 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/telemetry/include/general/anglemodetelemetry.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/moduleengine.h>
#include <ghoul/logging/logmanager.h>
namespace {
constexpr std::string_view _loggerCat = "AngleModeTelemetry";
// Indices for data items
constexpr int NumDataItems = 2;
constexpr int AngleModeIndex = 0;
constexpr int IncludeElevationIndex = 1;
static const openspace::properties::PropertyOwner::PropertyOwnerInfo
AngleModeTelemetryInfo =
{
"AngleModeTelemetry",
"Angle Mode Telemetry",
"Telemetry that gathers data of what angle calculation mode is currently used for "
"all telemetries in the telemetry module"
};
} // namespace
namespace openspace {
AngleModeTelemetry::AngleModeTelemetry(const std::string& ip, int port)
: TelemetryBase(AngleModeTelemetryInfo, ip, port)
{}
bool AngleModeTelemetry::updateData(const Camera*) {
// Get the current angle settings
TelemetryModule* module = global::moduleEngine->module<TelemetryModule>();
if (!module) {
LERROR("Could not find the TelemetryModule");
return false;
}
TelemetryModule::AngleCalculationMode angleMode = module->angleCalculationMode();
bool includeElevation = module->includeElevationAngle();
// Check if this data is new, otherwise don't update it
TelemetryModule::AngleCalculationMode prevAngleMode = _angleMode;
bool prevIncludeElevation = _includeElevation;
bool dataWasUpdated = false;
if (angleMode != prevAngleMode) {
_angleMode = angleMode;
dataWasUpdated = true;
}
if (includeElevation != prevIncludeElevation) {
_includeElevation = includeElevation;
dataWasUpdated = true;
}
// Make sure that the first message is sent, even if the values are default and no
// change has been detected
if (!_isInitialized) {
_isInitialized = true;
return true;
}
return dataWasUpdated;
}
void AngleModeTelemetry::sendData() {
std::string label = "/Mode";
std::vector<OpenSoundControlDataType> data(NumDataItems);
data[AngleModeIndex] = static_cast<int>(_angleMode);
data[IncludeElevationIndex] = static_cast<int>(_includeElevation);
_connection->send(label, data);
}
} // namespace openspace
@@ -0,0 +1,199 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/telemetry/include/general/cameratelemetry.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/util/distanceconversion.h>
namespace {
// Indices for data items
constexpr int NumDataItems = 9;
constexpr int CameraPosXIndex = 0;
constexpr int CameraPosYIndex = 1;
constexpr int CameraPosZIndex = 2;
constexpr int CameraQuatRotWIndex = 3;
constexpr int CameraQuatRotXIndex = 4;
constexpr int CameraQuatRotYIndex = 5;
constexpr int CameraQuatRotZIndex = 6;
constexpr int CameraSpeedIndex = 7;
constexpr int CameraSpeedUnitIndex = 8;
static const openspace::properties::PropertyOwner::PropertyOwnerInfo
CameraTelemetryInfo =
{
"CameraTelemetryInfo",
"Camera Telemetry",
"Telemetry that sends out camera information to the Open Sound Control receiver."
};
constexpr openspace::properties::Property::PropertyInfo CameraSpeedDistanceUnitInfo =
{
"CameraSpeedDistanceUnit",
"Camera Speed Unit (Distance)",
"Choose a distance unit that is used for the camera speed. "
"For example, if the distacne unit 'Kilometer' is chosen, then the unit used for "
"the camera speed will be kilometers per second.",
openspace::properties::Property::Visibility::User
};
const openspace::properties::PropertyOwner::PropertyOwnerInfo PrecisionInfo = {
"Precision",
"Precision",
"Settings for the precision of the camera telemetry information."
};
constexpr openspace::properties::Property::PropertyInfo PositionPrecisionInfo = {
"PositionPrecision",
"Position Precision",
"The precision in meters used to determin when to send updated camera positional "
"data to the Open Sound Control receiver.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo RotationPrecisionInfo = {
"RotationPrecision",
"Rotation Precision",
"The precision used to determin when to send updated camera rotational "
"data to the Open Sound Control receiver.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SpeedPrecisionInfo = {
"SpeedPrecision",
"Speed Precision",
"The precision in meters per second used to determin when to send updated camera "
"speed data to the Open Sound Control receiver.",
openspace::properties::Property::Visibility::User
};
} // namespace
namespace openspace {
CameraTelemetry::CameraTelemetry(const std::string& ip, int port)
: TelemetryBase(CameraTelemetryInfo, ip, port)
, _cameraSpeedDistanceUnitOption(CameraSpeedDistanceUnitInfo)
, _precisionProperties(CameraTelemetry::PrecisionProperties(PrecisionInfo))
{
for (int i = 0; i < DistanceUnitNames.size(); ++i) {
_cameraSpeedDistanceUnitOption.addOption(i, DistanceUnitNames[i].singular.data());
}
_cameraSpeedDistanceUnitOption.setValue(static_cast<int>(DistanceUnit::Kilometer));
addProperty(_cameraSpeedDistanceUnitOption);
addPropertySubOwner(_precisionProperties);
}
CameraTelemetry::PrecisionProperties::PrecisionProperties(
properties::PropertyOwner::PropertyOwnerInfo precisionInfo)
: properties::PropertyOwner(precisionInfo)
, positionPrecision(PositionPrecisionInfo, 1000.0, 0.0, 1.0e+25)
, rotationPrecision(RotationPrecisionInfo, 0.05, 0.0, 10.0)
, speedPrecision(SpeedPrecisionInfo, 1000.0, 0.0, std::numeric_limits<double>::max())
{
positionPrecision.setExponent(20.f);
addProperty(positionPrecision);
addProperty(rotationPrecision);
speedPrecision.setExponent(100.f);
addProperty(speedPrecision);
}
bool CameraTelemetry::updateData(const Camera* camera) {
const glm::dvec3 cameraPosition = camera->positionVec3();
const double distanceMoved = glm::length(_cameraPosition - cameraPosition);
const glm::dquat cameraRotation = camera->rotationQuaternion();
// To check if the rotation has changed above the precision threshold, check the
// angle and axis of the quaternion seperatly
const double rotationAngleDifference = std::abs(_cameraRotation.w - cameraRotation.w);
const double rotationAxisDifference = glm::length(
glm::dvec3(_cameraRotation.x, _cameraRotation.y, _cameraRotation.z) -
glm::dvec3(cameraRotation.x, cameraRotation.y, cameraRotation.z)
);
double frameTime = global::windowDelegate->deltaTime();
bool hasFps = false;
double cameraSpeed = 0.0;
// Avoid division by 0 by first checking the current frame time
if (std::abs(frameTime) > std::numeric_limits<double>::epsilon()) {
hasFps = true;
double distanceMovedInUnit = convertMeters(
distanceMoved,
DistanceUnits[_cameraSpeedDistanceUnitOption]
);
cameraSpeed = distanceMovedInUnit / frameTime;
}
// Check if this data is new, otherwise don't send it
double prevCameraSpeed = _cameraSpeed;
bool dataWasUpdated = false;
if (distanceMoved > _precisionProperties.positionPrecision) {
_cameraPosition = cameraPosition;
dataWasUpdated = true;
}
if (rotationAngleDifference > _precisionProperties.rotationPrecision ||
rotationAxisDifference > _precisionProperties.rotationPrecision)
{
_cameraRotation = cameraRotation;
dataWasUpdated = true;
}
if (hasFps &&
abs(prevCameraSpeed - cameraSpeed) > _precisionProperties.speedPrecision)
{
_cameraSpeed = cameraSpeed;
dataWasUpdated = true;
}
return dataWasUpdated;
}
void CameraTelemetry::sendData() {
std::string label = "/Camera";
std::vector<OpenSoundControlDataType> data(NumDataItems);
data[CameraPosXIndex] = _cameraPosition.x;
data[CameraPosYIndex] = _cameraPosition.y;
data[CameraPosZIndex] = _cameraPosition.z;
data[CameraQuatRotWIndex] = _cameraRotation.w;
data[CameraQuatRotXIndex] = _cameraRotation.x;
data[CameraQuatRotYIndex] = _cameraRotation.y;
data[CameraQuatRotZIndex] = _cameraRotation.z;
data[CameraSpeedIndex] = _cameraSpeed;
data[CameraSpeedUnitIndex] =_cameraSpeedDistanceUnitOption.getDescriptionByValue(
_cameraSpeedDistanceUnitOption.value()
);
_connection->send(label, data);
}
} // namespace openspace
@@ -0,0 +1,84 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/telemetry/include/general/focustelemetry.h>
#include <openspace/engine/globals.h>
#include <openspace/navigation/navigationhandler.h>
#include <openspace/navigation/orbitalnavigator.h>
#include <openspace/scene/scenegraphnode.h>
namespace {
// Indices for data items
constexpr int NumDataItems = 1;
constexpr int FocusNodeIndex = 0;
static const openspace::properties::PropertyOwner::PropertyOwnerInfo
FocusTelemetryInfo =
{
"FocusTelemetry",
"Focus Telemetry",
"Telemetry that sends out the current focus node to the Open Sound Control "
"receiver."
};
} // namespace
namespace openspace {
FocusTelemetry::FocusTelemetry(const std::string& ip, int port)
: TelemetryBase(FocusTelemetryInfo, ip, port)
{}
bool FocusTelemetry::updateData(const Camera*) {
const SceneGraphNode* focusNode =
global::navigationHandler->orbitalNavigator().anchorNode();
if (!focusNode) {
// Scene is likely not yet initialized
return false;
}
// Check if this data is new, otherwise don't update it
std::string prevFocus = _currentFocus;
bool dataWasUpdated = false;
if (focusNode->identifier() != prevFocus) {
_currentFocus = focusNode->identifier();
dataWasUpdated = true;
}
return dataWasUpdated;
}
void FocusTelemetry::sendData() {
std::string label = "/Focus";
std::vector<OpenSoundControlDataType> data(NumDataItems);
data[FocusNodeIndex] = _currentFocus;
_connection->send(label, data);
}
} // namespace openspace
@@ -0,0 +1,280 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/telemetry/include/general/nodestelemetry.h>
#include <modules/telemetry/include/util.h>
#include <openspace/engine/globals.h>
#include <openspace/navigation/navigationhandler.h>
#include <openspace/navigation/orbitalnavigator.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/scripting/lualibrary.h>
#include <openspace/util/distanceconversion.h>
#include "nodestelemetry_lua.inl"
namespace {
constexpr std::string_view _loggerCat = "NodesTelemetry";
// Indices for data items
constexpr int DistanceIndex = 0;
constexpr int HorizontalAngleIndex = 1;
constexpr int VerticalAngleIndex = 2;
constexpr int DistanceUnitIndex = 3;
static const openspace::properties::PropertyOwner::PropertyOwnerInfo
NodesTelemetryInfo =
{
"NodesTelemetry",
"Nodes Telemetry",
"Telemetry of a list of nodes that has been added by the user."
};
constexpr openspace::properties::Property::PropertyInfo DistanceUnitInfo = {
"DistanceUnit",
"Distance Unit",
"The unit used for the distance part of the telemetry information.",
openspace::properties::Property::Visibility::User
};
const openspace::properties::PropertyOwner::PropertyOwnerInfo PrecisionInfo = {
"Precision",
"Precision",
"Settings for the precision of the nodes telemetry information."
};
constexpr openspace::properties::Property::PropertyInfo LowDistancePrecisionInfo = {
"LowDistancePrecision",
"Distance Precision (Low)",
"The precision in meters used to determine when to send updated distance data "
"to the Open Sound Control receiver. This is the low precision value (low level "
"of detail) that is used for objects that are not the current focus, saving "
"performance.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo HighDistancePrecisionInfo = {
"HighDistancePrecision",
"Distance Precision (High)",
"The precision in meters used to determine when to send updated distance data "
"to the Open Sound Control receiver. This is the high precision value (high "
"level of detail) that is used when the monitored object is the current focus, "
"providing more accurate data.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo LowAnglePrecisionInfo = {
"LowAnglePrecision",
"Angle Precision (Low)",
"The precision in radians used to determine when to send updated angle data "
"to the Open Sound Control receiver. This is the low precision value (low level "
"of detail) that is used for objects that are not the current focus, saving "
"performance.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo HighAnglePrecisionInfo = {
"HighAnglePrecision",
"Angle Precision (High)",
"The precision in radians used to determine when to send updated angle data "
"to the Open Sound Control receiver. This is the high precision value (high "
"level of detail) that is used when the monitored object is the current focus, "
"providing more accurate data.",
openspace::properties::Property::Visibility::User
};
} // namespace
namespace openspace {
NodesTelemetry::NodesTelemetry(const std::string& ip, int port)
: TelemetryBase(NodesTelemetryInfo, ip, port)
, _distanceUnitOption(DistanceUnitInfo)
, _precisionProperties(NodesTelemetry::PrecisionProperties(PrecisionInfo))
{
for (int i = 0; i < DistanceUnitNames.size(); ++i) {
_distanceUnitOption.addOption(i, DistanceUnitNames[i].singular.data());
}
_distanceUnitOption.setValue(static_cast<int>(DistanceUnit::Meter));
addProperty(_distanceUnitOption);
_anglePrecision = _precisionProperties.lowAnglePrecision;
_distancePrecision = _precisionProperties.lowDistancePrecision;
addPropertySubOwner(_precisionProperties);
}
NodesTelemetry::TelemetryNode::TelemetryNode(std::string id)
: identifier(std::move(id))
{}
NodesTelemetry::PrecisionProperties::PrecisionProperties(
properties::PropertyOwner::PropertyOwnerInfo precisionInfo)
: properties::PropertyOwner(precisionInfo)
, lowDistancePrecision(LowDistancePrecisionInfo, 10000.0, 0.0, 1.0e+25)
, highDistancePrecision(HighDistancePrecisionInfo, 1000.0, 0.0, 1.0e+25)
, lowAnglePrecision(LowAnglePrecisionInfo, 0.1, 0.0, 10.0)
, highAnglePrecision(HighAnglePrecisionInfo, 0.05, 0.0, 10.0)
{
lowDistancePrecision.setExponent(20.f);
addProperty(lowDistancePrecision);
highDistancePrecision.setExponent(20.f);
addProperty(highDistancePrecision);
addProperty(lowAnglePrecision);
addProperty(highAnglePrecision);
}
void NodesTelemetry::update(const Camera* camera) {
if (!_enabled) {
return;
}
const SceneGraphNode* focusNode =
global::navigationHandler->orbitalNavigator().anchorNode();
if (!focusNode) {
// Scene is likely not yet initialized
return;
}
// Get the current angle settings
TelemetryModule* module = global::moduleEngine->module<TelemetryModule>();
if (!module) {
LERROR("Could not find the TelemetryModule");
return;
}
TelemetryModule::AngleCalculationMode angleMode = module->angleCalculationMode();
bool includeElevation = module->includeElevationAngle();
// Update data for all nodes
for (int i = 0; i < _nodes.size(); ++i) {
// Increase precision if the node is in focus
if (focusNode->identifier() == _nodes[i].identifier) {
_anglePrecision = _precisionProperties.highAnglePrecision;
_distancePrecision = _precisionProperties.highDistancePrecision;
}
else {
_anglePrecision = _precisionProperties.lowAnglePrecision;
_distancePrecision = _precisionProperties.lowDistancePrecision;
}
const bool dataWasUpdated = updateData(camera, i, angleMode, includeElevation);
if (dataWasUpdated) {
sendData(i);
}
}
}
void NodesTelemetry::addNode(std::string node) {
_nodes.push_back(std::move(node));
}
// Empty overidded functions
bool NodesTelemetry::updateData(const Camera*) {
return false;
}
void NodesTelemetry::sendData() {}
bool NodesTelemetry::updateData(const Camera* camera, int nodeIndex,
TelemetryModule::AngleCalculationMode angleCalculationMode,
bool includeElevation)
{
double distance = calculateDistanceTo(
camera,
_nodes[nodeIndex].identifier,
DistanceUnits[_distanceUnitOption]
);
if (std::abs(distance) < std::numeric_limits<double>::epsilon()) {
// Scene is likely not yet initialized
return false;
}
double horizontalAngle =
calculateAngleTo(camera, _nodes[nodeIndex].identifier, angleCalculationMode);
double verticalAngle = 0.0;
if (includeElevation) {
verticalAngle = calculateElevationAngleTo(
camera,
_nodes[nodeIndex].identifier,
angleCalculationMode
);
}
// Check if this data is new, otherwise don't update it
double prevDistance = _nodes[nodeIndex].data[DistanceIndex];
double prevHorizontalAngle = _nodes[nodeIndex].data[HorizontalAngleIndex];
double prevVerticalAngle = _nodes[nodeIndex].data[VerticalAngleIndex];
bool dataWasUpdated = false;
if (std::abs(prevDistance - distance) > _distancePrecision) {
_nodes[nodeIndex].data[DistanceIndex] = distance;
dataWasUpdated = true;
}
if (std::abs(prevHorizontalAngle - horizontalAngle) > _anglePrecision) {
_nodes[nodeIndex].data[HorizontalAngleIndex] = horizontalAngle;
dataWasUpdated = true;
}
if (std::abs(prevVerticalAngle - verticalAngle) > _anglePrecision) {
_nodes[nodeIndex].data[VerticalAngleIndex] = verticalAngle;
dataWasUpdated = true;
}
return dataWasUpdated;
}
void NodesTelemetry::sendData(int nodeIndex) {
if (nodeIndex < 0 || _nodes.size() <= nodeIndex) {
LWARNING(std::format("Node list does not include index {}", nodeIndex));
return;
}
std::string label = "/" + _nodes[nodeIndex].identifier;
std::vector<OpenSoundControlDataType> data(NumDataItems);
data[DistanceIndex] = _nodes[nodeIndex].data[DistanceIndex];
data[HorizontalAngleIndex] = _nodes[nodeIndex].data[HorizontalAngleIndex];
data[VerticalAngleIndex] = _nodes[nodeIndex].data[VerticalAngleIndex];
data[DistanceUnitIndex] = _distanceUnitOption.getDescriptionByValue(
_distanceUnitOption.value()
);
_connection->send(label, data);
}
scripting::LuaLibrary NodesTelemetry::luaLibrary() {
return {
"telemetry",
{
codegen::lua::AddNodes
}
};
}
} // namespace openspace
@@ -0,0 +1,57 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/telemetry/telemetrymodule.h>
#include <openspace/engine/moduleengine.h>
namespace {
/**
* Adds the given list of nodes to the NodesTelemetry's internal list.
*/
[[codegen::luawrap]] void addNodes(ghoul::Dictionary nodes) {
openspace::TelemetryModule* module =
openspace::global::moduleEngine->module<openspace::TelemetryModule>();
if (!module) {
throw ghoul::lua::LuaError("Could not find the TelemetryModule");
return;
}
openspace::TelemetryBase* ptr = module->telemetry("NodesTelemetry");
if (!ptr) {
throw ghoul::lua::LuaError("Could not find the NodesTelemetry");
return;
}
openspace::NodesTelemetry* nodesTelemetry =
reinterpret_cast<openspace::NodesTelemetry*>(ptr);
for (const std::string_view& k : nodes.keys()) {
std::string node = nodes.value<std::string>(k);
nodesTelemetry->addNode(std::move(node));
}
}
#include "nodestelemetry_lua_codegen.cpp"
} // namespace
@@ -0,0 +1,136 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/telemetry/include/general/timetelemetry.h>
#include <openspace/engine/globals.h>
#include <openspace/util/timeconversion.h>
#include <openspace/util/timemanager.h>
namespace {
// Indices for data items
constexpr int NumDataItems = 3;
constexpr int TimeSpeedIndex = 0;
constexpr int TimeSpeedUnitIndex = 1;
constexpr int CurrentTimeIndex = 2;
static const openspace::properties::PropertyOwner::PropertyOwnerInfo
TimeTelemetryInfo =
{
"TimeTelemetry",
"Time Telemetry",
"Telemetry that sends out time information to the Open Sound Control receiver."
};
constexpr openspace::properties::Property::PropertyInfo TimeUnitOptionInfo = {
"TimeUnit",
"Time Unit",
"The time unit that the telemetry should use for the time speed. For example, if "
"the unit is set to 'Hour' then the unit for the time speed is simulation hours "
"per real life second.",
openspace::properties::Property::Visibility::User
};
const openspace::properties::PropertyOwner::PropertyOwnerInfo PrecisionInfo = {
"Precision",
"Precision",
"Settings for the precision of the telemetry information."
};
constexpr openspace::properties::Property::PropertyInfo TimePrecisionInfo = {
"TimePrecision",
"TimePrecision",
"The precision in seconds used to determine when to send updated time data to "
"the Open Sound Control receiver.",
openspace::properties::Property::Visibility::User
};
} // namespace
namespace openspace {
TimeTelemetry::TimeTelemetry(const std::string& ip, int port)
: TelemetryBase(TimeTelemetryInfo, ip, port)
, _timeUnitOption(TimeUnitOptionInfo)
, _precisionProperties(TimeTelemetry::PrecisionProperties(PrecisionInfo))
{
for (size_t i = 0; i < TimeUnitNames.size(); ++i) {
_timeUnitOption.addOption(i, TimeUnitNames[i].singular.data());
}
_timeUnitOption.setValue(static_cast<int>(TimeUnit::Day));
addProperty(_timeUnitOption);
addPropertySubOwner(_precisionProperties);
}
TimeTelemetry::PrecisionProperties::PrecisionProperties(
properties::PropertyOwner::PropertyOwnerInfo precisionInfo)
: properties::PropertyOwner(precisionInfo)
, timePrecision(TimePrecisionInfo, 0.0001, 0.0, 1.0e8)
{
timePrecision.setExponent(10.f);
addProperty(timePrecision);
}
bool TimeTelemetry::updateData(const Camera*) {
double timeSpeed = convertTime(
global::timeManager->deltaTime(),
TimeUnit::Second,
TimeUnits[_timeUnitOption]
);
double currentTime = global::timeManager->time().j2000Seconds();
// Check if this data is new, otherwise don't send it
double prevTimeSpeed = _timeSpeed;
double prevTime = _currentTime;
bool dataWasUpdated = false;
if (abs(prevTimeSpeed - timeSpeed) > _precisionProperties.timePrecision) {
_timeSpeed = timeSpeed;
dataWasUpdated = true;
}
if (abs(prevTime - currentTime) > _precisionProperties.timePrecision) {
_currentTime = currentTime;
dataWasUpdated = true;
}
return dataWasUpdated;
}
void TimeTelemetry::sendData() {
std::string label = "/Time";
std::vector<OpenSoundControlDataType> data(NumDataItems);
data[TimeSpeedIndex] = _timeSpeed;
data[TimeSpeedUnitIndex] =
_timeUnitOption.getDescriptionByValue(_timeUnitOption.value());
data[CurrentTimeIndex] = _currentTime;
_connection->send(label, data);
}
} // namespace openspace
@@ -0,0 +1,321 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/telemetry/include/specific/planetscomparesonification.h>
#include <openspace/engine/globals.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/scripting/scriptengine.h>
#include <openspace/util/memorymanager.h>
#include <ghoul/logging/logmanager.h>
namespace {
constexpr std::string_view _loggerCat = "PlanetsCompareSonification";
// Indices for data items
constexpr int NumDataItems = 3;
constexpr int FirstPlanetIndex = 0;
constexpr int SecondPlanetIndex = 1;
constexpr int SettingsIndex = 2;
// Indicies for the gui settings
constexpr int NumSettings = 6;
constexpr int SizeDayIndex = 0;
constexpr int GravityIndex = 1;
constexpr int TemperatureIndex = 2;
constexpr int AtmosphereIndex = 3;
constexpr int MoonsIndex = 4;
constexpr int RingsIndex = 5;
// List of planets that can be selected
constexpr std::array<std::string_view, 9> PlanetsOptions = {
"Choose Planet", "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn",
"Uranus", "Neptune"
};
static const openspace::properties::PropertyOwner::PropertyOwnerInfo
PlanetsCompareSonificationInfo =
{
"PlanetsCompareSonification",
"Planets Compare Sonification",
"Sonification that compares two different planets to each other in a variety of "
"aspects."
};
constexpr openspace::properties::Property::PropertyInfo SelectedUpscaleInfo = {
"SelectedUpscale",
"Selected Planet Upscale Multiplier",
"When a planet is selected to be compared, it is also upscaled as a visual "
"indicator of which planets are currently being compared. This property "
"determines how much the planet is scaled up as a multiplier of the original "
"size.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo
SelectedScaleInterpolationTimeInfo =
{
"SelectedScaleInterpolationTimeInfo",
"Selected Planet Scale Interpolation Time",
"When a planet is selected to be compared, it is also upscaled as a visual "
"indicator of which planets are currently being compared. This property "
"determines over how many seconds the scaling animation should play.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo FirstOptionInfo = {
"FirstOption",
"Choose a planet to compare",
"Choose a planet in the list to compare to the other selected planet.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SecondOptionInfo = {
"SecondOption",
"Choose another planet to compare",
"Choose another planet in the list to compare to the other selected planet.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ToggleAllInfo = {
"ToggleAll",
"All",
"Toggle all comparing sonification varieties for both selected planets.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SizeDayInfo = {
"SizeDay",
"Size/Day",
"Toggle size/day sonification for both selected planets.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo GravityInfo = {
"Gravity",
"Gravity",
"Toggle gravity sonification for both selected planets.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo TemperatureInfo = {
"Temperature",
"Temperature",
"Toggle temperature sonification for both selected planets.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo AtmosphereInfo = {
"Atmosphere",
"Atmosphere",
"Toggle atmosphere sonification for both selected planets.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo MoonsInfo = {
"Moons",
"Moons",
"Toggle moons sonification for both selected planets.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo RingsInfo = {
"Rings",
"Rings",
"Toggle rings sonification for both selected planets.",
openspace::properties::Property::Visibility::User
};
} // namespace
namespace openspace {
PlanetsCompareSonification::PlanetsCompareSonification(const std::string& ip, int port)
: TelemetryBase(PlanetsCompareSonificationInfo, ip, port)
, _selectedUpscale(SelectedUpscaleInfo, 2000.0, 0.0, 1.0e+20)
, _selectedScaleInterpolationTime(SelectedScaleInterpolationTimeInfo, 1.0, 0.0, 60.0)
, _firstPlanet(FirstOptionInfo)
, _secondPlanet(SecondOptionInfo)
, _toggleAll(ToggleAllInfo, false)
, _sizeDayEnabled(SizeDayInfo, false)
, _gravityEnabled(GravityInfo, false)
, _temperatureEnabled(TemperatureInfo, false)
, _atmosphereEnabled(AtmosphereInfo, false)
, _moonsEnabled(MoonsInfo, false)
, _ringsEnabled(RingsInfo, false)
{
// Scaling animation properties
_selectedUpscale.setExponent(15.f);
_selectedUpscale.onChange([this]() { onUpscaleChanged(); });
addProperty(_selectedUpscale);
addProperty(_selectedScaleInterpolationTime);
// Planet selection properties
for (int i = 0; i < PlanetsOptions.size(); ++i) {
_firstPlanet.addOption(i, PlanetsOptions[i].data());
_secondPlanet.addOption(i, PlanetsOptions[i].data());
}
_firstPlanet.onChange([this]() { onFirstChanged(); });
addProperty(_firstPlanet);
_secondPlanet.onChange([this]() { onSecondChanged(); });
addProperty(_secondPlanet);
// Add the sonifiation aspects properties
_toggleAll.onChange([this]() { onToggleAllChanged(); });
addProperty(_toggleAll);
_sizeDayEnabled.onChange([this]() { sendData(); });
addProperty(_sizeDayEnabled);
_gravityEnabled.onChange([this]() { sendData(); });
addProperty(_gravityEnabled);
_temperatureEnabled.onChange([this]() { sendData(); });
addProperty(_temperatureEnabled);
_atmosphereEnabled.onChange([this]() { sendData(); });
addProperty(_atmosphereEnabled);
_moonsEnabled.onChange([this]() { sendData(); });
addProperty(_moonsEnabled);
_ringsEnabled.onChange([this]() { sendData(); });
addProperty(_ringsEnabled);
}
void PlanetsCompareSonification::stop() {
_toggleAll = false;
_firstPlanet = 0;
_secondPlanet = 0;
}
osc::Blob PlanetsCompareSonification::createSettingsBlob() const {
int8_t* settings = reinterpret_cast<int8_t*>(
global::memoryManager->TemporaryMemory.allocate(NumSettings)
);
settings[SizeDayIndex] = _sizeDayEnabled;
settings[GravityIndex] = _gravityEnabled;
settings[TemperatureIndex] = _temperatureEnabled;
settings[AtmosphereIndex] = _atmosphereEnabled;
settings[MoonsIndex] = _moonsEnabled;
settings[RingsIndex] = _ringsEnabled;
return osc::Blob(settings, NumSettings);
}
bool PlanetsCompareSonification::updateData(const Camera*) {
return false;
}
void PlanetsCompareSonification::sendData() {
if (!_enabled) {
return;
}
std::string label = "/Compare";
std::vector<OpenSoundControlDataType> data(NumDataItems);
data[FirstPlanetIndex] = _firstPlanet;
data[SecondPlanetIndex] = _secondPlanet;
data[SettingsIndex] = createSettingsBlob();
_connection->send(label, data);
}
void PlanetsCompareSonification::onPlanetSelectionChanged(
properties::OptionProperty& changedPlanet,
properties::OptionProperty& otherPlanet,
std::string& prevChangedPlanet)
{
if (changedPlanet != 0 && changedPlanet == otherPlanet) {
LINFO("Cannot compare a planet to itself");
changedPlanet.setValue(0);
return;
}
if (!prevChangedPlanet.empty()) {
// Reset the scale of the previously compared planet
scalePlanet(prevChangedPlanet, 1.0, _selectedScaleInterpolationTime);
}
if (changedPlanet != 0) {
scalePlanet(
changedPlanet.getDescriptionByValue(changedPlanet.value()),
_selectedUpscale,
_selectedScaleInterpolationTime
);
prevChangedPlanet = changedPlanet.getDescriptionByValue(changedPlanet.value());
}
else {
prevChangedPlanet = "";
}
sendData();
}
void PlanetsCompareSonification::scalePlanet(const std::string& planet, double scale,
double interpolationTime)
{
// Scale the selected planet to visually indicate which planets are currently compared
std::string script = std::format(
"openspace.setPropertyValueSingle('Scene.{}.Scale.Scale', {}, {});",
planet, scale, interpolationTime
);
global::scriptEngine->queueScript(std::move(script));
}
void PlanetsCompareSonification::onUpscaleChanged() {
// Update the scale value for the first planet if something is selected
if (_firstPlanet != 0) {
scalePlanet(
_firstPlanet.getDescriptionByValue(_firstPlanet.value()),
_selectedUpscale,
0.0
);
}
// Update the scale value for the second planet if something is selected
if (_secondPlanet != 0) {
scalePlanet(
_secondPlanet.getDescriptionByValue(_secondPlanet.value()),
_selectedUpscale,
0.0
);
}
}
void PlanetsCompareSonification::onFirstChanged() {
onPlanetSelectionChanged(_firstPlanet, _secondPlanet, _oldFirst);
}
void PlanetsCompareSonification::onSecondChanged() {
onPlanetSelectionChanged(_secondPlanet, _firstPlanet, _oldSecond);
}
void PlanetsCompareSonification::onToggleAllChanged() {
_sizeDayEnabled.setValue(_toggleAll);
_gravityEnabled.setValue(_toggleAll);
_temperatureEnabled.setValue(_toggleAll);
_atmosphereEnabled.setValue(_toggleAll);
_moonsEnabled.setValue(_toggleAll);
_ringsEnabled.setValue(_toggleAll);
}
} // namespace openspace
@@ -0,0 +1,205 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/telemetry/include/specific/planetsoverviewsonification.h>
#include <openspace/engine/globals.h>
#include <openspace/util/memorymanager.h>
namespace {
constexpr std::string_view _loggerCat = "PlanetsOverviewSonification";
// Indices for data items
constexpr int NumDataItems = 1;
constexpr int GuiSettingsIndex = 0;
// Indices for the planets
constexpr int NumPlanets = 8;
constexpr int MercuryIndex = 0;
constexpr int VenusIndex = 1;
constexpr int EarthIndex = 2;
constexpr int MarsIndex = 3;
constexpr int JupiterIndex = 4;
constexpr int SaturnIndex = 5;
constexpr int UranusIndex = 6;
constexpr int NeptuneIndex = 7;
static const openspace::properties::PropertyOwner::PropertyOwnerInfo
PlanetsOverviewSonificationInfo =
{
"PlanetsOverviewSonification",
"Planets Overview Sonification",
"Sonification that gives an overview of the planets in the Solar System with "
"fewer audible features for each planet compared to the full planets "
"sonification."
};
constexpr openspace::properties::Property::PropertyInfo ToggleAllInfo = {
"ToggleAll",
"Toggle All",
"Toggle the sonification for all planets.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo EnableMercuryInfo = {
"Mercury",
"Mercury",
"Toggle sonification for Mercury.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo EnableVenusInfo = {
"Venus",
"Venus",
"Toggle sonification for Venus.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo EnableEarthInfo = {
"Earth",
"Earth",
"Toggle sonification for Earth.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo EnableMarsInfo = {
"Mars",
"Mars",
"Toggle sonification for Mars.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo EnableJupiterInfo = {
"Jupiter",
"Jupiter",
"Toggle sonification for Jupiter.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo EnableSaturnInfo = {
"Saturn",
"Saturn",
"Toggle sonification for Saturn.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo EnableUranusInfo = {
"Uranus",
"Uranus",
"Toggle sonification for Uranus.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo EnableNeptuneInfo = {
"Neptune",
"Neptune",
"Toggle sonification for Neptune.",
openspace::properties::Property::Visibility::User
};
} // namespace
namespace openspace {
PlanetsOverviewSonification::PlanetsOverviewSonification(const std::string& ip, int port)
: TelemetryBase(PlanetsOverviewSonificationInfo, ip, port)
, _toggleAll(ToggleAllInfo, false)
, _mercuryEnabled(EnableMercuryInfo, false)
, _venusEnabled(EnableVenusInfo, false)
, _earthEnabled(EnableEarthInfo, false)
, _marsEnabled(EnableMarsInfo, false)
, _jupiterEnabled(EnableJupiterInfo, false)
, _saturnEnabled(EnableSaturnInfo, false)
, _uranusEnabled(EnableUranusInfo, false)
, _neptuneEnabled(EnableNeptuneInfo, false)
{
_toggleAll.onChange([this]() { onToggleAllChanged(); });
addProperty(_toggleAll);
_mercuryEnabled.onChange([this]() { sendData(); });
addProperty(_mercuryEnabled);
_venusEnabled.onChange([this]() { sendData(); });
addProperty(_venusEnabled);
_earthEnabled.onChange([this]() { sendData(); });
addProperty(_earthEnabled);
_marsEnabled.onChange([this]() { sendData(); });
addProperty(_marsEnabled);
_jupiterEnabled.onChange([this]() { sendData(); });
addProperty(_jupiterEnabled);
_saturnEnabled.onChange([this]() { sendData(); });
addProperty(_saturnEnabled);
_uranusEnabled.onChange([this]() { sendData(); });
addProperty(_uranusEnabled);
_neptuneEnabled.onChange([this]() { sendData(); });
addProperty(_neptuneEnabled);
}
void PlanetsOverviewSonification::stop() {
_toggleAll = false;
}
osc::Blob PlanetsOverviewSonification::createSettingsBlob() const {
int8_t* settings = reinterpret_cast<int8_t*>(
global::memoryManager->TemporaryMemory.allocate(NumPlanets)
);
settings[MercuryIndex] = _mercuryEnabled;
settings[VenusIndex] = _venusEnabled;
settings[EarthIndex] = _earthEnabled;
settings[MarsIndex] = _marsEnabled;
settings[JupiterIndex] = _jupiterEnabled;
settings[SaturnIndex] = _saturnEnabled;
settings[UranusIndex] = _uranusEnabled;
settings[NeptuneIndex] = _neptuneEnabled;
return osc::Blob(settings, NumPlanets);
}
bool PlanetsOverviewSonification::updateData(const Camera*) {
return false;
}
void PlanetsOverviewSonification::sendData() {
if (!_enabled) {
return;
}
std::string label = "/Overview";
std::vector<OpenSoundControlDataType> data(NumDataItems);
data[GuiSettingsIndex] = createSettingsBlob();
_connection->send(label, data);
}
void PlanetsOverviewSonification::onToggleAllChanged() {
_mercuryEnabled.setValue(_toggleAll);
_venusEnabled.setValue(_toggleAll);
_earthEnabled.setValue(_toggleAll);
_marsEnabled.setValue(_toggleAll);
_jupiterEnabled.setValue(_toggleAll);
_saturnEnabled.setValue(_toggleAll);
_uranusEnabled.setValue(_toggleAll);
_neptuneEnabled.setValue(_toggleAll);
}
} // namespace openspace
@@ -0,0 +1,813 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/telemetry/include/specific/planetssonification.h>
#include <modules/telemetry/include/util.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/moduleengine.h>
#include <openspace/navigation/navigationhandler.h>
#include <openspace/navigation/orbitalnavigator.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/scripting/lualibrary.h>
#include <openspace/util/distanceconversion.h>
#include <openspace/util/memorymanager.h>
#include "planetssonification_lua.inl"
namespace {
constexpr std::string_view _loggerCat = "PlanetsSonification";
// Number of data items for planets and moons, which is used to calculate the total
// size of the data vector sent to the Open Sound Control receiver.
constexpr int NumDataItemsPlanet = 4;
constexpr int NumDataItemsMoon = 3;
// Indices for the planets
constexpr int MercuryIndex = 0;
constexpr int VenusIndex = 1;
constexpr int EarthIndex = 2;
constexpr int MarsIndex = 3;
constexpr int JupiterIndex = 4;
constexpr int SaturnIndex = 5;
constexpr int UranusIndex = 6;
constexpr int NeptuneIndex = 7;
// Indices for the settings for the planets
constexpr int NumSettings = 6;
constexpr int SizeDayIndex = 0;
constexpr int GravityIndex = 1;
constexpr int TemperatureIndex = 2;
constexpr int AtmosphereIndex = 3;
constexpr int MoonsIndex = 4;
constexpr int RingsIndex = 5;
static const openspace::properties::PropertyOwner::PropertyOwnerInfo
PlanetsSonificationInfo =
{
"PlanetsSonification",
"Planets Sonification",
"Sonification of the planets in the solar system."
};
constexpr openspace::properties::Property::PropertyInfo ToggleAllInfo = {
"ToggleAll",
"Toggle All",
"Toggle all sonifications for all planets.",
openspace::properties::Property::Visibility::User
};
static const openspace::properties::PropertyOwner::PropertyOwnerInfo MercuryInfo = {
"Mercury",
"Mercury",
"Settings for the sonification of Mercury."
};
static const openspace::properties::PropertyOwner::PropertyOwnerInfo VenusInfo = {
"Venus",
"Venus",
"Settings for the sonification of Venus."
};
static const openspace::properties::PropertyOwner::PropertyOwnerInfo EarthInfo = {
"Earth",
"Earth",
"Settings for the sonification of Earth."
};
static const openspace::properties::PropertyOwner::PropertyOwnerInfo MarsInfo = {
"Mars",
"Mars",
"Settings for the sonification of Mars."
};
static const openspace::properties::PropertyOwner::PropertyOwnerInfo JupiterInfo = {
"Jupiter",
"Jupiter",
"Settings for the sonification of Jupiter."
};
static const openspace::properties::PropertyOwner::PropertyOwnerInfo SaturnInfo = {
"Saturn",
"Saturn",
"Settings for the sonification of Saturn."
};
static const openspace::properties::PropertyOwner::PropertyOwnerInfo UranusInfo = {
"Uranus",
"Uranus",
"Settings for the sonification of Uranus."
};
static const openspace::properties::PropertyOwner::PropertyOwnerInfo NeptuneInfo = {
"Neptune",
"Neptune",
"Settings for the sonification of Neptune."
};
constexpr openspace::properties::Property::PropertyInfo PlanetToggleAllInfo = {
"ToggleAll",
"Toggle All",
"Toggle all sonifications for the planet.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SizeDayInfo = {
"SizeDay",
"Size/Day",
"Toggle the size/day sonification for the planet.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo GravityInfo = {
"Gravity",
"Gravity",
"Toggle the gravity sonification for the planet.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo TemperatureInfo = {
"Temperature",
"Temperature",
"Toggle the temperature sonification for the planet.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo AtmosphereInfo = {
"Atmosphere",
"Atmosphere",
"Toggle the atmosphere sonification for the planet.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo MoonsInfo = {
"Moons",
"Moons",
"Toggle the moons sonification for the planet.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo RingsInfo = {
"Rings",
"Rings",
"Toggle the rings sonification for the planet.",
openspace::properties::Property::Visibility::User
};
const openspace::properties::PropertyOwner::PropertyOwnerInfo PrecisionInfo = {
"Precision",
"Precision",
"Settings for the distance and angle precision of the planets sonification."
};
constexpr openspace::properties::Property::PropertyInfo LowDistancePrecisionInfo = {
"LowDistancePrecision",
"Distance Precision (Low)",
"The precision in meters used to determine when to send updated distance data "
"to the Open Sound Control receiver. This is the low precision value (low level "
"of detail) that is used for objects that are not the current focus, saving "
"performance.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo HighDistancePrecisionInfo = {
"HighDistancePrecision",
"Distance Precision (High)",
"The precision in meters used to determine when to send updated distance data "
"to the Open Sound Control receiver. This is the high precision value (high "
"level of detail) that is used when the monitored object is the current focus, "
"providing more accurate data.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo LowAnglePrecisionInfo = {
"LowAnglePrecision",
"Angle Precision (Low)",
"The precision in radians used to determine when to send updated angle data "
"to the Open Sound Control receiver. This is the low precision value (low level "
"of detail) that is used for objects that are not the current focus, saving "
"performance.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo HighAnglePrecisionInfo = {
"HighAnglePrecision",
"Angle Precision (High)",
"The precision in radians used to determine when to send updated angle data "
"to the Open Sound Control receiver. This is the high precision value (high "
"level of detail) that is used when the monitored object is the current focus, "
"providing more accurate data.",
openspace::properties::Property::Visibility::User
};
struct [[codegen::Dictionary(PlanetsSonification)]] Parameters {
// The name of the planet
std::string name;
// Names of the moons for the planet
std::optional<std::vector<std::string>> moons;
};
#include "planetssonification_codegen.cpp"
} // namespace
namespace openspace {
PlanetsSonification::PlanetsSonification(const std::string& ip, int port)
: TelemetryBase(PlanetsSonificationInfo, ip, port)
, _toggleAll(ToggleAllInfo, false)
, _mercuryProperty(PlanetsSonification::PlanetProperties(MercuryInfo))
, _venusProperty(PlanetsSonification::PlanetProperties(VenusInfo))
, _earthProperty(PlanetsSonification::PlanetProperties(EarthInfo))
, _marsProperty(PlanetsSonification::PlanetProperties(MarsInfo))
, _jupiterProperty(PlanetsSonification::PlanetProperties(JupiterInfo))
, _saturnProperty(PlanetsSonification::PlanetProperties(SaturnInfo))
, _uranusProperty(PlanetsSonification::PlanetProperties(UranusInfo))
, _neptuneProperty(PlanetsSonification::PlanetProperties(NeptuneInfo))
, _precisionProperties(PlanetsSonification::PrecisionProperties(PrecisionInfo))
{
_toggleAll.onChange([this]() { onToggleAllChanged(); });
addProperty(_toggleAll);
// Mercury
_mercuryProperty.toggleAll.onChange([this]() { onMercuryAllChanged(); });
_mercuryProperty.sizeDayEnabled.onChange([this]() { onMercurySettingChanged(); });
_mercuryProperty.gravityEnabled.onChange([this]() { onMercurySettingChanged(); });
_mercuryProperty.temperatureEnabled.onChange([this]() { onMercurySettingChanged(); });
// No atmosphere
// No moons
// No rings
addPropertySubOwner(_mercuryProperty);
// Venus
_venusProperty.toggleAll.onChange([this]() { onVenusAllChanged(); });
_venusProperty.sizeDayEnabled.onChange([this]() { onVenusSettingChanged(); });
_venusProperty.gravityEnabled.onChange([this]() { onVenusSettingChanged(); });
_venusProperty.temperatureEnabled.onChange([this]() { onVenusSettingChanged(); });
_venusProperty.atmosphereEnabled.onChange([this]() { onVenusSettingChanged(); });
// No moons
// No rings
addPropertySubOwner(_venusProperty);
// Earth
_earthProperty.toggleAll.onChange([this]() { onEarthAllChanged(); });
_earthProperty.sizeDayEnabled.onChange([this]() { onEarthSettingChanged(); });
_earthProperty.gravityEnabled.onChange([this]() { onEarthSettingChanged(); });
_earthProperty.temperatureEnabled.onChange([this]() { onEarthSettingChanged(); });
_earthProperty.atmosphereEnabled.onChange([this]() { onEarthSettingChanged(); });
_earthProperty.moonsEnabled.onChange([this]() { onEarthSettingChanged(); });
// No rings
addPropertySubOwner(_earthProperty);
// Mars
_marsProperty.toggleAll.onChange([this]() { onMarsAllChanged(); });
_marsProperty.sizeDayEnabled.onChange([this]() { onMarsSettingChanged(); });
_marsProperty.gravityEnabled.onChange([this]() { onMarsSettingChanged(); });
_marsProperty.temperatureEnabled.onChange([this]() { onMarsSettingChanged(); });
_marsProperty.atmosphereEnabled.onChange([this]() { onMarsSettingChanged(); });
_marsProperty.moonsEnabled.onChange([this]() { onMarsSettingChanged(); });
// No rings
addPropertySubOwner(_marsProperty);
// Jupiter
_jupiterProperty.toggleAll.onChange([this]() { onJupiterAllChanged(); });
_jupiterProperty.sizeDayEnabled.onChange([this]() { onJupiterSettingChanged(); });
_jupiterProperty.gravityEnabled.onChange([this]() { onJupiterSettingChanged(); });
_jupiterProperty.temperatureEnabled.onChange([this]() { onJupiterSettingChanged(); });
_jupiterProperty.atmosphereEnabled.onChange([this]() { onJupiterSettingChanged(); });
_jupiterProperty.moonsEnabled.onChange([this]() { onJupiterSettingChanged(); });
// No rings visible in OpenSpace
addPropertySubOwner(_jupiterProperty);
// Saturn
_saturnProperty.toggleAll.onChange([this]() { onSaturnAllChanged(); });
_saturnProperty.sizeDayEnabled.onChange([this]() { onSaturnSettingChanged(); });
_saturnProperty.gravityEnabled.onChange([this]() { onSaturnSettingChanged(); });
_saturnProperty.temperatureEnabled.onChange([this]() { onSaturnSettingChanged(); });
_saturnProperty.atmosphereEnabled.onChange([this]() { onSaturnSettingChanged(); });
_saturnProperty.moonsEnabled.onChange([this]() { onSaturnSettingChanged(); });
_saturnProperty.ringsEnabled.onChange([this]() { onSaturnSettingChanged(); });
addPropertySubOwner(_saturnProperty);
// Uranus
_uranusProperty.toggleAll.onChange([this]() { onUranusAllChanged(); });
_uranusProperty.sizeDayEnabled.onChange([this]() { onUranusSettingChanged(); });
_uranusProperty.gravityEnabled.onChange([this]() { onUranusSettingChanged(); });
_uranusProperty.temperatureEnabled.onChange([this]() { onUranusSettingChanged(); });
_uranusProperty.atmosphereEnabled.onChange([this]() { onUranusSettingChanged(); });
_uranusProperty.moonsEnabled.onChange([this]() { onUranusSettingChanged(); });
// No rings visible in OpenSpace
addPropertySubOwner(_uranusProperty);
// Neptune
_neptuneProperty.toggleAll.onChange([this]() { onNeptuneAllChanged(); });
_neptuneProperty.sizeDayEnabled.onChange([this]() { onNeptuneSettingChanged(); });
_neptuneProperty.gravityEnabled.onChange([this]() { onNeptuneSettingChanged(); });
_neptuneProperty.temperatureEnabled.onChange([this]() { onNeptuneSettingChanged(); });
_neptuneProperty.atmosphereEnabled.onChange([this]() { onNeptuneSettingChanged(); });
_neptuneProperty.moonsEnabled.onChange([this]() { onNeptuneSettingChanged(); });
// No rings visible in OpenSpace
addPropertySubOwner(_neptuneProperty);
_anglePrecision = _precisionProperties.lowAnglePrecision;
_distancePrecision = _precisionProperties.lowDistancePrecision;
addPropertySubOwner(_precisionProperties);
}
PlanetsSonification::DataBody::DataBody(std::string inName)
: name(std::move(inName))
{}
PlanetsSonification::PlanetProperties::PlanetProperties(
properties::PropertyOwner::PropertyOwnerInfo planetInfo)
: properties::PropertyOwner(planetInfo)
, toggleAll(PlanetToggleAllInfo, false)
, sizeDayEnabled(SizeDayInfo, false)
, gravityEnabled(GravityInfo, false)
, temperatureEnabled(TemperatureInfo, false)
, atmosphereEnabled(AtmosphereInfo, false)
, moonsEnabled(MoonsInfo, false)
, ringsEnabled(RingsInfo, false)
{
// Add the common settings
addProperty(toggleAll);
addProperty(sizeDayEnabled);
addProperty(gravityEnabled);
addProperty(temperatureEnabled);
addProperty(atmosphereEnabled);
addProperty(moonsEnabled);
addProperty(ringsEnabled);
// Not all planets have all features, set them as read only if they do not exist
// Mercury is the only planet that does not have any form of atmosphere
if (planetInfo.guiName == "Mercury") {
atmosphereEnabled.setReadOnly(true);
}
// Mercury and Venus are the only planets that does not have any moon
if (planetInfo.guiName == "Mercury" || planetInfo.guiName == "Venus") {
moonsEnabled.setReadOnly(true);
}
// Saturn is the only planet with rings (visible in OpenSpace)
if (planetInfo.guiName != "Saturn") {
ringsEnabled.setReadOnly(true);
}
}
PlanetsSonification::PrecisionProperties::PrecisionProperties(
properties::PropertyOwner::PropertyOwnerInfo precisionInfo)
: properties::PropertyOwner(precisionInfo)
, lowDistancePrecision(LowDistancePrecisionInfo, 10000.0, 0.0, 1.0e+25)
, highDistancePrecision(HighDistancePrecisionInfo, 1000.0, 0.0, 1.0e+25)
, lowAnglePrecision(LowAnglePrecisionInfo, 0.1, 0.0, 10.0)
, highAnglePrecision(HighAnglePrecisionInfo, 0.05, 0.0, 10.0)
{
addProperty(lowDistancePrecision);
lowDistancePrecision.setExponent(20.f);
addProperty(highDistancePrecision);
highDistancePrecision.setExponent(20.f);
addProperty(lowAnglePrecision);
addProperty(highAnglePrecision);
}
void PlanetsSonification::update(const Camera* camera) {
TelemetryModule* module = global::moduleEngine->module<TelemetryModule>();
if (!module) {
LERROR("Could not find the TelemetryModule");
return;
}
// The compare planets and planets overview sonifications depend on this
// sonification. If any of them are enabled, then this sonificaiton sends planetary
// data to the Open Sound Control receiver, even if this sonificaiton is disabled.
// @NOTE (malej 2024-11-28): Enabling any combination of these three sonifications
// at the same time is not supported, only one will be used by SuperCollider
const TelemetryBase* compare = module->telemetry("PlanetsCompareSonification");
if (!compare) {
LERROR("Could not find the PlanetsCompareSonification");
return;
}
const TelemetryBase* overview = module->telemetry("PlanetsOverviewSonification");
if (!overview) {
LERROR("Could not find the PlanetsOverviewSonification");
return;
}
bool compareEnabled = compare->enabled();
_overviewEnabled = overview->enabled();
if (!_enabled && !compareEnabled && !_overviewEnabled) {
return;
}
// Get the angle settings from the module
TelemetryModule::AngleCalculationMode angleMode = module->angleCalculationMode();
bool includeElevation = module->includeElevationAngle();
const SceneGraphNode* focusNode =
global::navigationHandler->orbitalNavigator().anchorNode();
if (!focusNode) {
// The scene is likely not yet initialized
return;
}
// Update data for all planets
for (int i = 0; i < _planets.size(); ++i) {
// Increase presision if the planet is in focus
if (focusNode->identifier() == _planets[i].name) {
_anglePrecision = _precisionProperties.highAnglePrecision;
_distancePrecision = _precisionProperties.highDistancePrecision;
}
else {
_anglePrecision = _precisionProperties.lowAnglePrecision;
_distancePrecision = _precisionProperties.lowDistancePrecision;
}
const bool dataWasUpdated = updateData(camera, i, angleMode, includeElevation);
// Only send data if something new has happened
if (dataWasUpdated) {
sendData(i);
}
}
}
void PlanetsSonification::stop() {
_toggleAll = false;
}
void PlanetsSonification::addPlanet(const ghoul::Dictionary& dict) {
const Parameters p = codegen::bake<Parameters>(dict);
DataBody planet = DataBody(p.name);
if (p.moons.has_value()) {
for (const std::string& moon : *p.moons) {
planet.moons.push_back(moon);
}
}
_planets.push_back(planet);
}
osc::Blob PlanetsSonification::createSettingsBlob(int planetIndex) const {
int8_t* settings = reinterpret_cast<int8_t*>(
global::memoryManager->TemporaryMemory.allocate(NumSettings)
);
// Initialize the settings that does not exist for all planets
settings[AtmosphereIndex] = 0;
settings[MoonsIndex] = 0;
settings[RingsIndex] = 0;
switch (planetIndex) {
case MercuryIndex:
settings[SizeDayIndex] = _mercuryProperty.sizeDayEnabled;
settings[GravityIndex] = _mercuryProperty.gravityEnabled;
settings[TemperatureIndex] = _mercuryProperty.temperatureEnabled;
break;
case VenusIndex:
settings[SizeDayIndex] = _venusProperty.sizeDayEnabled;
settings[GravityIndex] = _venusProperty.gravityEnabled;
settings[TemperatureIndex] = _venusProperty.temperatureEnabled;
settings[AtmosphereIndex] = _venusProperty.atmosphereEnabled;
break;
case EarthIndex:
settings[SizeDayIndex] = _earthProperty.sizeDayEnabled;
settings[GravityIndex] = _earthProperty.gravityEnabled;
settings[TemperatureIndex] = _earthProperty.temperatureEnabled;
settings[AtmosphereIndex] = _earthProperty.atmosphereEnabled;
settings[MoonsIndex] = _earthProperty.moonsEnabled;
break;
case MarsIndex:
settings[SizeDayIndex] = _marsProperty.sizeDayEnabled;
settings[GravityIndex] = _marsProperty.gravityEnabled;
settings[TemperatureIndex] = _marsProperty.temperatureEnabled;
settings[AtmosphereIndex] = _marsProperty.atmosphereEnabled;
settings[MoonsIndex] = _marsProperty.moonsEnabled;
break;
case JupiterIndex:
settings[SizeDayIndex] = _jupiterProperty.sizeDayEnabled;
settings[GravityIndex] = _jupiterProperty.gravityEnabled;
settings[TemperatureIndex] = _jupiterProperty.temperatureEnabled;
settings[AtmosphereIndex] = _jupiterProperty.atmosphereEnabled;
settings[MoonsIndex] = _jupiterProperty.moonsEnabled;
break;
case SaturnIndex:
settings[SizeDayIndex] = _saturnProperty.sizeDayEnabled;
settings[GravityIndex] = _saturnProperty.gravityEnabled;
settings[TemperatureIndex] = _saturnProperty.temperatureEnabled;
settings[AtmosphereIndex] = _saturnProperty.atmosphereEnabled;
settings[MoonsIndex] = _saturnProperty.moonsEnabled;
settings[RingsIndex] = _saturnProperty.ringsEnabled;
break;
case UranusIndex:
settings[SizeDayIndex] = _uranusProperty.sizeDayEnabled;
settings[GravityIndex] = _uranusProperty.gravityEnabled;
settings[TemperatureIndex] = _uranusProperty.temperatureEnabled;
settings[AtmosphereIndex] = _uranusProperty.atmosphereEnabled;
settings[MoonsIndex] = _uranusProperty.moonsEnabled;
break;
case NeptuneIndex:
settings[SizeDayIndex] = _neptuneProperty.sizeDayEnabled;
settings[GravityIndex] = _neptuneProperty.gravityEnabled;
settings[TemperatureIndex] = _neptuneProperty.temperatureEnabled;
settings[AtmosphereIndex] = _neptuneProperty.atmosphereEnabled;
settings[MoonsIndex] = _neptuneProperty.moonsEnabled;
break;
default:
throw ghoul::MissingCaseException();
break;
}
return osc::Blob(settings, NumSettings);
}
// Empty overidded functions
bool PlanetsSonification::updateData(const Camera*) {
return false;
}
void PlanetsSonification::sendData() {}
bool PlanetsSonification::updateData(const Camera* camera, int planetIndex,
TelemetryModule::AngleCalculationMode angleCalculationMode,
bool includeElevation)
{
double distance = calculateDistanceTo(
camera,
_planets[planetIndex].name,
DistanceUnit::Kilometer
);
if (std::abs(distance) < std::numeric_limits<double>::epsilon()) {
// The scene is likely not yet initialized
return false;
}
// Calculate the angles depending on the Angle calculation mode and if the overview
// mode is enabled, then calculate the angles with respect to the Sun instead of
// the camera.
double horizontalAngle = 0.0;
if (_overviewEnabled) {
horizontalAngle = calculateAngleFromAToB(
camera,
"Sun",
_planets[planetIndex].name,
angleCalculationMode
);
}
else {
horizontalAngle =
calculateAngleTo(camera, _planets[planetIndex].name, angleCalculationMode);
}
double verticalAngle = 0.0;
if (includeElevation) {
if (_overviewEnabled) {
verticalAngle = calculateElevationAngleFromAToB(
camera,
"Sun",
_planets[planetIndex].name,
angleCalculationMode
);
}
else {
verticalAngle = calculateElevationAngleTo(
camera,
_planets[planetIndex].name,
angleCalculationMode
);
}
}
// Calculate the angles to the moons from the planet. These angles are used for the
// sonification of the moons. The reason why the angle is calculated from the planet
// and not the camera is to give a feeling that the moons are orbiting the audience.
bool dataWasUpdated = false;
for (DataBody& moon : _planets[planetIndex].moons) {
double dist = calculateDistanceTo(camera, moon.name, DistanceUnit::Kilometer);
if (std::abs(dist) < std::numeric_limits<double>::epsilon()) {
// The scene is likely not yet initialized
return false;
}
if (std::abs(moon.distance - dist) > _distancePrecision) {
dataWasUpdated = true;
moon.distance = dist;
}
double moonHAngle = calculateAngleFromAToB(
camera,
_planets[planetIndex].name,
moon.name,
angleCalculationMode
);
if (std::abs(moon.horizontalAngle - moonHAngle) > _anglePrecision) {
dataWasUpdated = true;
moon.horizontalAngle = moonHAngle;
}
double moonVAngle = 0.0;
if (includeElevation) {
moonVAngle = calculateElevationAngleFromAToB(
camera,
_planets[planetIndex].name,
moon.name,
angleCalculationMode
);
}
if (std::abs(moon.verticalAngle - moonVAngle) > _anglePrecision) {
dataWasUpdated = true;
moon.verticalAngle = moonVAngle;
}
}
// Check if this data is new, otherwise don't send it
double prevDistance = _planets[planetIndex].distance;
double prevHorizontalAngle = _planets[planetIndex].horizontalAngle;
double prevVerticalAngle = _planets[planetIndex].verticalAngle;
if (std::abs(prevDistance - distance) > _distancePrecision ||
std::abs(prevHorizontalAngle - horizontalAngle) > _anglePrecision ||
std::abs(prevVerticalAngle - verticalAngle) > _anglePrecision ||
dataWasUpdated)
{
// Update the saved data for the planet
_planets[planetIndex].distance = distance;
_planets[planetIndex].horizontalAngle = horizontalAngle;
_planets[planetIndex].verticalAngle = verticalAngle;
dataWasUpdated = true;
}
return dataWasUpdated;
}
void PlanetsSonification::sendData(int planetIndex) {
if (planetIndex < 0 || planetIndex > _planets.size() - 1) {
LWARNING(std::format("Planet list does not include index {}", planetIndex));
return;
}
std::string label = "/" + _planets[planetIndex].name;
std::vector<OpenSoundControlDataType> data;
// The total size of the data vector is NumDataItemsPlanet for the
// planet, and then NumDataItemsMoon per moon
data.reserve(
NumDataItemsPlanet + NumDataItemsMoon * _planets[planetIndex].moons.size()
);
data.push_back(_planets[planetIndex].distance);
data.push_back(_planets[planetIndex].horizontalAngle);
data.push_back(_planets[planetIndex].verticalAngle);
osc::Blob settingsBlob = createSettingsBlob(planetIndex);
data.push_back(settingsBlob);
// Moons
for (const DataBody& moon : _planets[planetIndex].moons) {
data.push_back(moon.distance);
data.push_back(moon.horizontalAngle);
data.push_back(moon.verticalAngle);
}
_connection->send(label, data);
}
void PlanetsSonification::onToggleAllChanged() {
_mercuryProperty.toggleAll.setValue(_toggleAll);
_venusProperty.toggleAll.setValue(_toggleAll);
_earthProperty.toggleAll.setValue(_toggleAll);
_marsProperty.toggleAll.setValue(_toggleAll);
_jupiterProperty.toggleAll.setValue(_toggleAll);
_saturnProperty.toggleAll.setValue(_toggleAll);
_uranusProperty.toggleAll.setValue(_toggleAll);
_neptuneProperty.toggleAll.setValue(_toggleAll);
}
// Mercury
void PlanetsSonification::onMercuryAllChanged() {
_mercuryProperty.sizeDayEnabled.setValue(_mercuryProperty.toggleAll);
_mercuryProperty.gravityEnabled.setValue(_mercuryProperty.toggleAll);
_mercuryProperty.temperatureEnabled.setValue(_mercuryProperty.toggleAll);
}
void PlanetsSonification::onMercurySettingChanged() {
sendData(MercuryIndex);
}
// Venus
void PlanetsSonification::onVenusAllChanged() {
_venusProperty.sizeDayEnabled.setValue(_venusProperty.toggleAll);
_venusProperty.gravityEnabled.setValue(_venusProperty.toggleAll);
_venusProperty.temperatureEnabled.setValue(_venusProperty.toggleAll);
_venusProperty.atmosphereEnabled.setValue(_venusProperty.toggleAll);
}
void PlanetsSonification::onVenusSettingChanged() {
sendData(VenusIndex);
}
// Earth
void PlanetsSonification::onEarthAllChanged() {
_earthProperty.sizeDayEnabled.setValue(_earthProperty.toggleAll);
_earthProperty.gravityEnabled.setValue(_earthProperty.toggleAll);
_earthProperty.temperatureEnabled.setValue(_earthProperty.toggleAll);
_earthProperty.atmosphereEnabled.setValue(_earthProperty.toggleAll);
_earthProperty.moonsEnabled.setValue(_earthProperty.toggleAll);
}
void PlanetsSonification::onEarthSettingChanged() {
sendData(EarthIndex);
}
// Mars
void PlanetsSonification::onMarsAllChanged() {
_marsProperty.sizeDayEnabled.setValue(_marsProperty.toggleAll);
_marsProperty.gravityEnabled.setValue(_marsProperty.toggleAll);
_marsProperty.temperatureEnabled.setValue(_marsProperty.toggleAll);
_marsProperty.atmosphereEnabled.setValue(_marsProperty.toggleAll);
_marsProperty.moonsEnabled.setValue(_marsProperty.toggleAll);
}
void PlanetsSonification::onMarsSettingChanged() {
sendData(MarsIndex);
}
// Jupiter
void PlanetsSonification::onJupiterAllChanged() {
_jupiterProperty.sizeDayEnabled.setValue(_jupiterProperty.toggleAll);
_jupiterProperty.gravityEnabled.setValue(_jupiterProperty.toggleAll);
_jupiterProperty.temperatureEnabled.setValue(_jupiterProperty.toggleAll);
_jupiterProperty.atmosphereEnabled.setValue(_jupiterProperty.toggleAll);
_jupiterProperty.moonsEnabled.setValue(_jupiterProperty.toggleAll);
}
void PlanetsSonification::onJupiterSettingChanged() {
sendData(JupiterIndex);
}
// Saturn
void PlanetsSonification::onSaturnAllChanged() {
_saturnProperty.sizeDayEnabled.setValue(_saturnProperty.toggleAll);
_saturnProperty.gravityEnabled.setValue(_saturnProperty.toggleAll);
_saturnProperty.temperatureEnabled.setValue(_saturnProperty.toggleAll);
_saturnProperty.atmosphereEnabled.setValue(_saturnProperty.toggleAll);
_saturnProperty.moonsEnabled.setValue(_saturnProperty.toggleAll);
_saturnProperty.ringsEnabled.setValue(_saturnProperty.toggleAll);
}
void PlanetsSonification::onSaturnSettingChanged() {
sendData(SaturnIndex);
}
// Uranus
void PlanetsSonification::onUranusAllChanged() {
_uranusProperty.sizeDayEnabled.setValue(_uranusProperty.toggleAll);
_uranusProperty.gravityEnabled.setValue(_uranusProperty.toggleAll);
_uranusProperty.temperatureEnabled.setValue(_uranusProperty.toggleAll);
_uranusProperty.atmosphereEnabled.setValue(_uranusProperty.toggleAll);
_uranusProperty.moonsEnabled.setValue(_uranusProperty.toggleAll);
}
void PlanetsSonification::onUranusSettingChanged() {
sendData(UranusIndex);
}
// Neptune
void PlanetsSonification::onNeptuneAllChanged() {
_neptuneProperty.sizeDayEnabled.setValue(_neptuneProperty.toggleAll);
_neptuneProperty.gravityEnabled.setValue(_neptuneProperty.toggleAll);
_neptuneProperty.temperatureEnabled.setValue(_neptuneProperty.toggleAll);
_neptuneProperty.atmosphereEnabled.setValue(_neptuneProperty.toggleAll);
_neptuneProperty.moonsEnabled.setValue(_neptuneProperty.toggleAll);
}
void PlanetsSonification::onNeptuneSettingChanged() {
sendData(NeptuneIndex);
}
scripting::LuaLibrary PlanetsSonification::luaLibrary() {
return {
"sonification",
{
codegen::lua::AddPlanets
}
};
}
} // namespace openspace
@@ -0,0 +1,59 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
namespace {
/**
* Adds the given list of planets to the PlanetsSonification internal list of Planets
* and Moons.
*/
[[codegen::luawrap]] void addPlanets(ghoul::Dictionary planets) {
openspace::TelemetryModule* module =
openspace::global::moduleEngine->module<openspace::TelemetryModule>();
if (!module) {
throw ghoul::lua::LuaError("Could not find the Telemetry Module");
return;
}
openspace::TelemetryBase* ptr = module->telemetry("PlanetsSonification");
if (!ptr) {
throw ghoul::lua::LuaError("Could not find the Planets Sonification");
return;
}
openspace::PlanetsSonification* planetsSonification =
dynamic_cast<openspace::PlanetsSonification*>(ptr);
if (!planetsSonification) {
throw ghoul::lua::LuaError("Could not cast to PlanetsSonification");
}
for (const std::string_view& k : planets.keys()) {
const ghoul::Dictionary& planet = planets.value<ghoul::Dictionary>(k);
planetsSonification->addPlanet(planet);
}
}
#include "planetssonification_lua_codegen.cpp"
} // namespace
+89
View File
@@ -0,0 +1,89 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/telemetry/include/telemetrybase.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/moduleengine.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/query/query.h>
namespace {
constexpr std::string_view _loggerCat = "TelemetryBase";
constexpr openspace::properties::Property::PropertyInfo EnabledInfo = {
"Enabled",
"Enabled",
"This setting determines whether this telemetry gathering is enabled or not.",
openspace::properties::Property::Visibility::User
};
} // namespace
namespace openspace {
TelemetryBase::TelemetryBase(properties::PropertyOwner::PropertyOwnerInfo info,
const std::string& ip, int port)
: properties::PropertyOwner(info)
, _enabled(EnabledInfo, false)
, _identifier(info.identifier)
, _connection(new OpenSoundControlConnection(ip, port))
{
addProperty(_enabled);
_enabled.onChange([this]() {
if (!_enabled) {
// Disable sending of data
stop();
}
});
}
TelemetryBase::~TelemetryBase() {
stop();
delete _connection;
}
void TelemetryBase::update(const Camera* camera) {
if (!_enabled) {
return;
}
const bool dataWasUpdated = updateData(camera);
if (dataWasUpdated) {
sendData();
}
}
void TelemetryBase::stop() {}
std::string TelemetryBase::identifier() const {
return _identifier;
}
bool TelemetryBase::enabled() const {
return _enabled;
}
} // namespace openspace
+457
View File
@@ -0,0 +1,457 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/telemetry/include/util.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/moduleengine.h>
#include <openspace/query/query.h>
#include <openspace/scene/scenegraphnode.h>
#include <glm/gtx/projection.hpp>
#include <glm/gtx/vector_angle.hpp>
namespace {
/**
* Get the position of the node with the given identifier.
*
* \param nodeIdentifier The identifier of the node to get the position for
*
* \return The position of the node with the given identifier
*/
glm::dvec3 nodePosition(const std::string& nodeIdentifier) {
if (nodeIdentifier.empty()) {
return glm::dvec3(0.0);
}
// Find the node
openspace::SceneGraphNode* node = openspace::sceneGraphNode(nodeIdentifier);
return node ? node->worldPosition() : glm::dvec3(0.0);
}
} // namespace
namespace openspace {
// Distances
double calculateDistanceTo(const Camera* camera, const std::string& nodeIdentifier,
DistanceUnit unit)
{
glm::dvec3 nodePos = nodePosition(nodeIdentifier);
return calculateDistanceTo(camera, nodePos, unit);
}
double calculateDistanceTo(const Camera* camera, glm::dvec3 nodePosition,
DistanceUnit unit)
{
if (glm::length(nodePosition) < std::numeric_limits<double>::epsilon()) {
return 0.0;
}
// Calculate distance to the node from the camera
double distance = glm::distance(nodePosition, camera->positionVec3());
// Convert from meters to desired unit
return convertMeters(distance, unit);
}
// Horizontal angles
double calculateAngleTo(const Camera* camera, const std::string& nodeIdentifier,
TelemetryModule::AngleCalculationMode angleCalculationMode)
{
glm::dvec3 nodePos = nodePosition(nodeIdentifier);
return calculateAngleTo(camera, nodePos, angleCalculationMode);
}
double calculateAngleTo(const Camera* camera, glm::dvec3 nodePosition,
TelemetryModule::AngleCalculationMode angleCalculationMode)
{
if (glm::length(nodePosition) < std::numeric_limits<double>::epsilon()) {
return 0.0;
}
// Camera state
glm::dvec3 cameraUpVector = camera->lookUpVectorWorldSpace();
glm::dvec3 cameraViewVector = camera->viewDirectionWorldSpace();
// Get the vector from the camera to the node
glm::dvec3 cameraToNode = nodePosition - camera->positionVec3();
// Calculate the horizontal angle depending on the surround mode
if (angleCalculationMode == TelemetryModule::AngleCalculationMode::Horizontal) {
// Calculate the angle from the camera, to the node, within the camera plane. The
// camera plane is the plane of the camera view direction + camera left direction
// (i.e. the negative camera right direction), with the camera up direction as
// the normal.
//
// Pplane(v) is v projected to the camera plane,
// Pup(v) is v projected on camera up direction (which is the normal of the camera
// plane). This gives the formula:
// Pplane(v) = v - Pup(v)
glm::dvec3 cameraToNodeProjected =
cameraToNode - glm::proj(cameraToNode, cameraUpVector);
// Get the angle between the camera view direction, and the vector from the camera
// to the node projected to the camera plane, with the camera up vector as
// reference axis (i.e. the normal of the camera plane). When the node is located
// towards the left relative the center of the screen, then the angle will be a
// positive value. If instead, the node is located towards the right, the angle
// will become negative.
return glm::orientedAngle(
glm::normalize(cameraViewVector),
glm::normalize(cameraToNodeProjected),
glm::normalize(cameraUpVector)
);
}
else if (angleCalculationMode == TelemetryModule::AngleCalculationMode::Circular) {
// Calculate the angle from the camera, to the node, within the camera view plane.
// The camera view plane is the plane of the camera up direction + camera left
// direction (i.e. the negative camera right direction), with the negative camera
// view direction as the normal.
//
// Pvplane(v) is v projected onto the camera view plane,
// Pview(v) is v projected on the camera view direction (which is the negative
// normal of the camera view plane). This gives the formula:
// Pvplane(v) = v - Pview(v)
glm::dvec3 cameraToNodeProjected =
cameraToNode - glm::proj(cameraToNode, cameraViewVector);
// Get the angle between the camera up direction, and the vector from the camera
// to the node projected to the camera view plane, with the negative camera view
// direction as reference axis (i.e. the normal of the camera view plane). When
// the node is located towards the left relative the center of the screen, then
// the angle will be a positive value. If instead, the node is located towards the
// right, the angle will become negative.
return glm::orientedAngle(
glm::normalize(cameraUpVector),
glm::normalize(cameraToNodeProjected),
glm::normalize(-cameraViewVector)
);
}
// 0.0 is the angle straight forward, in the camera view direction
return 0.0;
}
double calculateAngleFromAToB(const Camera* camera,
const std::string& nodeIdentifierA,
const std::string& nodeIdentifierB,
TelemetryModule::AngleCalculationMode angleCalculationMode)
{
glm::dvec3 nodeAPosition = nodePosition(nodeIdentifierA);
glm::dvec3 nodeBPosition = nodePosition(nodeIdentifierB);
return calculateAngleFromAToB(
camera,
nodeAPosition,
nodeBPosition,
angleCalculationMode
);
}
double calculateAngleFromAToB(const Camera* camera, glm::dvec3 nodePositionA,
glm::dvec3 nodePositionB,
TelemetryModule::AngleCalculationMode angleCalculationMode)
{
if (glm::length(nodePositionA) < std::numeric_limits<double>::epsilon() ||
glm::length(nodePositionB) < std::numeric_limits<double>::epsilon())
{
return 0.0;
}
// Camera state
glm::dvec3 cameraUpVector = camera->lookUpVectorWorldSpace();
glm::dvec3 cameraViewVector = camera->viewDirectionWorldSpace();
// Get the vector from the first node (A) to the second node (B)
glm::dvec3 AToB = nodePositionB - nodePositionA;
// Calculate the horizontal angle depending on the surround mode
if (angleCalculationMode == TelemetryModule::AngleCalculationMode::Horizontal) {
// Calculate the angle from node A, to node B, within the camera plane. The camera
// plane is the plane of the camera view direction + camera left direction
// (i.e. the negative camera right direction), with the camera up direction as
// the normal.
//
// Pplane(v) is v projected to the camera plane,
// Pup(v) is v projected on camera up direction (which is the normal of the camera
// plane). This gives the formula:
// Pplane(v) = v - Pup(v)
glm::dvec3 AToBProjected = AToB - glm::proj(AToB, cameraUpVector);
// Get the angle between the camera view direction, and the vector from node A to
// node B projected to the camera plane, with the camera up vector as reference
// axis (i.e. the normal of the camera plane). When the vector from A to B, points
// to the left relative the camera view direction, then the angle will be a
// positive value. If instead, the vector points towards the right, the angle will
// become negative.
// @NOTE (malej 2023-02-06): This might not work if the camera is looking straight
// up/down on node A.
return glm::orientedAngle(
glm::normalize(cameraViewVector),
glm::normalize(AToBProjected),
glm::normalize(cameraUpVector)
);
}
else if (angleCalculationMode == TelemetryModule::AngleCalculationMode::Circular) {
// Calculate the angle from node A to the node B within the camera view plane. The
// camera view plane is the plane of the camera up direction + camera left
// direction (i.e. the negative camera right direction), with the negative camera
// view direction as the normal.
//
// Pvplane(v) is v projected onto the camera view plane,
// Pview(v) is v projected on the camera view direction (which is the negative
// normal of the camera view plane). This gives the formula:
// Pvplane(v) = v - Pview(v)
glm::dvec3 AToBProjected = AToB - glm::proj(AToB, cameraViewVector);
// Get the angle between the camera up direction, and the vector from node A to
// node B projected to the camera view plane, with the negative camera view vector
// as reference axis (i.e. the normal of the camera view plane). When the vector
// from A to B, points to the left relative the camera up direction, then the
// angle will be a positive value. If instead, the vector points towards the
// right, the angle will become negative.
// @NOTE (malej 2023-02-06): This might not work if the camera is looking straight
// up/down on node A.
return glm::orientedAngle(
glm::normalize(cameraUpVector),
glm::normalize(AToBProjected),
glm::normalize(-cameraViewVector)
);
}
// 0.0 is the angle straight forward, in the camera view direction
return 0.0;
}
// Elevation angles
double calculateElevationAngleTo(const Camera* camera,
const std::string& nodeIdentifier,
TelemetryModule::AngleCalculationMode angleCalculationMode)
{
glm::dvec3 nodePos = nodePosition(nodeIdentifier);
return calculateElevationAngleTo(camera, nodePos, angleCalculationMode);
}
double calculateElevationAngleTo(const Camera* camera, glm::dvec3 nodePosition,
TelemetryModule::AngleCalculationMode angleCalculationMode)
{
if (glm::length(nodePosition) < std::numeric_limits<double>::epsilon()) {
return 0.0;
}
// Camera state
glm::dvec3 cameraUpVector = camera->lookUpVectorWorldSpace();
glm::dvec3 cameraViewVector = camera->viewDirectionWorldSpace();
glm::dvec3 cameraRightVector = glm::cross(cameraViewVector, cameraUpVector);
// Get the vector from the camera to the node
glm::dvec3 cameraToNode = nodePosition - camera->positionVec3();
// Calculate the elevation angle depending on the surround mode
if (angleCalculationMode == TelemetryModule::AngleCalculationMode::Horizontal) {
// Calculate the elevation angle from the camera, to the node, within the camera
// view + up plane. The camera view + up plane is the plane of the camera view
// direction + camera up direction, with the camera right direction as the normal.
//
// Pvupplane(v) is v projected on the camrea view + up plane
// Pright(v) is v projected onto the camera right vector (which is the normal of
// the camera view + up plane). This gives the formula:
// Pvupplane(v) = v - Pright(v)
glm::dvec3 cameraToNodeProjected =
cameraToNode - glm::proj(cameraToNode, cameraRightVector);
// Get the angle between the camera view direction, and the vector from the camera
// to the node projected to the camera view + up plane, with the camera right
// vector as reference axis (i.e. the normal of the camera view + up plane). When
// the node is located above the center of the screen, then the angle will be a
// positive value. If instead, the node is located below, the angle will become
// negative.
return glm::orientedAngle(
glm::normalize(cameraViewVector),
glm::normalize(cameraToNodeProjected),
glm::normalize(cameraRightVector)
);
}
else if (angleCalculationMode == TelemetryModule::AngleCalculationMode::Circular) {
// Calculate the elevation angle from the camera, to the node, within the camera
// view + up plane. The camera view + up plane is the plane of the camera view
// direction + camera up direction, with the camera right direction as the normal.
// In case of the circular surround mode, this is done in several steps.
// In the first step, the vector from the camera to the node is projected to the
// camera view plane. The camera view plane is the plane of the camera up
// direction + camera left direction (i.e. the negative camera right direction),
// with the negative camera view direction as the normal.
//
// Pvplane(v) is v projected onto the camera view plane,
// Pview(v) is v projected on the camera view direction (which is the normal of
// the camera view plane). This gives the formula:
// Pvplane(v) = v - Pview(v)
glm::dvec3 cameraToNodeProjected =
cameraToNode - glm::proj(cameraToNode, cameraViewVector);
// In the next step, get the angle between the camera up direction, and the vector
// from the camera to the node projected to the camera view plane, with the
// negative camera view direction as reference axis (i.e. the normal of the camera
// view plane). When the node is located towards the left relative the center of
// the screen, then the angle will be a positive value. If instead, the node is
// located towards the right, the angle will become negative.
double rotationAngle = glm::orientedAngle(
glm::normalize(cameraUpVector),
glm::normalize(cameraToNodeProjected),
glm::normalize(-cameraViewVector)
);
// Then we counter-rotate with the angle from the previous step, so the projected
// vector to the node (from the first step) is inside the camera view + up plane.
glm::dvec3 rotatedVector = glm::rotate(
cameraToNode,
rotationAngle,
glm::normalize(cameraViewVector)
);
// Lastly, we calculate the elavation angle in the same way as in the horizontal
// with elevation surround mode above.
return std::abs(glm::orientedAngle(
glm::normalize(cameraViewVector),
glm::normalize(rotatedVector),
glm::normalize(cameraRightVector)
));
}
// 0.0 is the angle straight forward, in the camera view direction
return 0.0;
}
double calculateElevationAngleFromAToB(const Camera* camera,
const std::string& nodeIdentifierA,
const std::string& nodeIdentifierB,
TelemetryModule::AngleCalculationMode angleCalculationMode)
{
glm::dvec3 nodeAPosition = nodePosition(nodeIdentifierA);
glm::dvec3 nodeBPosition = nodePosition(nodeIdentifierB);
return calculateElevationAngleFromAToB(
camera,
nodeAPosition,
nodeBPosition,
angleCalculationMode
);
}
double calculateElevationAngleFromAToB(const Camera* camera, glm::dvec3 nodePositionA,
glm::dvec3 nodePositionB,
TelemetryModule::AngleCalculationMode angleCalculationMode)
{
if (glm::length(nodePositionA) < std::numeric_limits<double>::epsilon() ||
glm::length(nodePositionB) < std::numeric_limits<double>::epsilon())
{
return 0.0;
}
// Camera state
glm::dvec3 cameraUpVector = camera->lookUpVectorWorldSpace();
glm::dvec3 cameraViewVector = camera->viewDirectionWorldSpace();
glm::dvec3 cameraRightVector = glm::cross(cameraViewVector, cameraUpVector);
// Get the vector from the first node (A) to the second node (B)
glm::dvec3 AToB = nodePositionB - nodePositionA;
// Calculate the elevation angle depending on the surround mode
if (angleCalculationMode == TelemetryModule::AngleCalculationMode::Horizontal) {
// Calculate the elevation angle from node A, to node B, within the camera view +
// up plane. The camera view + up plane is the plane of the camera view direction
// + camera up direction, with the camera right direction as the normal.
//
// Pvupplane(v) is v projected on the camrea view + up plane,
// Pright(v) is v projected onto the camera right vector (which is the normal of
// the camera view + up plane). This gives the formula:
// Pvupplane(v) = v - Pright(v)
glm::dvec3 AToProjectedB = AToB - glm::proj(AToB, cameraRightVector);
// Get the angle between the camera view direction, and the vector from the camera
// to the node projected to the camera view + up plane, with the camera right
// vector as reference axis (i.e. the normal of the camera view + up plane). When
// the node is located above the center of the screen, then the angle will be a
// positive value. If instead, the node is located below, the angle will become
// negative.
// Get the angle between the camera view direction, and the vector from node A to
// node B projected to the camera view + up plane, with the camera right vector as
// reference axis (i.e. the normal of the camera view + up plane). When the vector
// from A to B, points upwards relative to the camera view direction, then the
// angle will be a positive value. If instead, the vector points downwards, the
// angle will become negative.
// @NOTE (malej 2023-02-06): This might not work if the camera is looking straight
// up/down on node A.
return glm::orientedAngle(
glm::normalize(cameraViewVector),
glm::normalize(AToProjectedB),
glm::normalize(cameraRightVector)
);
}
else if (angleCalculationMode == TelemetryModule::AngleCalculationMode::Circular) {
// Calculate the elevation angle from node A, to node B, within the camera view +
// up plane. The camera view + up plane is the plane of the camera view direction
// + camera up direction, with the camera right direction as the normal. In case
// of the circular surround mode, this is done in several steps.
// In the first step, the vector from the camera to the node is projected to the
// camera view plane. The camera view plane is the plane of the camera up
// direction + camera left direction (i.e. the negative camera right direction),
// with the negative camera view direction as the normal.
//
// Pvplane(v) is v projected onto the camera view plane,
// Pview(v) is v projected on the camera view direction (which is the normal of
// the camera view plane). This gives the formula:
// Pvplane(v) = v - Pview(v)
glm::dvec3 AToProjectedB = AToB - glm::proj(AToB, cameraViewVector);
// Then we counter-rotate with the angle from the previous step, so the projected
// vector to the node (from the first step) is inside the camera view + up plane.
double rotationAngle = glm::orientedAngle(
glm::normalize(cameraUpVector),
glm::normalize(AToProjectedB),
glm::normalize(-cameraViewVector)
);
// First we counter-rotate the circular angle to make the cameraToNode vector be
// inside the view-up plane
glm::dvec3 rotatedVector =
glm::rotate(AToB, rotationAngle, glm::normalize(cameraViewVector));
// Lastly, we calculate the elavation angle in the same way as in the horizontal
// with elevation surround mode above.
return std::abs(glm::orientedAngle(
glm::normalize(cameraViewVector),
glm::normalize(rotatedVector),
glm::normalize(cameraRightVector)
));
}
// 0.0 is the angle straight forward, in the camera view direction
return 0.0;
}
} // namespace openspace
+319
View File
@@ -0,0 +1,319 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/telemetry/telemetrymodule.h>
#include <modules/telemetry/include/general/anglemodetelemetry.h>
#include <modules/telemetry/include/general/cameratelemetry.h>
#include <modules/telemetry/include/general/focustelemetry.h>
#include <modules/telemetry/include/general/nodestelemetry.h>
#include <modules/telemetry/include/general/timetelemetry.h>
#include <modules/telemetry/include/specific/planetscomparesonification.h>
#include <modules/telemetry/include/specific/planetsoverviewsonification.h>
#include <modules/telemetry/include/specific/planetssonification.h>
#include <openspace/camera/camera.h>
#include <openspace/documentation/documentation.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/globalscallbacks.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scene.h>
#include <openspace/scripting/lualibrary.h>
#include <ghoul/logging/logmanager.h>
namespace {
constexpr std::string_view _loggerCat = "TelemetryModule";
// The default Open Sound Control receiver is SuperCollider with these default values.
// However, the user can define any receiver in the openspace.cfg file as the
// ModuleConfiguration for the Telemetry module.
constexpr std::string_view DefaultSuperColliderIp = "127.0.0.1";
constexpr int DefaultSuperColliderPort = 57120;
constexpr openspace::properties::Property::PropertyInfo EnabledInfo = {
"Enabled",
"Enabled",
"Enable or disable all gathering of telemetry information.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo IpAddressInfo = {
"IpAddress",
"IP Address",
"The network IP address that the telemetry Open Sound Control messages is sent "
"to.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo PortInfo = {
"Port",
"Port",
"The network port that the telemetry Open Sound Control messages are sent to.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo AngleCalculationModeInfo = {
"AngleCalculationMode",
"Angle Calculation Mode",
"This setting changes the method used to calculate any angles in the "
"telemetries. The Horizontal mode, generally works well for flat displays or "
"forward facing immersive environments. The Circular mode, generally works well "
"for centered fisheye displays or omnidirectional immersive environments. For "
"more information, see the pages \"Angle Calculations\" and \"Surround Sound "
"Configurations\"on the OpenSpace documentation page.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo IncludeElevationAngleInfo = {
"IncludeElevationAngle",
"Include Elevation Angle",
"This setting determines if an additional elevation angle should be calculated "
"for the telemetries that calculate angles. This angle determines where the "
"object is placed within a vertical plane of reference in relation to the "
"camera, i.e the height in relation to the horizontal plane of reference. The "
"method used for this calculation also depends on the angle calculation mode. "
"For more information, see the page \"Angle Calculations\" on the OpenSpace "
"documentation page.",
openspace::properties::Property::Visibility::User
};
struct [[codegen::Dictionary(TelemetryModule)]] Parameters {
// [[codegen::verbatim(IpAddressInfo.description)]]
std::optional<std::string> ipAddress;
// [[codegen::verbatim(PortInfo.description)]]
std::optional<int> port;
enum class
[[codegen::map(openspace::TelemetryModule::AngleCalculationMode)]]
AngleCalculationMode {
Horizontal,
Circular
};
// [[codegen::verbatim(AngleCalculationModeInfo.description)]]
std::optional<AngleCalculationMode> angleCalculationMode;
// [[codegen::verbatim(IncludeElevationAngleInfo.description)]]
std::optional<bool> includeElevationAngle;
};
#include "telemetrymodule_codegen.cpp"
} // namespace
namespace openspace {
TelemetryModule::TelemetryModule()
: OpenSpaceModule("Telemetry")
, _enabled(EnabledInfo, false)
, _ipAddress(IpAddressInfo, DefaultSuperColliderIp.data())
, _port(PortInfo, DefaultSuperColliderPort, 1025, 65536)
, _modeOptions(AngleCalculationModeInfo)
, _includeElevationAngle(IncludeElevationAngleInfo, false)
{
addProperty(_enabled);
_ipAddress.setReadOnly(true);
addProperty(_ipAddress);
_port.setReadOnly(true);
addProperty(_port);
// Add options to the drop down menu
_modeOptions.addOptions({
{ 0, "Horizontal" },
{ 1, "Circular" }
});
_modeOptions.onChange([this]() { guiOnChangeAngleCalculationMode(); });
// Select Horizontal angle calculation mode as the default
_modeOptions.setValue(static_cast<int>(AngleCalculationMode::Horizontal));
addProperty(_modeOptions);
addProperty(_includeElevationAngle);
}
TelemetryModule::~TelemetryModule() {
// Clear the telemetries list
for (TelemetryBase* telemetry : _telemetries) {
delete telemetry;
}
}
void TelemetryModule::guiOnChangeAngleCalculationMode() {
_angleCalculationMode = static_cast<AngleCalculationMode>(_modeOptions.value());
}
void TelemetryModule::internalInitialize(const ghoul::Dictionary& dictionary) {
const Parameters p = codegen::bake<Parameters>(dictionary);
_ipAddress = p.ipAddress.value_or(_ipAddress);
_port = p.port.value_or(_port);
if (p.angleCalculationMode.has_value()) {
Parameters::AngleCalculationMode mode =
Parameters::AngleCalculationMode(*p.angleCalculationMode);
_angleCalculationMode = codegen::map<AngleCalculationMode>(mode);
}
// Fill telemetry list
TelemetryBase* telemetry = new AngleModeTelemetry(_ipAddress, _port);
addTelemetry(telemetry);
telemetry = new CameraTelemetry(_ipAddress, _port);
addTelemetry(telemetry);
telemetry = new FocusTelemetry(_ipAddress, _port);
addTelemetry(telemetry);
telemetry = new TimeTelemetry(_ipAddress, _port);
addTelemetry(telemetry);
telemetry = new NodesTelemetry(_ipAddress, _port);
addTelemetry(telemetry);
telemetry = new PlanetsCompareSonification(_ipAddress, _port);
addTelemetry(telemetry);
telemetry = new PlanetsOverviewSonification(_ipAddress, _port);
addTelemetry(telemetry);
telemetry = new PlanetsSonification(_ipAddress, _port);
addTelemetry(telemetry);
// Only the master runs the TelemetryModule update thread
if (global::windowDelegate->isMaster()) {
_isRunning = true;
_updateThread = std::thread([this]() { update(std::ref(_isRunning)); });
// Make sure the telemetry thread is synced with the main thread
global::callback::postSyncPreDraw->emplace_back([this]() {
// Tell the telemetry thread that a new frame is starting
syncToMain.notify_one();
});
// When the program shuts down, make sure this module turns itself off.
// If the module is turned on while the scene is being destroyed, then it will crash.
global::callback::deinitialize->emplace_back([this]() {
_enabled = false;
});
}
}
void TelemetryModule::addTelemetry(TelemetryBase* telemetry) {
_telemetries.push_back(telemetry);
addPropertySubOwner(telemetry);
}
void TelemetryModule::internalDeinitialize() {
// Stop the loop and tell the thread it is ok to run the last itteration
_isRunning = false;
syncToMain.notify_one();
_updateThread.join();
}
const std::vector<TelemetryBase*>& TelemetryModule::telemetries() const {
return _telemetries;
}
const TelemetryBase* TelemetryModule::telemetry(const std::string_view& id) const {
for (const TelemetryBase* t : _telemetries) {
if (t->identifier() == id) {
return t;
}
}
return nullptr;
}
TelemetryBase* TelemetryModule::telemetry(const std::string_view& id) {
for (TelemetryBase* t : _telemetries) {
if (t->identifier() == id) {
return t;
}
}
return nullptr;
}
TelemetryModule::AngleCalculationMode TelemetryModule::angleCalculationMode() const {
return _angleCalculationMode;
}
bool TelemetryModule::includeElevationAngle() const {
return _includeElevationAngle;
}
void TelemetryModule::update(std::atomic<bool>& isRunning) {
Scene* scene = nullptr;
Camera* camera = nullptr;
bool isInitialized = false;
while (isRunning) {
// Wait for the main thread
std::unique_lock<std::mutex> lk(mutexLock);
syncToMain.wait(lk);
// No need to update if the module isn't currently enabled
if (!_enabled) {
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
}
// Initialize the scena and camera information if that has not already been done
if (!isInitialized) {
// Find the scene
if (!scene) {
scene = global::renderEngine->scene();
}
// Find the camera in the scene
if (!camera) {
camera = scene ? scene->camera() : nullptr;
}
// Check status
isInitialized = scene && !scene->isInitializing() &&
!scene->root()->children().empty() && camera &&
glm::length(camera->positionVec3()) >
std::numeric_limits<double>::epsilon();
}
// Process the telemetries
if (isInitialized) {
for (TelemetryBase* telemetry : _telemetries) {
if (telemetry) {
telemetry->update(camera);
}
}
}
}
}
std::vector<scripting::LuaLibrary> TelemetryModule::luaLibraries() const {
return {
NodesTelemetry::luaLibrary(),
PlanetsSonification::luaLibrary()
};
}
} // namespace openspace
+170
View File
@@ -0,0 +1,170 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2025 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_TELEMETRY___TELEMETRYMODULE___H__
#define __OPENSPACE_MODULE_TELEMETRY___TELEMETRYMODULE___H__
#include "openspace/util/openspacemodule.h"
#include <modules/telemetry/include/telemetrybase.h>
#include <openspace/properties/misc/optionproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/intproperty.h>
#include <openspace/properties/misc/stringproperty.h>
#include <ghoul/misc/boolean.h>
#include <atomic>
#include <condition_variable>
namespace openspace {
class TelemetryBase;
class TelemetryModule : public OpenSpaceModule {
public:
constexpr static const char* Name = "Telemetry";
/**
* This mode only affects telemetries that send angle information. For example, the
* NodesTelemetry and the PlanetsSonification. More documentation for the elevation
* part of the angles is located further below.
*
* Horizontal: This angle calculation mode is suitable for flat displays or
* forward-facing immersive environments. For more information about
* surround sound configurations, see "Surround Sound Configurations" on
* the OpenSpace documentation page. This angle determines where the
* object is placed within a horizontal plane of reference in relation to
* the camera.
*
* Circular: This angle calculation mode is suitable for centered fisheye displays or
* omnidirectional immersive environments. For more information about
* surround sound configurations, see "Surround Sound Configurations" on
* the OpenSpace documentation page. The computed angle determines the
* object's position in a circular space around the center of the screen.
* Compared to the horizontal mode, this mode calculates the angles in a
* circular (or radial) manner around the center point instead of the
* deviation from the camera view direction.
*/
enum class AngleCalculationMode {
Horizontal = 0,
Circular
};
TelemetryModule();
~TelemetryModule();
/**
* Returns the Lua libraries for all telemetries available in the telemetry module.
*
* \return The Lua libraries for all telemetries available in the telemetry module
*/
std::vector<scripting::LuaLibrary> luaLibraries() const override;
virtual void internalInitialize(const ghoul::Dictionary& dictionary) override;
virtual void internalDeinitialize() override;
/**
* Get the list of telemetries that are currently registered in the module.
*
* \return A list of all registered telemetries
*/
const std::vector<TelemetryBase*>& telemetries() const;
/**
* Get a specified telemetry from the list of registered telemetries in the module.
*
* \param id The identifier of the telemetry to fetch
*
* \return The requested telemetry
*/
const TelemetryBase* telemetry(const std::string_view& id) const;
TelemetryBase* telemetry(const std::string_view& id);
/**
* Get the current angle calculation mode used in the telemetry module.
*
* \return The angle calculation mode
*/
AngleCalculationMode angleCalculationMode() const;
/**
* Return whether any elevation angles are being caclulated and sent to the Open Sound
* Control receiver or not.
*
* \return `true` if elevation angles are being calculated and sent to the Open Sound
* Control receiver, `false` otherwise
*/
bool includeElevationAngle() const;
private:
/**
* Main update function that keeps track of all telemetries and keeps the update
* thread running and synced to the OpenSpace main thread.
*
* \param isRunning Whether the thread should be kept running or shut down and joined
*/
void update(std::atomic<bool>& isRunning);
/**
* Add a given telemetry to the list of registered telemetries in the module.
*
* \param telemetry The telemetry to register in the module
*/
void addTelemetry(TelemetryBase* telemetry);
/**
* Function that gets called when the angle calculation mode is changed in the GUI.
*/
void guiOnChangeAngleCalculationMode();
// To sync the sonification thread with the main thread
std::mutex mutexLock;
std::condition_variable syncToMain;
properties::BoolProperty _enabled;
properties::StringProperty _ipAddress;
properties::IntProperty _port;
properties::OptionProperty _modeOptions;
/**
* This setting only affects telemetries that send angle information. For example, the
* NodesTelemetry and the PlanetsSonification.
*
* `true`: This angle determines where the object is placed within a vertical plane of
* reference in relation to the camera, i.e the height in relation to the
* horizontal plane of reference.
*
* `false`: The elevation angle sent to the Open Sound Control receiver is always set
* to 0.0
*/
properties::BoolProperty _includeElevationAngle;
std::thread _updateThread;
std::atomic<bool> _isRunning = false;
std::vector<TelemetryBase*> _telemetries;
AngleCalculationMode _angleCalculationMode = AngleCalculationMode::Horizontal;
};
} // namespace openspace
#endif __OPENSPACE_MODULE_TELEMETRY___TELEMETRYMODULE___H__
+6
View File
@@ -163,6 +163,12 @@ ModuleConfigurations = {
},
Space = {
ShowExceptions = false
},
Telemetry = {
IpAddress = "127.0.0.1",
Port = 57120,
AngleCalculationMode = "Horizontal",
IncludeElevationAngle = false
}
-- OBS! The settings for the SkyBrowser and Exoplanets modules are
-- set in individual assets, see "data/assets/modules". Note that