env probe bounces
This commit is contained in:
parent
1a0a12bad0
commit
d74f1148ff
10 changed files with 136 additions and 125 deletions
|
@ -2256,7 +2256,12 @@ public:
|
|||
while (lua_next(L, -2) != 0)
|
||||
{
|
||||
const char* parameter_name = luaL_checkstring(L, -2);
|
||||
if (equalStrings(parameter_name, "position"))
|
||||
if (equalStrings(parameter_name, "name"))
|
||||
{
|
||||
const char* name = LuaWrapper::toType<const char*>(L, -1);
|
||||
editor.setEntityName(e, name);
|
||||
}
|
||||
else if (equalStrings(parameter_name, "position"))
|
||||
{
|
||||
const DVec3 pos = LuaWrapper::toType<DVec3>(L, -1);
|
||||
editor.setEntitiesPositions(&e, &pos, 1);
|
||||
|
|
|
@ -282,6 +282,13 @@ namespace Lumix
|
|||
}
|
||||
}
|
||||
|
||||
Span<Key> keys() const {
|
||||
Span<Key> res;
|
||||
res.m_begin = m_keys;
|
||||
res.m_end = m_keys + m_size;
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T> void callDestructors(T* ptr, int count)
|
||||
{
|
||||
|
|
|
@ -162,24 +162,6 @@ float Vec4::length() const
|
|||
const Quat Quat::IDENTITY = { 0, 0, 0, 1 };
|
||||
|
||||
|
||||
Quat::AxisAngle Quat::getAxisAngle() const
|
||||
{
|
||||
AxisAngle ret;
|
||||
if (fabs(1 - w*w) < 0.00001f)
|
||||
{
|
||||
ret.angle = 0;
|
||||
ret.axis.set(0, 1, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.angle = 2 * acosf(w);
|
||||
float tmp = 1 / sqrt(1 - w*w);
|
||||
ret.axis.set(x * tmp, y * tmp, z * tmp);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
Quat::Quat(const Vec3& axis, float angle)
|
||||
{
|
||||
float half_angle = angle * 0.5f;
|
||||
|
|
|
@ -600,19 +600,12 @@ inline Vec2 lerp(const Vec2& op1, const Vec2& op2, float t)
|
|||
|
||||
struct LUMIX_ENGINE_API Quat
|
||||
{
|
||||
struct AxisAngle
|
||||
{
|
||||
Vec3 axis;
|
||||
float angle;
|
||||
};
|
||||
|
||||
Quat() {}
|
||||
Quat(const Vec3& axis, float angle);
|
||||
Quat(float _x, float _y, float _z, float _w) { x = _x; y = _y; z = _z; w = _w; }
|
||||
|
||||
void fromEuler(const Vec3& euler);
|
||||
Vec3 toEuler() const;
|
||||
AxisAngle getAxisAngle() const;
|
||||
void set(float _x, float _y, float _z, float _w) { x = _x; y = _y; z = _z; w = _w; }
|
||||
void conjugate();
|
||||
Quat conjugated() const;
|
||||
|
@ -712,28 +705,6 @@ struct LUMIX_ENGINE_API RigidTransform
|
|||
return{ DVec3(rot.rotate(rhs.pos)) + pos, rot * rhs.rot };
|
||||
}
|
||||
|
||||
/*
|
||||
Vec3 transform(const Vec3& value) const
|
||||
{
|
||||
return pos + rot.rotate(value);
|
||||
}
|
||||
|
||||
|
||||
RigidTransform interpolate(const RigidTransform& rhs, float t)
|
||||
{
|
||||
RigidTransform ret;
|
||||
lerp(pos, rhs.pos, &ret.pos, t);
|
||||
nlerp(rot, rhs.rot, &ret.rot, t);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
inline Transform toScaled(float scale) const;
|
||||
|
||||
|
||||
Matrix toMatrix() const;
|
||||
*/
|
||||
|
||||
Quat rot;
|
||||
DVec3 pos;
|
||||
};
|
||||
|
|
|
@ -109,7 +109,6 @@ static bool saveAsDDS(const char* path, const u8* data, int w, int h) {
|
|||
}
|
||||
|
||||
|
||||
|
||||
struct FontPlugin final : public AssetBrowser::IPlugin, AssetCompiler::IPlugin
|
||||
{
|
||||
FontPlugin(StudioApp& app)
|
||||
|
@ -1665,6 +1664,7 @@ struct EnvironmentProbePlugin final : public PropertyGrid::IPlugin
|
|||
explicit EnvironmentProbePlugin(StudioApp& app)
|
||||
: m_app(app)
|
||||
, m_data(app.getWorldEditor().getAllocator())
|
||||
, m_probes(app.getWorldEditor().getAllocator())
|
||||
{
|
||||
WorldEditor& world_editor = app.getWorldEditor();
|
||||
Engine& engine = world_editor.getEngine();
|
||||
|
@ -1697,7 +1697,7 @@ struct EnvironmentProbePlugin final : public PropertyGrid::IPlugin
|
|||
if (!OS::makePath(path) && !OS::dirExists(path)) {
|
||||
logError("Editor") << "Failed to create " << path;
|
||||
}
|
||||
path << "/probes/";
|
||||
path << "/probes_tmp/";
|
||||
if (!OS::makePath(path) && !OS::dirExists(path)) {
|
||||
logError("Editor") << "Failed to create " << path;
|
||||
}
|
||||
|
@ -1774,15 +1774,32 @@ struct EnvironmentProbePlugin final : public PropertyGrid::IPlugin
|
|||
}
|
||||
|
||||
|
||||
void generateCubemap(ComponentUID cmp)
|
||||
{
|
||||
ASSERT(cmp.isValid());
|
||||
void generateCubemaps(bool bounce) {
|
||||
ASSERT(!m_in_progress);
|
||||
m_in_progress = true;
|
||||
MT::memoryBarrier();
|
||||
ASSERT(m_probes.empty());
|
||||
|
||||
const EntityRef entity = (EntityRef)cmp.entity;
|
||||
// TODO block user interaction
|
||||
Universe* universe = m_app.getWorldEditor().getUniverse();
|
||||
if (universe->getName()[0] == '\0') {
|
||||
logError("Editor") << "Universe must be saved before environment probe can be generated.";
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO handle in .pln
|
||||
m_pipeline->define("PROBE_BOUNCE", bounce);
|
||||
|
||||
auto* scene = static_cast<RenderScene*>(universe->getScene(ENVIRONMENT_PROBE_TYPE));
|
||||
const Span<EntityRef> probes = scene->getAllEnvironmentProbes();
|
||||
m_probes.reserve(probes.length());
|
||||
for (const EntityRef& p : probes) {
|
||||
m_probes.push(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void generateCubemap(EntityRef entity)
|
||||
{
|
||||
ASSERT(!m_in_progress);
|
||||
|
||||
Universe* universe = m_app.getWorldEditor().getUniverse();
|
||||
if (universe->getName()[0] == '\0') {
|
||||
|
@ -1790,18 +1807,22 @@ struct EnvironmentProbePlugin final : public PropertyGrid::IPlugin
|
|||
return;
|
||||
}
|
||||
|
||||
m_in_progress = true;
|
||||
MT::memoryBarrier();
|
||||
|
||||
WorldEditor& world_editor = m_app.getWorldEditor();
|
||||
Engine& engine = world_editor.getEngine();
|
||||
auto& plugin_manager = engine.getPluginManager();
|
||||
IAllocator& allocator = engine.getAllocator();
|
||||
|
||||
const DVec3 probe_position = universe->getPosition((EntityRef)cmp.entity);
|
||||
auto* scene = static_cast<RenderScene*>(universe->getScene(CAMERA_TYPE));
|
||||
const EnvironmentProbe& probe = scene->getEnvironmentProbe(entity);
|
||||
|
||||
const DVec3 probe_position = universe->getPosition(entity);
|
||||
Viewport viewport;
|
||||
viewport.is_ortho = false;
|
||||
viewport.fov = degreesToRadians(90.f);
|
||||
viewport.near = 0.1f;
|
||||
viewport.far = 10000.f;
|
||||
viewport.far = probe.radius;
|
||||
viewport.w = TEXTURE_SIZE;
|
||||
viewport.h = TEXTURE_SIZE;
|
||||
|
||||
|
@ -1815,9 +1836,8 @@ struct EnvironmentProbePlugin final : public PropertyGrid::IPlugin
|
|||
|
||||
m_data.resize(6 * TEXTURE_SIZE * TEXTURE_SIZE * 4);
|
||||
|
||||
renderer->startCapture();
|
||||
const bool ndc_bottom_left = ffr::isOriginBottomLeft();
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
const bool ndc_bottom_left = ffr::isOriginBottomLeft();
|
||||
Vec3 side = crossProduct(ndc_bottom_left ? ups_opengl[i] : ups[i], dirs[i]);
|
||||
Matrix mtx = Matrix::IDENTITY;
|
||||
mtx.setZVector(dirs[i]);
|
||||
|
@ -1831,35 +1851,35 @@ struct EnvironmentProbePlugin final : public PropertyGrid::IPlugin
|
|||
const ffr::TextureHandle res = m_pipeline->getOutput();
|
||||
ASSERT(res.isValid());
|
||||
renderer->getTextureImage(res, TEXTURE_SIZE * TEXTURE_SIZE * 4, &m_data[i * TEXTURE_SIZE * TEXTURE_SIZE * 4]);
|
||||
}
|
||||
|
||||
if (ndc_bottom_left) continue;
|
||||
renderer->frame();
|
||||
renderer->frame();
|
||||
|
||||
u32* tmp = (u32*)&m_data[i * TEXTURE_SIZE * TEXTURE_SIZE * 4];
|
||||
if (i == 2 || i == 3)
|
||||
{
|
||||
flipY(tmp, TEXTURE_SIZE);
|
||||
}
|
||||
else
|
||||
{
|
||||
flipX(tmp, TEXTURE_SIZE);
|
||||
if (!ndc_bottom_left) {
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
u32* tmp = (u32*)&m_data[i * TEXTURE_SIZE * TEXTURE_SIZE * 4];
|
||||
if (i == 2 || i == 3) {
|
||||
flipY(tmp, TEXTURE_SIZE);
|
||||
}
|
||||
else {
|
||||
flipX(tmp, TEXTURE_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
renderer->stopCapture();
|
||||
renderer->frame();
|
||||
renderer->frame();
|
||||
|
||||
|
||||
m_irradiance_size = 32;
|
||||
m_radiance_size = 128;
|
||||
m_reflection_size = TEXTURE_SIZE;
|
||||
|
||||
if (scene->isEnvironmentProbeCustomSize(entity)) {
|
||||
m_irradiance_size = scene->getEnvironmentProbe(entity).irradiance_size;
|
||||
m_radiance_size = scene->getEnvironmentProbe(entity).radiance_size;
|
||||
m_reflection_size = scene->getEnvironmentProbeReflectionSize(entity);
|
||||
if (probe.flags.isSet(EnvironmentProbe::OVERRIDE_GLOBAL_SIZE)) {
|
||||
m_irradiance_size = probe.irradiance_size;
|
||||
m_radiance_size = probe.radiance_size;
|
||||
// TODO the size of m_data should be m_reflection_size^2 instead of TEXTURE_SIZE^2
|
||||
m_reflection_size = probe.reflection_size;
|
||||
}
|
||||
m_save_reflection = scene->isEnvironmentProbeReflectionEnabled(entity);
|
||||
m_probe_guid = scene->getEnvironmentProbeGUID(entity);
|
||||
m_reload_probe = entity;
|
||||
m_save_reflection = probe.flags.isSet(EnvironmentProbe::REFLECTION);
|
||||
m_probe_guid = probe.guid;
|
||||
|
||||
JobSystem::run(this, [](void* ptr) {
|
||||
((EnvironmentProbePlugin*)ptr)->processData();
|
||||
|
@ -1869,15 +1889,35 @@ struct EnvironmentProbePlugin final : public PropertyGrid::IPlugin
|
|||
|
||||
void update() override
|
||||
{
|
||||
if (m_reload_probe.isValid() && !m_in_progress) {
|
||||
if (m_reload_probes && !m_in_progress) {
|
||||
m_reload_probes = false;
|
||||
Universe* universe = m_app.getWorldEditor().getUniverse();
|
||||
auto* scene = static_cast<RenderScene*>(universe->getScene(CAMERA_TYPE));
|
||||
if(universe->hasEntity((EntityRef)m_reload_probe)
|
||||
&& universe->hasComponent((EntityRef)m_reload_probe, ENVIRONMENT_PROBE_TYPE))
|
||||
{
|
||||
scene->reloadEnvironmentProbe((EntityRef)m_reload_probe);
|
||||
const char* universe_name = universe->getName();
|
||||
auto* scene = static_cast<RenderScene*>(universe->getScene(ENVIRONMENT_PROBE_TYPE));
|
||||
const Span<EntityRef> probes = scene->getAllEnvironmentProbes();
|
||||
|
||||
auto move = [universe_name](u64 guid, const char* postfix){
|
||||
const StaticString<MAX_PATH_LENGTH> tmp_path("universes/", universe_name, "/probes_tmp/", guid, postfix, ".dds");
|
||||
const StaticString<MAX_PATH_LENGTH> path("universes/", universe_name, "/probes/", guid, postfix, ".dds");
|
||||
if (!OS::fileExists(tmp_path)) return;
|
||||
if (!OS::moveFile(tmp_path, path)) {
|
||||
logError("Editor") << "Failed to move file " << tmp_path;
|
||||
}
|
||||
};
|
||||
|
||||
for (EntityRef e : probes) {
|
||||
const EnvironmentProbe& probe = scene->getEnvironmentProbe(e);
|
||||
move(probe.guid, "");
|
||||
move(probe.guid, "_radiance");
|
||||
move(probe.guid, "_irradiance");
|
||||
}
|
||||
m_reload_probe = INVALID_ENTITY;
|
||||
}
|
||||
else if (!m_probes.empty() && !m_in_progress) {
|
||||
const EntityRef e = m_probes.back();
|
||||
m_probes.pop();
|
||||
generateCubemap(e);
|
||||
|
||||
if (m_probes.empty()) m_reload_probes = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1953,7 +1993,10 @@ struct EnvironmentProbePlugin final : public PropertyGrid::IPlugin
|
|||
if (ImGui::Button("View radiance")) m_app.getAssetBrowser().selectResource(texture->getPath(), true, false);
|
||||
}
|
||||
if (m_in_progress) ImGui::Text("Generating...");
|
||||
else if (ImGui::Button("Generate")) generateCubemap(cmp);
|
||||
else {
|
||||
if (ImGui::Button("Generate")) generateCubemaps(false);
|
||||
if (ImGui::Button("Add bounce")) generateCubemaps(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1962,12 +2005,13 @@ struct EnvironmentProbePlugin final : public PropertyGrid::IPlugin
|
|||
|
||||
Array<u8> m_data;
|
||||
bool m_in_progress = false;
|
||||
EntityPtr m_reload_probe = INVALID_ENTITY;
|
||||
bool m_reload_probes = false;
|
||||
int m_irradiance_size;
|
||||
int m_radiance_size;
|
||||
int m_reflection_size;
|
||||
bool m_save_reflection;
|
||||
u64 m_probe_guid;
|
||||
Array<EntityRef> m_probes;
|
||||
|
||||
JobSystem::SignalHandle m_signal = JobSystem::INVALID_HANDLE;
|
||||
|
||||
|
|
|
@ -229,7 +229,7 @@ void SceneView::renderSelection()
|
|||
item.mesh = mesh.render_data;
|
||||
item.shader = mesh.material->getShader()->m_render_data;
|
||||
item.mtx = universe.getRelativeMatrix(e, m_editor->getViewport().pos);
|
||||
item.material_render_states = mesh.material->getRenderStates();
|
||||
item.material = mesh.material->getRenderData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -241,15 +241,16 @@ void SceneView::renderSelection()
|
|||
|
||||
for (const Item& item : m_items) {
|
||||
const Mesh::RenderData* rd = item.mesh;
|
||||
const ffr::ProgramHandle prog = Shader::getProgram(item.shader, rd->vertex_decl, m_define_mask);
|
||||
const ffr::ProgramHandle prog = Shader::getProgram(item.shader, rd->vertex_decl, m_define_mask | item.material->define_mask);
|
||||
|
||||
if (!prog.isValid()) continue;
|
||||
|
||||
ffr::update(drawcall_ub, &item.mtx.m11, sizeof(item.mtx));
|
||||
ffr::bindTextures(item.material->textures, 0, item.material->textures_count);
|
||||
ffr::useProgram(prog);
|
||||
ffr::bindVertexBuffer(0, rd->vertex_buffer_handle, 0, rd->vb_stride);
|
||||
ffr::bindIndexBuffer(rd->index_buffer_handle);
|
||||
ffr::setState(item.material_render_states);
|
||||
ffr::setState(item.material->render_states);
|
||||
ffr::drawTriangles(rd->indices_count, rd->index_type);
|
||||
}
|
||||
}
|
||||
|
@ -257,7 +258,7 @@ void SceneView::renderSelection()
|
|||
struct Item {
|
||||
ShaderRenderData* shader;
|
||||
Mesh::RenderData* mesh;
|
||||
u64 material_render_states;
|
||||
Material::RenderData* material;
|
||||
Matrix mtx;
|
||||
};
|
||||
|
||||
|
|
|
@ -524,9 +524,7 @@ struct PipelineImpl final : Pipeline
|
|||
if (m_scene) callInitScene();
|
||||
}
|
||||
|
||||
|
||||
void clearBuffers()
|
||||
{
|
||||
void clearBuffers() {
|
||||
PROFILE_FUNCTION();
|
||||
for (Renderbuffer& rb : m_renderbuffers) {
|
||||
++rb.frame_counter;
|
||||
|
@ -540,6 +538,13 @@ struct PipelineImpl final : Pipeline
|
|||
}
|
||||
}
|
||||
|
||||
void define(const char* define, bool enable) override {
|
||||
LuaWrapper::DebugGuard guard(m_lua_state);
|
||||
lua_rawgeti(m_lua_state, LUA_REGISTRYINDEX, m_lua_env);
|
||||
LuaWrapper::setField(m_lua_state, -1, define, enable);
|
||||
lua_pop(m_lua_state, 1);
|
||||
}
|
||||
|
||||
ffr::BufferHandle getDrawcallUniformBuffer() override { return m_drawcall_ub; }
|
||||
|
||||
void setViewport(const Viewport& viewport) override
|
||||
|
|
|
@ -73,6 +73,7 @@ public:
|
|||
virtual void callLuaFunction(const char* func) = 0;
|
||||
virtual void setViewport(const Viewport& viewport) = 0;
|
||||
virtual ffr::BufferHandle getDrawcallUniformBuffer() = 0;
|
||||
virtual void define(const char* define, bool enable) = 0;
|
||||
|
||||
virtual Draw2D& getDraw2D() = 0;
|
||||
virtual void clearDraw2D() = 0;
|
||||
|
|
|
@ -2872,29 +2872,6 @@ public:
|
|||
return m_active_global_light_entity;
|
||||
}
|
||||
|
||||
|
||||
void reloadEnvironmentProbe(EntityRef entity) override
|
||||
{
|
||||
auto& probe = m_environment_probes[entity];
|
||||
ResourceManagerHub& rm = m_engine.getResourceManager();
|
||||
if (probe.texture) probe.texture->getResourceManager().unload(*probe.texture);
|
||||
probe.texture = nullptr;
|
||||
StaticString<MAX_PATH_LENGTH> path;
|
||||
if (probe.flags.isSet(EnvironmentProbe::REFLECTION)) {
|
||||
path << "universes/" << m_universe.getName() << "/probes/" << probe.guid << ".dds";
|
||||
probe.texture = rm.load<Texture>(Path(path));
|
||||
probe.texture->setFlag(Texture::Flags::SRGB, true);
|
||||
}
|
||||
path = "universes/";
|
||||
path << m_universe.getName() << "/probes/" << probe.guid << "_irradiance.dds";
|
||||
if(probe.irradiance) probe.irradiance->getResourceManager().unload(*probe.irradiance);
|
||||
probe.irradiance = rm.load<Texture>(Path(path));
|
||||
path = "universes/";
|
||||
path << m_universe.getName() << "/probes/" << probe.guid << "_radiance.dds";
|
||||
if (probe.radiance) probe.irradiance->getResourceManager().unload(*probe.radiance);
|
||||
probe.radiance = rm.load<Texture>(Path(path));
|
||||
}
|
||||
|
||||
|
||||
void getEnvironmentProbes(Array<EnvProbeInfo>& probes) override
|
||||
{
|
||||
|
@ -2914,6 +2891,9 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
Span<EntityRef> getAllEnvironmentProbes() override {
|
||||
return m_environment_probes.keys();
|
||||
}
|
||||
|
||||
EnvironmentProbe& getEnvironmentProbe(EntityRef entity) override
|
||||
{
|
||||
|
@ -3262,12 +3242,27 @@ public:
|
|||
{
|
||||
EnvironmentProbe& probe = m_environment_probes.insert(entity);
|
||||
ResourceManagerHub& rm = m_engine.getResourceManager();
|
||||
probe.texture = rm.load<Texture>(Path("textures/common/default_probe.dds"));
|
||||
probe.texture->setFlag(Texture::Flags::SRGB, true);
|
||||
probe.irradiance = rm.load<Texture>(Path("textures/common/default_probe.dds"));
|
||||
|
||||
StaticString<MAX_PATH_LENGTH> path;
|
||||
if (probe.flags.isSet(EnvironmentProbe::REFLECTION)) {
|
||||
path << "universes/" << m_universe.getName() << "/probes/" << probe.guid << ".dds";
|
||||
probe.texture = rm.load<Texture>(Path(path));
|
||||
probe.texture->setFlag(Texture::Flags::SRGB, true);
|
||||
}
|
||||
else {
|
||||
probe.texture = nullptr;
|
||||
}
|
||||
|
||||
path = "universes/";
|
||||
path << m_universe.getName() << "/probes/" << probe.guid << "_irradiance.dds";
|
||||
probe.irradiance = rm.load<Texture>(Path(path));
|
||||
probe.irradiance->setFlag(Texture::Flags::SRGB, true);
|
||||
probe.radiance = rm.load<Texture>(Path("textures/common/default_probe.dds"));
|
||||
|
||||
path = "universes/";
|
||||
path << m_universe.getName() << "/probes/" << probe.guid << "_radiance.dds";
|
||||
probe.radiance = rm.load<Texture>(Path(path));
|
||||
probe.radiance->setFlag(Texture::Flags::SRGB, true);
|
||||
|
||||
probe.radius = 1;
|
||||
probe.flags.set(EnvironmentProbe::ENABLED);
|
||||
probe.guid = randGUID();
|
||||
|
|
|
@ -324,6 +324,7 @@ public:
|
|||
virtual float getLightRange(EntityRef entity) = 0;
|
||||
virtual void setLightRange(EntityRef entity, float value) = 0;
|
||||
|
||||
virtual Span<EntityRef> getAllEnvironmentProbes() = 0;
|
||||
virtual EnvironmentProbe& getEnvironmentProbe(EntityRef entity) = 0;
|
||||
virtual void enableEnvironmentProbe(EntityRef entity, bool enable) = 0;
|
||||
virtual bool isEnvironmentProbeEnabled(EntityRef entity) = 0;
|
||||
|
@ -337,7 +338,6 @@ public:
|
|||
virtual Texture* getEnvironmentProbeTexture(EntityRef entity) const = 0;
|
||||
virtual Texture* getEnvironmentProbeIrradiance(EntityRef entity) const = 0;
|
||||
virtual Texture* getEnvironmentProbeRadiance(EntityRef entity) const = 0;
|
||||
virtual void reloadEnvironmentProbe(EntityRef entity) = 0;
|
||||
virtual u64 getEnvironmentProbeGUID(EntityRef entity) const = 0;
|
||||
virtual float getEnvironmentProbeRadius(EntityRef entity) = 0;
|
||||
virtual void setEnvironmentProbeRadius(EntityRef entity, float radius) = 0;
|
||||
|
|
Loading…
Reference in a new issue