Files

123 lines
5.0 KiB
C++

#include "editor/VolumeBrushTool.h"
#include "editor/BrushKernel.h"
#include "editor/EditorPresentation.h"
#include "editor/EditorSession.h"
#include "editor/EditorVolumeTarget.h"
#include <algorithm>
#include <cmath>
#include <utility>
namespace eve::editor {
namespace {
float falloffWeight(const IBrushFalloff* falloff, float normalizedDistance) {
return falloff ? falloff->evaluate(normalizedDistance) : (normalizedDistance <= 1.f ? 1.f : 0.f);
}
} // namespace
void SphereVolumeBrushKernel::sample(float x, float y, float z, float radius,
std::vector<VolumeBrushPoint>& out) const {
out.clear();
radius = std::max(0.5f, radius);
const int minX = static_cast<int>(std::floor(x - radius));
const int maxX = static_cast<int>(std::ceil(x + radius));
const int minY = static_cast<int>(std::floor(y - radius));
const int maxY = static_cast<int>(std::ceil(y + radius));
const int minZ = static_cast<int>(std::floor(z - radius));
const int maxZ = static_cast<int>(std::ceil(z + radius));
for (int iz = minZ; iz <= maxZ; ++iz)
for (int iy = minY; iy <= maxY; ++iy)
for (int ix = minX; ix <= maxX; ++ix) {
const float dx = static_cast<float>(ix) - x;
const float dy = static_cast<float>(iy) - y;
const float dz = static_cast<float>(iz) - z;
const float distance = std::sqrt(dx * dx + dy * dy + dz * dz);
if (distance <= radius) out.push_back({ix, iy, iz, falloffWeight(falloff_, distance / radius)});
}
}
void BoxVolumeBrushKernel::sample(float x, float y, float z, float radius,
std::vector<VolumeBrushPoint>& out) const {
out.clear();
radius = std::max(0.5f, radius);
const int minX = static_cast<int>(std::ceil(x - radius));
const int maxX = static_cast<int>(std::floor(x + radius));
const int minY = static_cast<int>(std::ceil(y - radius));
const int maxY = static_cast<int>(std::floor(y + radius));
const int minZ = static_cast<int>(std::ceil(z - radius));
const int maxZ = static_cast<int>(std::floor(z + radius));
for (int iz = minZ; iz <= maxZ; ++iz)
for (int iy = minY; iy <= maxY; ++iy)
for (int ix = minX; ix <= maxX; ++ix) {
const float distance = std::max({std::fabs(static_cast<float>(ix) - x),
std::fabs(static_cast<float>(iy) - y),
std::fabs(static_cast<float>(iz) - z)});
out.push_back({ix, iy, iz, falloffWeight(falloff_, distance / radius)});
}
}
PaintIntVolumeOperation::PaintIntVolumeOperation(int value) { setValue(value); }
void PaintIntVolumeOperation::setValue(int value) { value_ = std::clamp(value, 0, 255); }
std::unique_ptr<IEditCommand> PaintIntVolumeOperation::createCommand(
IEditableTarget* target, const std::vector<VolumeBrushPoint>& points) const {
if (!target || !target->query<IIntVolumeTarget>()) return nullptr;
auto command = std::make_unique<IntVolumeEditCommand>("paint integer volume", target);
for (const auto& point : points) {
if (point.weight > 0.f) command->record(point.x, point.y, point.z, value_);
}
return command;
}
VolumeBrushTool::VolumeBrushTool(std::string id, std::string label)
: descriptor_{std::move(id), std::move(label), {}} {}
void VolumeBrushTool::setRadius(float radius) { radius_ = std::max(0.5f, radius); }
bool VolumeBrushTool::stamp(EditorContext& context, float x, float y, float z) {
if (!kernel_ || !operation_ || !context.target()) return false;
kernel_->sample(x, y, z, radius_, points_);
auto command = operation_->createCommand(context.target(), points_);
return command && context.execute(std::move(command));
}
ToolResponse VolumeBrushTool::pointerEvent(EditorContext& context, const EditorPointerEvent& event) {
if (event.phase == EditorPointerEvent::Phase::Down) {
if (!kernel_ || !operation_ || !context.target() ||
!context.transactions().begin(descriptor_.label)) return ToolResponse::ignored();
stroking_ = true;
stamp(context, event.x, event.y, event.z);
return ToolResponse::capture();
}
if (!stroking_) return ToolResponse::ignored();
if (event.phase == EditorPointerEvent::Phase::Move) {
stamp(context, event.x, event.y, event.z);
return ToolResponse::consumed();
}
if (event.phase == EditorPointerEvent::Phase::Up) {
context.transactions().commit();
stroking_ = false;
return ToolResponse::release();
}
cancel(context);
return ToolResponse::release();
}
void VolumeBrushTool::cancel(EditorContext& context) {
if (stroking_) context.transactions().rollback();
stroking_ = false;
}
void VolumeBrushTool::inspect(EditorContext&, IEditorInspector& inspector) {
inspector.beginGroup(descriptor_.id, descriptor_.label);
inspector.scalar("radius", "Radius", radius_, 0.5f, 256.f);
inspector.endGroup();
setRadius(radius_);
}
} // namespace eve::editor