LumixEngine/src/renderer/render_scene.cpp

3026 lines
No EOL
81 KiB
C++

#include "render_scene.h"
#include "engine/array.h"
#include "engine/associative_array.h"
#include "engine/crc32.h"
#include "engine/crt.h"
#include "engine/engine.h"
#include "engine/file_system.h"
#include "engine/geometry.h"
#include "engine/log.h"
#include "engine/lua_wrapper.h"
#include "engine/math.h"
#include "engine/os.h"
#include "engine/page_allocator.h"
#include "engine/profiler.h"
#include "engine/reflection.h"
#include "engine/resource_manager.h"
#include "engine/stream.h"
#include "engine/universe.h"
#include "renderer/culling_system.h"
#include "renderer/font.h"
#include "renderer/material.h"
#include "renderer/model.h"
#include "renderer/particle_system.h"
#include "renderer/pipeline.h"
#include "renderer/pose.h"
#include "renderer/renderer.h"
#include "renderer/terrain.h"
#include "renderer/texture.h"
namespace Lumix
{
enum class RenderSceneVersion : int
{
LATEST
};
static const ComponentType MODEL_INSTANCE_TYPE = Reflection::getComponentType("model_instance");
static const ComponentType DECAL_TYPE = Reflection::getComponentType("decal");
static const ComponentType POINT_LIGHT_TYPE = Reflection::getComponentType("point_light");
static const ComponentType PARTICLE_EMITTER_TYPE = Reflection::getComponentType("particle_emitter");
static const ComponentType ENVIRONMENT_TYPE = Reflection::getComponentType("environment");
static const ComponentType CAMERA_TYPE = Reflection::getComponentType("camera");
static const ComponentType TERRAIN_TYPE = Reflection::getComponentType("terrain");
static const ComponentType BONE_ATTACHMENT_TYPE = Reflection::getComponentType("bone_attachment");
static const ComponentType ENVIRONMENT_PROBE_TYPE = Reflection::getComponentType("environment_probe");
static const ComponentType LIGHT_PROBE_GRID_TYPE = Reflection::getComponentType("light_probe_grid");
static const ComponentType TEXT_MESH_TYPE = Reflection::getComponentType("text_mesh");
struct Decal
{
Material* material = nullptr;
Transform transform;
float radius;
EntityRef entity;
EntityPtr prev_decal = INVALID_ENTITY;
EntityPtr next_decal = INVALID_ENTITY;
Vec3 half_extents;
};
struct BoneAttachment
{
EntityRef entity;
EntityPtr parent_entity;
int bone_index;
LocalRigidTransform relative_transform;
};
struct TextMesh
{
enum Flags : u32
{
CAMERA_ORIENTED = 1 << 0
};
TextMesh(IAllocator& allocator) : text("", allocator) {}
~TextMesh() { setFontResource(nullptr); }
void setFontResource(FontResource* res)
{
if (m_font_resource)
{
if (m_font)
{
m_font_resource->removeRef(*m_font);
m_font = nullptr;
}
m_font_resource->getObserverCb().unbind<&TextMesh::onFontLoaded>(this);
m_font_resource->getResourceManager().unload(*m_font_resource);
}
m_font_resource = res;
if (res) res->onLoaded<&TextMesh::onFontLoaded>(this);
}
void onFontLoaded(Resource::State, Resource::State new_state, Resource&)
{
if (new_state != Resource::State::READY)
{
m_font = nullptr;
}
else
{
m_font = m_font_resource->addRef(m_font_size);
}
}
void setFontSize(int value)
{
m_font_size = value;
if (m_font_resource && m_font_resource->isReady())
{
if(m_font) m_font_resource->removeRef(*m_font);
m_font = m_font_resource->addRef(m_font_size);
}
}
FontResource* getFontResource() const { return m_font_resource; }
Font* getFont() const { return m_font; }
int getFontSize() const { return m_font_size; }
String text;
u32 color = 0xff000000;
FlagSet<Flags, u32> m_flags;
private:
int m_font_size = 13;
Font* m_font = nullptr;
FontResource* m_font_resource = nullptr;
};
static RenderableTypes getRenderableType(const Model& model)
{
ASSERT(model.isReady());
if (model.isSkinned()) return RenderableTypes::SKINNED;
if (model.getMeshCount() > 1) return RenderableTypes::MESH_GROUP;
return RenderableTypes::MESH;
}
struct RenderSceneImpl final : RenderScene
{
public:
RenderSceneImpl(Renderer& renderer,
Engine& engine,
Universe& universe,
IAllocator& allocator);
~RenderSceneImpl()
{
m_universe.entityTransformed().unbind<&RenderSceneImpl::onEntityMoved>(this);
m_universe.entityDestroyed().unbind<&RenderSceneImpl::onEntityDestroyed>(this);
CullingSystem::destroy(*m_culling_system);
}
void decalMaterialStateChanged(Resource::State old_state, Resource::State new_state, Resource& resource)
{
Material& material = static_cast<Material&>(resource);
if (new_state == Resource::State::READY) {
auto map_iter = m_material_decal_map.find(&material);
EntityPtr e = map_iter.value();
while(e.isValid()) {
const float radius = m_decals[(EntityRef)e].half_extents.length();
const DVec3 pos = m_universe.getPosition((EntityRef)e);
m_culling_system->add((EntityRef)e, (u8)RenderableTypes::DECAL, pos, radius);
e = m_decals[(EntityRef)e].next_decal;
}
return;
}
if (old_state == Resource::State::READY) {
auto map_iter = m_material_decal_map.find(&material);
EntityPtr e = map_iter.value();
while(e.isValid()) {
m_culling_system->remove((EntityRef)e);
e = m_decals[(EntityRef)e].next_decal;
}
}
}
void modelStateChanged(Resource::State old_state, Resource::State new_state, Resource& resource)
{
Model* model = static_cast<Model*>(&resource);
if (new_state == Resource::State::READY)
{
modelLoaded(model);
}
else if (old_state == Resource::State::READY)
{
modelUnloaded(model);
}
}
void clear() override
{
auto& rm = m_engine.getResourceManager();
auto* material_manager = rm.get(Material::TYPE);
for (TextMesh* text_mesh : m_text_meshes)
{
LUMIX_DELETE(m_allocator, text_mesh);
}
m_text_meshes.clear();
for (Decal& decal : m_decals)
{
if (decal.material) material_manager->unload(*decal.material);
}
m_decals.clear();
m_cameras.clear();
for (auto* terrain : m_terrains)
{
LUMIX_DELETE(m_allocator, terrain);
}
m_terrains.clear();
for (auto* emitter : m_particle_emitters)
{
LUMIX_DELETE(m_allocator, emitter);
}
m_particle_emitters.clear();
for (ModelInstance& i : m_model_instances)
{
if (i.flags.isSet(ModelInstance::VALID) && i.model)
{
i.model->getResourceManager().unload(*i.model);
LUMIX_DELETE(m_allocator, i.pose);
i.pose = nullptr;
}
}
m_model_instances.clear();
for(auto iter = m_model_entity_map.begin(), end = m_model_entity_map.end(); iter != end; ++iter) {
Model* model = iter.key();
model->getObserverCb().unbind<&RenderSceneImpl::modelStateChanged>(this);
}
m_model_entity_map.clear();
for(auto iter = m_material_decal_map.begin(), end = m_material_decal_map.end(); iter != end; ++iter) {
Material* mat = iter.key();
mat->getObserverCb().unbind<&RenderSceneImpl::decalMaterialStateChanged>(this);
}
m_material_decal_map.clear();
m_culling_system->clear();
for (auto& probe : m_environment_probes)
{
if (probe.reflection) probe.reflection->getResourceManager().unload(*probe.reflection);
if (probe.radiance) probe.radiance->getResourceManager().unload(*probe.radiance);
}
for (auto& lpg : m_light_probe_grids)
{
for (Texture* t : lpg.data) {
if (t) t->getResourceManager().unload(*t);
}
}
m_environment_probes.clear();
}
Universe& getUniverse() override { return m_universe; }
IPlugin& getPlugin() const override { return m_renderer; }
void getRay(EntityRef camera_entity,
const Vec2& screen_pos,
DVec3& origin,
Vec3& dir) override
{
Camera& camera = m_cameras[camera_entity];
origin = m_universe.getPosition(camera_entity);
float width = camera.screen_width;
float height = camera.screen_height;
if (width <= 0 || height <= 0)
{
dir = m_universe.getRotation(camera_entity).rotate(Vec3(0, 0, 1));
return;
}
float nx = 2 * (screen_pos.x / width) - 1;
float ny = 2 * ((height - screen_pos.y) / height) - 1;
const Matrix projection_matrix = getCameraProjection(camera_entity);
const Transform view = m_universe.getTransform(camera_entity);
if (camera.is_ortho) {
const float ratio = camera.screen_height > 0 ? camera.screen_width / camera.screen_height : 1;
origin += view.rot * Vec3(1, 0, 0) * nx * camera.ortho_size * ratio
+ view.rot * Vec3(0, 1, 0) * ny * camera.ortho_size;
}
Matrix inv_projection = projection_matrix;
inv_projection.inverse();
Vec4 p0 = inv_projection * Vec4(nx, ny, -1, 1);
Vec4 p1 = inv_projection * Vec4(nx, ny, 1, 1);
p0 *= 1 / p0.w;
p1 *= 1 / p1.w;
dir = (p1 - p0).xyz();
dir.normalize();
dir = view.rot * dir;
}
EntityPtr getActiveCamera() const override
{
return m_active_camera;
}
Viewport getCameraViewport(EntityRef entity) const override
{
Viewport vp;
const Camera& cam = m_cameras[entity];
vp.far = cam.far;
vp.near = cam.near;
vp.is_ortho = cam.is_ortho;
vp.h = (int)cam.screen_height;
vp.w = (int)cam.screen_width;
if(vp.is_ortho) {
vp.ortho_size = cam.ortho_size;
}
else {
vp.fov = cam.fov;
}
vp.pos = m_universe.getPosition(entity);
vp.rot = m_universe.getRotation(entity);
return vp;
}
float getCameraLODMultiplier(float fov, bool is_ortho) const override
{
if (is_ortho) return 1;
const float lod_multiplier = fov / degreesToRadians(60);
return lod_multiplier * lod_multiplier;
}
float getCameraLODMultiplier(EntityRef entity) const override
{
const Camera& camera = m_cameras[entity];
return getCameraLODMultiplier(camera.fov, camera.is_ortho);
}
ShiftedFrustum getCameraFrustum(EntityRef entity) const override
{
ShiftedFrustum ret;
const Camera& camera = m_cameras[entity];
const Transform tr = m_universe.getTransform(entity);
float ratio = camera.screen_height > 0 ? camera.screen_width / camera.screen_height : 1;
if (camera.is_ortho) {
ret.computeOrtho(tr.pos,
tr.rot * Vec3(0, 0, 1),
tr.rot * Vec3(0, 1, 0),
camera.ortho_size * ratio,
camera.ortho_size,
camera.near,
camera.far);
return ret;
}
ret.computePerspective(tr.pos,
tr.rot * Vec3(0, 0, -1),
tr.rot * Vec3(0, 1, 0),
camera.fov,
ratio,
camera.near,
camera.far);
return ret;
}
ShiftedFrustum getCameraFrustum(EntityRef entity, const Vec2& viewport_min_px, const Vec2& viewport_max_px) const override
{
ShiftedFrustum ret;
const Camera& camera = m_cameras[entity];
const Transform tr = m_universe.getTransform(entity);
float ratio = camera.screen_height > 0 ? camera.screen_width / camera.screen_height : 1;
Vec2 viewport_min = { viewport_min_px.x / camera.screen_width * 2 - 1, (1 - viewport_max_px.y / camera.screen_height) * 2 - 1 };
Vec2 viewport_max = { viewport_max_px.x / camera.screen_width * 2 - 1, (1 - viewport_min_px.y / camera.screen_height) * 2 - 1 };
if (camera.is_ortho) {
ret.computeOrtho(tr.pos,
tr.rot * Vec3(0, 0, 1),
tr.rot * Vec3(0, 1, 0),
camera.ortho_size * ratio,
camera.ortho_size,
camera.near,
camera.far,
viewport_min,
viewport_max);
return ret;
}
ret.computePerspective(tr.pos,
tr.rot * Vec3(0, 0, -1),
tr.rot * Vec3(0, 1, 0),
camera.fov,
ratio,
camera.near,
camera.far,
viewport_min,
viewport_max);
return ret;
}
void updateBoneAttachment(const BoneAttachment& bone_attachment)
{
if (!bone_attachment.parent_entity.isValid()) return;
const EntityPtr model_instance_ptr = bone_attachment.parent_entity;
if (!model_instance_ptr.isValid()) return;
const EntityRef model_instance = (EntityRef)model_instance_ptr;
if (!m_universe.hasComponent(model_instance, MODEL_INSTANCE_TYPE)) return;
const Pose* parent_pose = lockPose(model_instance);
if (!parent_pose) return;
Transform parent_entity_transform = m_universe.getTransform((EntityRef)bone_attachment.parent_entity);
int idx = bone_attachment.bone_index;
if (idx < 0 || idx >= (int)parent_pose->count) {
unlockPose(model_instance, false);
return;
}
float original_scale = m_universe.getScale(bone_attachment.entity);
const LocalRigidTransform bone_transform = {parent_pose->positions[idx], parent_pose->rotations[idx] };
const LocalRigidTransform relative_transform = { bone_attachment.relative_transform.pos, bone_attachment.relative_transform.rot };
Transform result = parent_entity_transform * bone_transform * relative_transform;
result.scale = original_scale;
m_universe.setTransform(bone_attachment.entity, result);
unlockPose(model_instance, false);
}
EntityPtr getBoneAttachmentParent(EntityRef entity) override
{
return m_bone_attachments[entity].parent_entity;
}
void updateRelativeMatrix(BoneAttachment& attachment)
{
if (!attachment.parent_entity.isValid()) return;
if (attachment.bone_index < 0) return;
const EntityPtr model_instance_ptr = attachment.parent_entity;
if (!model_instance_ptr.isValid()) return;
const EntityRef model_instance = (EntityRef)model_instance_ptr;
if (!m_universe.hasComponent(model_instance, MODEL_INSTANCE_TYPE)) return;
const Pose* pose = lockPose(model_instance);
if (!pose) return;
ASSERT(pose->is_absolute);
if (attachment.bone_index >= (int)pose->count) {
unlockPose(model_instance, false);
return;
}
const LocalRigidTransform bone_transform = {pose->positions[attachment.bone_index], pose->rotations[attachment.bone_index]};
const EntityRef parent = (EntityRef)attachment.parent_entity;
Transform inv_parent_transform = m_universe.getTransform(parent) * bone_transform;
inv_parent_transform = inv_parent_transform.inverted();
const Transform child_transform = m_universe.getTransform(attachment.entity);
const Transform res = inv_parent_transform * child_transform;
attachment.relative_transform = {res.pos.toFloat(), res.rot};
unlockPose(model_instance, false);
}
Vec3 getBoneAttachmentPosition(EntityRef entity) override
{
return m_bone_attachments[entity].relative_transform.pos;
}
void setBoneAttachmentPosition(EntityRef entity, const Vec3& pos) override
{
BoneAttachment& attachment = m_bone_attachments[entity];
attachment.relative_transform.pos = pos;
m_is_updating_attachments = true;
updateBoneAttachment(attachment);
m_is_updating_attachments = false;
}
Vec3 getBoneAttachmentRotation(EntityRef entity) override
{
return m_bone_attachments[entity].relative_transform.rot.toEuler();
}
void setBoneAttachmentRotation(EntityRef entity, const Vec3& rot) override
{
BoneAttachment& attachment = m_bone_attachments[entity];
Vec3 euler = rot;
euler.x = clamp(euler.x, -PI * 0.5f, PI * 0.5f);
attachment.relative_transform.rot.fromEuler(euler);
m_is_updating_attachments = true;
updateBoneAttachment(attachment);
m_is_updating_attachments = false;
}
void setBoneAttachmentRotationQuat(EntityRef entity, const Quat& rot) override
{
BoneAttachment& attachment = m_bone_attachments[entity];
attachment.relative_transform.rot = rot;
m_is_updating_attachments = true;
updateBoneAttachment(attachment);
m_is_updating_attachments = false;
}
int getBoneAttachmentBone(EntityRef entity) override
{
return m_bone_attachments[entity].bone_index;
}
void setBoneAttachmentBone(EntityRef entity, int value) override
{
BoneAttachment& ba = m_bone_attachments[entity];
ba.bone_index = value;
updateRelativeMatrix(ba);
}
void setBoneAttachmentParent(EntityRef entity, EntityPtr parent) override
{
BoneAttachment& ba = m_bone_attachments[entity];
ba.parent_entity = parent;
if (parent.isValid() && parent.index < m_model_instances.size())
{
ModelInstance& mi = m_model_instances[parent.index];
mi.flags.set(ModelInstance::IS_BONE_ATTACHMENT_PARENT);
}
updateRelativeMatrix(ba);
}
void startGame() override
{
m_is_game_running = true;
}
void stopGame() override
{
m_is_game_running = false;
}
void update(float dt, bool paused) override
{
PROFILE_FUNCTION();
m_time += dt;
if (m_is_game_running && !paused)
{
for (auto* emitter : m_particle_emitters)
{
emitter->update(dt);
}
}
}
void loadLightProbeGridData(LightProbeGrid& lp) const {
StaticString<MAX_PATH_LENGTH> dir("universes/", m_universe.getName(), "/probes/");
ResourceManagerHub& manager = m_engine.getResourceManager();
for (u32 i = 0; i < lengthOf(lp.data); ++i) {
const StaticString<MAX_PATH_LENGTH> path_str(dir, lp.guid, "_grid", i, ".raw");
lp.data[i] = manager.load<Texture>(Path(path_str));
lp.data[i]->setFlag(Texture::Flags::CLAMP_U, true);
lp.data[i]->setFlag(Texture::Flags::CLAMP_V, true);
lp.data[i]->setFlag(Texture::Flags::CLAMP_W, true);
}
}
void setTextMeshText(EntityRef entity, const char* text) override
{
m_text_meshes.get(entity)->text = text;
}
const char* getTextMeshText(EntityRef entity) override
{
return m_text_meshes.get(entity)->text.c_str();
}
bool isTextMeshCameraOriented(EntityRef entity) override
{
TextMesh& text = *m_text_meshes.get(entity);
return text.m_flags.isSet(TextMesh::CAMERA_ORIENTED);
}
void setTextMeshCameraOriented(EntityRef entity, bool is_oriented) override
{
TextMesh& text = *m_text_meshes.get(entity);
text.m_flags.set(TextMesh::CAMERA_ORIENTED, is_oriented);
}
void setTextMeshFontSize(EntityRef entity, int value) override
{
TextMesh& text = *m_text_meshes.get(entity);
text.setFontSize(value);
}
int getTextMeshFontSize(EntityRef entity) override
{
return m_text_meshes.get(entity)->getFontSize();
}
static Vec4 ABGRu32ToRGBAVec4(u32 value)
{
float inv = 1 / 255.0f;
return {
((value >> 0) & 0xFF) * inv,
((value >> 8) & 0xFF) * inv,
((value >> 16) & 0xFF) * inv,
((value >> 24) & 0xFF) * inv,
};
}
static u32 RGBAVec4ToABGRu32(const Vec4& value)
{
u8 r = u8(value.x * 255 + 0.5f);
u8 g = u8(value.y * 255 + 0.5f);
u8 b = u8(value.z * 255 + 0.5f);
u8 a = u8(value.w * 255 + 0.5f);
return (a << 24) + (b << 16) + (g << 8) + r;
}
Vec4 getTextMeshColorRGBA(EntityRef entity) override
{
return ABGRu32ToRGBAVec4(m_text_meshes.get(entity)->color);
}
void setTextMeshColorRGBA(EntityRef entity, const Vec4& color) override
{
m_text_meshes.get(entity)->color = RGBAVec4ToABGRu32(color);
}
Path getTextMeshFontPath(EntityRef entity) override
{
TextMesh& text = *m_text_meshes.get(entity);
return text.getFontResource() == nullptr ? Path() : text.getFontResource()->getPath();
}
u32 getTextMeshesVerticesCount() const override {
u32 count = 0;
for (int j = 0, nj = m_text_meshes.size(); j < nj; ++j) {
const TextMesh& text = *m_text_meshes.at(j);
count += 6 * text.text.length();
}
return count;
}
void getTextMeshesVertices(TextMeshVertex* vertices, const DVec3& cam_pos, const Quat& cam_rot) override
{
const Vec3 cam_right = cam_rot * Vec3(1, 0, 0);
const Vec3 cam_up = cam_rot * Vec3(0, -1, 0);
u32 idx = 0;
for (int j = 0, nj = m_text_meshes.size(); j < nj; ++j) {
const TextMesh& text = *m_text_meshes.at(j);
const Font* font = text.getFont();
if (!font) continue;
const EntityRef entity = m_text_meshes.getKey(j);
const char* str = text.text.c_str();
Vec3 base = (m_universe.getPosition(entity) - cam_pos).toFloat();
const Quat rot = m_universe.getRotation(entity);
const float scale = m_universe.getScale(entity);
Vec3 right = rot.rotate(Vec3(1, 0, 0)) * scale;
Vec3 up = rot.rotate(Vec3(0, -1, 0)) * scale;
if (text.m_flags.isSet(TextMesh::CAMERA_ORIENTED)) {
right = cam_right * scale;
up = cam_up * scale;
}
u32 color = text.color;
const Vec2 text_size = measureTextA(*font, str, nullptr);
base += right * text_size.x * -0.5f;
base += up * text_size.y * -0.5f;
for (int i = 0, n = text.text.length(); i < n; ++i) {
const Glyph* glyph = findGlyph(*font, str[i]);
if (!glyph) continue;
const Vec3 x0y0 = base + right * float(glyph->x0) + up * float(glyph->y0);
const Vec3 x1y0 = base + right * float(glyph->x1) + up * float(glyph->y0);
const Vec3 x1y1 = base + right * float(glyph->x1) + up * float(glyph->y1);
const Vec3 x0y1 = base + right * float(glyph->x0) + up * float(glyph->y1);
vertices[idx + 0] = { x0y0, color, { glyph->u0, glyph->v0 } };
vertices[idx + 1] = { x1y0, color, { glyph->u1, glyph->v0 } };
vertices[idx + 2] = { x1y1, color, { glyph->u1, glyph->v1 } };
vertices[idx + 3] = { x0y0, color, { glyph->u0, glyph->v0 } };
vertices[idx + 4] = { x1y1, color, { glyph->u1, glyph->v1 } };
vertices[idx + 5] = { x0y1, color, { glyph->u0, glyph->v1 } };
idx += 6;
base += right * float(glyph->advance_x);
}
}
}
void setTextMeshFontPath(EntityRef entity, const Path& path) override
{
TextMesh& text = *m_text_meshes.get(entity);
ResourceManagerHub& manager = m_renderer.getEngine().getResourceManager();
FontResource* res = path.isValid() ? manager.load<FontResource>(path) : nullptr;
text.setFontResource(res);
}
int getVersion() const override { return (int)RenderSceneVersion::LATEST; }
void serializeBoneAttachments(OutputMemoryStream& serializer)
{
serializer.write((i32)m_bone_attachments.size());
for (auto& attachment : m_bone_attachments)
{
serializer.write(attachment.bone_index);
serializer.write(attachment.entity);
serializer.write(attachment.parent_entity);
serializer.write(attachment.relative_transform);
}
}
void serializeCameras(OutputMemoryStream& serializer)
{
serializer.write((i32)m_cameras.size());
for (Camera& camera : m_cameras)
{
serializer.write(camera);
}
}
void serializeLights(OutputMemoryStream& serializer)
{
serializer.write((i32)m_point_lights.size());
for (const PointLight& pl : m_point_lights) {
serializer.write(pl);
}
serializer.write((i32)m_environments.size());
for (const Environment& light : m_environments)
{
serializer.write(light);
}
serializer.write(m_active_global_light_entity);
}
void serializeModelInstances(OutputMemoryStream& serializer)
{
serializer.write((i32)m_model_instances.size());
for (auto& r : m_model_instances)
{
serializer.write(r.flags.base);
if(r.flags.isSet(ModelInstance::VALID))
{
serializer.write(r.model ? r.model->getPath().getHash() : 0);
}
}
}
void serializeTerrains(OutputMemoryStream& serializer)
{
serializer.write((i32)m_terrains.size());
for (auto* terrain : m_terrains)
{
serializer.write(terrain->getEntity());
terrain->serialize(serializer);
}
}
void serializeTextMeshes(OutputMemoryStream& serializer)
{
serializer.write(m_text_meshes.size());
for (int i = 0, n = m_text_meshes.size(); i < n; ++i)
{
TextMesh& text = *m_text_meshes.at(i);
EntityRef e = m_text_meshes.getKey(i);
serializer.write(e);
serializer.writeString(text.getFontResource() ? text.getFontResource()->getPath().c_str() : "");
serializer.write(text.color);
serializer.write(text.getFontSize());
serializer.write(text.text);
}
}
void deserializeTextMeshes(InputMemoryStream& serializer, const EntityMap& entity_map)
{
u32 count;
serializer.read(count);
ResourceManagerHub& manager = m_renderer.getEngine().getResourceManager();
for (u32 i = 0; i < count; ++i) {
EntityRef e;
serializer.read(e);
e = entity_map.get(e);
TextMesh& text = *LUMIX_NEW(m_allocator, TextMesh)(m_allocator);
m_text_meshes.insert(e, &text);
const char* tmp = serializer.readString();
serializer.read(text.color);
int font_size;
serializer.read(font_size);
text.setFontSize(font_size);
serializer.read(text.text);
FontResource* res = tmp[0] ? manager.load<FontResource>(Path(tmp)) : nullptr;
text.setFontResource(res);
m_universe.onComponentCreated(e, TEXT_MESH_TYPE, this);
}
}
void deserializeDecals(InputMemoryStream& serializer, const EntityMap& entity_map)
{
u32 count;
serializer.read(count);
m_decals.reserve(count + m_decals.size());
for (u32 i = 0; i < count; ++i) {
Decal decal;
serializer.read(decal.entity);
decal.entity = entity_map.get(decal.entity);
serializer.read(decal.half_extents);
const char* tmp = serializer.readString();
updateDecalInfo(decal);
m_decals.insert(decal.entity, decal);
setDecalMaterialPath(decal.entity, Path(tmp));
m_universe.onComponentCreated(decal.entity, DECAL_TYPE, this);
}
}
void serializeDecals(OutputMemoryStream& serializer)
{
serializer.write(m_decals.size());
for (auto& decal : m_decals)
{
serializer.write(decal.entity);
serializer.write(decal.half_extents);
serializer.writeString(decal.material ? decal.material->getPath().c_str() : "");
}
}
void serializeLightProbeGrids(OutputMemoryStream& serializer) {
const i32 count = m_light_probe_grids.size();
serializer.write(count);
for (auto iter : m_light_probe_grids) {
serializer.write(iter.entity);
serializer.write(iter.guid);
serializer.write(iter.resolution);
serializer.write(iter.half_extents);
}
}
void serializeEnvironmentProbes(OutputMemoryStream& serializer)
{
i32 count = m_environment_probes.size();
serializer.write(count);
for (int i = 0; i < count; ++i)
{
EntityRef entity = m_environment_probes.getKey(i);
serializer.write(entity);
const EnvironmentProbe& probe = m_environment_probes.at(i);
serializer.write(probe.guid);
serializer.write(probe.flags.base);
serializer.write(probe.half_extents);
serializer.write(probe.radiance_size);
serializer.write(probe.reflection_size);
serializer.write(probe.sh_coefs);
}
}
void deserializeLightProbeGrids(InputMemoryStream& serializer, const EntityMap& entity_map) {
u32 count;
serializer.read(count);
m_light_probe_grids.reserve(count + m_light_probe_grids.size());
for (u32 i = 0; i < count; ++i) {
LightProbeGrid lp;
serializer.read(lp.entity);
lp.entity = entity_map.get(lp.entity);
serializer.read(lp.guid);
serializer.read(lp.resolution);
serializer.read(lp.half_extents);
loadLightProbeGridData(lp);
m_light_probe_grids.insert(lp.entity, lp);
m_universe.onComponentCreated(lp.entity, LIGHT_PROBE_GRID_TYPE, this);
}
}
void deserializeEnvironmentProbes(InputMemoryStream& serializer, const EntityMap& entity_map)
{
u32 count;
serializer.read(count);
m_environment_probes.reserve(count + m_environment_probes.size());
ResourceManagerHub& manager = m_engine.getResourceManager();
StaticString<MAX_PATH_LENGTH> probe_dir("universes/", m_universe.getName(), "/probes/");
for (u32 i = 0; i < count; ++i) {
EntityRef entity;
serializer.read(entity);
entity = entity_map.get(entity);
EnvironmentProbe& probe = m_environment_probes.insert(entity);
// TODO probes are stored in per-universe directory, that won't work with additive loading
serializer.read(probe.guid);
serializer.read(probe.flags.base);
serializer.read(probe.half_extents);
serializer.read(probe.radiance_size);
serializer.read(probe.reflection_size);
serializer.read(probe.sh_coefs);
ASSERT(probe.reflection == nullptr);
if (probe.flags.isSet(EnvironmentProbe::REFLECTION)) {
StaticString<MAX_PATH_LENGTH> path_str(probe_dir, probe.guid, ".dds");
probe.reflection = manager.load<Texture>(Path(path_str));
}
ASSERT(probe.radiance == nullptr);
if (probe.flags.isSet(EnvironmentProbe::SPECULAR)) {
StaticString<MAX_PATH_LENGTH> r_path_str(probe_dir, probe.guid, "_radiance.dds");
probe.radiance = manager.load<Texture>(Path(r_path_str));
}
m_universe.onComponentCreated(entity, ENVIRONMENT_PROBE_TYPE, this);
}
}
void deserializeBoneAttachments(InputMemoryStream& serializer, const EntityMap& entity_map)
{
u32 count;
serializer.read(count);
m_bone_attachments.reserve(count + m_bone_attachments.size());
for (u32 i = 0; i < count; ++i) {
BoneAttachment bone_attachment;
serializer.read(bone_attachment.bone_index);
serializer.read(bone_attachment.entity);
bone_attachment.entity = entity_map.get(bone_attachment.entity);
serializer.read(bone_attachment.parent_entity);
serializer.read(bone_attachment.relative_transform);
m_bone_attachments.insert(bone_attachment.entity, bone_attachment);
m_universe.onComponentCreated(bone_attachment.entity, BONE_ATTACHMENT_TYPE, this);
}
}
void deserializeParticleEmitters(InputMemoryStream& serializer, const EntityMap& entity_map)
{
const u32 count = serializer.read<u32>();
m_particle_emitters.reserve(count + m_particle_emitters.size());
for (u32 i = 0; i < count; ++i) {
ParticleEmitter* emitter = LUMIX_NEW(m_allocator, ParticleEmitter)(INVALID_ENTITY, m_allocator);
emitter->deserialize(serializer, m_engine.getResourceManager());
emitter->m_entity = entity_map.get(emitter->m_entity);
if(emitter->m_entity.isValid()) {
m_particle_emitters.insert((EntityRef)emitter->m_entity, emitter);
m_universe.onComponentCreated((EntityRef)emitter->m_entity, PARTICLE_EMITTER_TYPE, this);
}
else {
LUMIX_DELETE(m_allocator, emitter);
}
}
}
void serializeParticleEmitters(OutputMemoryStream& serializer)
{
serializer.write(m_particle_emitters.size());
for (auto* emitter : m_particle_emitters)
{
emitter->serialize(serializer);
}
}
void serialize(OutputMemoryStream& serializer) override
{
serializeCameras(serializer);
serializeModelInstances(serializer);
serializeLights(serializer);
serializeTerrains(serializer);
serializeParticleEmitters(serializer);
serializeBoneAttachments(serializer);
serializeEnvironmentProbes(serializer);
serializeLightProbeGrids(serializer);
serializeDecals(serializer);
serializeTextMeshes(serializer);
}
void deserializeCameras(InputMemoryStream& serializer, const EntityMap& entity_map)
{
u32 size;
serializer.read(size);
m_cameras.reserve(size + m_cameras.size());
for (u32 i = 0; i < size; ++i)
{
Camera camera;
serializer.read(camera);
camera.entity = entity_map.get(camera.entity);
m_cameras.insert(camera.entity, camera);
m_universe.onComponentCreated(camera.entity, CAMERA_TYPE, this);
if (!m_active_camera.isValid()) m_active_camera = camera.entity;
}
}
void deserializeModelInstances(IInputStream& serializer, const EntityMap& entity_map)
{
u32 size = 0;
serializer.read(size);
m_model_instances.reserve(size + m_model_instances.size());
m_mesh_sort_data.reserve(size + m_mesh_sort_data.size());
for (u32 i = 0; i < size; ++i) {
FlagSet<ModelInstance::Flags, u8> flags;
serializer.read(flags);
if(flags.isSet(ModelInstance::VALID)) {
const EntityRef e = entity_map.get(EntityRef{(i32)i});
while (e.index >= m_model_instances.size()) {
auto& r = m_model_instances.emplace();
r.flags.clear();
r.flags.set(ModelInstance::VALID, false);
r.model = nullptr;
r.pose = nullptr;
}
ModelInstance& r = m_model_instances[e.index];
r.flags = flags;
r.model = nullptr;
r.pose = nullptr;
r.meshes = nullptr;
r.mesh_count = 0;
u32 path;
serializer.read(path);
if (path != 0) {
Model* model = m_engine.getResourceManager().load<Model>(Path(path));
setModel(e, model);
}
m_universe.onComponentCreated(e, MODEL_INSTANCE_TYPE, this);
}
}
}
void deserializeLights(IInputStream& serializer, const EntityMap& entity_map)
{
u32 size = 0;
serializer.read(size);
m_point_lights.reserve(size + m_point_lights.size());
for (u32 i = 0; i < size; ++i) {
PointLight light;
serializer.read(light);
light.entity = entity_map.get(light.entity);
m_point_lights.insert(light.entity, light);
const DVec3 pos = m_universe.getPosition(light.entity);
m_culling_system->add(light.entity, (u8)RenderableTypes::LOCAL_LIGHT, pos, light.range);
m_universe.onComponentCreated(light.entity, POINT_LIGHT_TYPE, this);
}
serializer.read(size);
for (u32 i = 0; i < size; ++i) {
Environment light;
serializer.read(light);
light.entity = entity_map.get(light.entity);
m_environments.insert(light.entity, light);
m_universe.onComponentCreated(light.entity, ENVIRONMENT_TYPE, this);
}
EntityPtr tmp;
serializer.read(tmp);
if (!m_active_global_light_entity.isValid()) {
m_active_global_light_entity = tmp;
}
}
void deserializeTerrains(InputMemoryStream& serializer, const EntityMap& entity_map)
{
i32 size = 0;
serializer.read(size);
for (int i = 0; i < size; ++i)
{
EntityRef entity;
serializer.read(entity);
entity = entity_map.get(entity);
auto* terrain = LUMIX_NEW(m_allocator, Terrain)(m_renderer, entity, *this, m_allocator);
terrain->deserialize(entity, serializer, m_universe, *this);
m_terrains.insert(entity, terrain);
}
}
void deserialize(InputMemoryStream& serializer, const EntityMap& entity_map) override
{
deserializeCameras(serializer, entity_map);
deserializeModelInstances(serializer, entity_map);
deserializeLights(serializer, entity_map);
deserializeTerrains(serializer, entity_map);
deserializeParticleEmitters(serializer, entity_map);
deserializeBoneAttachments(serializer, entity_map);
deserializeEnvironmentProbes(serializer, entity_map);
deserializeLightProbeGrids(serializer, entity_map);
deserializeDecals(serializer, entity_map);
deserializeTextMeshes(serializer, entity_map);
}
void destroyBoneAttachment(EntityRef entity)
{
const BoneAttachment& bone_attachment = m_bone_attachments[entity];
const EntityPtr parent_entity = bone_attachment.parent_entity;
if (parent_entity.isValid() && parent_entity.index < m_model_instances.size())
{
ModelInstance& mi = m_model_instances[bone_attachment.parent_entity.index];
mi.flags.unset(ModelInstance::IS_BONE_ATTACHMENT_PARENT);
}
m_bone_attachments.erase(entity);
m_universe.onComponentDestroyed(entity, BONE_ATTACHMENT_TYPE, this);
}
void destroyEnvironmentProbe(EntityRef entity)
{
auto& probe = m_environment_probes[entity];
if (probe.reflection) probe.reflection->getResourceManager().unload(*probe.reflection);
if (probe.radiance) probe.radiance->getResourceManager().unload(*probe.radiance);
m_environment_probes.erase(entity);
m_universe.onComponentDestroyed(entity, ENVIRONMENT_PROBE_TYPE, this);
}
void destroyModelInstance(EntityRef entity)
{
setModel(entity, nullptr);
auto& model_instance = m_model_instances[entity.index];
LUMIX_DELETE(m_allocator, model_instance.pose);
model_instance.pose = nullptr;
model_instance.flags.clear();
model_instance.flags.set(ModelInstance::VALID, false);
m_universe.onComponentDestroyed(entity, MODEL_INSTANCE_TYPE, this);
}
void destroyLightProbeGrid(EntityRef entity) {
const LightProbeGrid& lp = m_light_probe_grids[entity];
m_universe.onComponentDestroyed(entity, LIGHT_PROBE_GRID_TYPE, this);
for (Texture* t : lp.data) {
if (t) t->getResourceManager().unload(*t);
}
m_light_probe_grids.erase(entity);
}
void destroyEnvironment(EntityRef entity)
{
m_universe.onComponentDestroyed(entity, ENVIRONMENT_TYPE, this);
if ((EntityPtr)entity == m_active_global_light_entity)
{
m_active_global_light_entity = INVALID_ENTITY;
}
m_environments.erase(entity);
}
void destroyDecal(EntityRef entity)
{
m_culling_system->remove(entity);
m_decals.erase(entity);
m_universe.onComponentDestroyed(entity, DECAL_TYPE, this);
}
void destroyPointLight(EntityRef entity)
{
m_point_lights.erase(entity);
m_culling_system->remove(entity);
m_universe.onComponentDestroyed(entity, POINT_LIGHT_TYPE, this);
}
void destroyTextMesh(EntityRef entity)
{
TextMesh* text = m_text_meshes[entity];
LUMIX_DELETE(m_allocator, text);
m_text_meshes.erase(entity);
m_universe.onComponentDestroyed(entity, TEXT_MESH_TYPE, this);
}
void destroyCamera(EntityRef entity)
{
m_cameras.erase(entity);
m_universe.onComponentDestroyed(entity, CAMERA_TYPE, this);
if (m_active_camera == entity) m_active_camera = INVALID_ENTITY;
}
void destroyTerrain(EntityRef entity)
{
LUMIX_DELETE(m_allocator, m_terrains[entity]);
m_terrains.erase(entity);
m_universe.onComponentDestroyed(entity, TERRAIN_TYPE, this);
}
void destroyParticleEmitter(EntityRef entity)
{
auto* emitter = m_particle_emitters[entity];
m_universe.onComponentDestroyed((EntityRef)emitter->m_entity, PARTICLE_EMITTER_TYPE, this);
m_particle_emitters.erase((EntityRef)emitter->m_entity);
LUMIX_DELETE(m_allocator, emitter);
}
void createTextMesh(EntityRef entity)
{
TextMesh* text = LUMIX_NEW(m_allocator, TextMesh)(m_allocator);
m_text_meshes.insert(entity, text);
m_universe.onComponentCreated(entity, TEXT_MESH_TYPE, this);
}
void createCamera(EntityRef entity)
{
Camera camera;
camera.is_ortho = false;
camera.ortho_size = 10;
camera.entity = entity;
camera.fov = degreesToRadians(60);
camera.screen_width = 800;
camera.screen_height = 600;
camera.near = 0.1f;
camera.far = 10000.0f;
m_cameras.insert(entity, camera);
m_universe.onComponentCreated(entity, CAMERA_TYPE, this);
if (!m_active_camera.isValid()) m_active_camera = entity;
}
void createTerrain(EntityRef entity)
{
Terrain* terrain = LUMIX_NEW(m_allocator, Terrain)(m_renderer, entity, *this, m_allocator);
m_terrains.insert(entity, terrain);
m_universe.onComponentCreated(entity, TERRAIN_TYPE, this);
}
void createParticleEmitter(EntityRef entity)
{
m_particle_emitters.insert(entity, LUMIX_NEW(m_allocator, ParticleEmitter)(entity, m_allocator));
m_universe.onComponentCreated(entity, PARTICLE_EMITTER_TYPE, this);
}
int getClosestShadowcastingPointLights(const DVec3& reference_pos, u32 max_lights, PointLight* lights) override
{
float dists[16];
ASSERT(max_lights <= lengthOf(dists));
ASSERT(max_lights > 0);
if (m_point_lights.empty()) return 0;
u32 light_count = 0;
auto iter = m_point_lights.begin();
auto end = m_point_lights.end();
while (iter != end && light_count < max_lights) {
const PointLight& light = iter.value();
++iter;
if (!light.cast_shadows) continue;
const DVec3 light_pos = m_universe.getPosition(light.entity);
float dist_squared = float((reference_pos - light_pos).squaredLength());
dists[light_count] = dist_squared;
lights[light_count] = light;
for (int i = light_count; i > 0 && dists[i - 1] > dists[i]; --i) {
float tmp = dists[i];
dists[i] = dists[i - 1];
dists[i - 1] = tmp;
const PointLight tmp2 = lights[i];
lights[i] = lights[i - 1];
lights[i - 1] = tmp2;
}
++light_count;
}
while(iter != end) {
const PointLight& light = iter.value();
++iter;
if (!light.cast_shadows) continue;
const DVec3 light_pos = m_universe.getPosition(light.entity);
float dist_squared = float((reference_pos - light_pos).squaredLength());
if (dist_squared < dists[max_lights - 1]) {
dists[max_lights - 1] = dist_squared;
lights[max_lights - 1] = light;
for (int i = max_lights - 1; i > 0 && dists[i - 1] > dists[i];
--i)
{
float tmp = dists[i];
dists[i] = dists[i - 1];
dists[i - 1] = tmp;
const PointLight tmp2 = lights[i];
lights[i] = lights[i - 1];
lights[i - 1] = tmp2;
}
}
}
return light_count;
}
bool getEnvironmentCastShadows(EntityRef entity) override {
return m_environments[entity].flags.isSet(Environment::CAST_SHADOWS);
}
void setEnvironmentCastShadows(EntityRef entity, bool enable) override {
m_environments[entity].flags.set(Environment::CAST_SHADOWS, enable);
}
Environment& getEnvironment(EntityRef entity) override
{
return m_environments[entity];
}
PointLight& getPointLight(EntityRef entity) override
{
return m_point_lights[entity];
}
const MeshSortData* getMeshSortData() const override
{
return m_mesh_sort_data.empty() ? nullptr : m_mesh_sort_data.begin();
}
const ModelInstance* getModelInstances() const override
{
return m_model_instances.empty() ? nullptr : &m_model_instances[0];
}
ModelInstance* getModelInstance(EntityRef entity) override
{
return &m_model_instances[entity.index];
}
Vec3 getPoseBonePosition(EntityRef model_instance, int bone_index)
{
Pose* pose = m_model_instances[model_instance.index].pose;
return pose->positions[bone_index];
}
void onEntityDestroyed(EntityRef entity)
{
for (auto& i : m_bone_attachments)
{
if (i.parent_entity == entity)
{
i.parent_entity = INVALID_ENTITY;
break;
}
}
}
void onEntityMoved(EntityRef entity)
{
const u64 cmp_mask = m_universe.getComponentsMask(entity);
if ((cmp_mask & m_render_cmps_mask) == 0) {
return;
}
if (m_culling_system->isAdded(entity)) {
if (m_universe.hasComponent(entity, MODEL_INSTANCE_TYPE)) {
const DVec3 position = m_universe.getPosition(entity);
m_culling_system->setPosition(entity, position);
}
else if (m_universe.hasComponent(entity, DECAL_TYPE)) {
auto iter = m_decals.find(entity);
updateDecalInfo(iter.value());
const DVec3 position = m_universe.getPosition(entity);
m_culling_system->setPosition(entity, position);
}
else if (m_universe.hasComponent(entity, POINT_LIGHT_TYPE)) {
const DVec3 pos = m_universe.getPosition(entity);
m_culling_system->setPosition(entity, pos);
}
}
bool was_updating = m_is_updating_attachments;
m_is_updating_attachments = true;
for (auto& attachment : m_bone_attachments)
{
if (attachment.parent_entity == entity)
{
updateBoneAttachment(attachment);
}
}
m_is_updating_attachments = was_updating;
if (m_is_updating_attachments || m_is_game_running) return;
if(m_universe.hasComponent(entity, BONE_ATTACHMENT_TYPE)) {
for (auto& attachment : m_bone_attachments)
{
if (attachment.entity == entity)
{
updateRelativeMatrix(attachment);
break;
}
}
}
}
Engine& getEngine() const override { return m_engine; }
Terrain* getTerrain(EntityRef entity) override
{
return m_terrains[entity];
}
IVec2 getTerrainResolution(EntityRef entity) override
{
auto* terrain = m_terrains[entity];
return IVec2(terrain->getWidth(), terrain->getHeight());
}
EntityPtr getFirstTerrain() override
{
if (m_terrains.empty()) return INVALID_ENTITY;
auto iter = m_terrains.begin();
return iter.value()->getEntity();
}
EntityPtr getNextTerrain(EntityRef entity) override
{
auto iter = m_terrains.find(entity);
++iter;
if (!iter.isValid()) return INVALID_ENTITY;
return iter.value()->getEntity();
}
Vec3 getTerrainNormalAt(EntityRef entity, float x, float z) override
{
return m_terrains[entity]->getNormal(x, z);
}
float getTerrainHeightAt(EntityRef entity, float x, float z) override
{
return m_terrains[entity]->getHeight(x, z);
}
AABB getTerrainAABB(EntityRef entity) override
{
return m_terrains[entity]->getAABB();
}
Vec2 getTerrainSize(EntityRef entity) override
{
return m_terrains[entity]->getSize();
}
void setTerrainMaterialPath(EntityRef entity, const Path& path) override
{
if (path.isValid())
{
Material* material = m_engine.getResourceManager().load<Material>(path);
m_terrains[entity]->setMaterial(material);
}
else
{
m_terrains[entity]->setMaterial(nullptr);
}
}
Material* getTerrainMaterial(EntityRef entity) override { return m_terrains[entity]->getMaterial(); }
void setDecalHalfExtents(EntityRef entity, const Vec3& value) override
{
Decal& decal = m_decals[entity];
decal.half_extents = value;
if (decal.material && decal.material->isReady()) {
m_culling_system->setRadius(entity, value.length());
}
updateDecalInfo(decal);
}
Vec3 getDecalHalfExtents(EntityRef entity) override
{
return m_decals[entity].half_extents;
}
void setDecalMaterialPath(EntityRef entity, const Path& path) override
{
Decal& decal = m_decals[entity];
if (decal.material) {
removeFromMaterialDecalMap(decal.material, entity);
decal.material->getResourceManager().unload(*decal.material);
}
if (path.isValid()) {
decal.material = m_engine.getResourceManager().load<Material>(path);
addToMaterialDecalMap(decal.material, entity);
if (decal.material->isReady()) {
const float radius = m_decals[entity].half_extents.length();
const DVec3 pos = m_universe.getPosition(entity);
m_culling_system->add(entity, (u8)RenderableTypes::DECAL, pos, radius);
}
}
else {
decal.material = nullptr;
}
}
Material* getDecalMaterial(EntityRef entity) const override
{
auto iter = m_decals.find(entity);
return iter.value().material;
}
Path getDecalMaterialPath(EntityRef entity) override
{
Decal& decal = m_decals[entity];
return decal.material ? decal.material->getPath() : Path("");
}
Path getTerrainMaterialPath(EntityRef entity) override
{
Terrain* terrain = m_terrains[entity];
if (terrain->getMaterial())
{
return terrain->getMaterial()->getPath();
}
else
{
return Path("");
}
}
void setTerrainXZScale(EntityRef entity, float scale) override
{
m_terrains[entity]->setXZScale(scale);
}
float getTerrainXZScale(EntityRef entity) override { return m_terrains[entity]->getXZScale(); }
void setTerrainYScale(EntityRef entity, float scale) override
{
m_terrains[entity]->setYScale(scale);
}
float getTerrainYScale(EntityRef entity) override { return m_terrains[entity]->getYScale(); }
Pose* lockPose(EntityRef entity) override { return m_model_instances[entity.index].pose; }
void unlockPose(EntityRef entity, bool changed) override
{
if (!changed) return;
if (entity.index < m_model_instances.size()
&& (m_model_instances[entity.index].flags.isSet(ModelInstance::IS_BONE_ATTACHMENT_PARENT)) == 0)
{
return;
}
EntityRef parent = entity;
for (BoneAttachment& ba : m_bone_attachments)
{
if (ba.parent_entity != parent) continue;
m_is_updating_attachments = true;
updateBoneAttachment(ba);
m_is_updating_attachments = false;
}
}
Model* getModelInstanceModel(EntityRef entity) override { return m_model_instances[entity.index].model; }
bool isModelInstanceEnabled(EntityRef entity) override
{
ModelInstance& model_instance = m_model_instances[entity.index];
return model_instance.flags.isSet(ModelInstance::ENABLED);
}
void enableModelInstance(EntityRef entity, bool enable) override
{
ModelInstance& model_instance = m_model_instances[entity.index];
model_instance.flags.set(ModelInstance::ENABLED, enable);
if (enable)
{
if (!model_instance.model || !model_instance.model->isReady()) return;
const DVec3 pos = m_universe.getPosition(entity);
const float radius = model_instance.model->getBoundingRadius();
if (!m_culling_system->isAdded(entity)) {
const RenderableTypes type = getRenderableType(*model_instance.model);
m_culling_system->add(entity, (u8)type, pos, radius);
}
}
else
{
m_culling_system->remove(entity);
}
}
Path getModelInstancePath(EntityRef entity) override
{
return m_model_instances[entity.index].model ? m_model_instances[entity.index].model->getPath() : Path("");
}
void setModelInstancePath(EntityRef entity, const Path& path) override
{
if (path.isValid()) {
Model* model = m_engine.getResourceManager().load<Model>(path);
setModel(entity, model);
}
else {
setModel(entity, nullptr);
}
}
void forceGrassUpdate(EntityRef entity) override { m_terrains[entity]->forceGrassUpdate(); }
void getTerrainInfos(Array<TerrainInfo>& infos) override
{
PROFILE_FUNCTION();
infos.reserve(m_terrains.size());
for (auto* terrain : m_terrains) {
const TerrainInfo info = terrain->getInfo();
if (info.terrain) infos.push(info);
}
}
static int LUA_castCameraRay(lua_State* L)
{
auto* scene = LuaWrapper::checkArg<RenderSceneImpl*>(L, 1);
EntityRef camera_entity = LuaWrapper::checkArg<EntityRef>(L, 2);
float x, y;
if (lua_gettop(L) > 3) {
x = LuaWrapper::checkArg<float>(L, 3);
y = LuaWrapper::checkArg<float>(L, 4);
}
else {
x = scene->getCameraScreenWidth(camera_entity) * 0.5f;
y = scene->getCameraScreenHeight(camera_entity) * 0.5f;
}
DVec3 origin;
Vec3 dir;
scene->getRay(camera_entity, {x, y}, origin, dir);
RayCastModelHit hit = scene->castRay(origin, dir, INVALID_ENTITY);
LuaWrapper::push(L, hit.is_hit);
LuaWrapper::push(L, hit.is_hit ? hit.origin + hit.dir * hit.t : DVec3(0));
return 2;
}
static float LUA_getTerrainHeightAt(RenderSceneImpl* render_scene, EntityRef entity, int x, int z)
{
return render_scene->m_terrains[entity]->getHeight(x, z);
}
void setTerrainHeightAt(EntityRef entity, int x, int z, float height)
{
m_terrains[entity]->setHeight(x, z, height);
}
static Pipeline* LUA_createPipeline(Engine* engine, const char* path)
{
Renderer& renderer = *static_cast<Renderer*>(engine->getPluginManager().getPlugin("renderer"));
PipelineResource* pres = engine->getResourceManager().load<PipelineResource>(Path(path));
return Pipeline::create(renderer, pres, "", renderer.getEngine().getAllocator());
}
static void LUA_destroyPipeline(Pipeline* pipeline)
{
Pipeline::destroy(pipeline);
}
static void LUA_setPipelineScene(Pipeline* pipeline, RenderScene* scene)
{
pipeline->setUniverse(&scene->getUniverse());
}
static RenderScene* LUA_getPipelineScene(Pipeline* pipeline)
{
return pipeline->getScene();
}
static void LUA_setModelInstancePath(IScene* scene, int component, const char* path)
{
RenderScene* render_scene = (RenderScene*)scene;
render_scene->setModelInstancePath({component}, Path(path));
}
static int LUA_getModelBoneIndex(Model* model, const char* bone)
{
if (!model) return 0;
return model->getBoneIndex(crc32(bone)).value();
}
static unsigned int LUA_compareTGA(RenderSceneImpl* scene, const char* path, const char* path_preimage, int min_diff)
{
OS::InputFile file1, file2;
if (!file1.open(path))
{
logError("render_test") << "Failed to open " << path;
return 0xffffFFFF;
}
else if (!file2.open(path_preimage))
{
file1.close();
logError("render_test") << "Failed to open " << path_preimage;
return 0xffffFFFF;
}
unsigned int result = Texture::compareTGA(&file1, &file2, min_diff, scene->m_allocator);
file1.close();
file2.close();
return result;
}
static void LUA_makeScreenshot(RenderSceneImpl* scene, const char* path)
{
scene->m_renderer.makeScreenshot(Path(path));
}
bool isGrassEnabled() const override
{
return m_is_grass_enabled;
}
int getGrassRotationMode(EntityRef entity, int index) override
{
return (int)m_terrains[entity]->getGrassTypeRotationMode(index);
}
void setGrassRotationMode(EntityRef entity, int index, int value) override
{
m_terrains[entity]->setGrassTypeRotationMode(index, (Terrain::GrassType::RotationMode)value);
}
float getGrassDistance(EntityRef entity, int index) override
{
return m_terrains[entity]->getGrassTypeDistance(index);
}
void setGrassDistance(EntityRef entity, int index, float value) override
{
m_terrains[entity]->setGrassTypeDistance(index, value);
}
void enableGrass(bool enabled) override { m_is_grass_enabled = enabled; }
void setGrassDensity(EntityRef entity, int index, int density) override
{
m_terrains[entity]->setGrassTypeDensity(index, density);
}
int getGrassDensity(EntityRef entity, int index) override
{
return m_terrains[entity]->getGrassTypeDensity(index);
}
void setGrassPath(EntityRef entity, int index, const Path& path) override
{
m_terrains[entity]->setGrassTypePath(index, path);
}
Path getGrassPath(EntityRef entity, int index) override
{
return m_terrains[entity]->getGrassTypePath(index);
}
int getGrassCount(EntityRef entity) override
{
return m_terrains[entity]->getGrassTypeCount();
}
void addGrass(EntityRef entity, int index) override
{
m_terrains[entity]->addGrassType(index);
}
void removeGrass(EntityRef entity, int index) override
{
m_terrains[entity]->removeGrassType(index);
}
EntityPtr getFirstModelInstance() override
{
return getNextModelInstance(INVALID_ENTITY);
}
EntityPtr getNextModelInstance(EntityPtr entity) override
{
for(int i = entity.index + 1; i < m_model_instances.size(); ++i)
{
if (m_model_instances[i].flags.isSet(ModelInstance::VALID)) return {i};
}
return INVALID_ENTITY;
}
float getCameraLODMultiplier(EntityRef camera)
{
float lod_multiplier;
if (getCamera(camera).is_ortho)
{
lod_multiplier = 1;
}
else
{
lod_multiplier = getCamera(camera).fov / degreesToRadians(60);
lod_multiplier *= lod_multiplier;
}
return lod_multiplier;
}
CullResult* getRenderables(const ShiftedFrustum& frustum, RenderableTypes type) const override
{
if(type == RenderableTypes::GRASS) {
if (m_is_grass_enabled && !m_terrains.empty()) {
PageAllocator& page_allocator = m_engine.getPageAllocator();
CullResult* result = (CullResult*)page_allocator.allocate(true);
CullResult* iter = result;
result->header.count = 0;
result->header.next = nullptr;
for (auto* terrain : m_terrains) {
terrain->updateGrass(0, frustum.origin);
if(iter->header.count == lengthOf(iter->entities)) {
iter->header.next = (CullResult*)page_allocator.allocate(true);
iter->header.next->header.next = nullptr;
iter->header.next->header.count = 0;
iter = iter->header.next;
}
iter->entities[iter->header.count] = terrain->m_entity;
++iter->header.count;
}
return result;
}
}
return m_culling_system->cull(frustum, static_cast<u8>(type));
}
float getCameraScreenWidth(EntityRef camera) override { return m_cameras[camera].screen_width; }
float getCameraScreenHeight(EntityRef camera) override { return m_cameras[camera].screen_height; }
void setGlobalLODMultiplier(float multiplier) { m_lod_multiplier = multiplier; }
float getGlobalLODMultiplier() const { return m_lod_multiplier; }
Camera& getCamera(EntityRef entity) override { return m_cameras[entity]; }
Matrix getCameraProjection(EntityRef entity) override
{
const Camera& camera = m_cameras[entity];
Matrix mtx;
const float ratio = camera.screen_height > 0 ? camera.screen_width / camera.screen_height : 1;
const bool is_homogenous_depth = gpu::isHomogenousDepth();
if (camera.is_ortho) {
mtx.setOrtho(-camera.ortho_size * ratio,
camera.ortho_size * ratio,
-camera.ortho_size,
camera.ortho_size,
camera.near,
camera.far,
is_homogenous_depth,
true);
}
else {
mtx.setPerspective(camera.fov, ratio, camera.near, camera.far, is_homogenous_depth, true);
}
return mtx;
}
void setCameraScreenSize(EntityRef camera, int w, int h) override
{
auto& cam = m_cameras[{camera.index}];
cam.screen_width = (float)w;
cam.screen_height = (float)h;
}
Vec2 getCameraScreenSize(EntityRef camera) override
{
auto& cam = m_cameras[{camera.index}];
return Vec2(cam.screen_width, cam.screen_height);
}
void clearDebugLines() override { m_debug_lines.clear(); }
void clearDebugTriangles() override { m_debug_triangles.clear(); }
const Array<DebugTriangle>& getDebugTriangles() const override { return m_debug_triangles; }
const Array<DebugLine>& getDebugLines() const override { return m_debug_lines; }
void addDebugHalfSphere(const RigidTransform& transform, float radius, bool top, u32 color)
{
const DVec3 center = transform.pos;
const Vec3 x_vec = transform.rot * Vec3(1, 0, 0);
const Vec3 y_vec = transform.rot * Vec3(0, top ? 1.f : -1.f, 0);
const Vec3 z_vec = transform.rot * Vec3(0, 0, 1);
static const int COLS = 36;
static const int ROWS = COLS >> 1;
static const float STEP = degreesToRadians(360.0f) / COLS;
for (int y = 0; y < ROWS >> 1; ++y)
{
float cy = cosf(y * STEP);
float cy1 = cosf((y + 1) * STEP);
float sy = sinf(y * STEP);
float sy1 = sinf((y + 1) * STEP);
float prev_ci = cosf(-STEP);
float prev_si = sinf(-STEP);
Vec3 y_offset = y_vec * sy;
Vec3 y_offset1 = y_vec * sy1;
for (int i = 0; i < COLS; ++i)
{
float ci = cosf(i * STEP);
float si = sinf(i * STEP);
addDebugLine(
center + radius * (x_vec * ci * cy + z_vec * si * cy + y_offset),
center + radius * (x_vec * prev_ci * cy + z_vec * prev_si * cy + y_offset),
color);
addDebugLine(
center + radius * (x_vec * ci * cy + z_vec * si * cy + y_offset),
center + radius * (x_vec * ci * cy1 + z_vec * si * cy1 + y_offset1),
color);
prev_ci = ci;
prev_si = si;
}
}
}
void addDebugHalfSphere(const DVec3& center, float radius, bool top, u32 color)
{
static const int COLS = 36;
static const int ROWS = COLS >> 1;
static const float STEP = (PI / 180.0f) * 360.0f / COLS;
int p2 = COLS >> 1;
int yfrom = top ? 0 : -(ROWS >> 1);
int yto = top ? ROWS >> 1 : 0;
for (int y = yfrom; y < yto; ++y)
{
float cy = cosf(y * STEP);
float cy1 = cosf((y + 1) * STEP);
float sy = sinf(y * STEP);
float sy1 = sinf((y + 1) * STEP);
float prev_ci = cosf((-p2 - 1) * STEP);
float prev_si = sinf((-p2 - 1) * STEP);
for (int i = -p2; i < p2; ++i)
{
float ci = cosf(i * STEP);
float si = sinf(i * STEP);
addDebugLine(DVec3(center.x + radius * ci * cy,
center.y + radius * sy,
center.z + radius * si * cy),
DVec3(center.x + radius * ci * cy1,
center.y + radius * sy1,
center.z + radius * si * cy1),
color);
addDebugLine(DVec3(center.x + radius * ci * cy,
center.y + radius * sy,
center.z + radius * si * cy),
DVec3(center.x + radius * prev_ci * cy,
center.y + radius * sy,
center.z + radius * prev_si * cy),
color);
addDebugLine(DVec3(center.x + radius * prev_ci * cy1,
center.y + radius * sy1,
center.z + radius * prev_si * cy1),
DVec3(center.x + radius * ci * cy1,
center.y + radius * sy1,
center.z + radius * si * cy1),
color);
prev_ci = ci;
prev_si = si;
}
}
}
void addDebugTriangle(const DVec3& p0,
const DVec3& p1,
const DVec3& p2,
u32 color) override
{
DebugTriangle& tri = m_debug_triangles.emplace();
tri.p0 = p0;
tri.p1 = p1;
tri.p2 = p2;
tri.color = ARGBToABGR(color);
}
void addDebugCube(const DVec3& pos,
const Vec3& dir,
const Vec3& up,
const Vec3& right,
u32 color) override
{
addDebugLine(pos + dir + up + right, pos + dir + up - right, color);
addDebugLine(pos - dir + up + right, pos - dir + up - right, color);
addDebugLine(pos + dir + up + right, pos - dir + up + right, color);
addDebugLine(pos + dir + up - right, pos - dir + up - right, color);
addDebugLine(pos + dir - up + right, pos + dir - up - right, color);
addDebugLine(pos - dir - up + right, pos - dir - up - right, color);
addDebugLine(pos + dir - up + right, pos - dir - up + right, color);
addDebugLine(pos + dir - up - right, pos - dir - up - right, color);
addDebugLine(pos + dir + up + right, pos + dir - up + right, color);
addDebugLine(pos + dir + up - right, pos + dir - up - right, color);
addDebugLine(pos - dir + up + right, pos - dir - up + right, color);
addDebugLine(pos - dir + up - right, pos - dir - up - right, color);
}
void addDebugCubeSolid(const DVec3& min,
const DVec3& max,
u32 color) override
{
DVec3 a = min;
DVec3 b = min;
DVec3 c = max;
b.x = max.x;
c.z = min.z;
addDebugTriangle(a, c, b, color);
b.x = min.x;
b.y = max.y;
addDebugTriangle(a, b, c, color);
b = max;
c = max;
a.z = max.z;
b.y = min.y;
addDebugTriangle(a, b, c, color);
b.x = min.x;
b.y = max.y;
addDebugTriangle(a, c, b, color);
a = min;
b = min;
c = max;
b.x = max.x;
c.y = min.y;
addDebugTriangle(a, b, c, color);
b.x = min.x;
b.z = max.z;
addDebugTriangle(a, c, b, color);
b = max;
c = max;
a.y = max.y;
b.z = min.z;
addDebugTriangle(a, c, b, color);
b.x = min.x;
b.z = max.z;
addDebugTriangle(a, b, c, color);
a = min;
b = min;
c = max;
b.y = max.y;
c.x = min.x;
addDebugTriangle(a, c, b, color);
b.y = min.y;
b.z = max.z;
addDebugTriangle(a, b, c, color);
b = max;
c = max;
a.x = max.x;
b.z = min.z;
addDebugTriangle(a, b, c, color);
b.y = min.y;
b.z = max.z;
addDebugTriangle(a, c, b, color);
}
void addDebugCube(const DVec3& min,
const DVec3& max,
u32 color) override
{
DVec3 a = min;
DVec3 b = min;
b.x = max.x;
addDebugLine(a, b, color);
a = DVec3(b.x, b.y, max.z);
addDebugLine(a, b, color);
b = DVec3(min.x, a.y, a.z);
addDebugLine(a, b, color);
a = DVec3(b.x, b.y, min.z);
addDebugLine(a, b, color);
a = min;
a.y = max.y;
b = a;
b.x = max.x;
addDebugLine(a, b, color);
a = DVec3(b.x, b.y, max.z);
addDebugLine(a, b, color);
b = DVec3(min.x, a.y, a.z);
addDebugLine(a, b, color);
a = DVec3(b.x, b.y, min.z);
addDebugLine(a, b, color);
a = min;
b = a;
b.y = max.y;
addDebugLine(a, b, color);
a.x = max.x;
b.x = max.x;
addDebugLine(a, b, color);
a.z = max.z;
b.z = max.z;
addDebugLine(a, b, color);
a.x = min.x;
b.x = min.x;
addDebugLine(a, b, color);
}
void addDebugCross(const DVec3& center, float size, u32 color) override
{
addDebugLine(center, DVec3(center.x - size, center.y, center.z), color);
addDebugLine(center, DVec3(center.x + size, center.y, center.z), color);
addDebugLine(center, DVec3(center.x, center.y - size, center.z), color);
addDebugLine(center, DVec3(center.x, center.y + size, center.z), color);
addDebugLine(center, DVec3(center.x, center.y, center.z - size), color);
addDebugLine(center, DVec3(center.x, center.y, center.z + size), color);
}
static u32 ARGBToABGR(u32 color)
{
return ((color & 0xff) << 16) | (color & 0xff00) | ((color & 0xff0000) >> 16) | (color & 0xff000000);
}
void addDebugLine(const DVec3& from, const DVec3& to, u32 color) override
{
DebugLine& line = m_debug_lines.emplace();
line.from = from;
line.to = to;
line.color = ARGBToABGR(color);
}
Span<LightProbeGrid> getLightProbeGrids() override {
return Span(m_light_probe_grids.begin(), m_light_probe_grids.size());
}
LightProbeGrid& getLightProbeGrid(EntityRef entity) override {
return m_light_probe_grids[entity];
}
DebugTriangle* addDebugTriangles(int count) override
{
const u32 new_size = m_debug_triangles.size() + count;
if (new_size < m_debug_triangles.capacity()) {
m_debug_triangles.reserve(maximum(new_size, m_debug_triangles.capacity() * 3 / 2));
}
m_debug_triangles.resize(new_size);
return &m_debug_triangles[new_size - count];
}
DebugLine* addDebugLines(int count) override
{
const u32 new_size = m_debug_lines.size() + count;
if (new_size < m_debug_lines.capacity()) {
m_debug_lines.reserve(maximum(new_size, m_debug_lines.capacity() * 3 / 2));
}
m_debug_lines.resize(new_size);
return &m_debug_lines[new_size - count];
}
RayCastModelHit castRayTerrain(EntityRef entity, const DVec3& origin, const Vec3& dir) override
{
RayCastModelHit hit;
hit.is_hit = false;
auto iter = m_terrains.find(entity);
if (!iter.isValid()) return hit;
Terrain* terrain = iter.value();
hit = terrain->castRay(origin, dir);
hit.component_type = TERRAIN_TYPE;
hit.entity = terrain->getEntity();
return hit;
}
RayCastModelHit castRay(const DVec3& origin, const Vec3& dir, EntityPtr ignored_model_instance) override
{
PROFILE_FUNCTION();
RayCastModelHit hit;
hit.is_hit = false;
double cur_dist = DBL_MAX;
const Universe& universe = getUniverse();
for (int i = 0; i < m_model_instances.size(); ++i) {
auto& r = m_model_instances[i];
if (ignored_model_instance.index == i || !r.model) continue;
if (!r.flags.isSet(ModelInstance::ENABLED)) continue;
const EntityRef entity{i};
const DVec3& pos = universe.getPosition(entity);
float scale = universe.getScale(entity);
float radius = r.model->getBoundingRadius() * scale;
const double dist = (pos - origin).length();
if (dist - radius > cur_dist) continue;
Vec3 intersection;
const Vec3 rel_pos = (origin - pos).toFloat();
if (getRaySphereIntersection(rel_pos, dir, Vec3::ZERO, radius, intersection)) {
RayCastModelHit new_hit = r.model->castRay(rel_pos / scale, dir, r.pose);
if (new_hit.is_hit && (!hit.is_hit || new_hit.t * scale < hit.t)) {
new_hit.entity = entity;
new_hit.component_type = MODEL_INSTANCE_TYPE;
hit = new_hit;
hit.t *= scale;
hit.is_hit = true;
cur_dist = dir.length() * hit.t;
}
}
}
for (auto* terrain : m_terrains) {
RayCastModelHit terrain_hit = terrain->castRay(origin, dir);
if (terrain_hit.is_hit && (!hit.is_hit || terrain_hit.t < hit.t)) {
terrain_hit.component_type = TERRAIN_TYPE;
terrain_hit.entity = terrain->getEntity();
terrain_hit.mesh = nullptr;
hit = terrain_hit;
}
}
hit.origin = origin;
hit.dir = dir;
return hit;
}
Vec4 getShadowmapCascades(EntityRef entity) override
{
return m_environments[entity].cascades;
}
void setShadowmapCascades(EntityRef entity, const Vec4& value) override
{
Vec4 valid_value = value;
valid_value.x = maximum(valid_value.x, 0.02f);
valid_value.y = maximum(valid_value.x + 0.01f, valid_value.y);
valid_value.z = maximum(valid_value.y + 0.01f, valid_value.z);
valid_value.w = maximum(valid_value.z + 0.01f, valid_value.w);
m_environments[entity].cascades = valid_value;
}
float getLightRange(EntityRef entity) override
{
return m_point_lights[entity].range;
}
void setLightRange(EntityRef entity, float value) override
{
m_point_lights[entity].range = value;
m_culling_system->setRadius(entity, value);
}
void setActiveEnvironment(EntityRef entity) override
{
m_active_global_light_entity = entity;
}
EntityPtr getActiveEnvironment() override
{
return m_active_global_light_entity;
}
void getEnvironmentProbes(Array<EnvProbeInfo>& probes) override
{
// TODO probes in culling system
PROFILE_FUNCTION();
probes.reserve(m_environment_probes.size());
for (int i = 0; i < m_environment_probes.size(); ++i) {
const EnvironmentProbe& probe = m_environment_probes.at(i);
const EntityRef entity = m_environment_probes.getKey(i);
if(!probe.flags.isSet(EnvironmentProbe::ENABLED)) continue;
EnvProbeInfo& out = probes.emplace();
out.half_extents = probe.half_extents;
out.position = m_universe.getPosition(entity);
out.radiance = probe.flags.isSet(EnvironmentProbe::SPECULAR) && probe.radiance && probe.radiance->isReady() ? probe.radiance->handle : gpu::INVALID_TEXTURE;
out.reflection = probe.flags.isSet(EnvironmentProbe::REFLECTION) && probe.reflection && probe.reflection->isReady() ? probe.reflection->handle : gpu::INVALID_TEXTURE;
out.use_irradiance = probe.flags.isSet(EnvironmentProbe::DIFFUSE);
memcpy(out.sh_coefs, probe.sh_coefs, sizeof(out.sh_coefs));
}
}
Span<EntityRef> getAllEnvironmentProbes() override {
return m_environment_probes.keys();
}
EnvironmentProbe& getEnvironmentProbe(EntityRef entity) override
{
return m_environment_probes[entity];
}
void enableEnvironmentProbe(EntityRef entity, bool enable) override
{
return m_environment_probes[entity].flags.set(EnvironmentProbe::ENABLED, enable);
}
bool isEnvironmentProbeEnabled(EntityRef entity) override
{
return m_environment_probes[entity].flags.isSet(EnvironmentProbe::ENABLED);
}
bool isEnvironmentProbeCustomSize(EntityRef entity) override
{
return m_environment_probes[entity].flags.isSet(EnvironmentProbe::OVERRIDE_GLOBAL_SIZE);
}
void enableEnvironmentProbeCustomSize(EntityRef entity, bool enable) override
{
m_environment_probes[entity].flags.set(EnvironmentProbe::OVERRIDE_GLOBAL_SIZE, enable);
}
bool isEnvironmentProbeReflectionEnabled(EntityRef entity) override
{
return m_environment_probes[entity].flags.isSet(EnvironmentProbe::REFLECTION);
}
void enableEnvironmentProbeReflection(EntityRef entity, bool enable) override
{
EnvironmentProbe& p = m_environment_probes[entity];
p.flags.set(EnvironmentProbe::REFLECTION, enable);
if (enable) {
ResourceManagerHub& rm = m_engine.getResourceManager();
StaticString<MAX_PATH_LENGTH> path("universes/", m_universe.getName(), "/probes/", p.guid, ".dds");
p.reflection = rm.load<Texture>(Path(path));
}
else {
if (p.reflection) {
p.reflection->getResourceManager().unload(*p.reflection);
p.reflection = nullptr;
}
}
}
bool isEnvironmentProbeSpecular(EntityRef entity) override
{
return m_environment_probes[entity].flags.isSet(EnvironmentProbe::SPECULAR);
}
void enableEnvironmentProbeSpecular(EntityRef entity, bool enable) override
{
EnvironmentProbe& p = m_environment_probes[entity];
p.flags.set(EnvironmentProbe::SPECULAR, enable);
if (enable) {
ResourceManagerHub& rm = m_engine.getResourceManager();
StaticString<MAX_PATH_LENGTH> path("universes/", m_universe.getName(), "/probes/", p.guid, "_radiance.dds");
p.radiance = rm.load<Texture>(Path(path));
}
else {
if (p.radiance) {
p.radiance->getResourceManager().unload(*p.radiance);
p.radiance = nullptr;
}
}
}
bool isEnvironmentProbeDiffuse(EntityRef entity) override
{
return m_environment_probes[entity].flags.isSet(EnvironmentProbe::DIFFUSE);
}
void enableEnvironmentProbeDiffuse(EntityRef entity, bool enable) override
{
m_environment_probes[entity].flags.set(EnvironmentProbe::DIFFUSE, enable);
}
float getTime() const override { return m_time; }
void modelUnloaded(Model*, EntityRef entity)
{
auto& r = m_model_instances[entity.index];
r.meshes = nullptr;
r.mesh_count = 0;
LUMIX_DELETE(m_allocator, r.pose);
r.pose = nullptr;
m_culling_system->remove(entity);
}
void modelLoaded(Model* model, EntityRef entity)
{
auto& r = m_model_instances[entity.index];
float bounding_radius = r.model->getBoundingRadius();
float scale = m_universe.getScale(entity);
const DVec3 pos = m_universe.getPosition(entity);
const float radius = bounding_radius * scale;
if(r.flags.isSet(ModelInstance::ENABLED)) {
const RenderableTypes type = getRenderableType(*model);
m_culling_system->add(entity, (u8)type, pos, radius);
}
ASSERT(!r.pose);
if (model->getBoneCount() > 0)
{
r.pose = LUMIX_NEW(m_allocator, Pose)(m_allocator);
r.pose->resize(model->getBoneCount());
model->getPose(*r.pose);
}
r.meshes = &r.model->getMesh(0);
r.mesh_count = r.model->getMeshCount();
if (r.flags.isSet(ModelInstance::IS_BONE_ATTACHMENT_PARENT))
{
updateBoneAttachment(m_bone_attachments[entity]);
}
while (m_mesh_sort_data.size() < m_model_instances.size()) {
m_mesh_sort_data.emplace();
}
m_mesh_sort_data[entity.index].layer = r.meshes[0].layer;
m_mesh_sort_data[entity.index].sort_key = r.meshes[0].sort_key;
}
void modelUnloaded(Model* model)
{
for (int i = 0, c = m_model_instances.size(); i < c; ++i)
{
if (m_model_instances[i].flags.isSet(ModelInstance::VALID) && m_model_instances[i].model == model)
{
modelUnloaded(model, {i});
}
}
}
void modelLoaded(Model* model)
{
auto map_iter = m_model_entity_map.find(model);
EntityPtr e = map_iter.value();
while(e.isValid()) {
modelLoaded(model, (EntityRef)e);
e = m_model_instances[e.index].next_model;
}
}
void addToMaterialDecalMap(Material* material, EntityRef entity)
{
Decal& d = m_decals[entity];
d.prev_decal = INVALID_ENTITY;
auto map_iter = m_material_decal_map.find(material);
if(map_iter.isValid()) {
d.next_decal = map_iter.value();
m_material_decal_map[material] = entity;
}
else {
d.next_decal = INVALID_ENTITY;
m_material_decal_map.insert(material, entity);
material->getObserverCb().bind<&RenderSceneImpl::decalMaterialStateChanged>(this);
}
}
void addToModelEntityMap(Model* model, EntityRef entity)
{
ModelInstance& r = m_model_instances[entity.index];
r.prev_model = INVALID_ENTITY;
auto map_iter = m_model_entity_map.find(model);
if(map_iter.isValid()) {
r.next_model = map_iter.value();
m_model_entity_map[model] = entity;
}
else {
r.next_model = INVALID_ENTITY;
m_model_entity_map.insert(model, entity);
model->getObserverCb().bind<&RenderSceneImpl::modelStateChanged>(this);
}
}
void removeFromModelEntityMap(Model* model, EntityRef entity)
{
ModelInstance& r = m_model_instances[entity.index];
if(r.prev_model.isValid()) {
m_model_instances[r.prev_model.index].next_model = r.next_model;
}
if(r.next_model.isValid()) {
m_model_instances[r.next_model.index].prev_model = r.prev_model;
}
auto map_iter = m_model_entity_map.find(model);
if(map_iter.value() == entity) {
if(r.next_model.isValid()) {
m_model_entity_map[model] = (EntityRef)r.next_model;
}
else {
m_model_entity_map.erase(model);
model->getObserverCb().unbind<&RenderSceneImpl::modelStateChanged>(this);
}
}
}
void removeFromMaterialDecalMap(Material* material, EntityRef entity)
{
Decal& d = m_decals[entity];
if(d.prev_decal.isValid()) {
m_decals[(EntityRef)d.prev_decal].next_decal = d.next_decal;
}
if(d.next_decal.isValid()) {
m_decals[(EntityRef)d.next_decal].prev_decal = d.prev_decal;
}
auto map_iter = m_material_decal_map.find(material);
if(map_iter.value() == entity) {
if(d.next_decal.isValid()) {
m_material_decal_map[material] = (EntityRef)d.next_decal;
}
else {
m_material_decal_map.erase(material);
material->getObserverCb().unbind<&RenderSceneImpl::decalMaterialStateChanged>(this);
}
}
}
void setModel(EntityRef entity, Model* model)
{
auto& model_instance = m_model_instances[entity.index];
ASSERT(model_instance.flags.isSet(ModelInstance::VALID));
Model* old_model = model_instance.model;
bool no_change = model == old_model && old_model;
if (no_change)
{
old_model->getResourceManager().unload(*old_model);
return;
}
if (old_model)
{
removeFromModelEntityMap(old_model, entity);
if (old_model->isReady())
{
m_culling_system->remove(entity);
}
old_model->getResourceManager().unload(*old_model);
}
model_instance.model = model;
model_instance.meshes = nullptr;
model_instance.mesh_count = 0;
LUMIX_DELETE(m_allocator, model_instance.pose);
model_instance.pose = nullptr;
if (model)
{
addToModelEntityMap(model, entity);
if (model->isReady())
{
modelLoaded(model, entity);
}
}
}
IAllocator& getAllocator() override { return m_allocator; }
void createLightProbeGrid(EntityRef entity) {
LightProbeGrid lp = {};
lp.entity = entity;
lp.guid = randGUID();
lp.resolution = IVec3(32, 8, 32);
lp.half_extents = Vec3(16.f, 4.f, 16.f);
loadLightProbeGridData(lp);
m_light_probe_grids.insert(entity, lp);
m_universe.onComponentCreated(entity, LIGHT_PROBE_GRID_TYPE, this);
}
void createEnvironment(EntityRef entity)
{
Environment light;
light.flags.set(Environment::CAST_SHADOWS);
light.entity = entity;
light.diffuse_color.set(1, 1, 1);
light.diffuse_intensity = 0;
light.indirect_intensity = 1;
light.fog_color.set(1, 1, 1);
light.fog_density = 0;
light.cascades.set(3, 8, 100, 300);
light.fog_bottom = 0.0f;
light.fog_height = 10.0f;
if (m_environments.empty()) m_active_global_light_entity = entity;
m_environments.insert(entity, light);
m_universe.onComponentCreated(entity, ENVIRONMENT_TYPE, this);
}
void createPointLight(EntityRef entity)
{
PointLight light;
light.entity = entity;
light.color.set(1, 1, 1);
light.intensity = 1;
light.fov = degreesToRadians(360);
light.cast_shadows = false;
light.attenuation_param = 2;
light.range = 10;
const DVec3 pos = m_universe.getPosition(entity);
m_point_lights.insert(entity, light);
m_culling_system->add(entity, (u8)RenderableTypes::LOCAL_LIGHT, pos, light.range);
m_universe.onComponentCreated(entity, POINT_LIGHT_TYPE, this);
}
void updateDecalInfo(Decal& decal) const
{
decal.radius = decal.half_extents.length();
decal.transform = m_universe.getTransform(decal.entity);
}
void createDecal(EntityRef entity)
{
m_decals.insert(entity, Decal());
Decal& decal = m_decals[entity];
decal.material = nullptr;
decal.entity = entity;
decal.half_extents.set(1, 1, 1);
updateDecalInfo(decal);
m_universe.onComponentCreated(entity, DECAL_TYPE, this);
}
void createEnvironmentProbe(EntityRef entity)
{
EnvironmentProbe& probe = m_environment_probes.insert(entity);
probe.guid = randGUID();
StaticString<MAX_PATH_LENGTH> path;
probe.reflection = nullptr;
probe.radiance = nullptr;
probe.half_extents = Vec3(9001.f);
probe.flags.set(EnvironmentProbe::ENABLED);
probe.flags.set(EnvironmentProbe::DIFFUSE);
memset(probe.sh_coefs, 0, sizeof(probe.sh_coefs));
m_universe.onComponentCreated(entity, ENVIRONMENT_PROBE_TYPE, this);
}
void createBoneAttachment(EntityRef entity)
{
BoneAttachment& attachment = m_bone_attachments.emplace(entity);
attachment.entity = entity;
attachment.parent_entity = INVALID_ENTITY;
attachment.bone_index = -1;
m_universe.onComponentCreated(entity, BONE_ATTACHMENT_TYPE, this);
}
void createModelInstance(EntityRef entity)
{
while(entity.index >= m_model_instances.size())
{
auto& r = m_model_instances.emplace();
r.flags.clear();
r.flags.set(ModelInstance::VALID, false);
r.model = nullptr;
r.pose = nullptr;
}
auto& r = m_model_instances[entity.index];
r.model = nullptr;
r.meshes = nullptr;
r.pose = nullptr;
r.flags.clear();
r.flags.set(ModelInstance::VALID);
r.flags.set(ModelInstance::ENABLED);
r.mesh_count = 0;
m_universe.onComponentCreated(entity, MODEL_INSTANCE_TYPE, this);
}
void setParticleEmitterPath(EntityRef entity, const Path& path) override
{
if (!m_particle_emitters[entity]) return;
ParticleEmitterResource* res = m_engine.getResourceManager().load<ParticleEmitterResource>(path);
m_particle_emitters[entity]->setResource(res);
}
Path getParticleEmitterPath(EntityRef entity) override
{
ParticleEmitter* emitter = m_particle_emitters[entity];
if (!emitter) return Path("");
if (!emitter->getResource()) return Path("");
return emitter->getResource()->getPath();
}
const AssociativeArray<EntityRef, ParticleEmitter*>& getParticleEmitters() const override
{
return m_particle_emitters;
}
private:
IAllocator& m_allocator;
Universe& m_universe;
Renderer& m_renderer;
Engine& m_engine;
CullingSystem* m_culling_system;
u64 m_render_cmps_mask;
EntityPtr m_active_global_light_entity;
HashMap<EntityRef, PointLight> m_point_lights;
HashMap<EntityRef, Decal> m_decals;
Array<ModelInstance> m_model_instances;
Array<MeshSortData> m_mesh_sort_data;
HashMap<EntityRef, Environment> m_environments;
AssociativeArray<EntityRef, LightProbeGrid> m_light_probe_grids;
HashMap<EntityRef, Camera> m_cameras;
EntityPtr m_active_camera = INVALID_ENTITY;
AssociativeArray<EntityRef, TextMesh*> m_text_meshes;
AssociativeArray<EntityRef, BoneAttachment> m_bone_attachments;
AssociativeArray<EntityRef, EnvironmentProbe> m_environment_probes;
HashMap<EntityRef, Terrain*> m_terrains;
AssociativeArray<EntityRef, ParticleEmitter*> m_particle_emitters;
Array<DebugTriangle> m_debug_triangles;
Array<DebugLine> m_debug_lines;
float m_time;
float m_lod_multiplier;
bool m_is_updating_attachments;
bool m_is_grass_enabled;
bool m_is_game_running;
HashMap<Model*, EntityRef> m_model_entity_map;
HashMap<Material*, EntityRef> m_material_decal_map;
};
#define COMPONENT_TYPE(type, name) \
{ \
type \
, &RenderSceneImpl::create##name \
, &RenderSceneImpl::destroy##name \
}
static struct
{
ComponentType type;
void (RenderSceneImpl::*creator)(EntityRef);
void (RenderSceneImpl::*destroyer)(EntityRef);
} COMPONENT_INFOS[] = {
COMPONENT_TYPE(MODEL_INSTANCE_TYPE, ModelInstance),
COMPONENT_TYPE(ENVIRONMENT_TYPE, Environment),
COMPONENT_TYPE(LIGHT_PROBE_GRID_TYPE, LightProbeGrid),
COMPONENT_TYPE(POINT_LIGHT_TYPE, PointLight),
COMPONENT_TYPE(DECAL_TYPE, Decal),
COMPONENT_TYPE(CAMERA_TYPE, Camera),
COMPONENT_TYPE(TERRAIN_TYPE, Terrain),
COMPONENT_TYPE(BONE_ATTACHMENT_TYPE, BoneAttachment),
COMPONENT_TYPE(ENVIRONMENT_PROBE_TYPE, EnvironmentProbe),
COMPONENT_TYPE(PARTICLE_EMITTER_TYPE, ParticleEmitter),
COMPONENT_TYPE(TEXT_MESH_TYPE, TextMesh)
};
#undef COMPONENT_TYPE
RenderSceneImpl::RenderSceneImpl(Renderer& renderer,
Engine& engine,
Universe& universe,
IAllocator& allocator)
: m_engine(engine)
, m_universe(universe)
, m_renderer(renderer)
, m_allocator(allocator)
, m_model_entity_map(m_allocator)
, m_model_instances(m_allocator)
, m_cameras(m_allocator)
, m_text_meshes(m_allocator)
, m_terrains(m_allocator)
, m_point_lights(m_allocator)
, m_environments(m_allocator)
, m_decals(m_allocator)
, m_debug_triangles(m_allocator)
, m_debug_lines(m_allocator)
, m_active_global_light_entity(INVALID_ENTITY)
, m_active_camera(INVALID_ENTITY)
, m_is_grass_enabled(true)
, m_is_game_running(false)
, m_particle_emitters(m_allocator)
, m_bone_attachments(m_allocator)
, m_environment_probes(m_allocator)
, m_lod_multiplier(1.0f)
, m_time(0)
, m_is_updating_attachments(false)
, m_material_decal_map(m_allocator)
, m_mesh_sort_data(m_allocator)
, m_light_probe_grids(m_allocator)
{
m_universe.entityTransformed().bind<&RenderSceneImpl::onEntityMoved>(this);
m_universe.entityDestroyed().bind<&RenderSceneImpl::onEntityDestroyed>(this);
m_culling_system = CullingSystem::create(m_allocator, engine.getPageAllocator());
m_model_instances.reserve(5000);
m_mesh_sort_data.reserve(5000);
m_render_cmps_mask = 0;
for (auto& i : COMPONENT_INFOS)
{
m_render_cmps_mask |= (u64)1 << i.type.index;
universe.registerComponentType(i.type, this, i.creator, i.destroyer);
}
}
RenderScene* RenderScene::createInstance(Renderer& renderer,
Engine& engine,
Universe& universe,
IAllocator& allocator)
{
return LUMIX_NEW(allocator, RenderSceneImpl)(renderer, engine, universe, allocator);
}
void RenderScene::destroyInstance(RenderScene* scene)
{
LUMIX_DELETE(scene->getAllocator(), static_cast<RenderSceneImpl*>(scene));
}
void RenderScene::registerLuaAPI(lua_State* L)
{
Model::registerLuaAPI(L);
#define REGISTER_FUNCTION(F)\
do { \
auto f = &LuaWrapper::wrapMethod<&RenderSceneImpl::F>; \
LuaWrapper::createSystemFunction(L, "Renderer", #F, f); \
} while(false) \
REGISTER_FUNCTION(setGlobalLODMultiplier);
REGISTER_FUNCTION(getGlobalLODMultiplier);
REGISTER_FUNCTION(getActiveEnvironment);
REGISTER_FUNCTION(getModelInstanceModel);
REGISTER_FUNCTION(addDebugCross);
REGISTER_FUNCTION(addDebugLine);
REGISTER_FUNCTION(getTerrainMaterial);
REGISTER_FUNCTION(getTerrainNormalAt);
REGISTER_FUNCTION(setTerrainHeightAt);
//REGISTER_FUNCTION(enableModelInstance);
REGISTER_FUNCTION(getPoseBonePosition);
#undef REGISTER_FUNCTION
#define REGISTER_FUNCTION(F)\
do { \
auto f = &LuaWrapper::wrap<&RenderSceneImpl::LUA_##F>; \
LuaWrapper::createSystemFunction(L, "Renderer", #F, f); \
} while(false) \
REGISTER_FUNCTION(createPipeline);
REGISTER_FUNCTION(destroyPipeline);
REGISTER_FUNCTION(setPipelineScene);
REGISTER_FUNCTION(getPipelineScene);
//REGISTER_FUNCTION(setModelInstancePath);
REGISTER_FUNCTION(getModelBoneIndex);
REGISTER_FUNCTION(makeScreenshot);
REGISTER_FUNCTION(compareTGA);
REGISTER_FUNCTION(getTerrainHeightAt);
LuaWrapper::createSystemFunction(L, "Renderer", "castCameraRay", &RenderSceneImpl::LUA_castCameraRay);
#undef REGISTER_FUNCTION
}
} // namespace Lumix