Files
OpenSpace/src/util/time_lua.inl
2020-08-24 15:09:45 +02:00

789 lines
26 KiB
C++

/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2020 *
* *
* 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 <openspace/engine/globals.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scene.h>
#include <openspace/util/timeconversion.h>
#include <ghoul/fmt.h>
#include <ghoul/misc/assert.h>
#include <ghoul/misc/misc.h>
#include <cctype>
#include <ctime>
namespace openspace::luascriptfunctions {
/**
* \ingroup LuaScripts
* setDeltaTime(number):
* Sets the delta time by calling the Time::setDeltaTime method
*/
int time_setDeltaTime(lua_State* L) {
const int nArguments = lua_gettop(L);
if (nArguments == 1) {
const bool isNumber = (lua_isnumber(L, 1) != 0);
if (!isNumber) {
lua_settop(L, 0);
const char* msg = lua_pushfstring(
L,
"%s expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return luaL_error(L, "bad argument #%d (%s)", 2, msg);
}
const double newDeltaTime = lua_tonumber(L, 1);
global::timeManager.setDeltaTime(newDeltaTime);
}
else {
lua_settop(L, 0);
const char* msg = lua_pushfstring(L,
"Bad number of arguments. Expected 1 or 2.");
return ghoul::lua::luaError(L, fmt::format("bad argument ({})", msg));
}
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* interpolateNextDeltaTimeStep(list of numbers):
* Sets the list of discrete delta time steps for the simulation speed.
*/
int time_setDeltaTimeSteps(lua_State* L) {
ghoul::lua::checkArgumentsAndThrow(L, 1, "lua::time_setDeltaTimeSteps");
ghoul::Dictionary dict;
ghoul::lua::luaDictionaryFromState(L, dict);
const size_t nItems = dict.size();
std::vector<double> inputDeltaTimes;
inputDeltaTimes.reserve(nItems);
for (size_t i = 1; i <= nItems; ++i) {
std::string key = std::to_string(i);
if (dict.hasKeyAndValue<double>(key)) {
const double time = dict.value<double>(key);
inputDeltaTimes.push_back(time);
}
else {
const char* msg = lua_pushfstring(L,
"Error setting delta times. Expected list of numbers.");
return ghoul::lua::luaError(L, fmt::format("bad argument ({})", msg));
}
}
lua_pop(L, 1);
global::timeManager.setDeltaTimeSteps(inputDeltaTimes);
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* setNextDeltaTimeStep():
* Immediately set the simulation speed to the first delta time step in the list that is
* larger than the current choice of simulation speed, if any.
*/
int time_setNextDeltaTimeStep(lua_State* L) {
ghoul::lua::checkArgumentsAndThrow(L, 0, "lua::time_setNextDeltaTimeStep");
global::timeManager.setNextDeltaTimeStep();
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* setPreviousDeltaTimeStep():
* Immediately set the simulation speed to the first delta time step in the list that is
* smaller than the current choice of simulation speed, if any.
*/
int time_setPreviousDeltaTimeStep(lua_State* L) {
ghoul::lua::checkArgumentsAndThrow(L, 0, "lua::time_setPreviousDeltaTimeStep");
global::timeManager.setPreviousDeltaTimeStep();
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* interpolateNextDeltaTimeStep([interpolationDuration]):
* Interpolate the simulation speed to the next delta time step in the list. If an input
* value is given, the interpolation is done over the specified number of seconds.
* If interpolationDuration is not provided, the interpolation time will be based on the
* `defaultDeltaTimeInterpolationDuration` property of the TimeManager.
*/
int time_interpolateNextDeltaTimeStep(lua_State* L) {
ghoul::lua::checkArgumentsAndThrow(
L,
{ 0, 1 },
"lua::time_interpolateNextDeltaTimeStep"
);
double interpolationDuration =
global::timeManager.defaultDeltaTimeInterpolationDuration();
const int nArguments = lua_gettop(L);
if (nArguments == 1) {
const bool durationIsNumber = (lua_isnumber(L, 1) != 0);
if (!durationIsNumber) {
lua_settop(L, 0);
const char* msg = lua_pushfstring(
L,
"%s expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return luaL_error(L, "bad argument #%d (%s)", 1, msg);
}
interpolationDuration = lua_tonumber(L, 1);
}
global::timeManager.interpolateNextDeltaTimeStep(interpolationDuration);
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* interpolatePreviousDeltaTimeStep([interpolationDuration]):
* Interpolate the simulation speed to the previous delta time step in the list. If an
* input value is given, the interpolation is done over the specified number of seconds.
* If interpolationDuration is not provided, the interpolation time will be based on the
* `defaultDeltaTimeInterpolationDuration` property of the TimeManager.
*/
int time_interpolatePreviousDeltaTimeStep(lua_State* L) {
ghoul::lua::checkArgumentsAndThrow(
L,
{ 0, 1 },
"lua::time_interpolatePreviousDeltaTimeStep"
);
double interpolationDuration =
global::timeManager.defaultDeltaTimeInterpolationDuration();
const int nArguments = lua_gettop(L);
if (nArguments == 1) {
const bool durationIsNumber = (lua_isnumber(L, 1) != 0);
if (!durationIsNumber) {
lua_settop(L, 0);
const char* msg = lua_pushfstring(
L,
"%s expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return luaL_error(L, "bad argument #%d (%s)", 1, msg);
}
interpolationDuration = lua_tonumber(L, 1);
}
global::timeManager.interpolatePreviousDeltaTimeStep(interpolationDuration);
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* time_interpolateDeltaTime(number [, number]):
* Interpolates the delta time by calling the Time::interpolateDeltaTime method
* Same behaviour as setDeltaTime, but interpolates the delta time.
* If interpolationDuration is not provided, the interpolation time will be based on the
* `defaultDeltaTimeInterpolationDuration` property of the TimeManager.
*/
int time_interpolateDeltaTime(lua_State* L) {
const int nArguments = lua_gettop(L);
if (nArguments == 2) {
const bool deltaIsNumber = (lua_isnumber(L, 1) != 0);
if (!deltaIsNumber) {
lua_settop(L, 0);
const char* msg = lua_pushfstring(
L,
"%s expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return luaL_error(L, "bad argument #%d (%s)", 2, msg);
}
const bool durationIsNumber = (lua_isnumber(L, 2) != 0);
if (!durationIsNumber) {
lua_settop(L, 0);
const char* msg = lua_pushfstring(
L,
"%s expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return luaL_error(L, "bad argument #%d (%s)", 2, msg);
}
const double interpolationDuration = lua_tonumber(L, 2);
const double newDeltaTime = lua_tonumber(L, 1);
global::timeManager.interpolateDeltaTime(newDeltaTime, interpolationDuration);
}
else if (nArguments == 1) {
const bool isNumber = (lua_isnumber(L, 1) != 0);
if (!isNumber) {
lua_settop(L, 0);
const char* msg = lua_pushfstring(
L,
"%s expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return luaL_error(L, "bad argument #%d (%s)", 2, msg);
}
const double newDeltaTime = lua_tonumber(L, 1);
global::timeManager.interpolateDeltaTime(
newDeltaTime,
global::timeManager.defaultDeltaTimeInterpolationDuration()
);
}
else {
lua_settop(L, 0);
const char* msg = lua_pushfstring(L,
"Bad number of arguments. Expected 1 or 2.");
return ghoul::lua::luaError(L, fmt::format("bad argument ({})", msg));
}
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* deltaTime():
* Returns the delta time by calling the Time::deltaTime method
*/
int time_deltaTime(lua_State* L) {
lua_pushnumber(L, global::timeManager.deltaTime());
ghoul_assert(lua_gettop(L) == 1, "Incorrect number of items left on stack");
return 1;
}
/**
* \ingroup LuaScripts
* togglePause():
* Toggles pause, i.e. setting the delta time to 0 and restoring it afterwards
*/
int time_togglePause(lua_State* L) {
const int nArguments = lua_gettop(L);
if (nArguments == 0) {
global::timeManager.setPause(!global::timeManager.isPaused());
}
else {
lua_settop(L, 0);
return luaL_error(
L,
"bad number of arguments, expected 0, got %i",
nArguments
);
}
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* interpolateTogglePause([interpolationDuration]):
* Same behaviour as togglePause, but with interpolation.
* If no interpolation duration is provided, the interpolation time will be based on the
* `defaultPauseInterpolationDuration` and `defaultUnpauseInterpolationDuration` properties
* of the TimeManager.
*/
int time_interpolateTogglePause(lua_State* L) {
const int nArguments = lua_gettop(L);
if (nArguments == 1) {
const bool isNumber = (lua_isnumber(L, 1) != 0);
if (!isNumber) {
const char* msg = lua_pushfstring(
L,
"%s expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return luaL_error(L, "bad argument #%d (%s)", 1, msg);
}
const double interpolationDuration = lua_tonumber(L, 1);
global::timeManager.interpolatePause(
!global::timeManager.isPaused(),
interpolationDuration
);
}
else if (nArguments == 0) {
const bool pause = !global::timeManager.isPaused();
global::timeManager.interpolatePause(pause,
pause ?
global::timeManager.defaultPauseInterpolationDuration() :
global::timeManager.defaultUnpauseInterpolationDuration()
);
}
else {
lua_settop(L, 0);
return luaL_error(
L,
"bad number of arguments, expected 0 or 1, got %i",
nArguments
);
}
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* togglePause():
* Toggles a pause function i.e. setting the delta time to 0 and restoring it afterwards
*/
int time_setPause(lua_State* L) {
const int nArguments = lua_gettop(L);
if (nArguments == 1) {
const bool pause = lua_toboolean(L, 1) == 1;
global::timeManager.setPause(pause);
}
else {
lua_settop(L, 0);
return luaL_error(
L,
"bad number of arguments, expected 1, got %i",
nArguments
);
}
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* interpolateTogglePause(bool [, interpolationDuration]):
* Same behaviour as setPause, but with interpolation.
* If no interpolation duration is provided, the interpolation time will be based on the
* `defaultPauseInterpolationDuration` and `defaultUnpauseInterpolationDuration` properties
* of the TimeManager.
*/
int time_interpolatePause(lua_State* L) {
const int nArguments = lua_gettop(L);
if (nArguments == 2) {
const bool isNumber = (lua_isnumber(L, 2) != 0);
if (!isNumber) {
lua_settop(L, 0);
const char* msg = lua_pushfstring(
L,
"%s expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return luaL_error(L, "bad argument #%d (%s)", 2, msg);
}
const double interpolationDuration = lua_tonumber(L, 2);
const bool pause = lua_toboolean(L, 1) == 1;
global::timeManager.interpolatePause(pause, interpolationDuration);
}
else if (nArguments == 1) {
const bool pause = lua_toboolean(L, 1) == 1;
global::timeManager.interpolatePause(pause,
pause ?
global::timeManager.defaultPauseInterpolationDuration() :
global::timeManager.defaultUnpauseInterpolationDuration()
);
}
else {
lua_settop(L, 0);
return luaL_error(
L,
"bad number of arguments, expected 1 or 2, got %i",
nArguments
);
}
lua_settop(L, 0);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
/**
* \ingroup LuaScripts
* setTime({number, string}):
* Sets the simulation time to the passed value. If the parameter is a number, it is
* interpreted as the number of seconds past the J2000 epoch and the
* Time::setTime(double) method is called. If the parameter is a string, it is
* interpreted as a structured date string and the Time::setTime(std::string) method
* is called
*/
int time_setTime(lua_State* L) {
const bool isFunction = (lua_isfunction(L, -1) != 0);
if (isFunction) {
// If the top of the stack is a function, it is ourself
const char* msg = lua_pushfstring(L, "method called without argument");
return ghoul::lua::luaError(L, fmt::format("bad argument #1 ({})", msg));
}
const bool isNumber = (lua_isnumber(L, 1) != 0);
const bool isString = (lua_isstring(L, 1) != 0);
if (!isNumber && !isString) {
const char* msg = lua_pushfstring(
L,
"%s or %s expected, got %s",
lua_typename(L, LUA_TNUMBER),
lua_typename(L, LUA_TSTRING),
luaL_typename(L, -1)
);
return ghoul::lua::luaError(L, fmt::format("bad argument #1 ({})", msg));
}
const int nArguments = lua_gettop(L);
if (nArguments == 1) {
if (isNumber) {
double value = lua_tonumber(L, 1);
global::timeManager.setTimeNextFrame(Time(value));
return 0;
}
if (isString) {
const char* time = lua_tostring(L, 1);
global::timeManager.setTimeNextFrame(Time(Time::convertTime(time)));
return 0;
}
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
}
else {
return luaL_error(
L,
"bad number of arguments, expected 1 or 2, got %i",
nArguments
);
}
return 0;
}
/**
* \ingroup LuaScripts
* interpolateTime({number, string} [, interpolationDuration]):
* Interpolates the simulation time to the passed value.
* Same behaviour as setTime, but interpolates time.
* If interpolationDuration is not provided, the interpolation time will be based on the
* `defaultTimeInterpolationDuration` property of the TimeManager.
*/
int time_interpolateTime(lua_State* L) {
const bool isFunction = (lua_isfunction(L, -1) != 0);
if (isFunction) {
// If the top of the stack is a function, it is ourself
const char* msg = lua_pushfstring(L, "method called without argument");
return ghoul::lua::luaError(L, fmt::format("bad argument #1 ({})", msg));
}
const bool isNumber = (lua_isnumber(L, 1) != 0);
const bool isString = (lua_isstring(L, 1) != 0);
if (!isNumber && !isString) {
const char* msg = lua_pushfstring(
L,
"%s or %s expected, got %s",
lua_typename(L, LUA_TNUMBER),
lua_typename(L, LUA_TSTRING),
luaL_typename(L, -1)
);
return ghoul::lua::luaError(L, fmt::format("bad argument #1 ({})", msg));
}
if (lua_gettop(L) == 1) {
if (isNumber) {
double value = lua_tonumber(L, 1);
global::timeManager.interpolateTime(
value,
global::timeManager.defaultTimeInterpolationDuration()
);
return 0;
}
if (isString) {
const char* time = lua_tostring(L, 1);
global::timeManager.interpolateTime(
Time::convertTime(time),
global::timeManager.defaultTimeInterpolationDuration()
);
return 0;
}
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
}
else {
int nArguments = lua_gettop(L);
if (nArguments != 2) {
return luaL_error(
L,
"bad number of arguments, expected 1 or 2, got %i",
nArguments
);
}
double targetTime;
if (lua_isnumber(L, 1)) {
targetTime = lua_tonumber(L, 1);
}
else {
targetTime = Time::convertTime(lua_tostring(L, 1));
}
const double duration = lua_tonumber(L, 2);
if (duration > 0) {
global::timeManager.interpolateTime(targetTime, duration);
}
else {
global::timeManager.setTimeNextFrame(Time(targetTime));
}
}
return 0;
}
/**
* \ingroup LuaScripts
* interpolateTimeRelative(number [, interpolationDuration]):
* Interpolates the simulation time relatively, based on the specified number of seconds.
* If interpolationDuration is not provided, the interpolation time will be based on the
* `defaultTimeInterpolationDuration` property of the TimeManager.
*/
int time_interpolateTimeRelative(lua_State* L) {
const bool isFunction = (lua_isfunction(L, -1) != 0);
if (isFunction) {
// If the top of the stack is a function, it is ourself
const char* msg = lua_pushfstring(L, "method called without argument");
return ghoul::lua::luaError(L, fmt::format("bad argument #1 ({})", msg));
}
const bool isNumber = (lua_isnumber(L, 1) != 0);
if (!isNumber) {
const char* msg = lua_pushfstring(
L,
"%s or expected, got %s",
lua_typename(L, LUA_TNUMBER),
luaL_typename(L, -1)
);
return ghoul::lua::luaError(L, fmt::format("bad argument #1 ({})", msg));
}
if (lua_gettop(L) == 1 && isNumber) {
double delta = lua_tonumber(L, 1);
global::timeManager.interpolateTimeRelative(
delta,
global::timeManager.defaultTimeInterpolationDuration()
);
ghoul_assert(lua_gettop(L) == 0, "Incorrect number of items left on stack");
return 0;
}
else {
int nArguments = lua_gettop(L);
if (nArguments != 2) {
return luaL_error(
L,
"bad number of arguments, expected 1 or 2, got %i",
nArguments
);
}
double delta;
delta = lua_tonumber(L, 1);
const double duration = lua_tonumber(L, 2);
if (duration > 0) {
global::timeManager.interpolateTimeRelative(delta, duration);
}
else {
global::timeManager.setTimeNextFrame(
Time(global::timeManager.time().j2000Seconds() + delta)
);
}
}
return 0;
}
/**
* \ingroup LuaScripts
* currentTime():
* Returns the current simulation time as the number of seconds past the J2000 epoch.
* It is returned by calling the Time::currentTime method.
*/
int time_currentTime(lua_State* L) {
ghoul::lua::push(L, global::timeManager.time().j2000Seconds());
ghoul_assert(lua_gettop(L) == 1, "Incorrect number of items left on stack");
return 1;
}
/**
* \ingroup LuaScripts
* UTC():
* Returns the current simulation time as a structured ISO 8601 string using the UTC
* timezone by calling the Time::UTC method
*/
int time_currentTimeUTC(lua_State* L) {
ghoul::lua::push(L, global::timeManager.time().UTC());
ghoul_assert(lua_gettop(L) == 1, "Incorrect number of items left on stack");
return 1;
}
/**
* \ingroup LuaScripts
* currentWallTime():
* Returns the current wallclock time as a structured ISO 8601 string in the UTC timezone.
*/
int time_currentWallTime(lua_State* L) {
std::time_t t = std::time(nullptr);
std::tm* utcTime = std::gmtime(&t);
ghoul_assert(utcTime, "Conversion to UTC failed");
std::string time = fmt::format(
"{:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}",
utcTime->tm_year + 1900,
utcTime->tm_mon + 1,
utcTime->tm_mday,
utcTime->tm_hour,
utcTime->tm_min,
utcTime->tm_sec
);
lua_pushstring(L, time.c_str());
ghoul_assert(lua_gettop(L) == 1, "Incorrect number of items left on stack");
return 1;
}
int time_advancedTime(lua_State* L) {
ghoul::lua::checkArgumentsAndThrow(L, 2, "lua::time_advanceTime");
double j2000Seconds = -1.0;
Time t;
bool usesISO = false;
if (lua_type(L, 1) == LUA_TSTRING) {
j2000Seconds = Time::convertTime(ghoul::lua::value<std::string>(L, 1));
usesISO = true;
}
else if (lua_type(L, 1) == LUA_TNUMBER) {
j2000Seconds = ghoul::lua::value<double>(L, 1);
usesISO = false;
}
double dt = 0.0;
if (lua_type(L, 2) == LUA_TNUMBER) {
dt = ghoul::lua::value<double>(L, 2);
}
else {
std::string modifier = ghoul::lua::value<std::string>(L, 2);
if (modifier.empty()) {
return ghoul::lua::luaError(L, "Modifier string must not be empty");
}
ghoul::trimWhitespace(modifier);
bool isNegative = false;
if (modifier[0] == '-') {
isNegative = true;
modifier = modifier.substr(1);
}
auto it = std::find_if(
modifier.begin(),
modifier.end(),
[](unsigned char c) {
const bool digit = std::isdigit(c) != 0;
const bool isDot = c == '.';
return !digit && !isDot;
}
);
double value = std::stod(std::string(modifier.begin(), it));
std::string unitName = std::string(it, modifier.end());
TimeUnit unit = TimeUnit::Second;
if (unitName == "s") {
unit = TimeUnit::Second;
}
else if (unitName == "m") {
unit = TimeUnit::Minute;
}
else if (unitName == "h") {
unit = TimeUnit::Hour;
}
else if (unitName == "d") {
unit = TimeUnit::Day;
}
else if (unitName == "M") {
unit = TimeUnit::Month;
}
else if (unitName == "y") {
unit = TimeUnit::Year;
}
else {
return ghoul::lua::luaError(
L,
fmt::format("Unknown unit '{}'", unitName)
);
}
dt = convertTime(value, unit, TimeUnit::Second);
if (isNegative) {
dt *= -1.0;
}
}
lua_pop(L, 2);
if (usesISO) {
ghoul::lua::push(L, Time(j2000Seconds + dt).ISO8601());
}
else {
ghoul::lua::push(L, j2000Seconds + dt);
}
return 1;
}
} // namespace openspace::luascriptfunctions