From 12e410d8939d5ff1380e117cd9e15b0da8f86f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tur=C3=A1nszki=20J=C3=A1nos?= Date: Sun, 21 Aug 2022 17:13:43 +0200 Subject: [PATCH] Paint tool stabilizer, continuous stroke, multiselect (#519) - continuous stroke - stabilizer - multiselect - smoothness improvement --- Editor/Editor.cpp | 3 - Editor/Editor_SOURCE.vcxitems | 4 + Editor/Editor_SOURCE.vcxitems.filters | 1 + Editor/Editor_UWP.vcxproj | 4 - Editor/Editor_UWP.vcxproj.filters | 1 - Editor/Editor_Windows.vcxproj | 4 - Editor/PaintToolWindow.cpp | 1989 +++++++++-------- Editor/PaintToolWindow.h | 16 +- Editor/config.ini | 1 + Editor/main_Windows.cpp | 1 - WickedEngine/shaders/ShaderInterop_Renderer.h | 2 +- WickedEngine/shaders/paint_textureCS.hlsl | 2 +- WickedEngine/wiECS.h | 4 +- WickedEngine/wiInput.h | 4 +- WickedEngine/wiVersion.cpp | 2 +- 15 files changed, 1032 insertions(+), 1006 deletions(-) diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index 3278f6d3a..4bb98daeb 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -346,7 +346,6 @@ void EditorComponent::Load() wi::scene::Scene& scene = GetCurrentScene(); wi::renderer::ClearWorld(scene); optionsWnd.cameraWnd.SetEntity(INVALID_ENTITY); - optionsWnd.paintToolWnd.SetEntity(INVALID_ENTITY); componentsWnd.objectWnd.SetEntity(INVALID_ENTITY); componentsWnd.meshWnd.SetEntity(INVALID_ENTITY, -1); componentsWnd.lightWnd.SetEntity(INVALID_ENTITY); @@ -1294,7 +1293,6 @@ void EditorComponent::Update(float dt) if (translator.selected.empty()) { optionsWnd.cameraWnd.SetEntity(INVALID_ENTITY); - optionsWnd.paintToolWnd.SetEntity(INVALID_ENTITY); componentsWnd.objectWnd.SetEntity(INVALID_ENTITY); componentsWnd.emitterWnd.SetEntity(INVALID_ENTITY); componentsWnd.hairWnd.SetEntity(INVALID_ENTITY); @@ -1329,7 +1327,6 @@ void EditorComponent::Update(float dt) } optionsWnd.cameraWnd.SetEntity(picked.entity); - optionsWnd.paintToolWnd.SetEntity(picked.entity, picked.subsetIndex); componentsWnd.emitterWnd.SetEntity(picked.entity); componentsWnd.hairWnd.SetEntity(picked.entity); componentsWnd.lightWnd.SetEntity(picked.entity); diff --git a/Editor/Editor_SOURCE.vcxitems b/Editor/Editor_SOURCE.vcxitems index 2cea43ad1..d7f154681 100644 --- a/Editor/Editor_SOURCE.vcxitems +++ b/Editor/Editor_SOURCE.vcxitems @@ -162,6 +162,10 @@ + + true + true + true true diff --git a/Editor/Editor_SOURCE.vcxitems.filters b/Editor/Editor_SOURCE.vcxitems.filters index c1db4dfcb..fc3bd904b 100644 --- a/Editor/Editor_SOURCE.vcxitems.filters +++ b/Editor/Editor_SOURCE.vcxitems.filters @@ -128,6 +128,7 @@ terrain + diff --git a/Editor/Editor_UWP.vcxproj b/Editor/Editor_UWP.vcxproj index 7d98a3262..21fab2a9e 100644 --- a/Editor/Editor_UWP.vcxproj +++ b/Editor/Editor_UWP.vcxproj @@ -150,10 +150,6 @@ - - true - true - diff --git a/Editor/Editor_UWP.vcxproj.filters b/Editor/Editor_UWP.vcxproj.filters index abc655120..a6218e95d 100644 --- a/Editor/Editor_UWP.vcxproj.filters +++ b/Editor/Editor_UWP.vcxproj.filters @@ -37,6 +37,5 @@ - \ No newline at end of file diff --git a/Editor/Editor_Windows.vcxproj b/Editor/Editor_Windows.vcxproj index 65fdc5c4a..90cde43d4 100644 --- a/Editor/Editor_Windows.vcxproj +++ b/Editor/Editor_Windows.vcxproj @@ -139,10 +139,6 @@ Document - - Discomap - true - diff --git a/Editor/PaintToolWindow.cpp b/Editor/PaintToolWindow.cpp index 986bc5d53..046704af6 100644 --- a/Editor/PaintToolWindow.cpp +++ b/Editor/PaintToolWindow.cpp @@ -14,7 +14,7 @@ void PaintToolWindow::Create(EditorComponent* _editor) editor = _editor; wi::gui::Window::Create("Paint Tool", wi::gui::Window::WindowControls::COLLAPSE); - SetSize(XMFLOAT2(360, 600)); + SetSize(XMFLOAT2(360, 800)); float x = 105; float y = 0; @@ -98,16 +98,16 @@ void PaintToolWindow::Create(EditorComponent* _editor) AddWidget(&radiusSlider); amountSlider.Create(0, 1, 1, 10000, "Power: "); - amountSlider.SetTooltip("Set the brush amount. 0 = minimum affection, 1 = maximum affection"); + amountSlider.SetTooltip("Set the brush power amount. 0 = minimum affection, 1 = maximum affection"); amountSlider.SetSize(XMFLOAT2(wid, hei)); amountSlider.SetPos(XMFLOAT2(x, y += step)); AddWidget(&amountSlider); - falloffSlider.Create(0, 16, 0, 10000, "Smoothness: "); - falloffSlider.SetTooltip("Set the brush power. 0 = hard, greater values will be smoother"); - falloffSlider.SetSize(XMFLOAT2(wid, hei)); - falloffSlider.SetPos(XMFLOAT2(x, y += step)); - AddWidget(&falloffSlider); + smoothnessSlider.Create(0, 2, 1, 10000, "Smoothness: "); + smoothnessSlider.SetTooltip("Set the brush smoothness. 0 = hard, increase for more smoothness"); + smoothnessSlider.SetSize(XMFLOAT2(wid, hei)); + smoothnessSlider.SetPos(XMFLOAT2(x, y += step)); + AddWidget(&smoothnessSlider); spacingSlider.Create(0, 500, 1, 500, "Spacing: "); spacingSlider.SetTooltip("Brush spacing means how much brush movement (in pixels) starts a new stroke. 0 = new stroke every frame, 100 = every 100 pixel movement since last stroke will start a new stroke."); @@ -121,6 +121,20 @@ void PaintToolWindow::Create(EditorComponent* _editor) rotationSlider.SetPos(XMFLOAT2(x, y += step)); AddWidget(&rotationSlider); + stabilizerSlider.Create(1, 15, 8, 14, "Stabilizer: "); + stabilizerSlider.SetTooltip("The stabilizer generates a small delay between user input and painting, which will be used to compute a smoother paint stroke.."); + stabilizerSlider.SetSize(XMFLOAT2(wid, hei)); + stabilizerSlider.SetPos(XMFLOAT2(x, y += step)); + if (editor->main->config.GetSection("paint_tool").Has("stabilizer")) + { + stabilizerSlider.SetValue((float)editor->main->config.GetSection("paint_tool").GetInt("stabilizer")); + } + stabilizerSlider.OnSlide([=](wi::gui::EventArgs args) { + editor->main->config.GetSection("paint_tool").Set("stabilizer", args.iValue); + editor->main->config.Commit(); + }); + AddWidget(&stabilizerSlider); + backfaceCheckBox.Create("Backfaces: "); backfaceCheckBox.SetTooltip("Set whether to paint on backfaces of geometry or not"); backfaceCheckBox.SetSize(XMFLOAT2(hei, hei)); @@ -215,30 +229,33 @@ void PaintToolWindow::Create(EditorComponent* _editor) saveTextureButton.OnClick([this] (wi::gui::EventArgs args) { Scene& scene = editor->GetCurrentScene(); - ObjectComponent* object = scene.objects.GetComponent(entity); - if (object == nullptr || object->meshID == INVALID_ENTITY) - return; - - MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); - if (mesh == nullptr || (mesh->vertex_uvset_0.empty() && mesh->vertex_uvset_1.empty())) - return; - - MaterialComponent* material = subset >= 0 && subset < (int)mesh->subsets.size() ? scene.materials.GetComponent(mesh->subsets[subset].materialID) : nullptr; - if (material == nullptr) - return; - - Texture editTexture = GetEditTextureSlot(*material); - - uint64_t sel = textureSlotComboBox.GetItemUserData(textureSlotComboBox.GetSelected()); - - wi::vector texturefiledata; - if (wi::helper::saveTextureToMemoryFile(editTexture, "PNG", texturefiledata)) + for (auto& selected : editor->translator.selected) { - material->textures[sel].resource.SetFileData(texturefiledata); - } - else - { - wi::helper::messageBox("Saving texture failed! :("); + ObjectComponent* object = scene.objects.GetComponent(selected.entity); + if (object == nullptr || object->meshID == INVALID_ENTITY) + continue; + + MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); + if (mesh == nullptr || (mesh->vertex_uvset_0.empty() && mesh->vertex_uvset_1.empty())) + continue; + + MaterialComponent* material = selected.subsetIndex >= 0 && selected.subsetIndex < (int)mesh->subsets.size() ? scene.materials.GetComponent(mesh->subsets[selected.subsetIndex].materialID) : nullptr; + if (material == nullptr) + continue; + + Texture editTexture = GetEditTextureSlot(*material); + + uint64_t sel = textureSlotComboBox.GetItemUserData(textureSlotComboBox.GetSelected()); + + wi::vector texturefiledata; + if (wi::helper::saveTextureToMemoryFile(editTexture, "PNG", texturefiledata)) + { + material->textures[sel].resource.SetFileData(texturefiledata); + } + else + { + wi::helper::messageBox("Saving texture failed! :("); + } } }); @@ -348,9 +365,10 @@ void PaintToolWindow::Update(float dt) XMFLOAT2 posNew; posNew.x = editor->GetLogicalWidth() * 0.5f; posNew.y = editor->GetLogicalHeight() * 0.5f; - if (editor->GetGUI().HasFocus() || wi::backlog::isActive() || entity == INVALID_ENTITY) + if (editor->GetGUI().HasFocus() || wi::backlog::isActive()) { pos = posNew; + strokes.clear(); return; } @@ -359,175 +377,243 @@ void PaintToolWindow::Update(float dt) { stroke_dist = FLT_MAX; sculpting_normal = XMFLOAT3(0, 0, 0); + if (!strokes.empty()) + { + strokes.pop_front(); + } } auto pointer = wi::input::GetPointer(); posNew = XMFLOAT2(pointer.x, pointer.y); - stroke_dist += wi::math::Distance(pos, posNew); - const float pressure = pressureCheckBox.GetCheck() ? pointer.w : 1.0f; - - const float spacing = spacingSlider.GetValue(); - const bool pointer_moved = stroke_dist >= spacing; - const bool painting = pointer_moved && pointer_down; - - if (painting) - { - stroke_dist = 0; - } - - pos = posNew; + float pressureNew = pressureCheckBox.GetCheck() ? pointer.w : 1.0f; const MODE mode = GetMode(); const float radius = radiusSlider.GetValue(); - const float pressure_radius = radius * pressure; const float amount = amountSlider.GetValue(); - const float falloff = falloffSlider.GetValue(); + const float smoothness = smoothnessSlider.GetValue(); const wi::Color color = colorPicker.GetPickColor(); const XMFLOAT4 color_float = color.toFloat4(); const bool backfaces = backfaceCheckBox.GetCheck(); const bool wireframe = wireCheckBox.GetCheck(); + const float spacing = spacingSlider.GetValue(); + const size_t stabilizer = (size_t)stabilizerSlider.GetValue(); + CommandList cmd; - Scene& scene = editor->GetCurrentScene(); - const CameraComponent& camera = editor->GetCurrentEditorScene().camera; - const XMVECTOR C = XMLoadFloat2(&pos); - const XMMATRIX VP = camera.GetViewProjection(); - const XMVECTOR MUL = XMVectorSet(0.5f, -0.5f, 1, 1); - const XMVECTOR ADD = XMVectorSet(0.5f, 0.5f, 0, 0); - const XMVECTOR SCREEN = XMVectorSet((float)editor->GetLogicalWidth(), (float)editor->GetLogicalHeight(), 1, 1); - const XMVECTOR F = camera.GetAt(); - const float brush_rotation = wi::random::GetRandom(0.0f, rotationSlider.GetValue() * XM_2PI); - - switch (mode) + if (pointer_down) { - case MODE_TEXTURE: - { - const wi::scene::PickResult& intersect = editor->hovered; - if (intersect.entity != entity) - break; - - ObjectComponent* object = scene.objects.GetComponent(entity); - if (object == nullptr || object->meshID == INVALID_ENTITY) - break; - - MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); - if (mesh == nullptr || (mesh->vertex_uvset_0.empty() && mesh->vertex_uvset_1.empty())) - break; - - MaterialComponent* material = subset >= 0 && subset < (int)mesh->subsets.size() ? scene.materials.GetComponent(mesh->subsets[subset].materialID) : nullptr; - if (material == nullptr) - break; - - int uvset = 0; - Texture editTexture = GetEditTextureSlot(*material, &uvset); - if (!editTexture.IsValid()) - break; - const TextureDesc& desc = editTexture.GetDesc(); - auto& vertex_uvset = uvset == 0 ? mesh->vertex_uvset_0 : mesh->vertex_uvset_1; - - const float u = intersect.bary.x; - const float v = intersect.bary.y; - const float w = 1 - u - v; - XMFLOAT2 uv; - uv.x = vertex_uvset[intersect.vertexID0].x * w + - vertex_uvset[intersect.vertexID1].x * u + - vertex_uvset[intersect.vertexID2].x * v; - uv.y = vertex_uvset[intersect.vertexID0].y * w + - vertex_uvset[intersect.vertexID1].y * u + - vertex_uvset[intersect.vertexID2].y * v; - uint2 center = XMUINT2(uint32_t(uv.x * desc.width), uint32_t(uv.y * desc.height)); - - if (painting) - { - GraphicsDevice* device = wi::graphics::GetDevice(); - CommandList cmd = device->BeginCommandList(); - - RecordHistory(true, cmd); - - // Need to requery this because RecordHistory might swap textures on material: - editTexture = GetEditTextureSlot(*material, &uvset); - - device->BindComputeShader(wi::renderer::GetShader(wi::enums::CSTYPE_PAINT_TEXTURE), cmd); - - if (brushTex.IsValid()) - { - device->BindResource(&brushTex.GetTexture(), 0, cmd); - } - else - { - device->BindResource(wi::texturehelper::getWhite(), 0, cmd); - } - if (revealTex.IsValid()) - { - device->BindResource(&revealTex.GetTexture(), 1, cmd); - } - else - { - device->BindResource(wi::texturehelper::getWhite(), 1, cmd); - } - device->BindUAV(&editTexture, 0, cmd); - - PaintTextureCB cb; - cb.xPaintBrushCenter = center; - cb.xPaintBrushRadius = (uint32_t)pressure_radius; - if (brushShapeComboBox.GetSelected() == 1) - { - cb.xPaintBrushRadius = (uint)std::ceil((float(cb.xPaintBrushRadius) * 2 / std::sqrt(2.0f))); // square shape, diagonal dispatch size - } - cb.xPaintBrushAmount = amount; - cb.xPaintBrushFalloff = falloff; - cb.xPaintBrushColor = color.rgba; - cb.xPaintReveal = revealTex.IsValid() ? 1 : 0; - cb.xPaintBrushRotation = brush_rotation; - cb.xPaintBrushShape = (uint)brushShapeComboBox.GetSelected(); - device->PushConstants(&cb, sizeof(cb), cmd); - - const uint diameter = cb.xPaintBrushRadius * 2; - const uint dispatch_dim = (diameter + PAINT_TEXTURE_BLOCKSIZE - 1) / PAINT_TEXTURE_BLOCKSIZE; - device->Dispatch(dispatch_dim, dispatch_dim, 1, cmd); - - GPUBarrier barriers[] = { - GPUBarrier::Memory(), - }; - device->Barrier(barriers, arraysize(barriers), cmd); - - if (editTexture.desc.mip_levels > 1) - { - wi::renderer::GenerateMipChain(editTexture, wi::renderer::MIPGENFILTER::MIPGENFILTER_LINEAR, cmd); - } - } - - wi::renderer::PaintRadius paintrad; - paintrad.objectEntity = entity; - paintrad.subset = subset; - paintrad.radius = radius; - paintrad.center = center; - paintrad.uvset = uvset; - paintrad.dimensions.x = desc.width; - paintrad.dimensions.y = desc.height; - paintrad.rotation = brush_rotation; - paintrad.shape = (uint)brushShapeComboBox.GetSelected(); - wi::renderer::DrawPaintRadius(paintrad); + Stroke stroke; + stroke.position = posNew; + stroke.pressure = pressureNew; + strokes.push_back(stroke); } - break; - case MODE_VERTEXCOLOR: - case MODE_WIND: + if (strokes.empty()) { - ObjectComponent* object = scene.objects.GetComponent(entity); - if (object == nullptr || object->meshID == INVALID_ENTITY) - break; + pos = posNew; + } + else + { + pos = strokes.front().position; + } - MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); - if (mesh == nullptr) - break; + const XMVECTOR spline_p0 = strokes.empty() ? XMVectorSet(posNew.x, posNew.y, pressureNew, 0) : XMVectorSet(strokes[0].position.x, strokes[0].position.y, strokes[0].pressure, 0); + const XMVECTOR spline_p1 = strokes.size() < 2 ? spline_p0 : XMVectorSet(strokes[1].position.x, strokes[1].position.y, strokes[1].pressure, 0); + const XMVECTOR spline_p2 = strokes.size() < 3 ? spline_p1 : XMVectorSet(strokes[2].position.x, strokes[2].position.y, strokes[2].pressure, 0); + const XMVECTOR spline_p3 = strokes.size() < 4 ? spline_p2 : XMVectorSet(strokes[3].position.x, strokes[3].position.y, strokes[3].pressure, 0); - MaterialComponent* material = subset >= 0 && subset < (int)mesh->subsets.size() ? scene.materials.GetComponent(mesh->subsets[subset].materialID) : nullptr; - if (material == nullptr) + int substep_count = (int)std::ceil(wi::math::Distance(pos, posNew) / (radius * pressureNew)); + substep_count = std::max(1, std::min(100, substep_count)); + + for (int substep = 0; substep < substep_count; ++substep) + { + const float t = float(substep) / float(substep_count); + + float pressure = 1; + const XMVECTOR spline_p = XMVectorCatmullRom( + spline_p0, + spline_p1, + spline_p2, + spline_p3, + t + ); + XMFLOAT2 pos_eval; + XMStoreFloat2(&pos_eval, spline_p); + stroke_dist += wi::math::Distance(pos, pos_eval); + pos = pos_eval; + pressure = XMVectorGetZ(spline_p); + const float pressure_radius = radius * pressure; + bool pointer_moved = false; + if (stroke_dist >= spacing) { - for (auto& x : mesh->subsets) + pointer_moved = true; + stroke_dist = 0; + } + const bool painting = pointer_moved && strokes.size() >= stabilizer; + + Scene& scene = editor->GetCurrentScene(); + const CameraComponent& camera = editor->GetCurrentEditorScene().camera; + const XMVECTOR C = XMLoadFloat2(&pos); + const XMMATRIX VP = camera.GetViewProjection(); + const XMVECTOR MUL = XMVectorSet(0.5f, -0.5f, 1, 1); + const XMVECTOR ADD = XMVectorSet(0.5f, 0.5f, 0, 0); + const XMVECTOR SCREEN = XMVectorSet((float)editor->GetLogicalWidth(), (float)editor->GetLogicalHeight(), 1, 1); + const XMVECTOR F = camera.GetAt(); + const float brush_rotation = wi::random::GetRandom(0.0f, rotationSlider.GetValue() * XM_2PI); + + for (auto& selected : editor->translator.selected) + { + switch (mode) { - material = scene.materials.GetComponent(x.materialID); - if (material != nullptr) + case MODE_TEXTURE: + { + wi::primitive::Ray pickRay = wi::renderer::GetPickRay((long)pos.x, (long)pos.y, *editor, camera); + const wi::scene::PickResult intersect = wi::scene::Pick(pickRay, ~0u, ~0u, scene); + if (intersect.entity != selected.entity) + break; + + ObjectComponent* object = scene.objects.GetComponent(selected.entity); + if (object == nullptr || object->meshID == INVALID_ENTITY) + break; + + MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); + if (mesh == nullptr || (mesh->vertex_uvset_0.empty() && mesh->vertex_uvset_1.empty())) + break; + + MaterialComponent* material = selected.subsetIndex >= 0 && selected.subsetIndex < (int)mesh->subsets.size() ? scene.materials.GetComponent(mesh->subsets[selected.subsetIndex].materialID) : nullptr; + if (material == nullptr) + break; + + int uvset = 0; + Texture editTexture = GetEditTextureSlot(*material, &uvset); + if (!editTexture.IsValid()) + break; + const TextureDesc& desc = editTexture.GetDesc(); + auto& vertex_uvset = uvset == 0 ? mesh->vertex_uvset_0 : mesh->vertex_uvset_1; + + const float u = intersect.bary.x; + const float v = intersect.bary.y; + const float w = 1 - u - v; + XMFLOAT2 uv; + uv.x = vertex_uvset[intersect.vertexID0].x * w + + vertex_uvset[intersect.vertexID1].x * u + + vertex_uvset[intersect.vertexID2].x * v; + uv.y = vertex_uvset[intersect.vertexID0].y * w + + vertex_uvset[intersect.vertexID1].y * u + + vertex_uvset[intersect.vertexID2].y * v; + uint2 center = XMUINT2(uint32_t(uv.x * desc.width), uint32_t(uv.y * desc.height)); + + if (painting) + { + GraphicsDevice* device = wi::graphics::GetDevice(); + if (!cmd.IsValid()) + { + cmd = device->BeginCommandList(); + } + + RecordHistory(true, cmd); + + // Need to requery this because RecordHistory might swap textures on material: + editTexture = GetEditTextureSlot(*material, &uvset); + + device->BindComputeShader(wi::renderer::GetShader(wi::enums::CSTYPE_PAINT_TEXTURE), cmd); + + if (brushTex.IsValid()) + { + device->BindResource(&brushTex.GetTexture(), 0, cmd); + } + else + { + device->BindResource(wi::texturehelper::getWhite(), 0, cmd); + } + if (revealTex.IsValid()) + { + device->BindResource(&revealTex.GetTexture(), 1, cmd); + } + else + { + device->BindResource(wi::texturehelper::getWhite(), 1, cmd); + } + device->BindUAV(&editTexture, 0, cmd); + + PaintTextureCB cb; + cb.xPaintBrushCenter = center; + cb.xPaintBrushRadius = (uint32_t)pressure_radius; + if (brushShapeComboBox.GetSelected() == 1) + { + cb.xPaintBrushRadius = (uint)std::ceil((float(cb.xPaintBrushRadius) * 2 / std::sqrt(2.0f))); // square shape, diagonal dispatch size + } + cb.xPaintBrushAmount = amount; + cb.xPaintBrushSmoothness = smoothness; + cb.xPaintBrushColor = color.rgba; + cb.xPaintReveal = revealTex.IsValid() ? 1 : 0; + cb.xPaintBrushRotation = brush_rotation; + cb.xPaintBrushShape = (uint)brushShapeComboBox.GetSelected(); + device->PushConstants(&cb, sizeof(cb), cmd); + + const uint diameter = cb.xPaintBrushRadius * 2; + const uint dispatch_dim = (diameter + PAINT_TEXTURE_BLOCKSIZE - 1) / PAINT_TEXTURE_BLOCKSIZE; + device->Dispatch(dispatch_dim, dispatch_dim, 1, cmd); + + GPUBarrier barriers[] = { + GPUBarrier::Memory(), + }; + device->Barrier(barriers, arraysize(barriers), cmd); + + if (editTexture.desc.mip_levels > 1) + { + wi::renderer::GenerateMipChain(editTexture, wi::renderer::MIPGENFILTER::MIPGENFILTER_LINEAR, cmd); + } + } + if(substep == substep_count - 1) + { + wi::renderer::PaintRadius paintrad; + paintrad.objectEntity = selected.entity; + paintrad.subset = selected.subsetIndex; + paintrad.radius = radius; + paintrad.center = center; + paintrad.uvset = uvset; + paintrad.dimensions.x = desc.width; + paintrad.dimensions.y = desc.height; + paintrad.rotation = brush_rotation; + paintrad.shape = (uint)brushShapeComboBox.GetSelected(); + wi::renderer::DrawPaintRadius(paintrad); + } + } + break; + + case MODE_VERTEXCOLOR: + case MODE_WIND: + { + ObjectComponent* object = scene.objects.GetComponent(selected.entity); + if (object == nullptr || object->meshID == INVALID_ENTITY) + break; + + MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); + if (mesh == nullptr) + break; + + MaterialComponent* material = selected.subsetIndex >= 0 && selected.subsetIndex < (int)mesh->subsets.size() ? scene.materials.GetComponent(mesh->subsets[selected.subsetIndex].materialID) : nullptr; + if (material == nullptr) + { + for (auto& x : mesh->subsets) + { + material = scene.materials.GetComponent(x.materialID); + if (material != nullptr) + { + switch (mode) + { + case MODE_VERTEXCOLOR: + material->SetUseVertexColors(true); + break; + case MODE_WIND: + material->SetUseWind(true); + break; + } + } + } + material = nullptr; + } + else { switch (mode) { @@ -539,537 +625,529 @@ void PaintToolWindow::Update(float dt) break; } } - } - material = nullptr; - } - else - { - switch (mode) - { - case MODE_VERTEXCOLOR: - material->SetUseVertexColors(true); - break; - case MODE_WIND: - material->SetUseWind(true); - break; - } - } - - const ArmatureComponent* armature = mesh->IsSkinned() ? scene.armatures.GetComponent(mesh->armatureID) : nullptr; - const TransformComponent* transform = scene.transforms.GetComponent(entity); - if (transform == nullptr) - break; + const ArmatureComponent* armature = mesh->IsSkinned() ? scene.armatures.GetComponent(mesh->armatureID) : nullptr; - const XMMATRIX W = XMLoadFloat4x4(&transform->world); + const TransformComponent* transform = scene.transforms.GetComponent(selected.entity); + if (transform == nullptr) + break; - bool rebuild = false; + const XMMATRIX W = XMLoadFloat4x4(&transform->world); - switch (mode) - { - case MODE_VERTEXCOLOR: - if (mesh->vertex_colors.empty()) - { - mesh->vertex_colors.resize(mesh->vertex_positions.size()); - std::fill(mesh->vertex_colors.begin(), mesh->vertex_colors.end(), wi::Color::White().rgba); // fill white - rebuild = true; - } - break; - case MODE_WIND: - if (mesh->vertex_windweights.empty()) - { - mesh->vertex_windweights.resize(mesh->vertex_positions.size()); - std::fill(mesh->vertex_windweights.begin(), mesh->vertex_windweights.end(), 0xFF); // fill max affection - rebuild = true; - } - break; - } + bool rebuild = false; - if (painting) - { - for (size_t j = 0; j < mesh->vertex_positions.size(); ++j) - { - XMVECTOR P, N; - if (armature == nullptr) + switch (mode) { - P = XMLoadFloat3(&mesh->vertex_positions[j]); - N = XMLoadFloat3(&mesh->vertex_normals[j]); - } - else - { - P = wi::scene::SkinVertex(*mesh, *armature, (uint32_t)j, &N); - } - P = XMVector3Transform(P, W); - N = XMVector3Normalize(XMVector3TransformNormal(N, W)); - - if (!backfaces && XMVectorGetX(XMVector3Dot(F, N)) > 0) - continue; - - P = XMVector3TransformCoord(P, VP); - P = P * MUL + ADD; - P = P * SCREEN; - - if (subset >= 0 && mesh->vertex_subsets[j] != subset) - continue; - - const float z = XMVectorGetZ(P); - const float dist = XMVectorGetX(XMVector2Length(C - P)); - if (z >= 0 && z <= 1 && dist <= pressure_radius) - { - RecordHistory(true); - rebuild = true; - const float affection = amount * std::pow(1.0f - (dist / pressure_radius), falloff); - - switch (mode) + case MODE_VERTEXCOLOR: + if (mesh->vertex_colors.empty()) { - case MODE_VERTEXCOLOR: - { - wi::Color vcol = mesh->vertex_colors[j]; - vcol = wi::Color::lerp(vcol, color, affection); - mesh->vertex_colors[j] = vcol.rgba; + mesh->vertex_colors.resize(mesh->vertex_positions.size()); + std::fill(mesh->vertex_colors.begin(), mesh->vertex_colors.end(), wi::Color::White().rgba); // fill white + rebuild = true; } break; - case MODE_WIND: + case MODE_WIND: + if (mesh->vertex_windweights.empty()) { - wi::Color vcol = wi::Color(0, 0, 0, mesh->vertex_windweights[j]); - vcol = wi::Color::lerp(vcol, color, affection); - mesh->vertex_windweights[j] = vcol.getA(); + mesh->vertex_windweights.resize(mesh->vertex_positions.size()); + std::fill(mesh->vertex_windweights.begin(), mesh->vertex_windweights.end(), 0xFF); // fill max affection + rebuild = true; } break; - } } - } - } - if (wireframe) - { - uint32_t first_subset = 0; - uint32_t last_subset = 0; - mesh->GetLODSubsetRange(0, first_subset, last_subset); - for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) - { - const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; - for (size_t j = 0; j < subset.indexCount; j += 3) + if (painting) { - const uint32_t triangle[] = { - mesh->indices[j + 0], - mesh->indices[j + 1], - mesh->indices[j + 2], - }; - if (this->subset >= 0 && (mesh->vertex_subsets[triangle[0]] != this->subset || mesh->vertex_subsets[triangle[1]] != this->subset || mesh->vertex_subsets[triangle[2]] != this->subset)) - continue; - - const XMVECTOR P[arraysize(triangle)] = { - XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[0]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[0]), W), - XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[1]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[1]), W), - XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[2]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[2]), W), - }; - - wi::renderer::RenderableTriangle tri; - XMStoreFloat3(&tri.positionA, P[0]); - XMStoreFloat3(&tri.positionB, P[1]); - XMStoreFloat3(&tri.positionC, P[2]); - if (mode == MODE_WIND) + for (size_t j = 0; j < mesh->vertex_positions.size(); ++j) { - tri.colorA = wi::Color(mesh->vertex_windweights[triangle[0]], 0, 0, 255); - tri.colorB = wi::Color(mesh->vertex_windweights[triangle[1]], 0, 0, 255); - tri.colorC = wi::Color(mesh->vertex_windweights[triangle[2]], 0, 0, 255); + XMVECTOR P, N; + if (armature == nullptr) + { + P = XMLoadFloat3(&mesh->vertex_positions[j]); + N = XMLoadFloat3(&mesh->vertex_normals[j]); + } + else + { + P = wi::scene::SkinVertex(*mesh, *armature, (uint32_t)j, &N); + } + P = XMVector3Transform(P, W); + N = XMVector3Normalize(XMVector3TransformNormal(N, W)); + + if (!backfaces && XMVectorGetX(XMVector3Dot(F, N)) > 0) + continue; + + P = XMVector3TransformCoord(P, VP); + P = P * MUL + ADD; + P = P * SCREEN; + + if (selected.subsetIndex >= 0 && mesh->vertex_subsets[j] != selected.subsetIndex) + continue; + + const float z = XMVectorGetZ(P); + const float dist = XMVectorGetX(XMVector2Length(C - P)); + if (z >= 0 && z <= 1 && dist <= pressure_radius) + { + RecordHistory(true); + rebuild = true; + const float affection = amount * wi::math::SmoothStep(0, smoothness, 1 - dist / pressure_radius); + + switch (mode) + { + case MODE_VERTEXCOLOR: + { + wi::Color vcol = mesh->vertex_colors[j]; + vcol = wi::Color::lerp(vcol, color, affection); + mesh->vertex_colors[j] = vcol.rgba; + } + break; + case MODE_WIND: + { + wi::Color vcol = wi::Color(0, 0, 0, mesh->vertex_windweights[j]); + vcol = wi::Color::lerp(vcol, color, affection); + mesh->vertex_windweights[j] = vcol.getA(); + } + break; + } + } } - else - { - tri.colorA.w = 0.8f; - tri.colorB.w = 0.8f; - tri.colorC.w = 0.8f; - } - wi::renderer::DrawTriangle(tri, true); - } - } - } - - if (rebuild) - { - mesh->CreateRenderData(); - } - } - break; - - case MODE_SCULPTING_ADD: - case MODE_SCULPTING_SUBTRACT: - { - ObjectComponent* object = scene.objects.GetComponent(entity); - if (object == nullptr || object->meshID == INVALID_ENTITY) - break; - - MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); - if (mesh == nullptr) - break; - - const ArmatureComponent* armature = mesh->IsSkinned() ? scene.armatures.GetComponent(mesh->armatureID) : nullptr; - - const TransformComponent* transform = scene.transforms.GetComponent(entity); - if (transform == nullptr) - break; - - const XMMATRIX W = XMLoadFloat4x4(&transform->world); - - XMVECTOR sculptDir; - AxisLock axis_lock = (AxisLock)axisCombo.GetItemUserData(axisCombo.GetSelected()); - switch (axis_lock) - { - default: - case PaintToolWindow::AxisLock::Disabled: - if (sculpting_normal.x < FLT_EPSILON && sculpting_normal.y < FLT_EPSILON && sculpting_normal.z < FLT_EPSILON) - { - sculpting_normal = editor->hovered.normal; - } - sculptDir = XMVector3TransformNormal(XMVector3Normalize(XMLoadFloat3(&sculpting_normal)), XMMatrixInverse(nullptr, W)); - break; - case PaintToolWindow::AxisLock::X: - sculpting_normal = XMFLOAT3(1, 0, 0); - sculptDir = XMLoadFloat3(&sculpting_normal); - break; - case PaintToolWindow::AxisLock::Y: - sculpting_normal = XMFLOAT3(0, 1, 0); - sculptDir = XMLoadFloat3(&sculpting_normal); - break; - case PaintToolWindow::AxisLock::Z: - sculpting_normal = XMFLOAT3(0, 0, 1); - sculptDir = XMLoadFloat3(&sculpting_normal); - break; - } - - - bool rebuild = false; - if (painting) - { - sculpting_indices.clear(); - sculpting_indices.reserve(mesh->vertex_positions.size()); - - for (size_t j = 0; j < mesh->vertex_positions.size(); ++j) - { - XMVECTOR P, N; - if (armature == nullptr) - { - P = XMLoadFloat3(&mesh->vertex_positions[j]); - N = XMLoadFloat3(&mesh->vertex_normals[j]); - } - else - { - P = wi::scene::SkinVertex(*mesh, *armature, (uint32_t)j, &N); - } - P = XMVector3Transform(P, W); - N = XMVector3Normalize(XMVector3TransformNormal(N, W)); - - const float dotN = XMVectorGetX(XMVector3Dot(F, N)); - if (!backfaces && dotN > 0) - continue; - - P = XMVector3TransformCoord(P, VP); - P = P * MUL + ADD; - P = P * SCREEN; - - const float z = XMVectorGetZ(P); - const float dist = XMVectorGetX(XMVector2Length(C - P)); - if (z >= 0 && z <= 1 && dist <= pressure_radius) - { - const float affection = amount * std::pow(1.0f - (dist / pressure_radius), falloff); - sculpting_indices.push_back({ j, affection }); - } - } - - if (!sculpting_indices.empty()) - { - RecordHistory(true); - rebuild = true; - - for (auto& x : sculpting_indices) - { - XMVECTOR PL = XMLoadFloat3(&mesh->vertex_positions[x.ind]); - switch (mode) - { - case MODE_SCULPTING_ADD: - PL += sculptDir * x.affection; - break; - case MODE_SCULPTING_SUBTRACT: - PL -= sculptDir * x.affection; - break; - } - XMStoreFloat3(&mesh->vertex_positions[x.ind], PL); - } - } - } - - if (wireframe) - { - uint32_t first_subset = 0; - uint32_t last_subset = 0; - mesh->GetLODSubsetRange(0, first_subset, last_subset); - for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) - { - const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; - for (size_t j = 0; j < subset.indexCount; j += 3) - { - const uint32_t triangle[] = { - mesh->indices[j + 0], - mesh->indices[j + 1], - mesh->indices[j + 2], - }; - const XMVECTOR P[arraysize(triangle)] = { - XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[0]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[0]), W), - XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[1]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[1]), W), - XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[2]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[2]), W), - }; - - wi::renderer::RenderableTriangle tri; - XMStoreFloat3(&tri.positionA, P[0]); - XMStoreFloat3(&tri.positionB, P[1]); - XMStoreFloat3(&tri.positionC, P[2]); - tri.colorA.w = 0.8f; - tri.colorB.w = 0.8f; - tri.colorC.w = 0.8f; - wi::renderer::DrawTriangle(tri, true); } - wi::renderer::RenderableLine sculpt_dir_line; - sculpt_dir_line.color_start = XMFLOAT4(0, 1, 0, 1); - sculpt_dir_line.color_end = XMFLOAT4(0, 1, 0, 1); - sculpt_dir_line.start = editor->hovered.position; - XMStoreFloat3( - &sculpt_dir_line.end, - XMLoadFloat3(&sculpt_dir_line.start) + - XMVector3Normalize(XMLoadFloat3(&sculpting_normal)) - ); - wi::renderer::DrawLine(sculpt_dir_line); - } - } - - if (rebuild) - { - mesh->ComputeNormals(MeshComponent::COMPUTE_NORMALS_SMOOTH_FAST); - } - } - break; - - case MODE_SOFTBODY_PINNING: - case MODE_SOFTBODY_PHYSICS: - { - ObjectComponent* object = scene.objects.GetComponent(entity); - if (object == nullptr || object->meshID == INVALID_ENTITY) - break; - - const MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); - if (mesh == nullptr) - break; - - SoftBodyPhysicsComponent* softbody = scene.softbodies.GetComponent(object->meshID); - if (softbody == nullptr || softbody->vertex_positions_simulation.empty()) - break; - - // Painting: - if (pointer_moved && wi::input::Down(wi::input::MOUSE_BUTTON_LEFT)) - { - size_t j = 0; - for (auto& ind : softbody->physicsToGraphicsVertexMapping) - { - XMVECTOR P = softbody->vertex_positions_simulation[ind].LoadPOS(); - P = XMVector3TransformCoord(P, VP); - P = P * MUL + ADD; - P = P * SCREEN; - const float z = XMVectorGetZ(P); - if (z >= 0 && z <= 1 && XMVectorGetX(XMVector2Length(C - P)) <= pressure_radius) - { - RecordHistory(true); - softbody->weights[j] = (mode == MODE_SOFTBODY_PINNING ? 0.0f : 1.0f); - softbody->_flags |= SoftBodyPhysicsComponent::FORCE_RESET; - } - j++; - } - } - - // Visualizing: - const XMMATRIX W = XMLoadFloat4x4(&softbody->worldMatrix); - uint32_t first_subset = 0; - uint32_t last_subset = 0; - mesh->GetLODSubsetRange(0, first_subset, last_subset); - for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) - { - const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; - for (size_t j = 0; j < subset.indexCount; j += 3) - { - const uint32_t graphicsIndex0 = mesh->indices[j + 0]; - const uint32_t graphicsIndex1 = mesh->indices[j + 1]; - const uint32_t graphicsIndex2 = mesh->indices[j + 2]; - const uint32_t physicsIndex0 = softbody->graphicsToPhysicsVertexMapping[graphicsIndex0]; - const uint32_t physicsIndex1 = softbody->graphicsToPhysicsVertexMapping[graphicsIndex1]; - const uint32_t physicsIndex2 = softbody->graphicsToPhysicsVertexMapping[graphicsIndex2]; - const float weight0 = softbody->weights[physicsIndex0]; - const float weight1 = softbody->weights[physicsIndex1]; - const float weight2 = softbody->weights[physicsIndex2]; - wi::renderer::RenderableTriangle tri; - if (softbody->vertex_positions_simulation.empty()) - { - XMStoreFloat3(&tri.positionA, XMVector3Transform(XMLoadFloat3(&mesh->vertex_positions[graphicsIndex0]), W)); - XMStoreFloat3(&tri.positionB, XMVector3Transform(XMLoadFloat3(&mesh->vertex_positions[graphicsIndex1]), W)); - XMStoreFloat3(&tri.positionC, XMVector3Transform(XMLoadFloat3(&mesh->vertex_positions[graphicsIndex2]), W)); - } - else - { - tri.positionA = softbody->vertex_positions_simulation[graphicsIndex0].pos; - tri.positionB = softbody->vertex_positions_simulation[graphicsIndex1].pos; - tri.positionC = softbody->vertex_positions_simulation[graphicsIndex2].pos; - } - if (weight0 == 0) - tri.colorA = XMFLOAT4(1, 1, 0, 1); - else - tri.colorA = XMFLOAT4(1, 1, 1, 1); - if (weight1 == 0) - tri.colorB = XMFLOAT4(1, 1, 0, 1); - else - tri.colorB = XMFLOAT4(1, 1, 1, 1); - if (weight2 == 0) - tri.colorC = XMFLOAT4(1, 1, 0, 1); - else - tri.colorC = XMFLOAT4(1, 1, 1, 1); if (wireframe) { - wi::renderer::DrawTriangle(tri, true); - } - if (weight0 == 0 && weight1 == 0 && weight2 == 0) - { - tri.colorA = tri.colorB = tri.colorC = XMFLOAT4(1, 0, 0, 0.8f); - wi::renderer::DrawTriangle(tri); - } - } - } - } - break; - - case MODE_HAIRPARTICLE_ADD_TRIANGLE: - case MODE_HAIRPARTICLE_REMOVE_TRIANGLE: - case MODE_HAIRPARTICLE_LENGTH: - { - wi::HairParticleSystem* hair = scene.hairs.GetComponent(entity); - if (hair == nullptr || hair->meshID == INVALID_ENTITY) - break; - - MeshComponent* mesh = scene.meshes.GetComponent(hair->meshID); - if (mesh == nullptr) - break; - - const ArmatureComponent* armature = mesh->IsSkinned() ? scene.armatures.GetComponent(mesh->armatureID) : nullptr; - - const TransformComponent* transform = scene.transforms.GetComponent(entity); - if (transform == nullptr) - break; - - const XMMATRIX W = XMLoadFloat4x4(&transform->world); - - if (painting) - { - for (size_t j = 0; j < mesh->vertex_positions.size(); ++j) - { - XMVECTOR P, N; - if (armature == nullptr) - { - P = XMLoadFloat3(&mesh->vertex_positions[j]); - N = XMLoadFloat3(&mesh->vertex_normals[j]); - } - else - { - P = wi::scene::SkinVertex(*mesh, *armature, (uint32_t)j, &N); - } - P = XMVector3Transform(P, W); - N = XMVector3Normalize(XMVector3TransformNormal(N, W)); - - if (!backfaces && XMVectorGetX(XMVector3Dot(F, N)) > 0) - continue; - - P = XMVector3TransformCoord(P, VP); - P = P * MUL + ADD; - P = P * SCREEN; - - const float z = XMVectorGetZ(P); - const float dist = XMVectorGetX(XMVector2Length(C - P)); - if (z >= 0 && z <= 1 && dist <= pressure_radius) - { - RecordHistory(true); - switch (mode) + uint32_t first_subset = 0; + uint32_t last_subset = 0; + mesh->GetLODSubsetRange(0, first_subset, last_subset); + for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) { - case MODE_HAIRPARTICLE_ADD_TRIANGLE: - hair->vertex_lengths[j] = 1.0f; - break; - case MODE_HAIRPARTICLE_REMOVE_TRIANGLE: - hair->vertex_lengths[j] = 0; - break; - case MODE_HAIRPARTICLE_LENGTH: - if (hair->vertex_lengths[j] > 0) // don't change distribution + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + for (size_t j = 0; j < subset.indexCount; j += 3) { - const float affection = amount * std::pow(1.0f - (dist / pressure_radius), falloff); - hair->vertex_lengths[j] = wi::math::Lerp(hair->vertex_lengths[j], color_float.w, affection); - // don't let it "remove" the vertex by keeping its length above zero: - // (because if removed, distribution also changes which might be distracting) - hair->vertex_lengths[j] = wi::math::Clamp(hair->vertex_lengths[j], 1.0f / 255.0f, 1.0f); + const uint32_t triangle[] = { + mesh->indices[j + 0], + mesh->indices[j + 1], + mesh->indices[j + 2], + }; + if (selected.subsetIndex >= 0 && (mesh->vertex_subsets[triangle[0]] != selected.subsetIndex || mesh->vertex_subsets[triangle[1]] != selected.subsetIndex || mesh->vertex_subsets[triangle[2]] != selected.subsetIndex)) + continue; + + const XMVECTOR P[arraysize(triangle)] = { + XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[0]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[0]), W), + XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[1]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[1]), W), + XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[2]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[2]), W), + }; + + wi::renderer::RenderableTriangle tri; + XMStoreFloat3(&tri.positionA, P[0]); + XMStoreFloat3(&tri.positionB, P[1]); + XMStoreFloat3(&tri.positionC, P[2]); + if (mode == MODE_WIND) + { + tri.colorA = wi::Color(mesh->vertex_windweights[triangle[0]], 0, 0, 255); + tri.colorB = wi::Color(mesh->vertex_windweights[triangle[1]], 0, 0, 255); + tri.colorC = wi::Color(mesh->vertex_windweights[triangle[2]], 0, 0, 255); + } + else + { + tri.colorA.w = 0.8f; + tri.colorB.w = 0.8f; + tri.colorC.w = 0.8f; + } + wi::renderer::DrawTriangle(tri, true); } - break; } - hair->_flags |= wi::HairParticleSystem::REBUILD_BUFFERS; } - } - } - if (wireframe) - { - uint32_t first_subset = 0; - uint32_t last_subset = 0; - mesh->GetLODSubsetRange(0, first_subset, last_subset); - for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) - { - const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; - for (size_t j = 0; j < subset.indexCount; j += 3) + if (rebuild) { - const uint32_t triangle[] = { - mesh->indices[j + 0], - mesh->indices[j + 1], - mesh->indices[j + 2], - }; - const XMVECTOR P[arraysize(triangle)] = { - XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[0]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[0]), W), - XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[1]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[1]), W), - XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[2]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[2]), W), - }; - - wi::renderer::RenderableTriangle tri; - XMStoreFloat3(&tri.positionA, P[0]); - XMStoreFloat3(&tri.positionB, P[1]); - XMStoreFloat3(&tri.positionC, P[2]); - wi::renderer::DrawTriangle(tri, true); + mesh->CreateRenderData(); } } + break; - for (size_t j = 0; j < hair->indices.size() && wireframe; j += 3) + case MODE_SCULPTING_ADD: + case MODE_SCULPTING_SUBTRACT: { - const uint32_t triangle[] = { - hair->indices[j + 0], - hair->indices[j + 1], - hair->indices[j + 2], - }; - const XMVECTOR P[arraysize(triangle)] = { - XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[0]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[0]), W), - XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[1]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[1]), W), - XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[2]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[2]), W), - }; + ObjectComponent* object = scene.objects.GetComponent(selected.entity); + if (object == nullptr || object->meshID == INVALID_ENTITY) + break; + + MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); + if (mesh == nullptr) + break; + + const ArmatureComponent* armature = mesh->IsSkinned() ? scene.armatures.GetComponent(mesh->armatureID) : nullptr; + + const TransformComponent* transform = scene.transforms.GetComponent(selected.entity); + if (transform == nullptr) + break; + + const XMMATRIX W = XMLoadFloat4x4(&transform->world); + + XMVECTOR sculptDir; + AxisLock axis_lock = (AxisLock)axisCombo.GetItemUserData(axisCombo.GetSelected()); + switch (axis_lock) + { + default: + case PaintToolWindow::AxisLock::Disabled: + if (sculpting_normal.x < FLT_EPSILON && sculpting_normal.y < FLT_EPSILON && sculpting_normal.z < FLT_EPSILON) + { + sculpting_normal = editor->hovered.normal; + } + sculptDir = XMVector3TransformNormal(XMVector3Normalize(XMLoadFloat3(&sculpting_normal)), XMMatrixInverse(nullptr, W)); + break; + case PaintToolWindow::AxisLock::X: + sculpting_normal = XMFLOAT3(1, 0, 0); + sculptDir = XMLoadFloat3(&sculpting_normal); + break; + case PaintToolWindow::AxisLock::Y: + sculpting_normal = XMFLOAT3(0, 1, 0); + sculptDir = XMLoadFloat3(&sculpting_normal); + break; + case PaintToolWindow::AxisLock::Z: + sculpting_normal = XMFLOAT3(0, 0, 1); + sculptDir = XMLoadFloat3(&sculpting_normal); + break; + } + + + bool rebuild = false; + if (painting) + { + sculpting_indices.clear(); + sculpting_indices.reserve(mesh->vertex_positions.size()); + + for (size_t j = 0; j < mesh->vertex_positions.size(); ++j) + { + XMVECTOR P, N; + if (armature == nullptr) + { + P = XMLoadFloat3(&mesh->vertex_positions[j]); + N = XMLoadFloat3(&mesh->vertex_normals[j]); + } + else + { + P = wi::scene::SkinVertex(*mesh, *armature, (uint32_t)j, &N); + } + P = XMVector3Transform(P, W); + N = XMVector3Normalize(XMVector3TransformNormal(N, W)); + + const float dotN = XMVectorGetX(XMVector3Dot(F, N)); + if (!backfaces && dotN > 0) + continue; + + P = XMVector3TransformCoord(P, VP); + P = P * MUL + ADD; + P = P * SCREEN; + + const float z = XMVectorGetZ(P); + const float dist = XMVectorGetX(XMVector2Length(C - P)); + if (z >= 0 && z <= 1 && dist <= pressure_radius) + { + const float affection = amount * wi::math::SmoothStep(0, smoothness, 1 - dist / pressure_radius); + sculpting_indices.push_back({ j, affection }); + } + } + + if (!sculpting_indices.empty()) + { + RecordHistory(true); + rebuild = true; + + for (auto& x : sculpting_indices) + { + XMVECTOR PL = XMLoadFloat3(&mesh->vertex_positions[x.ind]); + switch (mode) + { + case MODE_SCULPTING_ADD: + PL += sculptDir * x.affection; + break; + case MODE_SCULPTING_SUBTRACT: + PL -= sculptDir * x.affection; + break; + } + XMStoreFloat3(&mesh->vertex_positions[x.ind], PL); + } + } + } + + if (wireframe) + { + uint32_t first_subset = 0; + uint32_t last_subset = 0; + mesh->GetLODSubsetRange(0, first_subset, last_subset); + for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) + { + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + for (size_t j = 0; j < subset.indexCount; j += 3) + { + const uint32_t triangle[] = { + mesh->indices[j + 0], + mesh->indices[j + 1], + mesh->indices[j + 2], + }; + const XMVECTOR P[arraysize(triangle)] = { + XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[0]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[0]), W), + XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[1]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[1]), W), + XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[2]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[2]), W), + }; + + wi::renderer::RenderableTriangle tri; + XMStoreFloat3(&tri.positionA, P[0]); + XMStoreFloat3(&tri.positionB, P[1]); + XMStoreFloat3(&tri.positionC, P[2]); + tri.colorA.w = 0.8f; + tri.colorB.w = 0.8f; + tri.colorC.w = 0.8f; + wi::renderer::DrawTriangle(tri, true); + } + + wi::renderer::RenderableLine sculpt_dir_line; + sculpt_dir_line.color_start = XMFLOAT4(0, 1, 0, 1); + sculpt_dir_line.color_end = XMFLOAT4(0, 1, 0, 1); + sculpt_dir_line.start = editor->hovered.position; + XMStoreFloat3( + &sculpt_dir_line.end, + XMLoadFloat3(&sculpt_dir_line.start) + + XMVector3Normalize(XMLoadFloat3(&sculpting_normal)) + ); + wi::renderer::DrawLine(sculpt_dir_line); + } + } + + if (rebuild) + { + mesh->ComputeNormals(MeshComponent::COMPUTE_NORMALS_SMOOTH_FAST); + } + } + break; + + case MODE_SOFTBODY_PINNING: + case MODE_SOFTBODY_PHYSICS: + { + ObjectComponent* object = scene.objects.GetComponent(selected.entity); + if (object == nullptr || object->meshID == INVALID_ENTITY) + break; + + const MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); + if (mesh == nullptr) + break; + + SoftBodyPhysicsComponent* softbody = scene.softbodies.GetComponent(object->meshID); + if (softbody == nullptr || softbody->vertex_positions_simulation.empty()) + break; + + // Painting: + if (painting) + { + size_t j = 0; + for (auto& ind : softbody->physicsToGraphicsVertexMapping) + { + XMVECTOR P = softbody->vertex_positions_simulation[ind].LoadPOS(); + P = XMVector3TransformCoord(P, VP); + P = P * MUL + ADD; + P = P * SCREEN; + const float z = XMVectorGetZ(P); + if (z >= 0 && z <= 1 && XMVectorGetX(XMVector2Length(C - P)) <= pressure_radius) + { + RecordHistory(true); + softbody->weights[j] = (mode == MODE_SOFTBODY_PINNING ? 0.0f : 1.0f); + softbody->_flags |= SoftBodyPhysicsComponent::FORCE_RESET; + } + j++; + } + } + + // Visualizing: + const XMMATRIX W = XMLoadFloat4x4(&softbody->worldMatrix); + uint32_t first_subset = 0; + uint32_t last_subset = 0; + mesh->GetLODSubsetRange(0, first_subset, last_subset); + for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) + { + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + for (size_t j = 0; j < subset.indexCount; j += 3) + { + const uint32_t graphicsIndex0 = mesh->indices[j + 0]; + const uint32_t graphicsIndex1 = mesh->indices[j + 1]; + const uint32_t graphicsIndex2 = mesh->indices[j + 2]; + const uint32_t physicsIndex0 = softbody->graphicsToPhysicsVertexMapping[graphicsIndex0]; + const uint32_t physicsIndex1 = softbody->graphicsToPhysicsVertexMapping[graphicsIndex1]; + const uint32_t physicsIndex2 = softbody->graphicsToPhysicsVertexMapping[graphicsIndex2]; + const float weight0 = softbody->weights[physicsIndex0]; + const float weight1 = softbody->weights[physicsIndex1]; + const float weight2 = softbody->weights[physicsIndex2]; + wi::renderer::RenderableTriangle tri; + if (softbody->vertex_positions_simulation.empty()) + { + XMStoreFloat3(&tri.positionA, XMVector3Transform(XMLoadFloat3(&mesh->vertex_positions[graphicsIndex0]), W)); + XMStoreFloat3(&tri.positionB, XMVector3Transform(XMLoadFloat3(&mesh->vertex_positions[graphicsIndex1]), W)); + XMStoreFloat3(&tri.positionC, XMVector3Transform(XMLoadFloat3(&mesh->vertex_positions[graphicsIndex2]), W)); + } + else + { + tri.positionA = softbody->vertex_positions_simulation[graphicsIndex0].pos; + tri.positionB = softbody->vertex_positions_simulation[graphicsIndex1].pos; + tri.positionC = softbody->vertex_positions_simulation[graphicsIndex2].pos; + } + if (weight0 == 0) + tri.colorA = XMFLOAT4(1, 1, 0, 1); + else + tri.colorA = XMFLOAT4(1, 1, 1, 1); + if (weight1 == 0) + tri.colorB = XMFLOAT4(1, 1, 0, 1); + else + tri.colorB = XMFLOAT4(1, 1, 1, 1); + if (weight2 == 0) + tri.colorC = XMFLOAT4(1, 1, 0, 1); + else + tri.colorC = XMFLOAT4(1, 1, 1, 1); + if (wireframe) + { + wi::renderer::DrawTriangle(tri, true); + } + if (weight0 == 0 && weight1 == 0 && weight2 == 0) + { + tri.colorA = tri.colorB = tri.colorC = XMFLOAT4(1, 0, 0, 0.8f); + wi::renderer::DrawTriangle(tri); + } + } + } + } + break; + + case MODE_HAIRPARTICLE_ADD_TRIANGLE: + case MODE_HAIRPARTICLE_REMOVE_TRIANGLE: + case MODE_HAIRPARTICLE_LENGTH: + { + wi::HairParticleSystem* hair = scene.hairs.GetComponent(selected.entity); + if (hair == nullptr || hair->meshID == INVALID_ENTITY) + break; + + MeshComponent* mesh = scene.meshes.GetComponent(hair->meshID); + if (mesh == nullptr) + break; + + const ArmatureComponent* armature = mesh->IsSkinned() ? scene.armatures.GetComponent(mesh->armatureID) : nullptr; + + const TransformComponent* transform = scene.transforms.GetComponent(selected.entity); + if (transform == nullptr) + break; + + const XMMATRIX W = XMLoadFloat4x4(&transform->world); + + if (painting) + { + for (size_t j = 0; j < mesh->vertex_positions.size(); ++j) + { + XMVECTOR P, N; + if (armature == nullptr) + { + P = XMLoadFloat3(&mesh->vertex_positions[j]); + N = XMLoadFloat3(&mesh->vertex_normals[j]); + } + else + { + P = wi::scene::SkinVertex(*mesh, *armature, (uint32_t)j, &N); + } + P = XMVector3Transform(P, W); + N = XMVector3Normalize(XMVector3TransformNormal(N, W)); + + if (!backfaces && XMVectorGetX(XMVector3Dot(F, N)) > 0) + continue; + + P = XMVector3TransformCoord(P, VP); + P = P * MUL + ADD; + P = P * SCREEN; + + const float z = XMVectorGetZ(P); + const float dist = XMVectorGetX(XMVector2Length(C - P)); + if (z >= 0 && z <= 1 && dist <= pressure_radius) + { + RecordHistory(true); + switch (mode) + { + case MODE_HAIRPARTICLE_ADD_TRIANGLE: + hair->vertex_lengths[j] = 1.0f; + break; + case MODE_HAIRPARTICLE_REMOVE_TRIANGLE: + hair->vertex_lengths[j] = 0; + break; + case MODE_HAIRPARTICLE_LENGTH: + if (hair->vertex_lengths[j] > 0) // don't change distribution + { + const float affection = amount * wi::math::SmoothStep(0, smoothness, 1 - dist / pressure_radius); + hair->vertex_lengths[j] = wi::math::Lerp(hair->vertex_lengths[j], color_float.w, affection); + // don't let it "remove" the vertex by keeping its length above zero: + // (because if removed, distribution also changes which might be distracting) + hair->vertex_lengths[j] = wi::math::Clamp(hair->vertex_lengths[j], 1.0f / 255.0f, 1.0f); + } + break; + } + hair->_flags |= wi::HairParticleSystem::REBUILD_BUFFERS; + } + } + } + + if (wireframe) + { + uint32_t first_subset = 0; + uint32_t last_subset = 0; + mesh->GetLODSubsetRange(0, first_subset, last_subset); + for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex) + { + const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex]; + for (size_t j = 0; j < subset.indexCount; j += 3) + { + const uint32_t triangle[] = { + mesh->indices[j + 0], + mesh->indices[j + 1], + mesh->indices[j + 2], + }; + const XMVECTOR P[arraysize(triangle)] = { + XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[0]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[0]), W), + XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[1]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[1]), W), + XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[2]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[2]), W), + }; + + wi::renderer::RenderableTriangle tri; + XMStoreFloat3(&tri.positionA, P[0]); + XMStoreFloat3(&tri.positionB, P[1]); + XMStoreFloat3(&tri.positionC, P[2]); + wi::renderer::DrawTriangle(tri, true); + } + } + + for (size_t j = 0; j < hair->indices.size() && wireframe; j += 3) + { + const uint32_t triangle[] = { + hair->indices[j + 0], + hair->indices[j + 1], + hair->indices[j + 2], + }; + const XMVECTOR P[arraysize(triangle)] = { + XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[0]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[0]), W), + XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[1]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[1]), W), + XMVector3Transform(armature == nullptr ? XMLoadFloat3(&mesh->vertex_positions[triangle[2]]) : wi::scene::SkinVertex(*mesh, *armature, triangle[2]), W), + }; + + wi::renderer::RenderableTriangle tri; + XMStoreFloat3(&tri.positionA, P[0]); + XMStoreFloat3(&tri.positionB, P[1]); + XMStoreFloat3(&tri.positionC, P[2]); + tri.colorA = tri.colorB = tri.colorC = XMFLOAT4(1, 0, 1, 0.9f); + wi::renderer::DrawTriangle(tri, false); + } + } + } + break; - wi::renderer::RenderableTriangle tri; - XMStoreFloat3(&tri.positionA, P[0]); - XMStoreFloat3(&tri.positionB, P[1]); - XMStoreFloat3(&tri.positionC, P[2]); - tri.colorA = tri.colorB = tri.colorC = XMFLOAT4(1, 0, 1, 0.9f); - wi::renderer::DrawTriangle(tri, false); } } } - break; + while (strokes.size() > stabilizer) + { + strokes.pop_front(); } } @@ -1108,7 +1186,7 @@ using namespace PaintTool_Internal; void PaintToolWindow::DrawBrush(const wi::Canvas& canvas, CommandList cmd) const { const MODE mode = GetMode(); - if (mode == MODE_DISABLED || mode == MODE_TEXTURE || entity == INVALID_ENTITY || wi::backlog::isActive()) + if (mode == MODE_DISABLED || mode == MODE_TEXTURE || wi::backlog::isActive()) return; static bool shaders_loaded = false; @@ -1177,20 +1255,6 @@ PaintToolWindow::MODE PaintToolWindow::GetMode() const { return (MODE)modeComboBox.GetSelected(); } -void PaintToolWindow::SetEntity(wi::ecs::Entity value, int subsetindex) -{ - entity = value; - subset = subsetindex; - - if (entity == INVALID_ENTITY) - { - saveTextureButton.SetEnabled(false); - } - else if (GetMode() == MODE_TEXTURE) - { - saveTextureButton.SetEnabled(true); - } -} void PaintToolWindow::RecordHistory(bool start, CommandList cmd) { @@ -1206,11 +1270,11 @@ void PaintToolWindow::RecordHistory(bool start, CommandList cmd) wi::Archive& archive = *currentHistory; archive << EditorComponent::HISTORYOP_PAINTTOOL; archive << (uint32_t)GetMode(); - archive << entity; + history_redo_jump_position = archive.WriteUnknownJumpPosition(); } else { - if (!history_needs_recording_end || wi::input::Down(wi::input::MOUSE_BUTTON_LEFT)) + if (!history_needs_recording_end || !strokes.empty()) return; history_needs_recording_end = false; history_needs_recording_start = false; @@ -1221,320 +1285,278 @@ void PaintToolWindow::RecordHistory(bool start, CommandList cmd) wi::Archive& archive = *currentHistory; Scene& scene = editor->GetCurrentScene(); - switch (GetMode()) + for (auto& selected : editor->translator.selected) { - case PaintToolWindow::MODE_TEXTURE: - { - ObjectComponent* object = scene.objects.GetComponent(entity); - if (object == nullptr || object->meshID == INVALID_ENTITY) - break; - - MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); - if (mesh == nullptr || (mesh->vertex_uvset_0.empty() && mesh->vertex_uvset_1.empty())) - break; - - MaterialComponent* material = subset >= 0 && subset < (int)mesh->subsets.size() ? scene.materials.GetComponent(mesh->subsets[subset].materialID) : nullptr; - if (material == nullptr) - break; - - auto editTexture = GetEditTextureSlot(*material); - - archive << textureSlotComboBox.GetSelected(); - - if (history_textureIndex >= history_textures.size()) + switch (GetMode()) { - history_textures.resize((history_textureIndex + 1) * 2); - } - history_textures[history_textureIndex] = editTexture; - archive << history_textureIndex; - history_textureIndex++; - - if (start) + case PaintToolWindow::MODE_TEXTURE: { - // Make a copy of texture to edit and replace material resource: - GraphicsDevice* device = wi::graphics::GetDevice(); - Texture newTex; - TextureDesc desc = editTexture.GetDesc(); - desc.format = Format::R8G8B8A8_UNORM; // force format to one that is writable by GPU - desc.bind_flags |= BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS; - device->CreateTexture(&desc, nullptr, &newTex); - for (uint32_t i = 0; i < newTex.GetDesc().mip_levels; ++i) + ObjectComponent* object = scene.objects.GetComponent(selected.entity); + if (object == nullptr || object->meshID == INVALID_ENTITY) + break; + + MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); + if (mesh == nullptr || (mesh->vertex_uvset_0.empty() && mesh->vertex_uvset_1.empty())) + break; + + MaterialComponent* material = selected.subsetIndex >= 0 && selected.subsetIndex < (int)mesh->subsets.size() ? scene.materials.GetComponent(mesh->subsets[selected.subsetIndex].materialID) : nullptr; + if (material == nullptr) + break; + + auto editTexture = GetEditTextureSlot(*material); + + archive << textureSlotComboBox.GetSelected(); + + if (history_textureIndex >= history_textures.size()) { - int subresource_index; - subresource_index = device->CreateSubresource(&newTex, SubresourceType::SRV, 0, 1, i, 1); - assert(subresource_index == i); - subresource_index = device->CreateSubresource(&newTex, SubresourceType::UAV, 0, 1, i, 1); - assert(subresource_index == i); + history_textures.resize((history_textureIndex + 1) * 2); } - assert(cmd.IsValid()); - wi::renderer::CopyTexture2D(newTex, -1, 0, 0, editTexture, 0, cmd); - ReplaceEditTextureSlot(*material, newTex); + history_textures[history_textureIndex] = editTexture; + archive << history_textureIndex; + history_textureIndex++; + + if (start) + { + // Make a copy of texture to edit and replace material resource: + GraphicsDevice* device = wi::graphics::GetDevice(); + Texture newTex; + TextureDesc desc = editTexture.GetDesc(); + desc.format = Format::R8G8B8A8_UNORM; // force format to one that is writable by GPU + desc.bind_flags |= BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS; + device->CreateTexture(&desc, nullptr, &newTex); + for (uint32_t i = 0; i < newTex.GetDesc().mip_levels; ++i) + { + int subresource_index; + subresource_index = device->CreateSubresource(&newTex, SubresourceType::SRV, 0, 1, i, 1); + assert(subresource_index == i); + subresource_index = device->CreateSubresource(&newTex, SubresourceType::UAV, 0, 1, i, 1); + assert(subresource_index == i); + } + assert(cmd.IsValid()); + wi::renderer::CopyTexture2D(newTex, -1, 0, 0, editTexture, 0, cmd); + ReplaceEditTextureSlot(*material, newTex); + } + } - - } - break; - case PaintToolWindow::MODE_VERTEXCOLOR: - { - ObjectComponent* object = scene.objects.GetComponent(entity); - if (object == nullptr || object->meshID == INVALID_ENTITY) - break; - - MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); - if (mesh == nullptr) - break; - - archive << mesh->vertex_colors; - } - break; - case PaintToolWindow::MODE_WIND: - { - ObjectComponent* object = scene.objects.GetComponent(entity); - if (object == nullptr || object->meshID == INVALID_ENTITY) - break; - - MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); - if (mesh == nullptr) - break; - - archive << mesh->vertex_windweights; - } - break; - case PaintToolWindow::MODE_SCULPTING_ADD: - case PaintToolWindow::MODE_SCULPTING_SUBTRACT: - { - ObjectComponent* object = scene.objects.GetComponent(entity); - if (object == nullptr || object->meshID == INVALID_ENTITY) - break; - - MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); - if (mesh == nullptr) - break; - - archive << mesh->vertex_positions; - archive << mesh->vertex_normals; - } - break; - case PaintToolWindow::MODE_SOFTBODY_PINNING: - case PaintToolWindow::MODE_SOFTBODY_PHYSICS: - { - ObjectComponent* object = scene.objects.GetComponent(entity); - if (object == nullptr || object->meshID == INVALID_ENTITY) - break; - - SoftBodyPhysicsComponent* softbody = scene.softbodies.GetComponent(object->meshID); - if (softbody == nullptr) - break; - - archive << softbody->weights; - } - break; - case PaintToolWindow::MODE_HAIRPARTICLE_ADD_TRIANGLE: - case PaintToolWindow::MODE_HAIRPARTICLE_REMOVE_TRIANGLE: - case PaintToolWindow::MODE_HAIRPARTICLE_LENGTH: - { - wi::HairParticleSystem* hair = scene.hairs.GetComponent(entity); - if (hair == nullptr) - break; - - archive << hair->vertex_lengths; - } - break; - default: - assert(0); break; + case PaintToolWindow::MODE_VERTEXCOLOR: + { + ObjectComponent* object = scene.objects.GetComponent(selected.entity); + if (object == nullptr || object->meshID == INVALID_ENTITY) + break; + + MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); + if (mesh == nullptr) + break; + + archive << mesh->vertex_colors; + } + break; + case PaintToolWindow::MODE_WIND: + { + ObjectComponent* object = scene.objects.GetComponent(selected.entity); + if (object == nullptr || object->meshID == INVALID_ENTITY) + break; + + MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); + if (mesh == nullptr) + break; + + archive << mesh->vertex_windweights; + } + break; + case PaintToolWindow::MODE_SCULPTING_ADD: + case PaintToolWindow::MODE_SCULPTING_SUBTRACT: + { + ObjectComponent* object = scene.objects.GetComponent(selected.entity); + if (object == nullptr || object->meshID == INVALID_ENTITY) + break; + + MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); + if (mesh == nullptr) + break; + + archive << mesh->vertex_positions; + archive << mesh->vertex_normals; + } + break; + case PaintToolWindow::MODE_SOFTBODY_PINNING: + case PaintToolWindow::MODE_SOFTBODY_PHYSICS: + { + ObjectComponent* object = scene.objects.GetComponent(selected.entity); + if (object == nullptr || object->meshID == INVALID_ENTITY) + break; + + SoftBodyPhysicsComponent* softbody = scene.softbodies.GetComponent(object->meshID); + if (softbody == nullptr) + break; + + archive << softbody->weights; + } + break; + case PaintToolWindow::MODE_HAIRPARTICLE_ADD_TRIANGLE: + case PaintToolWindow::MODE_HAIRPARTICLE_REMOVE_TRIANGLE: + case PaintToolWindow::MODE_HAIRPARTICLE_LENGTH: + { + wi::HairParticleSystem* hair = scene.hairs.GetComponent(selected.entity); + if (hair == nullptr) + break; + + archive << hair->vertex_lengths; + } + break; + default: + assert(0); + break; + } + } + + if (start) + { + archive.PatchUnknownJumpPosition(history_redo_jump_position); } } void PaintToolWindow::ConsumeHistoryOperation(wi::Archive& archive, bool undo) { MODE historymode; archive >> (uint32_t&)historymode; - archive >> entity; + + uint64_t jump_pos = 0; + archive >> jump_pos; + if (!undo) + { + archive.Jump(jump_pos); + } modeComboBox.SetSelected(historymode); Scene& scene = editor->GetCurrentScene(); - switch (historymode) + for (auto& selected : editor->translator.selected) { - case PaintToolWindow::MODE_TEXTURE: - { - ObjectComponent* object = scene.objects.GetComponent(entity); - if (object == nullptr || object->meshID == INVALID_ENTITY) - break; - - MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); - if (mesh == nullptr || (mesh->vertex_uvset_0.empty() && mesh->vertex_uvset_1.empty())) - break; - - MaterialComponent* material = subset >= 0 && subset < (int)mesh->subsets.size() ? scene.materials.GetComponent(mesh->subsets[subset].materialID) : nullptr; - if (material == nullptr) - break; - - int undo_slot; - archive >> undo_slot; - size_t undo_textureindex; - archive >> undo_textureindex; - int redo_slot; - archive >> redo_slot; - size_t redo_textureindex; - archive >> redo_textureindex; - - if (undo) + switch (historymode) { - textureSlotComboBox.SetSelected(undo_slot); - history_textureIndex = undo_textureindex; - } - else + case PaintToolWindow::MODE_TEXTURE: { - textureSlotComboBox.SetSelected(redo_slot); - history_textureIndex = redo_textureindex; + ObjectComponent* object = scene.objects.GetComponent(selected.entity); + if (object == nullptr || object->meshID == INVALID_ENTITY) + break; + + MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); + if (mesh == nullptr || (mesh->vertex_uvset_0.empty() && mesh->vertex_uvset_1.empty())) + break; + + MaterialComponent* material = selected.subsetIndex >= 0 && selected.subsetIndex < (int)mesh->subsets.size() ? scene.materials.GetComponent(mesh->subsets[selected.subsetIndex].materialID) : nullptr; + if (material == nullptr) + break; + + int slot; + archive >> slot; + size_t textureindex; + archive >> textureindex; + + textureSlotComboBox.SetSelected(slot); + history_textureIndex = textureindex; + ReplaceEditTextureSlot(*material, history_textures[history_textureIndex]); + } - ReplaceEditTextureSlot(*material, history_textures[history_textureIndex]); - - } - break; - case PaintToolWindow::MODE_VERTEXCOLOR: - { - ObjectComponent* object = scene.objects.GetComponent(entity); - if (object == nullptr || object->meshID == INVALID_ENTITY) - break; - - MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); - if (mesh == nullptr) - break; - - MeshComponent undo_mesh; - archive >> undo_mesh.vertex_colors; - MeshComponent redo_mesh; - archive >> redo_mesh.vertex_colors; - - if (undo) - { - mesh->vertex_colors = undo_mesh.vertex_colors; - } - else - { - mesh->vertex_colors = redo_mesh.vertex_colors; - } - - mesh->CreateRenderData(); - } - break; - case PaintToolWindow::MODE_WIND: - { - ObjectComponent* object = scene.objects.GetComponent(entity); - if (object == nullptr || object->meshID == INVALID_ENTITY) - break; - - MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); - if (mesh == nullptr) - break; - - MeshComponent undo_mesh; - archive >> undo_mesh.vertex_windweights; - MeshComponent redo_mesh; - archive >> redo_mesh.vertex_windweights; - - if (undo) - { - mesh->vertex_windweights = undo_mesh.vertex_windweights; - } - else - { - mesh->vertex_windweights = redo_mesh.vertex_windweights; - } - - mesh->CreateRenderData(); - } - break; - case PaintToolWindow::MODE_SCULPTING_ADD: - case PaintToolWindow::MODE_SCULPTING_SUBTRACT: - { - ObjectComponent* object = scene.objects.GetComponent(entity); - if (object == nullptr || object->meshID == INVALID_ENTITY) - break; - - MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); - if (mesh == nullptr) - break; - - MeshComponent undo_mesh; - archive >> undo_mesh.vertex_positions; - archive >> undo_mesh.vertex_normals; - MeshComponent redo_mesh; - archive >> redo_mesh.vertex_positions; - archive >> redo_mesh.vertex_normals; - - if (undo) - { - mesh->vertex_positions = undo_mesh.vertex_positions; - mesh->vertex_normals = undo_mesh.vertex_normals; - } - else - { - mesh->vertex_positions = redo_mesh.vertex_positions; - mesh->vertex_normals = redo_mesh.vertex_normals; - } - - mesh->CreateRenderData(); - } - break; - case PaintToolWindow::MODE_SOFTBODY_PINNING: - case PaintToolWindow::MODE_SOFTBODY_PHYSICS: - { - ObjectComponent* object = scene.objects.GetComponent(entity); - if (object == nullptr || object->meshID == INVALID_ENTITY) - break; - - SoftBodyPhysicsComponent* softbody = scene.softbodies.GetComponent(object->meshID); - if (softbody == nullptr) - break; - - SoftBodyPhysicsComponent undo_softbody; - archive >> undo_softbody.weights; - SoftBodyPhysicsComponent redo_softbody; - archive >> redo_softbody.weights; - - if (undo) - { - softbody->weights = undo_softbody.weights; - } - else - { - softbody->weights = redo_softbody.weights; - } - - softbody->_flags |= SoftBodyPhysicsComponent::FORCE_RESET; - } - break; - case PaintToolWindow::MODE_HAIRPARTICLE_ADD_TRIANGLE: - case PaintToolWindow::MODE_HAIRPARTICLE_REMOVE_TRIANGLE: - case PaintToolWindow::MODE_HAIRPARTICLE_LENGTH: - { - wi::HairParticleSystem* hair = scene.hairs.GetComponent(entity); - if (hair == nullptr) - break; - - wi::HairParticleSystem undo_hair; - archive >> undo_hair.vertex_lengths; - wi::HairParticleSystem redo_hair; - archive >> redo_hair.vertex_lengths; - - if (undo) - { - hair->vertex_lengths = undo_hair.vertex_lengths; - } - else - { - hair->vertex_lengths = redo_hair.vertex_lengths; - } - - hair->_flags |= wi::HairParticleSystem::REBUILD_BUFFERS; - } - break; - default: - assert(0); break; + case PaintToolWindow::MODE_VERTEXCOLOR: + { + ObjectComponent* object = scene.objects.GetComponent(selected.entity); + if (object == nullptr || object->meshID == INVALID_ENTITY) + break; + + MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); + if (mesh == nullptr) + break; + + MeshComponent archive_mesh; + archive >> archive_mesh.vertex_colors; + + mesh->vertex_colors = archive_mesh.vertex_colors; + + mesh->CreateRenderData(); + } + break; + case PaintToolWindow::MODE_WIND: + { + ObjectComponent* object = scene.objects.GetComponent(selected.entity); + if (object == nullptr || object->meshID == INVALID_ENTITY) + break; + + MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); + if (mesh == nullptr) + break; + + MeshComponent archive_mesh; + archive >> archive_mesh.vertex_windweights; + + mesh->vertex_windweights = archive_mesh.vertex_windweights; + + mesh->CreateRenderData(); + } + break; + case PaintToolWindow::MODE_SCULPTING_ADD: + case PaintToolWindow::MODE_SCULPTING_SUBTRACT: + { + ObjectComponent* object = scene.objects.GetComponent(selected.entity); + if (object == nullptr || object->meshID == INVALID_ENTITY) + break; + + MeshComponent* mesh = scene.meshes.GetComponent(object->meshID); + if (mesh == nullptr) + break; + + MeshComponent archive_mesh; + archive >> archive_mesh.vertex_positions; + archive >> archive_mesh.vertex_normals; + + mesh->vertex_positions = archive_mesh.vertex_positions; + mesh->vertex_normals = archive_mesh.vertex_normals; + + mesh->CreateRenderData(); + } + break; + case PaintToolWindow::MODE_SOFTBODY_PINNING: + case PaintToolWindow::MODE_SOFTBODY_PHYSICS: + { + ObjectComponent* object = scene.objects.GetComponent(selected.entity); + if (object == nullptr || object->meshID == INVALID_ENTITY) + break; + + SoftBodyPhysicsComponent* softbody = scene.softbodies.GetComponent(object->meshID); + if (softbody == nullptr) + break; + + SoftBodyPhysicsComponent archive_softbody; + archive >> archive_softbody.weights; + + softbody->weights = archive_softbody.weights; + + softbody->_flags |= SoftBodyPhysicsComponent::FORCE_RESET; + } + break; + case PaintToolWindow::MODE_HAIRPARTICLE_ADD_TRIANGLE: + case PaintToolWindow::MODE_HAIRPARTICLE_REMOVE_TRIANGLE: + case PaintToolWindow::MODE_HAIRPARTICLE_LENGTH: + { + wi::HairParticleSystem* hair = scene.hairs.GetComponent(selected.entity); + if (hair == nullptr) + break; + + wi::HairParticleSystem archive_hair; + archive >> archive_hair.vertex_lengths; + + hair->vertex_lengths = archive_hair.vertex_lengths; + + hair->_flags |= wi::HairParticleSystem::REBUILD_BUFFERS; + } + break; + default: + assert(0); + break; + } } } Texture PaintToolWindow::GetEditTextureSlot(const MaterialComponent& material, int* uvset) @@ -1595,11 +1617,12 @@ void PaintToolWindow::ResizeLayout() add_fullwidth(infoLabel); add(radiusSlider); add(amountSlider); - add(falloffSlider); + add(smoothnessSlider); add(spacingSlider); add(rotationSlider); + add(stabilizerSlider); add_right(backfaceCheckBox); - add_right(wireCheckBox); + wireCheckBox.SetPos(XMFLOAT2(backfaceCheckBox.GetPos().x - wireCheckBox.GetSize().x - 100, backfaceCheckBox.GetPos().y)); add_right(pressureCheckBox); add(textureSlotComboBox); add(brushShapeComboBox); diff --git a/Editor/PaintToolWindow.h b/Editor/PaintToolWindow.h index 9e453101a..4f0d01fea 100644 --- a/Editor/PaintToolWindow.h +++ b/Editor/PaintToolWindow.h @@ -1,6 +1,8 @@ #pragma once #include "WickedEngine.h" +#include + class EditorComponent; class PaintToolWindow : public wi::gui::Window @@ -9,6 +11,7 @@ class PaintToolWindow : public wi::gui::Window float stroke_dist = 0; bool history_needs_recording_start = false; bool history_needs_recording_end = false; + size_t history_redo_jump_position = 0; size_t history_textureIndex = 0; wi::vector history_textures; // we'd like to keep history textures in GPU memory to avoid GPU readback wi::graphics::Texture GetEditTextureSlot(const wi::scene::MaterialComponent& material, int* uvset = nullptr); @@ -25,20 +28,26 @@ class PaintToolWindow : public wi::gui::Window wi::Resource brushTex; wi::Resource revealTex; + struct Stroke + { + XMFLOAT2 position; + float pressure; + }; + std::deque strokes; + public: void Create(EditorComponent* editor); EditorComponent* editor = nullptr; - wi::ecs::Entity entity = wi::ecs::INVALID_ENTITY; - int subset = -1; wi::gui::ComboBox modeComboBox; wi::gui::Label infoLabel; wi::gui::Slider radiusSlider; wi::gui::Slider amountSlider; - wi::gui::Slider falloffSlider; + wi::gui::Slider smoothnessSlider; wi::gui::Slider spacingSlider; wi::gui::Slider rotationSlider; + wi::gui::Slider stabilizerSlider; wi::gui::CheckBox backfaceCheckBox; wi::gui::CheckBox wireCheckBox; wi::gui::CheckBox pressureCheckBox; @@ -70,7 +79,6 @@ public: MODE_WIND, }; MODE GetMode() const; - void SetEntity(wi::ecs::Entity value, int subsetindex = -1); enum class AxisLock { diff --git a/Editor/config.ini b/Editor/config.ini index 8eedeb200..526267549 100644 --- a/Editor/config.ini +++ b/Editor/config.ini @@ -78,3 +78,4 @@ fps = true pressure = true backfaces = false wireframe = true +stabilizer = 8 diff --git a/Editor/main_Windows.cpp b/Editor/main_Windows.cpp index 481e81daf..52e570550 100644 --- a/Editor/main_Windows.cpp +++ b/Editor/main_Windows.cpp @@ -261,7 +261,6 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_INPUT: wi::input::rawinput::ParseMessage((void*)lParam); break; - case WM_POINTERDOWN: case WM_POINTERUPDATE: { POINTER_PEN_INFO pen_info = {}; diff --git a/WickedEngine/shaders/ShaderInterop_Renderer.h b/WickedEngine/shaders/ShaderInterop_Renderer.h index cedadee3e..529a6eb2d 100644 --- a/WickedEngine/shaders/ShaderInterop_Renderer.h +++ b/WickedEngine/shaders/ShaderInterop_Renderer.h @@ -879,7 +879,7 @@ struct PaintTextureCB uint xPaintBrushRadius; float xPaintBrushAmount; - float xPaintBrushFalloff; + float xPaintBrushSmoothness; uint xPaintBrushColor; uint xPaintReveal; float xPaintBrushRotation; diff --git a/WickedEngine/shaders/paint_textureCS.hlsl b/WickedEngine/shaders/paint_textureCS.hlsl index e91d4aa1d..e9823fdac 100644 --- a/WickedEngine/shaders/paint_textureCS.hlsl +++ b/WickedEngine/shaders/paint_textureCS.hlsl @@ -49,7 +49,7 @@ void main( uint3 DTid : SV_DispatchThreadID ) const float2 reveal_uv_dy = reveal_uv - reveal_uv_quad_y; float4 reveal_color = texture_reveal.SampleGrad(sampler_linear_clamp, reveal_uv, reveal_uv_dx, reveal_uv_dy); - const float affection = push.xPaintBrushAmount * pow(1 - saturate(dist / radius), push.xPaintBrushFalloff); + const float affection = push.xPaintBrushAmount * smoothstep(0, push.xPaintBrushSmoothness, 1 - dist / radius); if (affection > 0 && dist < radius) { if (push.xPaintReveal) diff --git a/WickedEngine/wiECS.h b/WickedEngine/wiECS.h index 18f1bea5e..580a2bf9b 100644 --- a/WickedEngine/wiECS.h +++ b/WickedEngine/wiECS.h @@ -101,8 +101,10 @@ namespace wi::ecs }; // The ComponentManager is a container that stores components and matches them with entities + // Note: final keyword is used to indicate this is a final implementation. + // This allows function inlining and avoid calls, improves performance considerably template - class ComponentManager : public ComponentManager_Interface + class ComponentManager final : public ComponentManager_Interface { public: diff --git a/WickedEngine/wiInput.h b/WickedEngine/wiInput.h index d03b37f40..05b05a715 100644 --- a/WickedEngine/wiInput.h +++ b/WickedEngine/wiInput.h @@ -140,8 +140,8 @@ namespace wi::input // これはペンです struct Pen { - XMFLOAT2 position; - float pressure; + XMFLOAT2 position = {}; + float pressure = 0; }; // Set pen params, this will override mouse params void SetPen(const Pen& pen); diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index b5a8675e0..f522e957b 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wi::version // minor features, major updates, breaking compatibility changes const int minor = 71; // minor bug fixes, alterations, refactors, updates - const int revision = 19; + const int revision = 20; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);