428 lines
14 KiB
C++
428 lines
14 KiB
C++
#include "stdafx.h"
|
|
#include "PaintToolWindow.h"
|
|
|
|
using namespace wiECS;
|
|
using namespace wiScene;
|
|
using namespace wiGraphics;
|
|
|
|
PaintToolWindow::PaintToolWindow(wiGUI* gui) : GUI(gui)
|
|
{
|
|
assert(GUI && "Invalid GUI!");
|
|
|
|
window = new wiWindow(GUI, "Paint Tool Window");
|
|
window->SetSize(XMFLOAT2(400, 500));
|
|
GUI->AddWidget(window);
|
|
|
|
float x = 100;
|
|
float y = 10;
|
|
float step = 30;
|
|
|
|
modeComboBox = new wiComboBox("Mode: ");
|
|
modeComboBox->SetTooltip("Choose paint tool mode");
|
|
modeComboBox->SetPos(XMFLOAT2(x, y += step));
|
|
modeComboBox->SetSize(XMFLOAT2(200, 28));
|
|
modeComboBox->AddItem("Disabled");
|
|
modeComboBox->AddItem("Vertexcolor");
|
|
modeComboBox->AddItem("Softbody - Pinning");
|
|
modeComboBox->AddItem("Softbody - Physics");
|
|
modeComboBox->AddItem("Hairparticle - Add Triangle");
|
|
modeComboBox->AddItem("Hairparticle - Remove Triangle");
|
|
modeComboBox->AddItem("Hairparticle - Length (Alpha)");
|
|
modeComboBox->SetSelected(0);
|
|
window->AddWidget(modeComboBox);
|
|
|
|
y += step;
|
|
|
|
radiusSlider = new wiSlider(1.0f, 500.0f, 50, 10000, "Brush Radius: ");
|
|
radiusSlider->SetTooltip("Set the brush radius in pixel units");
|
|
radiusSlider->SetSize(XMFLOAT2(200, 20));
|
|
radiusSlider->SetPos(XMFLOAT2(x, y += step));
|
|
window->AddWidget(radiusSlider);
|
|
|
|
amountSlider = new wiSlider(0, 1, 1, 10000, "Brush Amount: ");
|
|
amountSlider->SetTooltip("Set the brush amount. 0 = minimum affection, 1 = maximum affection");
|
|
amountSlider->SetSize(XMFLOAT2(200, 20));
|
|
amountSlider->SetPos(XMFLOAT2(x, y += step));
|
|
window->AddWidget(amountSlider);
|
|
|
|
falloffSlider = new wiSlider(0, 16, 0, 10000, "Brush Falloff: ");
|
|
falloffSlider->SetTooltip("Set the brush power. 0 = no falloff, 1 = linear falloff, more = falloff power");
|
|
falloffSlider->SetSize(XMFLOAT2(200, 20));
|
|
falloffSlider->SetPos(XMFLOAT2(x, y += step));
|
|
window->AddWidget(falloffSlider);
|
|
|
|
backfaceCheckBox = new wiCheckBox("Backfaces: ");
|
|
backfaceCheckBox->SetTooltip("Set whether to paint on backfaces of geometry or not");
|
|
backfaceCheckBox->SetPos(XMFLOAT2(x, y += step));
|
|
window->AddWidget(backfaceCheckBox);
|
|
|
|
colorPicker = new wiColorPicker(GUI, "Color", false);
|
|
colorPicker->SetPos(XMFLOAT2(10, y += step));
|
|
window->AddWidget(colorPicker);
|
|
|
|
window->Translate(XMFLOAT3((float)wiRenderer::GetDevice()->GetScreenWidth() - 550, 50, 0));
|
|
window->SetVisible(false);
|
|
}
|
|
PaintToolWindow::~PaintToolWindow()
|
|
{
|
|
window->RemoveWidgets(true);
|
|
GUI->RemoveWidget(window);
|
|
delete window;
|
|
}
|
|
|
|
void PaintToolWindow::Update(float dt)
|
|
{
|
|
rot -= dt;
|
|
// by default, paint tool is on center of screen, this makes it easy to tweak radius with GUI:
|
|
pos.x = wiRenderer::GetDevice()->GetScreenWidth() * 0.5f;
|
|
pos.y = wiRenderer::GetDevice()->GetScreenHeight() * 0.5f;
|
|
if (GUI->HasFocus() || wiBackLog::isActive() || entity == INVALID_ENTITY)
|
|
return;
|
|
|
|
auto pointer = wiInput::GetPointer();
|
|
pos.x = pointer.x;
|
|
pos.y = pointer.y;
|
|
|
|
const bool pointer_moved = wiMath::Distance(pos, posPrev) > 1.0f;
|
|
const bool painting = pointer_moved && wiInput::Down(wiInput::MOUSE_BUTTON_LEFT);
|
|
|
|
if (wiInput::Down(wiInput::MOUSE_BUTTON_LEFT))
|
|
{
|
|
posPrev = pos;
|
|
}
|
|
else
|
|
{
|
|
posPrev = XMFLOAT2(0, 0);
|
|
}
|
|
|
|
const MODE mode = GetMode();
|
|
const float radius = radiusSlider->GetValue();
|
|
const float amount = amountSlider->GetValue();
|
|
const float falloff = falloffSlider->GetValue();
|
|
const wiColor color = colorPicker->GetPickColor();
|
|
const XMFLOAT4 color_float = color.toFloat4();
|
|
const bool backfaces = backfaceCheckBox->GetCheck();
|
|
|
|
Scene& scene = wiScene::GetScene();
|
|
const CameraComponent& camera = wiRenderer::GetCamera();
|
|
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)wiRenderer::GetDevice()->GetScreenWidth(), (float)wiRenderer::GetDevice()->GetScreenHeight(), 1, 1);
|
|
|
|
switch (mode)
|
|
{
|
|
case 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;
|
|
|
|
const TransformComponent* transform = scene.transforms.GetComponent(entity);
|
|
if (transform == nullptr)
|
|
break;
|
|
|
|
const XMMATRIX W = XMLoadFloat4x4(&transform->world);
|
|
|
|
bool rebuild = false;
|
|
for (size_t j = 0; j < mesh->indices.size(); j += 3)
|
|
{
|
|
const uint32_t triangle[] = {
|
|
mesh->indices[j + 0],
|
|
mesh->indices[j + 1],
|
|
mesh->indices[j + 2],
|
|
};
|
|
|
|
if (painting)
|
|
{
|
|
XMVECTOR P[arraysize(triangle)];
|
|
for (int k = 0; k < arraysize(triangle); ++k)
|
|
{
|
|
const XMFLOAT3& pos = mesh->vertex_positions[triangle[k]];
|
|
P[k] = XMLoadFloat3(&pos);
|
|
P[k] = XMVector3Transform(P[k], W);
|
|
P[k] = XMVector3TransformCoord(P[k], VP);
|
|
P[k] = P[k] * MUL + ADD;
|
|
P[k] = P[k] * SCREEN;
|
|
}
|
|
|
|
bool culled = false;
|
|
if (!backfaces)
|
|
{
|
|
const XMVECTOR N = XMVector3Normalize(XMVector3Cross(P[1] - P[0], P[2] - P[1]));
|
|
culled = XMVectorGetZ(N) > 0;
|
|
}
|
|
|
|
if (!culled)
|
|
{
|
|
for (int k = 0; k < arraysize(triangle); ++k)
|
|
{
|
|
const float z = XMVectorGetZ(P[k]);
|
|
const float dist = XMVectorGetX(XMVector2Length(C - P[k]));
|
|
if (z >= 0 && z <= 1 && dist <= radius)
|
|
{
|
|
if (mesh->vertex_colors.empty())
|
|
{
|
|
mesh->vertex_colors.resize(mesh->vertex_positions.size());
|
|
std::fill(mesh->vertex_colors.begin(), mesh->vertex_colors.end(), 0xFFFFFFFF); // fill white
|
|
}
|
|
wiColor vcol = mesh->vertex_colors[triangle[k]];
|
|
const float affection = amount * std::powf(1 - (dist / radius), falloff);
|
|
vcol = wiColor::lerp(vcol, color, affection);
|
|
mesh->vertex_colors[triangle[k]] = vcol.rgba;
|
|
rebuild = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
wiRenderer::RenderableTriangle tri;
|
|
XMStoreFloat3(&tri.positionA, XMVector3Transform(XMLoadFloat3(&mesh->vertex_positions[triangle[0]]), W));
|
|
XMStoreFloat3(&tri.positionB, XMVector3Transform(XMLoadFloat3(&mesh->vertex_positions[triangle[1]]), W));
|
|
XMStoreFloat3(&tri.positionC, XMVector3Transform(XMLoadFloat3(&mesh->vertex_positions[triangle[2]]), W));
|
|
tri.colorA.w = 0.8f;
|
|
tri.colorB.w = 0.8f;
|
|
tri.colorC.w = 0.8f;
|
|
wiRenderer::DrawTriangle(tri, true);
|
|
}
|
|
|
|
if (rebuild)
|
|
{
|
|
mesh->CreateRenderData();
|
|
}
|
|
}
|
|
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 && wiInput::Down(wiInput::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)) <= radius)
|
|
{
|
|
softbody->weights[j] = (mode == MODE_SOFTBODY_PINNING ? 0.0f : 1.0f);
|
|
softbody->_flags |= SoftBodyPhysicsComponent::FORCE_RESET;
|
|
}
|
|
j++;
|
|
}
|
|
}
|
|
|
|
// Visualizing:
|
|
const XMMATRIX W = XMLoadFloat4x4(&softbody->worldMatrix);
|
|
for (size_t j = 0; j < mesh->indices.size(); 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];
|
|
wiRenderer::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);
|
|
wiRenderer::DrawTriangle(tri, true);
|
|
if (weight0 == 0 && weight1 == 0 && weight2 == 0)
|
|
{
|
|
tri.colorA = tri.colorB = tri.colorC = XMFLOAT4(1, 0, 0, 0.8f);
|
|
wiRenderer::DrawTriangle(tri);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MODE_HAIRPARTICLE_ADD_TRIANGLE:
|
|
case MODE_HAIRPARTICLE_REMOVE_TRIANGLE:
|
|
case MODE_HAIRPARTICLE_LENGTH:
|
|
{
|
|
wiHairParticle* 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 TransformComponent* transform = scene.transforms.GetComponent(entity);
|
|
if (transform == nullptr)
|
|
break;
|
|
|
|
const XMMATRIX W = XMLoadFloat4x4(&transform->world);
|
|
|
|
for (size_t j = 0; j < mesh->indices.size(); j += 3)
|
|
{
|
|
const uint32_t triangle[] = {
|
|
mesh->indices[j + 0],
|
|
mesh->indices[j + 1],
|
|
mesh->indices[j + 2],
|
|
};
|
|
|
|
if (painting)
|
|
{
|
|
XMVECTOR P[arraysize(triangle)];
|
|
for (int k = 0; k < arraysize(triangle); ++k)
|
|
{
|
|
const XMFLOAT3& pos = mesh->vertex_positions[triangle[k]];
|
|
P[k] = XMLoadFloat3(&pos);
|
|
P[k] = XMVector3Transform(P[k], W);
|
|
P[k] = XMVector3TransformCoord(P[k], VP);
|
|
P[k] = P[k] * MUL + ADD;
|
|
P[k] = P[k] * SCREEN;
|
|
}
|
|
|
|
bool culled = false;
|
|
if (!backfaces)
|
|
{
|
|
const XMVECTOR N = XMVector3Normalize(XMVector3Cross(P[1] - P[0], P[2] - P[1]));
|
|
culled = XMVectorGetZ(N) > 0;
|
|
}
|
|
|
|
if (!culled)
|
|
{
|
|
for (int k = 0; k < arraysize(triangle); ++k)
|
|
{
|
|
const float z = XMVectorGetZ(P[k]);
|
|
const float dist = XMVectorGetX(XMVector2Length(C - P[k]));
|
|
if (z >= 0 && z <= 1 && dist <= radius)
|
|
{
|
|
switch (mode)
|
|
{
|
|
case MODE_HAIRPARTICLE_ADD_TRIANGLE:
|
|
hair->vertex_lengths[triangle[k]] = 1.0f;
|
|
break;
|
|
case MODE_HAIRPARTICLE_REMOVE_TRIANGLE:
|
|
hair->vertex_lengths[triangle[k]] = 0;
|
|
break;
|
|
case MODE_HAIRPARTICLE_LENGTH:
|
|
const float affection = amount * std::powf(1 - (dist / radius), falloff);
|
|
hair->vertex_lengths[triangle[k]] = wiMath::Lerp(hair->vertex_lengths[triangle[k]], 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[triangle[k]] = wiMath::Clamp(hair->vertex_lengths[triangle[k]], 1.0f / 255.0f, 1.0f);
|
|
break;
|
|
}
|
|
hair->_flags |= wiHairParticle::REBUILD_BUFFERS;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
wiRenderer::RenderableTriangle tri;
|
|
XMStoreFloat3(&tri.positionA, XMVector3Transform(XMLoadFloat3(&mesh->vertex_positions[triangle[0]]), W));
|
|
XMStoreFloat3(&tri.positionB, XMVector3Transform(XMLoadFloat3(&mesh->vertex_positions[triangle[1]]), W));
|
|
XMStoreFloat3(&tri.positionC, XMVector3Transform(XMLoadFloat3(&mesh->vertex_positions[triangle[2]]), W));
|
|
wiRenderer::DrawTriangle(tri, true);
|
|
}
|
|
|
|
for (size_t j = 0; j < hair->indices.size(); j += 3)
|
|
{
|
|
const uint32_t triangle[] = {
|
|
hair->indices[j + 0],
|
|
hair->indices[j + 1],
|
|
hair->indices[j + 2],
|
|
};
|
|
|
|
wiRenderer::RenderableTriangle tri;
|
|
XMStoreFloat3(&tri.positionA, XMVector3Transform(XMLoadFloat3(&mesh->vertex_positions[triangle[0]]), W));
|
|
XMStoreFloat3(&tri.positionB, XMVector3Transform(XMLoadFloat3(&mesh->vertex_positions[triangle[1]]), W));
|
|
XMStoreFloat3(&tri.positionC, XMVector3Transform(XMLoadFloat3(&mesh->vertex_positions[triangle[2]]), W));
|
|
tri.colorA = tri.colorB = tri.colorC = XMFLOAT4(1, 0, 1, 0.9f);
|
|
wiRenderer::DrawTriangle(tri, false);
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
void PaintToolWindow::DrawBrush() const
|
|
{
|
|
if (GetMode() == MODE_DISABLED || entity == INVALID_ENTITY || wiBackLog::isActive())
|
|
return;
|
|
|
|
const float radius = radiusSlider->GetValue();
|
|
const int segmentcount = 36;
|
|
for (int i = 0; i < segmentcount; i += 1)
|
|
{
|
|
const float angle0 = (float)i / (float)segmentcount * XM_2PI;
|
|
const float angle1 = (float)(i + 1) / (float)segmentcount * XM_2PI;
|
|
wiRenderer::RenderableLine2D line;
|
|
line.start.x = pos.x + sinf(angle0) * radius;
|
|
line.start.y = pos.y + cosf(angle0) * radius;
|
|
line.end.x = pos.x + sinf(angle1) * radius;
|
|
line.end.y = pos.y + cosf(angle1) * radius;
|
|
line.color_end = line.color_start = XMFLOAT4(0, 0, 0, 0.8f);
|
|
wiRenderer::DrawLine(line);
|
|
}
|
|
|
|
for (int i = 0; i < segmentcount; i += 2)
|
|
{
|
|
const float angle0 = rot + (float)i / (float)segmentcount * XM_2PI;
|
|
const float angle1 = rot + (float)(i + 1) / (float)segmentcount * XM_2PI;
|
|
wiRenderer::RenderableLine2D line;
|
|
line.start.x = pos.x + sinf(angle0) * radius;
|
|
line.start.y = pos.y + cosf(angle0) * radius;
|
|
line.end.x = pos.x + sinf(angle1) * radius;
|
|
line.end.y = pos.y + cosf(angle1) * radius;
|
|
wiRenderer::DrawLine(line);
|
|
}
|
|
}
|
|
|
|
PaintToolWindow::MODE PaintToolWindow::GetMode() const
|
|
{
|
|
return (MODE)modeComboBox->GetSelected();
|
|
}
|
|
void PaintToolWindow::SetEntity(wiECS::Entity value)
|
|
{
|
|
entity = value;
|
|
}
|