373 lines
No EOL
12 KiB
C++
373 lines
No EOL
12 KiB
C++
#define LUMIX_NO_CUSTOM_CRT
|
|
|
|
#include "../model.h"
|
|
#include "../renderer.h"
|
|
#include "../render_scene.h"
|
|
#include "editor/studio_app.h"
|
|
#include "editor/world_editor.h"
|
|
#include "engine/core.h"
|
|
#include "engine/engine.h"
|
|
#include "engine/universe.h"
|
|
#include "imgui/imgui.h"
|
|
#include "spline_geometry_plugin.h"
|
|
|
|
namespace Lumix {
|
|
|
|
static const ComponentType SPLINE_GEOMETRY_TYPE = reflection::getComponentType("spline_geometry");
|
|
static const ComponentType SPLINE_TYPE = reflection::getComponentType("spline");
|
|
|
|
struct SplineIterator {
|
|
SplineIterator(Span<const Vec3> points) : points(points) {}
|
|
|
|
void move(float delta) { t += delta; }
|
|
bool isEnd() { return u32(t) >= points.length() - 2; }
|
|
Vec3 getDir() {
|
|
const u32 segment = u32(t);
|
|
float rel_t = t - segment;
|
|
Vec3 p0 = points[segment + 0];
|
|
Vec3 p1 = points[segment + 1];
|
|
Vec3 p2 = points[segment + 2];
|
|
return lerp(p1 - p0, p2 - p1, rel_t);
|
|
}
|
|
|
|
Vec3 getPosition() {
|
|
const u32 segment = u32(t);
|
|
float rel_t = t - segment;
|
|
Vec3 p0 = points[segment + 0];
|
|
Vec3 p1 = points[segment + 1];
|
|
Vec3 p2 = points[segment + 2];
|
|
p0 = (p1 + p0) * 0.5f;
|
|
p2 = (p1 + p2) * 0.5f;
|
|
|
|
return lerp(lerp(p0, p1, rel_t), lerp(p1, p2, rel_t), rel_t);
|
|
}
|
|
|
|
float t = 0;
|
|
|
|
Span<const Vec3> points;
|
|
};
|
|
|
|
SplineGeometryPlugin::SplineGeometryPlugin(StudioApp& app)
|
|
: m_app(app)
|
|
{}
|
|
|
|
void SplineGeometryPlugin::paint(const DVec3& pos
|
|
, const Universe& universe
|
|
, EntityRef entity
|
|
, const SplineGeometry& sg
|
|
, ProceduralGeometry& pg
|
|
, Renderer& renderer) const
|
|
{
|
|
if (pg.vertex_data.size() == 0) return;
|
|
|
|
// TODO undo/redo
|
|
|
|
const Transform tr = universe.getTransform(entity);
|
|
const Vec3 center(tr.inverted().transform(pos));
|
|
|
|
const float R2 = m_brush_size * m_brush_size;
|
|
|
|
const u8* end = pg.vertex_data.data() + pg.vertex_data.size();
|
|
const u32 stride = pg.vertex_decl.getStride();
|
|
ASSERT(stride != 0);
|
|
const u32 offset = (sg.flags.isSet(SplineGeometry::HAS_UVS) ? 20 : 12) + m_brush_channel;
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
for (u8* iter = pg.vertex_data.getMutableData(); iter < end; iter += stride) {
|
|
Vec3 p;
|
|
memcpy(&p, iter, sizeof(p));
|
|
|
|
if (squaredLength(p - center) < R2) {
|
|
*(iter + offset) = io.KeyAlt ? 255 - m_brush_value : m_brush_value;
|
|
}
|
|
}
|
|
|
|
if (pg.vertex_buffer) renderer.destroy(pg.vertex_buffer);
|
|
const Renderer::MemRef mem = renderer.copy(pg.vertex_data.data(), (u32)pg.vertex_data.size());
|
|
pg.vertex_buffer = renderer.createBuffer(mem, gpu::BufferFlags::IMMUTABLE);
|
|
}
|
|
|
|
bool SplineGeometryPlugin::paint(UniverseView& view, i32 x, i32 y) {
|
|
WorldEditor& editor = view.getEditor();
|
|
const Array<EntityRef>& selected = editor.getSelectedEntities();
|
|
if (selected.size() != 1) return false;
|
|
|
|
const EntityRef entity = selected[0];
|
|
|
|
const Universe& universe = *editor.getUniverse();
|
|
const bool is_spline = universe.hasComponent(entity, SPLINE_GEOMETRY_TYPE);
|
|
if (!is_spline) return false;
|
|
|
|
RenderScene* scene = (RenderScene*)universe.getScene(SPLINE_GEOMETRY_TYPE);
|
|
DVec3 origin;
|
|
Vec3 dir;
|
|
view.getViewport().getRay({(float)x, (float)y}, origin, dir);
|
|
const RayCastModelHit hit = scene->castRayProceduralGeometry(origin, dir, [entity](const RayCastModelHit& hit) {
|
|
return hit.entity == entity;
|
|
});
|
|
if (!hit.is_hit) return false;
|
|
if (hit.entity != entity) return false;
|
|
|
|
Renderer* renderer = (Renderer*)editor.getEngine().getPluginManager().getPlugin("renderer");
|
|
ASSERT(renderer);
|
|
|
|
ProceduralGeometry& pg = scene->getProceduralGeometry(entity);
|
|
const SplineGeometry& sg = scene->getSplineGeometry(entity);
|
|
paint(hit.origin + hit.t * hit.dir, universe, entity, sg, pg, *renderer);
|
|
|
|
return true;
|
|
}
|
|
|
|
void SplineGeometryPlugin::onMouseWheel(float value) {
|
|
m_brush_size = maximum(0.f, m_brush_size + value * 0.2f);
|
|
}
|
|
|
|
bool SplineGeometryPlugin::onMouseDown(UniverseView& view, int x, int y) {
|
|
return paint(view, x, y);
|
|
}
|
|
|
|
void SplineGeometryPlugin::onMouseUp(UniverseView& view, int x, int y, os::MouseButton button) {
|
|
}
|
|
|
|
void SplineGeometryPlugin::onMouseMove(UniverseView& view, int x, int y, int rel_x, int rel_y) {
|
|
paint(view, x, y);
|
|
}
|
|
|
|
void SplineGeometryPlugin::drawCursor(WorldEditor& editor, EntityRef entity) const {
|
|
const UniverseView& view = editor.getView();
|
|
const Vec2 mp = view.getMousePos();
|
|
Universe& universe = *editor.getUniverse();
|
|
|
|
RenderScene* scene = static_cast<RenderScene*>(universe.getScene(SPLINE_GEOMETRY_TYPE));
|
|
DVec3 origin;
|
|
Vec3 dir;
|
|
editor.getView().getViewport().getRay(mp, origin, dir);
|
|
const RayCastModelHit hit = scene->castRayProceduralGeometry(origin, dir, [entity](const RayCastModelHit& hit){
|
|
return hit.entity == entity;
|
|
});
|
|
|
|
if (hit.is_hit) {
|
|
const DVec3 center = hit.origin + hit.dir * hit.t;
|
|
drawCursor(editor, *scene, entity, center);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void SplineGeometryPlugin::drawCursor(WorldEditor& editor, RenderScene& scene, EntityRef entity, const DVec3& center) const {
|
|
UniverseView& view = editor.getView();
|
|
addCircle(view, center, m_brush_size, Vec3(0, 1, 0), Color::GREEN);
|
|
const ProceduralGeometry& pg = scene.getProceduralGeometry(entity);
|
|
|
|
if (pg.vertex_data.size() == 0) return;
|
|
|
|
const u8* data = pg.vertex_data.data();
|
|
const u32 stride = pg.vertex_decl.getStride();
|
|
|
|
const float R2 = m_brush_size * m_brush_size;
|
|
|
|
const Transform tr = scene.getUniverse().getTransform(entity);
|
|
const Vec3 center_local = Vec3(tr.inverted().transform(center));
|
|
|
|
for (u32 i = 0, c = pg.getVertexCount(); i < c; ++i) {
|
|
Vec3 p;
|
|
memcpy(&p, data + stride * i, sizeof(p));
|
|
if (squaredLength(center_local - p) < R2) {
|
|
addCircle(view, tr.transform(p), 0.1f, Vec3(0, 1, 0), Color::BLUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char* toString(SplineGeometryPlugin::GeometryMode mode) {
|
|
switch (mode) {
|
|
case SplineGeometryPlugin::GeometryMode::NO_SNAP: return "No snap";
|
|
case SplineGeometryPlugin::GeometryMode::SNAP_CENTER: return "Snap center";
|
|
case SplineGeometryPlugin::GeometryMode::SNAP_ALL: return "Snap everything";
|
|
}
|
|
ASSERT(false);
|
|
return "N/A";
|
|
}
|
|
|
|
void SplineGeometryPlugin::onGUI(PropertyGrid& grid, Span<const EntityRef> entities, ComponentType cmp_type, WorldEditor& editor) {
|
|
if (cmp_type != SPLINE_GEOMETRY_TYPE) return;
|
|
|
|
Universe& universe = *editor.getUniverse();
|
|
for (EntityRef e : entities) {
|
|
if (!universe.hasComponent(e, SPLINE_GEOMETRY_TYPE)) return;
|
|
}
|
|
|
|
RenderScene* render_scene = (RenderScene*)universe.getScene(SPLINE_GEOMETRY_TYPE);
|
|
CoreScene* core_scene = (CoreScene*)universe.getScene(SPLINE_TYPE);
|
|
if (entities.length() == 1) {
|
|
const EntityRef e = entities[0];
|
|
if (!universe.hasComponent(e, SPLINE_TYPE)) {
|
|
ImGui::TextUnformatted("There's no spline component");
|
|
if (ImGui::Button("Create spline component")) {
|
|
editor.addComponent(Span(&e, 1), SPLINE_TYPE);
|
|
}
|
|
return;
|
|
}
|
|
else {
|
|
const Spline& spline = core_scene->getSpline(e);
|
|
const SplineGeometry& sg = render_scene->getSplineGeometry(e);
|
|
const ProceduralGeometry& pg = render_scene->getProceduralGeometry(e);
|
|
|
|
drawCursor(editor, e);
|
|
|
|
ImGuiEx::Label("Triangles");
|
|
ImGui::Text("%d", u32(pg.index_data.size() / (pg.index_type == gpu::DataType::U16 ? 2 : 4) / 3));
|
|
|
|
ImGui::Separator();
|
|
|
|
ImGuiEx::Label("Brush size");
|
|
ImGui::DragFloat("##bs", &m_brush_size, 0.1f, 0, FLT_MAX);
|
|
|
|
if (sg.num_user_channels > 1) {
|
|
ImGuiEx::Label("Paint channel");
|
|
ImGui::SliderInt("##pc", (int*)&m_brush_channel, 0, sg.num_user_channels - 1);
|
|
}
|
|
|
|
ImGuiEx::Label("Paint value");
|
|
ImGui::SliderInt("##pv", (int*)&m_brush_value, 0, 255);
|
|
}
|
|
}
|
|
|
|
ImGui::Separator();
|
|
ImGuiEx::Label("Mode");
|
|
if (ImGui::BeginCombo("##gm", toString(m_geometry_mode))) {
|
|
for (u32 i = 0; i < (u32)GeometryMode::COUNT; ++i) {
|
|
if (ImGui::Selectable(toString(GeometryMode(i)))) m_geometry_mode = GeometryMode(i);
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
const bool snap = m_geometry_mode != GeometryMode::NO_SNAP;
|
|
|
|
if (!snap) ImGuiEx::PushReadOnly();
|
|
ImGuiEx::Label("Snap height");
|
|
ImGui::DragFloat("##sh", &m_snap_height);
|
|
if (!snap) ImGuiEx::PopReadOnly();
|
|
|
|
if (ImGui::Button("Generate geometry")) {
|
|
for (EntityRef e : entities) {
|
|
if (!universe.hasComponent(e, SPLINE_TYPE)) continue;
|
|
|
|
const Spline& spline = core_scene->getSpline(e);
|
|
const SplineGeometry& sg = render_scene->getSplineGeometry(e);
|
|
const ProceduralGeometry& pg = render_scene->getProceduralGeometry(e);
|
|
if (spline.points.empty()) continue;
|
|
|
|
const float width = sg.width;
|
|
SplineIterator iterator(spline.points);
|
|
gpu::VertexDecl decl;
|
|
decl.addAttribute(0, 0, 3, gpu::AttributeType::FLOAT, 0);
|
|
|
|
OutputMemoryStream vertices(m_app.getAllocator());
|
|
OutputMemoryStream indices(m_app.getAllocator());
|
|
vertices.reserve(16 * 1024);
|
|
const bool has_uvs = sg.flags.isSet(SplineGeometry::HAS_UVS);
|
|
if (has_uvs) decl.addAttribute(1, 12, 2, gpu::AttributeType::FLOAT, 0);
|
|
struct Vertex {
|
|
Vec3 position;
|
|
Vec2 uv;
|
|
};
|
|
|
|
const Transform spline_tr = universe.getTransform(e);
|
|
const Transform spline_tr_inv = spline_tr.inverted();
|
|
|
|
auto write_vertex = [&](const Vertex& v){
|
|
Vec3 position = v.position;
|
|
if (m_geometry_mode == GeometryMode::SNAP_ALL) {
|
|
const DVec3 p = spline_tr.transform(v.position) + Vec3(0, 1 + m_snap_height, 0);
|
|
const RayCastModelHit hit = render_scene->castRayTerrain(p, Vec3(0, -1, 0));
|
|
if (hit.is_hit) {
|
|
const DVec3 hp = hit.origin + (hit.t - m_snap_height) * hit.dir;
|
|
position = Vec3(spline_tr_inv.transform(hp));
|
|
}
|
|
}
|
|
vertices.write(position);
|
|
if (has_uvs) {
|
|
vertices.write(v.uv);
|
|
}
|
|
if (sg.num_user_channels > 0) {
|
|
u32 tmp = 0;
|
|
vertices.write(tmp);
|
|
}
|
|
};
|
|
|
|
float u = 0;
|
|
u32 rows = 0;
|
|
Vec3 prev_p = spline.points[0];
|
|
const u32 u_density = sg.u_density;
|
|
while (!iterator.isEnd()) {
|
|
++rows;
|
|
Vec3 p = iterator.getPosition();
|
|
if (m_geometry_mode == GeometryMode::SNAP_CENTER) {
|
|
const DVec3 pglob = spline_tr.transform(p) + Vec3(0, 100 + m_snap_height, 0);
|
|
const RayCastModelHit hit = render_scene->castRayTerrain(pglob, Vec3(0, -1, 0));
|
|
if (hit.is_hit) {
|
|
const DVec3 hp = hit.origin + (hit.t - m_snap_height) * hit.dir;
|
|
p = Vec3(spline_tr_inv.transform(hp));
|
|
}
|
|
}
|
|
|
|
const Vec3 dir = iterator.getDir();
|
|
const Vec3 side = normalize(cross(Vec3(0, 1, 0), dir)) * width;
|
|
u += length(p - prev_p);
|
|
|
|
const Vec3 p0 = p - side;
|
|
|
|
for (u32 i = 0; i < u_density; ++i) {
|
|
|
|
Vertex v;
|
|
v.position = p0 + 2 * side * (i / float(u_density - 1));
|
|
v.uv.x = u;
|
|
v.uv.y = i / float(u_density - 1) * width;
|
|
|
|
write_vertex(v);
|
|
}
|
|
|
|
iterator.move(sg.v_density);
|
|
prev_p = p;
|
|
}
|
|
|
|
const bool u16indices = u_density * rows < 0xffFF;
|
|
|
|
if (u16indices) {
|
|
for (u32 row = 0; row < rows - 1; ++row) {
|
|
for (u32 i = 0; i < u_density - 1; ++i) {
|
|
indices.write(u16(u_density * row + i));
|
|
indices.write(u16(u_density * row + i + 1));
|
|
indices.write(u16(u_density * (row + 1) + i));
|
|
|
|
indices.write(u16(u_density * row + i + 1));
|
|
indices.write(u16(u_density * (row + 1) + i));
|
|
indices.write(u16(u_density * (row + 1) + i + 1));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (u32 row = 0; row < rows - 1; ++row) {
|
|
for (u32 i = 0; i < u_density - 1; ++i) {
|
|
indices.write(u32(u_density * row + i));
|
|
indices.write(u32(u_density * row + i + 1));
|
|
indices.write(u32(u_density * (row + 1) + i));
|
|
|
|
indices.write(u32(u_density * row + i + 1));
|
|
indices.write(u32(u_density * (row + 1) + i));
|
|
indices.write(u32(u_density * (row + 1) + i + 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sg.num_user_channels > 0) {
|
|
decl.addAttribute(2, has_uvs ? 20 : 12, sg.num_user_channels, gpu::AttributeType::U8, gpu::Attribute::NORMALIZED);
|
|
if (sg.num_user_channels < 4) {
|
|
decl.addAttribute(3, has_uvs ? 20 : 12 + sg.num_user_channels, 4 - sg.num_user_channels, gpu::AttributeType::U8, 0); // padding
|
|
}
|
|
}
|
|
|
|
render_scene->setProceduralGeometry(e, vertices, decl, gpu::PrimitiveType::TRIANGLES, indices, u16indices ? gpu::DataType::U16 : gpu::DataType::U32);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace Lumix
|