65 return absl::FailedPreconditionError(
"ROM is not loaded");
76 for (
int i = 0; i < 2; ++i) {
77 size_t base = i * kPolyEntrySize;
78 if (base + kPolyEntrySize > region.size()) {
83 shape.
name = ShapeNameForIndex(i);
87 static_cast<uint16_t
>(region[base + 2] | (region[base + 3] << 8));
89 static_cast<uint16_t
>(region[base + 4] | (region[base + 5] << 8));
92 const uint32_t vertex_pc = ToPc(shape.
vertex_ptr);
93 const size_t vertex_bytes =
static_cast<size_t>(shape.
vertex_count) * 3;
98 for (
size_t idx = 0; idx + 2 < vertex_blob.size(); idx += 3) {
100 v.
x =
static_cast<int8_t
>(vertex_blob[idx]);
101 v.
y =
static_cast<int8_t
>(vertex_blob[idx + 1]);
102 v.
z =
static_cast<int8_t
>(vertex_blob[idx + 2]);
107 uint32_t face_pc = ToPc(shape.
face_ptr);
114 for (
int j = 0; j < count_byte; ++j) {
120 face.
shade = shade_byte;
121 shape.
faces.push_back(std::move(face));
124 shapes_.push_back(std::move(shape));
130 return absl::OkStatus();
145 std::vector<uint8_t> vertex_blob;
146 vertex_blob.reserve(shape.
vertices.size() * 3);
147 for (
const auto& v : shape.
vertices) {
148 vertex_blob.push_back(
static_cast<uint8_t
>(
static_cast<int8_t
>(v.x)));
149 vertex_blob.push_back(
static_cast<uint8_t
>(
static_cast<int8_t
>(v.y)));
150 vertex_blob.push_back(
static_cast<uint8_t
>(
static_cast<int8_t
>(v.z)));
157 std::vector<uint8_t> face_blob;
158 for (
const auto& face : shape.
faces) {
159 face_blob.push_back(
static_cast<uint8_t
>(face.vertex_indices.size()));
160 for (
auto idx : face.vertex_indices) {
161 face_blob.push_back(idx);
163 face_blob.push_back(face.shade);
172 ImGui::TextUnformatted(
"Load a ROM to edit 3D objects.");
179 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
180 "Failed to load shapes: %s", status.message().data());
187 ImGui::Text(
"ALTTP polyhedral data @ $09:%04X (PC $%05X), %u bytes",
188 static_cast<uint16_t
>(kPolyTableSnes & 0xFFFF),
TablePc(),
190 ImGui::TextUnformatted(
191 "Shapes: 0 = Crystal, 1 = Triforce (IDs used by POLYSHAPE)");
195 ImGui::SetNextItemWidth(180);
197 for (
size_t i = 0; i <
shapes_.size(); ++i) {
199 if (ImGui::Selectable(
shapes_[i].name.c_str(), selected)) {
211 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
"Reload failed: %s",
212 status.message().data());
216 ImGui::BeginDisabled(!
dirty_);
220 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
"Save failed: %s",
221 status.message().data());
224 ImGui::EndDisabled();
227 ImGui::TextUnformatted(
"No polyhedral shapes found.");
319 ImGui::TextUnformatted(
"No vertices");
323 for (
size_t i = 0; i < shape.
vertices.size(); ++i) {
324 ImGui::PushID(
static_cast<int>(i));
326 std::string label = absl::StrFormat(
"Vertex %zu", i);
327 if (ImGui::Selectable(label.c_str(), is_selected)) {
332 ImGui::SetNextItemWidth(210);
335 if (ImGui::InputInt3(
"##coords", coords)) {
336 shape.
vertices[i].x = Clamp(coords[0], -127, 127);
337 shape.
vertices[i].y = Clamp(coords[1], -127, 127);
338 shape.
vertices[i].z = Clamp(coords[2], -127, 127);
346 if (shape.
faces.empty()) {
347 ImGui::TextUnformatted(
"No faces");
351 ImGui::TextUnformatted(
"Faces (vertex indices + shade)");
352 for (
size_t i = 0; i < shape.
faces.size(); ++i) {
353 ImGui::PushID(
static_cast<int>(i));
354 ImGui::Text(
"Face %zu", i);
356 int shade = shape.
faces[i].shade;
357 ImGui::SetNextItemWidth(70);
358 if (ImGui::InputInt(
"Shade##face", &shade, 0, 0)) {
359 shape.
faces[i].shade =
static_cast<uint8_t
>(Clamp(shade, 0, 0xFF));
364 ImGui::TextUnformatted(
"Vertices:");
365 const int max_idx = shape.
vertices.empty()
367 :
static_cast<int>(shape.
vertices.size() - 1);
368 for (
size_t v = 0; v < shape.
faces[i].vertex_indices.size(); ++v) {
370 int idx = shape.
faces[i].vertex_indices[v];
371 ImGui::SetNextItemWidth(40);
372 if (ImGui::InputInt(absl::StrFormat(
"##v%zu", v).c_str(), &idx, 0, 0)) {
373 idx = Clamp(idx, 0, max_idx);
374 shape.
faces[i].vertex_indices[v] =
static_cast<uint8_t
>(idx);
388 ImVec2 plot_size = ImVec2(-1, 220);
389 ImPlotFlags flags = ImPlotFlags_NoLegend | ImPlotFlags_Equal;
390 if (ImPlot::BeginPlot(label, plot_size, flags)) {
393 ImPlot::SetupAxes(x_label, y_label, ImPlotAxisFlags_AutoFit,
394 ImPlotAxisFlags_AutoFit);
395 ImPlot::SetupAxisLimits(ImAxis_X1, -80, 80, ImGuiCond_Once);
396 ImPlot::SetupAxisLimits(ImAxis_Y1, -80, 80, ImGuiCond_Once);
398 for (
size_t i = 0; i < shape.
vertices.size(); ++i) {
415 ImVec4 color = is_selected ? kSelectedVertexColor : kVertexColor;
417 int point_id =
static_cast<int>(i * 10 +
static_cast<size_t>(plane));
418 if (ImPlot::DragPoint(point_id, &x, &y, color, 6.0f)) {
420 int rounded_x = Clamp(
static_cast<int>(std::lround(x)), -127, 127);
421 int rounded_y = Clamp(
static_cast<int>(std::lround(y)), -127, 127);
453 static float rot_x = 0.35f;
454 static float rot_y = -0.4f;
455 static float rot_z = 0.0f;
456 static float zoom = 1.0f;
458 ImGui::TextUnformatted(
"Preview (orthographic)");
459 ImGui::SetNextItemWidth(120);
460 ImGui::SliderFloat(
"Rot X", &rot_x, -3.14f, 3.14f,
"%.2f");
462 ImGui::SetNextItemWidth(120);
463 ImGui::SliderFloat(
"Rot Y", &rot_y, -3.14f, 3.14f,
"%.2f");
465 ImGui::SetNextItemWidth(120);
466 ImGui::SliderFloat(
"Rot Z", &rot_z, -3.14f, 3.14f,
"%.2f");
468 ImGui::SetNextItemWidth(100);
469 ImGui::SliderFloat(
"Zoom", &zoom, 0.5f, 3.0f,
"%.2f");
477 std::vector<RotV> rotated(shape.
vertices.size());
479 const double cx = std::cos(rot_x);
480 const double sx = std::sin(rot_x);
481 const double cy = std::cos(rot_y);
482 const double sy = std::sin(rot_y);
483 const double cz = std::cos(rot_z);
484 const double sz = std::sin(rot_z);
486 for (
size_t i = 0; i < shape.
vertices.size(); ++i) {
493 double y1 = y * cx - z * sx;
494 double z1 = y * sx + z * cx;
496 double x2 = x * cy + z1 * sy;
497 double z2 = -x * sy + z1 * cy;
499 double x3 = x2 * cz - y1 * sz;
500 double y3 = x2 * sz + y1 * cz;
502 rotated[i] = {x3 * zoom, y3 * zoom, z2 * zoom};
509 std::vector<FaceDepth> order;
510 order.reserve(shape.
faces.size());
511 for (
size_t i = 0; i < shape.
faces.size(); ++i) {
513 for (
auto idx : shape.
faces[i].vertex_indices) {
514 if (idx < rotated.size()) {
515 accum += rotated[idx].z;
519 shape.
faces[i].vertex_indices.empty()
521 : accum /
static_cast<double>(shape.
faces[i].vertex_indices.size());
522 order.push_back({avg, i});
525 std::sort(order.begin(), order.end(),
526 [](
const FaceDepth& a,
const FaceDepth& b) {
527 return a.depth < b.depth;
530 ImVec2 preview_size(-1, 260);
531 ImPlotFlags flags = ImPlotFlags_NoLegend | ImPlotFlags_Equal;
532 if (ImPlot::BeginPlot(
"PreviewXY", preview_size, flags)) {
533 ImPlot::SetupAxes(
nullptr,
nullptr, ImPlotAxisFlags_NoDecorations,
534 ImPlotAxisFlags_NoDecorations);
535 ImPlot::SetupAxisLimits(ImAxis_X1, -120, 120, ImGuiCond_Always);
536 ImPlot::SetupAxisLimits(ImAxis_Y1, -120, 120, ImGuiCond_Always);
538 ImDrawList* dl = ImPlot::GetPlotDrawList();
539 ImVec4 base_color = ImVec4(0.8f, 0.9f, 1.0f, 0.55f);
541 for (
const auto& fd : order) {
542 const auto& face = shape.
faces[fd.idx];
543 if (face.vertex_indices.size() < 3) {
547 std::vector<ImVec2> pts;
548 pts.reserve(face.vertex_indices.size());
550 for (
auto idx : face.vertex_indices) {
551 if (idx >= rotated.size()) {
554 ImVec2 p = ImPlot::PlotToPixels(rotated[idx].x, rotated[idx].y);
558 if (pts.size() < 3) {
562 ImU32 fill_col = ImGui::GetColorU32(base_color);
563 ImU32 line_col = ImGui::GetColorU32(ImVec4(0.2f, 0.4f, 0.6f, 1.0f));
564 dl->AddConvexPolyFilled(pts.data(),
static_cast<int>(pts.size()),
566 dl->AddPolyline(pts.data(),
static_cast<int>(pts.size()), line_col,
567 ImDrawFlags_Closed, 2.0f);
571 for (
size_t i = 0; i < rotated.size(); ++i) {
572 ImVec2 p = ImPlot::PlotToPixels(rotated[i].x, rotated[i].y);
573 ImU32 col = ImGui::GetColorU32(kVertexColor);
574 dl->AddCircleFilled(p, 4.0f, col);