#include "stdafx.h" #include "ObjectWindow.h" #include "wiSceneSystem.h" #include "xatlas.h" #include using namespace wiECS; using namespace wiSceneSystem; static void SetPixel(uint8_t *dest, int destWidth, int x, int y, const uint8_t *color) { uint8_t *pixel = &dest[x * 4 + y * (destWidth * 4)]; pixel[0] = color[0]; pixel[1] = color[1]; pixel[2] = color[2]; pixel[3] = color[3]; } // https://github.com/miloyip/line/blob/master/line_bresenham.c static void RasterizeLine(uint8_t *dest, int destWidth, const int *p1, const int *p2, const uint8_t *color) { const int dx = abs(p2[0] - p1[0]), sx = p1[0] < p2[0] ? 1 : -1; const int dy = abs(p2[1] - p1[1]), sy = p1[1] < p2[1] ? 1 : -1; int err = (dx > dy ? dx : -dy) / 2; int current[2]; current[0] = p1[0]; current[1] = p1[1]; while (SetPixel(dest, destWidth, current[0], current[1], color), current[0] != p2[0] || current[1] != p2[1]) { const int e2 = err; if (e2 > -dx) { err -= dy; current[0] += sx; } if (e2 < dy) { err += dx; current[1] += sy; } } } // https://github.com/ssloy/tinyrenderer/wiki/Lesson-2:-Triangle-rasterization-and-back-face-culling static void RasterizeTriangle(uint8_t *dest, int destWidth, const int *t0, const int *t1, const int *t2, const uint8_t *color) { if (t0[1] > t1[1]) std::swap(t0, t1); if (t0[1] > t2[1]) std::swap(t0, t2); if (t1[1] > t2[1]) std::swap(t1, t2); int total_height = t2[1] - t0[1]; for (int i = 0; i < total_height; i++) { bool second_half = i > t1[1] - t0[1] || t1[1] == t0[1]; int segment_height = second_half ? t2[1] - t1[1] : t1[1] - t0[1]; float alpha = (float)i / total_height; float beta = (float)(i - (second_half ? t1[1] - t0[1] : 0)) / segment_height; int A[2], B[2]; for (int j = 0; j < 2; j++) { A[j] = int(t0[j] + (t2[j] - t0[j]) * alpha); B[j] = int(second_half ? t1[j] + (t2[j] - t1[j]) * beta : t0[j] + (t1[j] - t0[j]) * beta); } if (A[0] > B[0]) std::swap(A, B); for (int j = A[0]; j <= B[0]; j++) SetPixel(dest, destWidth, j, t0[1] + i, color); } } ObjectWindow::ObjectWindow(wiGUI* gui) : GUI(gui) { assert(GUI && "Invalid GUI!"); float screenW = (float)wiRenderer::GetDevice()->GetScreenWidth(); float screenH = (float)wiRenderer::GetDevice()->GetScreenHeight(); objectWindow = new wiWindow(GUI, "Object Window"); objectWindow->SetSize(XMFLOAT2(600, 520)); objectWindow->SetEnabled(false); GUI->AddWidget(objectWindow); float x = 450; float y = 0; nameLabel = new wiLabel("NAMELABEL"); nameLabel->SetText(""); nameLabel->SetPos(XMFLOAT2(x - 30, y += 30)); nameLabel->SetSize(XMFLOAT2(150, 20)); objectWindow->AddWidget(nameLabel); renderableCheckBox = new wiCheckBox("Renderable: "); renderableCheckBox->SetTooltip("Set object to be participating in rendering."); renderableCheckBox->SetPos(XMFLOAT2(x, y += 30)); renderableCheckBox->SetCheck(true); renderableCheckBox->OnClick([&](wiEventArgs args) { ObjectComponent* object = wiRenderer::GetScene().objects.GetComponent(entity); if (object != nullptr) { object->SetRenderable(args.bValue); } }); objectWindow->AddWidget(renderableCheckBox); ditherSlider = new wiSlider(0, 1, 0, 1000, "Dither: "); ditherSlider->SetTooltip("Adjust dithered transparency of the object. This disables some optimizations so performance can be affected."); ditherSlider->SetSize(XMFLOAT2(100, 30)); ditherSlider->SetPos(XMFLOAT2(x, y += 30)); ditherSlider->OnSlide([&](wiEventArgs args) { ObjectComponent* object = wiRenderer::GetScene().objects.GetComponent(entity); if (object != nullptr) { object->color.w = 1 - args.fValue; } }); objectWindow->AddWidget(ditherSlider); cascadeMaskSlider = new wiSlider(0, 3, 0, 3, "Cascade Mask: "); cascadeMaskSlider->SetTooltip("How many shadow cascades to skip when rendering this object into shadow maps? (0: skip none, it will be in all cascades, 1: skip first (biggest cascade), ...etc..."); cascadeMaskSlider->SetSize(XMFLOAT2(100, 30)); cascadeMaskSlider->SetPos(XMFLOAT2(x, y += 30)); cascadeMaskSlider->OnSlide([&](wiEventArgs args) { ObjectComponent* object = wiRenderer::GetScene().objects.GetComponent(entity); if (object != nullptr) { object->cascadeMask = (uint32_t)args.iValue; } }); objectWindow->AddWidget(cascadeMaskSlider); colorPicker = new wiColorPicker(GUI, "Object Color"); colorPicker->SetPos(XMFLOAT2(10, 30)); colorPicker->RemoveWidgets(); colorPicker->SetVisible(true); colorPicker->SetEnabled(true); colorPicker->OnColorChanged([&](wiEventArgs args) { ObjectComponent* object = wiRenderer::GetScene().objects.GetComponent(entity); if (object != nullptr) { XMFLOAT3 col = args.color.toFloat3(); object->color = XMFLOAT4(powf(col.x, 1.f / 2.2f), powf(col.y, 1.f / 2.2f), powf(col.z, 1.f / 2.2f), object->color.w); } }); objectWindow->AddWidget(colorPicker); y += 60; physicsLabel = new wiLabel("PHYSICSLABEL"); physicsLabel->SetText("PHYSICS SETTINGS"); physicsLabel->SetPos(XMFLOAT2(x - 30, y += 30)); physicsLabel->SetSize(XMFLOAT2(150, 20)); objectWindow->AddWidget(physicsLabel); rigidBodyCheckBox = new wiCheckBox("Rigid Body Physics: "); rigidBodyCheckBox->SetTooltip("Enable rigid body physics simulation."); rigidBodyCheckBox->SetPos(XMFLOAT2(x, y += 30)); rigidBodyCheckBox->SetCheck(false); rigidBodyCheckBox->OnClick([&](wiEventArgs args) { Scene& scene = wiRenderer::GetScene(); RigidBodyPhysicsComponent* physicscomponent = scene.rigidbodies.GetComponent(entity); if (args.bValue) { if (physicscomponent == nullptr) { RigidBodyPhysicsComponent& rigidbody = scene.rigidbodies.Create(entity); rigidbody.SetKinematic(kinematicCheckBox->GetCheck()); rigidbody.SetDisableDeactivation(disabledeactivationCheckBox->GetCheck()); rigidbody.shape = (RigidBodyPhysicsComponent::CollisionShape)collisionShapeComboBox->GetSelected(); } } else { if (physicscomponent != nullptr) { scene.rigidbodies.Remove(entity); } } }); objectWindow->AddWidget(rigidBodyCheckBox); kinematicCheckBox = new wiCheckBox("Kinematic: "); kinematicCheckBox->SetTooltip("Toggle kinematic behaviour."); kinematicCheckBox->SetPos(XMFLOAT2(x, y += 30)); kinematicCheckBox->SetCheck(false); kinematicCheckBox->OnClick([&](wiEventArgs args) { RigidBodyPhysicsComponent* physicscomponent = wiRenderer::GetScene().rigidbodies.GetComponent(entity); if (physicscomponent != nullptr) { physicscomponent->SetKinematic(args.bValue); } }); objectWindow->AddWidget(kinematicCheckBox); disabledeactivationCheckBox = new wiCheckBox("Disable Deactivation: "); disabledeactivationCheckBox->SetTooltip("Toggle kinematic behaviour."); disabledeactivationCheckBox->SetPos(XMFLOAT2(x, y += 30)); disabledeactivationCheckBox->SetCheck(false); disabledeactivationCheckBox->OnClick([&](wiEventArgs args) { RigidBodyPhysicsComponent* physicscomponent = wiRenderer::GetScene().rigidbodies.GetComponent(entity); if (physicscomponent != nullptr) { physicscomponent->SetDisableDeactivation(args.bValue); } }); objectWindow->AddWidget(disabledeactivationCheckBox); collisionShapeComboBox = new wiComboBox("Collision Shape:"); collisionShapeComboBox->SetSize(XMFLOAT2(100, 20)); collisionShapeComboBox->SetPos(XMFLOAT2(x, y += 30)); collisionShapeComboBox->AddItem("Box"); collisionShapeComboBox->AddItem("Sphere"); collisionShapeComboBox->AddItem("Capsule"); collisionShapeComboBox->AddItem("Convex Hull"); collisionShapeComboBox->AddItem("Triangle Mesh"); collisionShapeComboBox->OnSelect([&](wiEventArgs args) { RigidBodyPhysicsComponent* physicscomponent = wiRenderer::GetScene().rigidbodies.GetComponent(entity); if (physicscomponent != nullptr) { switch (args.iValue) { case 0: physicscomponent->shape = RigidBodyPhysicsComponent::CollisionShape::BOX; break; case 1: physicscomponent->shape = RigidBodyPhysicsComponent::CollisionShape::SPHERE; break; case 2: physicscomponent->shape = RigidBodyPhysicsComponent::CollisionShape::CAPSULE; break; case 3: physicscomponent->shape = RigidBodyPhysicsComponent::CollisionShape::CONVEX_HULL; break; case 4: physicscomponent->shape = RigidBodyPhysicsComponent::CollisionShape::TRIANGLE_MESH; break; default: break; } } }); collisionShapeComboBox->SetSelected(0); collisionShapeComboBox->SetEnabled(true); collisionShapeComboBox->SetTooltip("Set rigid body collision shape."); objectWindow->AddWidget(collisionShapeComboBox); y += 30; lightmapResolutionSlider = new wiSlider(32, 1024, 128, 1024 - 32, "Lightmap resolution: "); lightmapResolutionSlider->SetTooltip("Set the approximate resolution for this object's lightmap. This will be packed into the larger global lightmap later."); lightmapResolutionSlider->SetSize(XMFLOAT2(100, 30)); lightmapResolutionSlider->SetPos(XMFLOAT2(x, y += 30)); lightmapResolutionSlider->OnSlide([&](wiEventArgs args) { // unfortunately, we must be pow2 with full float lightmap format, otherwise it could be unlimited (but accumulation blending would suffer then) // or at least for me, downloading the lightmap was glitching out when non-pow 2 and RGBA32_FLOAT format lightmapResolutionSlider->SetValue(float(wiMath::GetNextPowerOfTwo(uint32_t(args.fValue)))); }); objectWindow->AddWidget(lightmapResolutionSlider); generateLightmapButton = new wiButton("Generate Lightmap"); generateLightmapButton->SetTooltip("Render the lightmap for only this object. It will automatically combined with the global lightmap."); generateLightmapButton->SetPos(XMFLOAT2(x, y += 30)); generateLightmapButton->SetSize(XMFLOAT2(140,30)); generateLightmapButton->OnClick([&](wiEventArgs args) { Scene& scene = wiRenderer::GetScene(); ObjectComponent* objectcomponent = scene.objects.GetComponent(entity); if (objectcomponent != nullptr) { MeshComponent* meshcomponent = scene.meshes.GetComponent(objectcomponent->meshID); if (meshcomponent != nullptr) { objectcomponent->ClearLightmap(); xatlas::Atlas* atlas = xatlas::Create(); // Prepare mesh to be processed by xatlas: { xatlas::InputMesh mesh; mesh.vertexCount = (int)meshcomponent->vertex_positions.size(); mesh.vertexPositionData = meshcomponent->vertex_positions.data(); mesh.vertexPositionStride = sizeof(float) * 3; if (!meshcomponent->vertex_normals.empty()) { mesh.vertexNormalData = meshcomponent->vertex_normals.data(); mesh.vertexNormalStride = sizeof(float) * 3; } if (!meshcomponent->vertex_texcoords.empty()) { mesh.vertexUvData = meshcomponent->vertex_texcoords.data(); mesh.vertexUvStride = sizeof(float) * 2; } mesh.indexCount = (int)meshcomponent->indices.size(); mesh.indexData = meshcomponent->indices.data(); mesh.indexFormat = xatlas::IndexFormat::UInt32; xatlas::AddMeshError::Enum error = xatlas::AddMesh(atlas, mesh); if (error != xatlas::AddMeshError::Success) { wiHelper::messageBox(xatlas::StringForEnum(error), "Adding mesh to xatlas failed!"); return; } } // Generate atlas: { xatlas::PackerOptions packerOptions; packerOptions.resolution = (uint32_t)lightmapResolutionSlider->GetValue(); packerOptions.conservative = true; packerOptions.padding = 1; xatlas::GenerateCharts(atlas, xatlas::CharterOptions(), nullptr, nullptr); xatlas::PackCharts(atlas, packerOptions, nullptr, nullptr); const uint32_t charts = xatlas::GetNumCharts(atlas); objectcomponent->lightmapWidth = xatlas::GetWidth(atlas); objectcomponent->lightmapHeight = xatlas::GetHeight(atlas); const xatlas::OutputMesh* mesh = xatlas::GetOutputMeshes(atlas)[0]; // Note: we must recreate all vertex buffers, because the index buffer will be different (the atlas could have removed shared vertices) meshcomponent->indices.clear(); meshcomponent->indices.resize(mesh->indexCount); std::vector positions(mesh->vertexCount); std::vector atlas(mesh->vertexCount); std::vector normals; std::vector texcoords; std::vector colors; std::vector boneindices; std::vector boneweights; if (!meshcomponent->vertex_normals.empty()) { normals.resize(mesh->vertexCount); } if (!meshcomponent->vertex_texcoords.empty()) { texcoords.resize(mesh->vertexCount); } if (!meshcomponent->vertex_colors.empty()) { colors.resize(mesh->vertexCount); } if (!meshcomponent->vertex_boneindices.empty()) { boneindices.resize(mesh->vertexCount); } if (!meshcomponent->vertex_boneweights.empty()) { boneweights.resize(mesh->vertexCount); } for (uint32_t j = 0; j < mesh->indexCount; ++j) { const uint32_t ind = mesh->indexArray[j]; const xatlas::OutputVertex &v = mesh->vertexArray[ind]; meshcomponent->indices[j] = ind; atlas[ind].x = v.uv[0] / float(objectcomponent->lightmapWidth); atlas[ind].y = v.uv[1] / float(objectcomponent->lightmapHeight); positions[ind] = meshcomponent->vertex_positions[v.xref]; if (!normals.empty()) { normals[ind] = meshcomponent->vertex_normals[v.xref]; } if (!texcoords.empty()) { texcoords[ind] = meshcomponent->vertex_texcoords[v.xref]; } if (!colors.empty()) { colors[ind] = meshcomponent->vertex_colors[v.xref]; } if (!boneindices.empty()) { boneindices[ind] = meshcomponent->vertex_boneindices[v.xref]; } if (!boneweights.empty()) { boneweights[ind] = meshcomponent->vertex_boneweights[v.xref]; } } meshcomponent->vertex_positions = positions; meshcomponent->vertex_atlas = atlas; if (!normals.empty()) { meshcomponent->vertex_normals = normals; } if (!texcoords.empty()) { meshcomponent->vertex_texcoords = texcoords; } if (!colors.empty()) { meshcomponent->vertex_colors = colors; } if (!boneindices.empty()) { meshcomponent->vertex_boneindices = boneindices; } if (!boneweights.empty()) { meshcomponent->vertex_boneweights = boneweights; } meshcomponent->CreateRenderData(); } //// DEBUG //{ // const uint32_t width = objectcomponent->lightmapWidth; // const uint32_t height = objectcomponent->lightmapHeight; // objectcomponent->lightmapTextureData.resize(width * height * 4); // const xatlas::OutputMesh *mesh = xatlas::GetOutputMeshes(atlas)[0]; // // Rasterize mesh triangles. // const uint8_t white[] = { 255, 255, 255 }; // for (uint32_t j = 0; j < mesh->indexCount; j += 3) { // int verts[3][2]; // uint8_t color[4]; // for (int k = 0; k < 3; k++) { // const xatlas::OutputVertex &v = mesh->vertexArray[mesh->indexArray[j + k]]; // verts[k][0] = int(v.uv[0]); // verts[k][1] = int(v.uv[1]); // color[k] = rand() % 255; // } // color[3] = 255; // if (!verts[0][0] && !verts[0][1] && !verts[1][0] && !verts[1][1] && !verts[2][0] && !verts[2][1]) // continue; // Skip triangles that weren't atlased. // RasterizeTriangle(objectcomponent->lightmapTextureData.data(), width, verts[0], verts[1], verts[2], color); // RasterizeLine(objectcomponent->lightmapTextureData.data(), width, verts[0], verts[1], white); // RasterizeLine(objectcomponent->lightmapTextureData.data(), width, verts[1], verts[2], white); // RasterizeLine(objectcomponent->lightmapTextureData.data(), width, verts[2], verts[0], white); // } //} xatlas::Destroy(atlas); objectcomponent->SetLightmapRenderRequest(true); wiRenderer::InvalidateBVH(); } } }); objectWindow->AddWidget(generateLightmapButton); stopLightmapGenButton = new wiButton("Stop Lightmap Gen"); stopLightmapGenButton->SetTooltip("Stop the lightmap rendering and save the lightmap."); stopLightmapGenButton->SetPos(XMFLOAT2(x, y += 30)); stopLightmapGenButton->SetSize(XMFLOAT2(140, 30)); stopLightmapGenButton->OnClick([&](wiEventArgs args) { Scene& scene = wiRenderer::GetScene(); ObjectComponent* objectcomponent = scene.objects.GetComponent(entity); if (objectcomponent != nullptr) { objectcomponent->SetLightmapRenderRequest(false); objectcomponent->SaveLightmap(); } }); objectWindow->AddWidget(stopLightmapGenButton); clearLightmapButton = new wiButton("Clear Lightmap"); clearLightmapButton->SetTooltip("Clear the lightmap from this object."); clearLightmapButton->SetPos(XMFLOAT2(x, y += 30)); clearLightmapButton->SetSize(XMFLOAT2(140, 30)); clearLightmapButton->OnClick([&](wiEventArgs args) { Scene& scene = wiRenderer::GetScene(); ObjectComponent* objectcomponent = scene.objects.GetComponent(entity); if (objectcomponent != nullptr) { objectcomponent->ClearLightmap(); } }); objectWindow->AddWidget(clearLightmapButton); objectWindow->Translate(XMFLOAT3(1300, 100, 0)); objectWindow->SetVisible(false); SetEntity(INVALID_ENTITY); } ObjectWindow::~ObjectWindow() { objectWindow->RemoveWidgets(true); GUI->RemoveWidget(objectWindow); SAFE_DELETE(objectWindow); } void ObjectWindow::SetEntity(Entity entity) { if (this->entity == entity) return; this->entity = entity; Scene& scene = wiRenderer::GetScene(); const ObjectComponent* object = scene.objects.GetComponent(entity); if (object != nullptr) { const NameComponent* name = scene.names.GetComponent(entity); if (name != nullptr) { std::stringstream ss(""); ss << name->name << " (" << entity << ")"; nameLabel->SetText(ss.str()); } else { std::stringstream ss(""); ss<< "(" << entity << ")"; nameLabel->SetText(ss.str()); } renderableCheckBox->SetCheck(object->IsRenderable()); cascadeMaskSlider->SetValue((float)object->cascadeMask); ditherSlider->SetValue(object->GetTransparency()); const RigidBodyPhysicsComponent* physicsComponent = scene.rigidbodies.GetComponent(entity); rigidBodyCheckBox->SetCheck(physicsComponent != nullptr); if (physicsComponent != nullptr) { kinematicCheckBox->SetCheck(physicsComponent->IsKinematic()); disabledeactivationCheckBox->SetCheck(physicsComponent->IsDisableDeactivation()); if (physicsComponent->shape == RigidBodyPhysicsComponent::CollisionShape::BOX) { collisionShapeComboBox->SetSelected(0); } else if (physicsComponent->shape == RigidBodyPhysicsComponent::CollisionShape::SPHERE) { collisionShapeComboBox->SetSelected(1); } else if (physicsComponent->shape == RigidBodyPhysicsComponent::CollisionShape::CAPSULE) { collisionShapeComboBox->SetSelected(2); } else if (physicsComponent->shape == RigidBodyPhysicsComponent::CollisionShape::CONVEX_HULL) { collisionShapeComboBox->SetSelected(3); } else if (physicsComponent->shape == RigidBodyPhysicsComponent::CollisionShape::TRIANGLE_MESH) { collisionShapeComboBox->SetSelected(4); } } objectWindow->SetEnabled(true); } else { objectWindow->SetEnabled(false); } }