Archived
1
Fork 0
This repository has been archived on 2025-04-12. You can view files and clone it, but cannot push or open issues or pull requests.
prism/tools/common/src/debugpass.cpp

582 lines
20 KiB
C++
Raw Normal View History

2020-08-11 12:07:21 -04:00
#include "debugpass.hpp"
#include "gfx_commandbuffer.hpp"
#include "engine.hpp"
#include "scene.hpp"
#include "transform.hpp"
#include "file.hpp"
#include "gfx.hpp"
#include "asset.hpp"
2020-09-21 09:37:52 -04:00
#include "renderer.hpp"
2020-08-11 12:07:21 -04:00
struct BillPushConstant {
2021-02-03 06:55:46 -05:00
Matrix4x4 mvp;
2021-05-12 09:56:44 -04:00
prism::float4 color;
2020-08-11 12:07:21 -04:00
};
void DebugPass::initialize() {
2021-02-03 06:55:46 -05:00
scene_info_buffer = engine->get_gfx()->create_buffer(nullptr, sizeof(Matrix4x4), true, GFXBufferUsage::Storage);
2020-08-11 12:07:21 -04:00
{
GFXGraphicsPipelineCreateInfo createInfo;
2021-05-12 09:05:56 -04:00
createInfo.shaders.vertex_src = ShaderSource(prism::path("debug.vert"));
createInfo.shaders.fragment_src = ShaderSource(prism::path("debug.frag"));
2020-08-11 12:07:21 -04:00
GFXVertexInput vertexInput = {};
2021-05-12 09:56:44 -04:00
vertexInput.stride = sizeof(prism::float3);
2020-08-11 12:07:21 -04:00
createInfo.vertex_input.inputs.push_back(vertexInput);
GFXVertexAttribute positionAttribute = {};
positionAttribute.format = GFXVertexFormat::FLOAT3;
createInfo.vertex_input.attributes.push_back(positionAttribute);
createInfo.shader_input.push_constants = {
2021-05-12 09:56:44 -04:00
{sizeof(Matrix4x4) + sizeof(prism::float4), 0}
2020-08-11 12:07:21 -04:00
};
createInfo.shader_input.bindings = {
{1, GFXBindingType::PushConstant}
};
createInfo.render_pass = engine->get_renderer()->offscreen_render_pass;
2020-08-11 12:07:21 -04:00
createInfo.rasterization.polygon_type = GFXPolygonType::Line;
primitive_pipeline = engine->get_gfx()->create_graphics_pipeline(createInfo);
cubeMesh = assetm->get<Mesh>(prism::app_domain / "models/cube.model");
sphereMesh = assetm->get<Mesh>(prism::app_domain / "models/sphere.model");
2020-08-11 12:07:21 -04:00
createInfo.rasterization.polygon_type = GFXPolygonType::Fill;
arrow_pipeline = engine->get_gfx()->create_graphics_pipeline(createInfo);
arrowMesh = assetm->get<Mesh>(prism::app_domain / "models/arrow.model");
2020-08-11 12:07:21 -04:00
}
{
// render pass
GFXRenderPassCreateInfo renderPassInfo = {};
2021-02-05 19:17:13 -05:00
renderPassInfo.label = "Select";
2020-08-11 12:07:21 -04:00
renderPassInfo.attachments.push_back(GFXPixelFormat::R8G8B8A8_UNORM);
renderPassInfo.attachments.push_back(GFXPixelFormat::DEPTH_32F);
selectRenderPass = engine->get_gfx()->create_render_pass(renderPassInfo);
// pipeline
GFXGraphicsPipelineCreateInfo pipelineInfo = {};
2021-05-12 09:05:56 -04:00
pipelineInfo.shaders.vertex_src = ShaderSource(prism::path("color.vert"));
pipelineInfo.shaders.fragment_src = ShaderSource(prism::path("color.frag"));
2020-08-11 12:07:21 -04:00
GFXVertexInput input;
2021-05-12 09:56:44 -04:00
input.stride = sizeof(prism::float3);
2020-08-11 12:07:21 -04:00
pipelineInfo.vertex_input.inputs.push_back(input);
GFXVertexAttribute attribute;
attribute.format = GFXVertexFormat::FLOAT3;
pipelineInfo.vertex_input.attributes.push_back(attribute);
pipelineInfo.shader_input.bindings = {
{1, GFXBindingType::PushConstant}
};
pipelineInfo.shader_input.push_constants = {
2021-05-12 09:56:44 -04:00
{sizeof(Matrix4x4) + sizeof(prism::float4), 0}
2020-08-11 12:07:21 -04:00
};
pipelineInfo.render_pass = selectRenderPass;
pipelineInfo.depth.depth_mode = GFXDepthMode::Less;
selectPipeline = engine->get_gfx()->create_graphics_pipeline(pipelineInfo);
}
// sobel
{
// render pass
GFXRenderPassCreateInfo renderPassInfo = {};
2021-02-05 19:17:13 -05:00
renderPassInfo.label = "Sobel";
2020-08-11 12:07:21 -04:00
renderPassInfo.attachments.push_back(GFXPixelFormat::R8_UNORM);
2021-02-15 19:01:17 -05:00
renderPassInfo.will_use_in_shader = true;
2020-08-11 12:07:21 -04:00
sobelRenderPass = engine->get_gfx()->create_render_pass(renderPassInfo);
// pipeline
GFXGraphicsPipelineCreateInfo pipelineInfo = {};
pipelineInfo.label = "Sobel";
2021-05-12 09:05:56 -04:00
pipelineInfo.shaders.vertex_src = ShaderSource(prism::path("color.vert"));
pipelineInfo.shaders.fragment_src = ShaderSource(prism::path("color.frag"));
2020-08-11 12:07:21 -04:00
GFXVertexInput input;
2021-05-12 09:56:44 -04:00
input.stride = sizeof(prism::float3);
2020-08-11 12:07:21 -04:00
pipelineInfo.vertex_input.inputs.push_back(input);
GFXVertexAttribute attribute;
attribute.format = GFXVertexFormat::FLOAT3;
pipelineInfo.vertex_input.attributes.push_back(attribute);
pipelineInfo.shader_input.bindings = {
{1, GFXBindingType::PushConstant}
};
pipelineInfo.shader_input.push_constants = {
2021-05-12 09:56:44 -04:00
{sizeof(Matrix4x4) + sizeof(prism::float4), 0}
2020-08-11 12:07:21 -04:00
};
pipelineInfo.render_pass = sobelRenderPass;
sobelPipeline = engine->get_gfx()->create_graphics_pipeline(pipelineInfo);
}
// billboard
{
// pipeline
GFXGraphicsPipelineCreateInfo pipelineInfo = {};
pipelineInfo.label = "Billboard";
2021-05-12 09:05:56 -04:00
pipelineInfo.shaders.vertex_src = ShaderSource(prism::path("billboard.vert"));
pipelineInfo.shaders.fragment_src = ShaderSource(prism::path("billboard.frag"));
2020-08-11 12:07:21 -04:00
pipelineInfo.shader_input.bindings = {
2021-02-03 06:55:46 -05:00
{1, GFXBindingType::PushConstant},
{2, GFXBindingType::Texture},
{3, GFXBindingType::StorageBuffer}
2020-08-11 12:07:21 -04:00
};
pipelineInfo.shader_input.push_constants = {
{sizeof(BillPushConstant), 0}
};
pipelineInfo.depth.depth_mode = GFXDepthMode::Less;
pipelineInfo.blending.enable_blending = true;
pipelineInfo.render_pass = engine->get_renderer()->offscreen_render_pass;
2020-08-11 12:07:21 -04:00
billboard_pipeline = engine->get_gfx()->create_graphics_pipeline(pipelineInfo);
pointTexture = assetm->get<Texture>(prism::app_domain / "textures/point.png");
spotTexture = assetm->get<Texture>(prism::app_domain / "textures/spot.png");
sunTexture = assetm->get<Texture>(prism::app_domain / "textures/sun.png");
probeTexture = assetm->get<Texture>(prism::app_domain / "textures/probe.png");
2020-08-11 12:07:21 -04:00
}
}
void DebugPass::create_render_target_resources(RenderTarget& target) {
this->extent = target.extent;
2020-08-11 12:07:21 -04:00
createOffscreenResources();
}
void DebugPass::createOffscreenResources() {
// selection resources
{
GFXTextureCreateInfo textureInfo = {};
textureInfo.label = "Select Color";
2020-08-11 12:07:21 -04:00
textureInfo.width = extent.width;
textureInfo.height = extent.height;
textureInfo.format = GFXPixelFormat::R8G8B8A8_UNORM;
textureInfo.usage = GFXTextureUsage::Attachment;
textureInfo.samplingMode = SamplingMode::ClampToEdge;
selectTexture = engine->get_gfx()->create_texture(textureInfo);
textureInfo.label = "Select Depth";
2020-08-11 12:07:21 -04:00
textureInfo.format = GFXPixelFormat::DEPTH_32F;
selectDepthTexture = engine->get_gfx()->create_texture(textureInfo);
GFXFramebufferCreateInfo info;
info.attachments = {selectTexture, selectDepthTexture};
info.render_pass = selectRenderPass;
selectFramebuffer = engine->get_gfx()->create_framebuffer(info);
selectBuffer = engine->get_gfx()->create_buffer(nullptr, extent.width * extent.height * 4 * sizeof(uint8_t), false, GFXBufferUsage::Storage);
}
// sobel
{
GFXTextureCreateInfo textureInfo = {};
textureInfo.label = "Sobel";
2020-08-11 12:07:21 -04:00
textureInfo.width = extent.width;
textureInfo.height = extent.height;
textureInfo.format = GFXPixelFormat::R8_UNORM;
textureInfo.usage = GFXTextureUsage::Attachment;
textureInfo.samplingMode = SamplingMode::ClampToEdge;
sobelTexture = engine->get_gfx()->create_texture(textureInfo);
GFXFramebufferCreateInfo info;
info.attachments = {sobelTexture};
info.render_pass = sobelRenderPass;
sobelFramebuffer = engine->get_gfx()->create_framebuffer(info);
}
}
2021-05-12 09:56:44 -04:00
void DebugPass::draw_arrow(GFXCommandBuffer* commandBuffer, prism::float3 color, Matrix4x4 model) {
2020-08-11 12:07:21 -04:00
struct PushConstant {
Matrix4x4 mvp;
2021-05-12 09:56:44 -04:00
prism::float4 color;
2020-08-11 12:07:21 -04:00
} pc;
pc.mvp = model;
pc.color = color;
commandBuffer->set_push_constant(&pc, sizeof(PushConstant));
commandBuffer->set_vertex_buffer(arrowMesh->position_buffer, 0, 0);
commandBuffer->set_index_buffer(arrowMesh->index_buffer, IndexType::UINT32);
commandBuffer->draw_indexed(arrowMesh->num_indices, 0, 0, 0);
2020-08-11 12:07:21 -04:00
}
void DebugPass::render_scene(Scene& scene, GFXCommandBuffer* commandBuffer) {
auto [camObj, camera] = scene.get_all<Camera>()[0];
struct PushConstant {
Matrix4x4 mvp;
2021-05-12 09:56:44 -04:00
prism::float4 color;
2020-08-11 12:07:21 -04:00
};
Matrix4x4 vp = camera.perspective * camera.view;
2020-08-11 12:07:21 -04:00
commandBuffer->set_graphics_pipeline(primitive_pipeline);
2020-08-11 12:07:21 -04:00
struct DebugPrimitive {
2021-05-12 09:56:44 -04:00
prism::float3 position, size;
2020-08-11 12:07:21 -04:00
Quaternion rotation;
2021-05-12 09:56:44 -04:00
prism::float4 color;
2020-08-11 12:07:21 -04:00
};
std::vector<DebugPrimitive> primitives;
struct DebugBillboard {
2021-05-12 09:56:44 -04:00
prism::float3 position;
2021-10-14 08:51:58 -04:00
GFXTexture* texture = nullptr;
2021-05-12 09:56:44 -04:00
prism::float4 color;
2020-08-11 12:07:21 -04:00
};
std::vector<DebugBillboard> billboards;
for(auto& obj : scene.get_objects()) {
if(scene.get(obj).editor_object)
continue;
auto& transform = scene.get<Transform>(obj);
if(scene.has<Collision>(obj)) {
auto& collision = scene.get<Collision>(obj);
{
DebugPrimitive prim;
prim.position = transform.get_world_position();
prim.rotation = transform.rotation;
prim.size = collision.size / 2.0f;
2021-05-12 09:56:44 -04:00
prim.color = collision.is_trigger ? prism::float4(0, 0, 1, 1) : prism::float4(0, 1, 0, 1);
2020-08-11 12:07:21 -04:00
primitives.push_back(prim);
}
}
if(scene.has<Light>(obj)) {
DebugBillboard bill;
bill.position = transform.get_world_position();
2021-05-12 09:56:44 -04:00
bill.color = prism::float4(scene.get<Light>(obj).color, 1.0f);
2020-08-11 12:07:21 -04:00
switch(scene.get<Light>(obj).type) {
case Light::Type::Point:
bill.texture = pointTexture->handle;
break;
case Light::Type::Spot:
bill.texture = spotTexture->handle;
break;
case Light::Type::Sun:
bill.texture = sunTexture->handle;
break;
}
billboards.push_back(bill);
}
if(scene.has<EnvironmentProbe>(obj)) {
if(selected_object == obj) {
auto& probe = scene.get<EnvironmentProbe>(obj);
DebugPrimitive prim;
prim.position = transform.get_world_position();
prim.rotation = transform.rotation;
prim.size = probe.size / 2.0f;
2021-05-12 09:56:44 -04:00
prim.color = prism::float4(0, 1, 1, 1);
2020-08-11 12:07:21 -04:00
primitives.push_back(prim);
}
DebugBillboard bill;
bill.position = transform.get_world_position();
2021-05-12 09:56:44 -04:00
bill.color = prism::float4(1.0f);
2020-08-11 12:07:21 -04:00
bill.texture = probeTexture->handle;
billboards.push_back(bill);
}
}
// draw primitives
for(auto& prim : primitives) {
PushConstant pc;
2021-05-12 09:56:44 -04:00
Matrix4x4 m = prism::translate(Matrix4x4(), prim.position);
2020-08-11 12:07:21 -04:00
m *= matrix_from_quat(prim.rotation);
2021-05-12 09:56:44 -04:00
m = prism::scale(m, prim.size);
2020-08-11 12:07:21 -04:00
pc.mvp = vp * m;
pc.color = prim.color;
commandBuffer->set_push_constant(&pc, sizeof(PushConstant));
commandBuffer->set_vertex_buffer(cubeMesh->position_buffer, 0, 0);
commandBuffer->set_index_buffer(cubeMesh->index_buffer, IndexType::UINT32);
commandBuffer->draw_indexed(cubeMesh->num_indices, 0, 0, 0);
2020-08-11 12:07:21 -04:00
}
commandBuffer->set_graphics_pipeline(billboard_pipeline);
2021-02-03 06:55:46 -05:00
engine->get_gfx()->copy_buffer(scene_info_buffer, &camera.view, 0, sizeof(Matrix4x4));
2020-08-11 12:07:21 -04:00
// draw primitives
for(auto& bill : billboards) {
2021-05-12 09:56:44 -04:00
Matrix4x4 m = prism::translate(Matrix4x4(), bill.position);
2020-08-11 12:07:21 -04:00
BillPushConstant pc;
pc.mvp = vp * m;
pc.color = bill.color;
commandBuffer->bind_texture(bill.texture, 2);
2021-02-03 06:55:46 -05:00
commandBuffer->bind_shader_buffer(scene_info_buffer, 0, 3, sizeof(Matrix4x4));
2020-08-11 12:07:21 -04:00
commandBuffer->set_push_constant(&pc, sizeof(BillPushConstant));
commandBuffer->draw_indexed(4, 0, 0, 0);
2020-08-11 12:07:21 -04:00
}
commandBuffer->set_graphics_pipeline(arrow_pipeline);
2020-08-11 12:07:21 -04:00
// draw handles for selected object;
2022-02-18 09:11:18 -05:00
if(selected_object != prism::NullObject && engine->get_scene()->has<Transform>(selected_object)) {
const auto position = engine->get_scene()->get<Transform>(selected_object).get_world_position();
2020-08-11 12:07:21 -04:00
const float base_scale = 0.05f;
2020-08-11 12:07:21 -04:00
const float scale_factor = length(position - scene.get<Transform>(camObj).get_world_position());
2021-05-12 09:56:44 -04:00
Matrix4x4 base_model = prism::translate(Matrix4x4(), position);
base_model = prism::scale(base_model, base_scale * scale_factor);
2020-08-11 12:07:21 -04:00
// draw y axis
2021-05-12 09:56:44 -04:00
draw_arrow(commandBuffer, prism::float3(0, 1, 0), vp * base_model);
2020-08-11 12:07:21 -04:00
// draw x axis
2021-05-12 09:56:44 -04:00
draw_arrow(commandBuffer, prism::float3(1, 0, 0), vp * base_model * matrix_from_quat(angle_axis(radians(-90.0f), prism::float3(0, 0, 1))));
2020-08-11 12:07:21 -04:00
// draw z axis
2021-05-12 09:56:44 -04:00
draw_arrow(commandBuffer, prism::float3(0, 0, 1), vp * base_model * matrix_from_quat(angle_axis(radians(90.0f), prism::float3(1, 0, 0))));
2020-08-11 12:07:21 -04:00
}
// draw sobel
GFXRenderPassBeginInfo info = {};
info.framebuffer = sobelFramebuffer;
info.render_pass = sobelRenderPass;
info.render_area.extent = extent;
commandBuffer->set_render_pass(info);
2022-02-18 09:11:18 -05:00
if(selected_object != prism::NullObject && engine->get_scene()->has<Renderable>(selected_object)) {
commandBuffer->set_graphics_pipeline(sobelPipeline);
2020-08-11 12:07:21 -04:00
auto renderable = engine->get_scene()->get<Renderable>(selected_object);
if(!renderable.mesh)
return;
struct PC {
Matrix4x4 mvp;
2021-05-12 09:56:44 -04:00
prism::float4 color;
2020-08-11 12:07:21 -04:00
} pc;
pc.mvp = vp * engine->get_scene()->get<Transform>(selected_object).model;
2021-05-12 09:56:44 -04:00
pc.color = prism::float4(1);
2020-08-11 12:07:21 -04:00
commandBuffer->set_push_constant(&pc, sizeof(PC));
commandBuffer->set_vertex_buffer(renderable.mesh->position_buffer, 0, 0);
commandBuffer->set_index_buffer(renderable.mesh->index_buffer, IndexType::UINT32);
if(renderable.mesh) {
for (auto& part : renderable.mesh->parts)
commandBuffer->draw_indexed(part.index_count, part.index_offset, part.vertex_offset, 0);
2020-08-11 12:07:21 -04:00
}
}
}
2022-02-21 00:15:24 -05:00
void DebugPass::get_selected_object(int x, int y, const std::function<void(SelectableObject)>& callback) {
2020-08-11 12:07:21 -04:00
if(engine->get_scene() == nullptr)
return;
auto cameras = engine->get_scene()->get_all<Camera>();
auto& [camObj, camera] = cameras[0];
// calculate selectable objects
selectable_objects.clear();
for(auto& [obj, mesh] : engine->get_scene()->get_all<Renderable>()) {
SelectableObject so;
so.type = SelectableObject::Type::Object;
so.object = obj;
so.render_type = SelectableObject::RenderType::Mesh;
selectable_objects.push_back(so);
}
for(auto& [obj, mesh] : engine->get_scene()->get_all<Light>()) {
SelectableObject so;
so.type = SelectableObject::Type::Object;
so.object = obj;
so.render_type = SelectableObject::RenderType::Sphere;
so.sphere_size = 0.5f;
selectable_objects.push_back(so);
}
for(auto& [obj, mesh] : engine->get_scene()->get_all<EnvironmentProbe>()) {
SelectableObject so;
so.type = SelectableObject::Type::Object;
so.object = obj;
so.render_type = SelectableObject::RenderType::Sphere;
so.sphere_size = 0.5f;
selectable_objects.push_back(so);
}
// add selections for currently selected object handles
const auto add_arrow = [this](SelectableObject::Axis axis, Matrix4x4 model) {
SelectableObject so;
so.type = SelectableObject::Type::Handle;
so.axis = axis;
so.axis_model = model;
so.object = selected_object;
2020-08-11 12:07:21 -04:00
selectable_objects.push_back(so);
};
2022-02-18 09:11:18 -05:00
if(selected_object != prism::NullObject) {
const auto position = engine->get_scene()->get<Transform>(selected_object).get_world_position();
2020-08-11 12:07:21 -04:00
const float base_scale = 0.05f;
2020-08-11 12:07:21 -04:00
const float scale_factor = length(position - engine->get_scene()->get<Transform>(camObj).position);
2021-05-12 09:56:44 -04:00
const Matrix4x4 translate_model = prism::translate(Matrix4x4(), position);
const Matrix4x4 scale_model = prism::scale(Matrix4x4(), base_scale * scale_factor);
2020-08-11 12:07:21 -04:00
add_arrow(SelectableObject::Axis::Y, translate_model * scale_model);
2020-08-11 12:07:21 -04:00
2021-05-12 09:56:44 -04:00
add_arrow(SelectableObject::Axis::X, translate_model * matrix_from_quat(angle_axis(radians(-90.0f), prism::float3(0, 0, 1))) * scale_model);
2020-08-11 12:07:21 -04:00
2021-05-12 09:56:44 -04:00
add_arrow(SelectableObject::Axis::Z, translate_model * matrix_from_quat(angle_axis(radians(90.0f), prism::float3(1, 0, 0))) * scale_model);
2020-08-11 12:07:21 -04:00
}
GFXCommandBuffer* commandBuffer = engine->get_gfx()->acquire_command_buffer();
GFXRenderPassBeginInfo info = {};
info.clear_color.a = 0.0;
info.framebuffer = selectFramebuffer;
info.render_pass = selectRenderPass;
info.render_area.extent = extent;
commandBuffer->set_render_pass(info);
Viewport viewport = {};
viewport.width = extent.width;
viewport.height = extent.height;
commandBuffer->set_viewport(viewport);
2020-08-11 12:07:21 -04:00
commandBuffer->set_graphics_pipeline(selectPipeline);
2020-08-11 12:07:21 -04:00
for(auto [i, object] : utility::enumerate(selectable_objects)) {
AssetPtr<Mesh> mesh;
Matrix4x4 model;
if(object.type == SelectableObject::Type::Object) {
if(object.render_type == SelectableObject::RenderType::Mesh) {
auto& renderable = engine->get_scene()->get<Renderable>(object.object);
if(!renderable.mesh)
continue;
mesh = renderable.mesh;
} else {
mesh = sphereMesh;
}
model = engine->get_scene()->get<Transform>(object.object).model;
} else {
mesh = arrowMesh;
model = object.axis_model;
}
commandBuffer->set_vertex_buffer(mesh->position_buffer, 0, 0);
commandBuffer->set_index_buffer(mesh->index_buffer, IndexType::UINT32);
struct PC {
Matrix4x4 mvp;
2021-05-12 09:56:44 -04:00
prism::float4 color;
2020-08-11 12:07:21 -04:00
} pc;
pc.mvp = camera.perspective * camera.view * model;
2020-08-11 12:07:21 -04:00
if(object.render_type == SelectableObject::RenderType::Sphere)
2021-05-12 09:56:44 -04:00
pc.mvp = prism::scale(pc.mvp, prism::float3(object.sphere_size));
2020-08-11 12:07:21 -04:00
pc.color = {
float((i & 0x000000FF) >> 0) / 255.0f,
float((i & 0x0000FF00) >> 8) / 255.0f,
float((i & 0x00FF0000) >> 16) / 255.0f,
1.0,
};
commandBuffer->set_push_constant(&pc, sizeof(PC));
for (auto& part : mesh->parts)
commandBuffer->draw_indexed(part.index_count, part.index_offset, part.vertex_offset, 0);
2020-08-11 12:07:21 -04:00
}
engine->get_gfx()->submit(commandBuffer);
engine->get_gfx()->copy_texture(selectTexture, selectBuffer);
2021-10-14 08:51:58 -04:00
auto mapped_texture = reinterpret_cast<uint8_t*>(engine->get_gfx()->get_buffer_contents(selectBuffer));
2020-08-11 12:07:21 -04:00
const int buffer_position = 4 * (y * extent.width + x);
2020-08-11 12:07:21 -04:00
uint8_t a = mapped_texture[buffer_position + 3];
2020-08-11 12:07:21 -04:00
const int id = mapped_texture[buffer_position] +
mapped_texture[buffer_position + 1] * 256 +
mapped_texture[buffer_position + 2] * 256 * 256;
2020-08-11 12:07:21 -04:00
engine->get_gfx()->release_buffer_contents(selectBuffer, mapped_texture);
2020-08-11 12:07:21 -04:00
if(a != 0) {
callback(selectable_objects[id]);
} else {
SelectableObject o;
2022-02-18 09:11:18 -05:00
o.object = prism::NullObject;
2020-08-11 12:07:21 -04:00
callback(o);
}
}