25constexpr uint32_t kPolyTableSnes = 0x09FF8C;
26constexpr uint32_t kPolyEntrySize = 6;
27constexpr uint32_t kPolyRegionSize = 0x74;
28constexpr uint8_t kPolyBank = 0x09;
30constexpr ImVec4 kVertexColor(0.3f, 0.8f, 1.0f, 1.0f);
31constexpr ImVec4 kSelectedVertexColor(1.0f, 0.75f, 0.2f, 1.0f);
34T Clamp(T value, T min_v, T max_v) {
35 return std::max(min_v, std::min(max_v, value));
38std::string ShapeNameForIndex(
int index) {
45 return absl::StrFormat(
"Shape %d", index);
49uint32_t ToPc(uint16_t bank_offset) {
50 return SnesToPc((kPolyBank << 16) | bank_offset);
67 return absl::FailedPreconditionError(
"ROM is not loaded");
78 for (
int i = 0; i < 2; ++i) {
79 size_t base = i * kPolyEntrySize;
80 if (base + kPolyEntrySize > region.size()) {
85 shape.
name = ShapeNameForIndex(i);
89 static_cast<uint16_t
>(region[base + 2] | (region[base + 3] << 8));
91 static_cast<uint16_t
>(region[base + 4] | (region[base + 5] << 8));
94 const uint32_t vertex_pc = ToPc(shape.
vertex_ptr);
95 const size_t vertex_bytes =
static_cast<size_t>(shape.
vertex_count) * 3;
100 for (
size_t idx = 0; idx + 2 < vertex_blob.size(); idx += 3) {
102 v.
x =
static_cast<int8_t
>(vertex_blob[idx]);
103 v.
y =
static_cast<int8_t
>(vertex_blob[idx + 1]);
104 v.
z =
static_cast<int8_t
>(vertex_blob[idx + 2]);
109 uint32_t face_pc = ToPc(shape.
face_ptr);
116 for (
int j = 0; j < count_byte; ++j) {
122 face.
shade = shade_byte;
123 shape.
faces.push_back(std::move(face));
126 shapes_.push_back(std::move(shape));
132 return absl::OkStatus();
147 std::vector<uint8_t> vertex_blob;
148 vertex_blob.reserve(shape.
vertices.size() * 3);
149 for (
const auto& v : shape.
vertices) {
150 vertex_blob.push_back(
static_cast<uint8_t
>(
static_cast<int8_t
>(v.x)));
151 vertex_blob.push_back(
static_cast<uint8_t
>(
static_cast<int8_t
>(v.y)));
152 vertex_blob.push_back(
static_cast<uint8_t
>(
static_cast<int8_t
>(v.z)));
159 std::vector<uint8_t> face_blob;
160 for (
const auto& face : shape.
faces) {
161 face_blob.push_back(
static_cast<uint8_t
>(face.vertex_indices.size()));
162 for (
auto idx : face.vertex_indices) {
163 face_blob.push_back(idx);
165 face_blob.push_back(face.shade);
174 ImGui::TextUnformatted(tr(
"Load a ROM to edit 3D objects."));
181 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
182 tr(
"Failed to load shapes: %s"),
183 status.message().data());
190 ImGui::Text(tr(
"ALTTP polyhedral data @ $09:%04X (PC $%05X), %u bytes"),
191 static_cast<uint16_t
>(kPolyTableSnes & 0xFFFF),
TablePc(),
193 ImGui::TextUnformatted(
194 tr(
"Shapes: 0 = Crystal, 1 = Triforce (IDs used by POLYSHAPE)"));
200 for (
size_t i = 0; i <
shapes_.size(); ++i) {
202 if (ImGui::Selectable(
shapes_[i].name.c_str(), selected)) {
214 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
215 tr(
"Reload failed: %s"), status.message().data());
219 ImGui::BeginDisabled(!
dirty_);
223 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), tr(
"Save failed: %s"),
224 status.message().data());
227 ImGui::EndDisabled();
230 ImGui::TextUnformatted(tr(
"No polyhedral shapes found."));
350 if (shape.
faces.empty()) {
351 ImGui::TextUnformatted(tr(
"No faces"));
355 ImGui::TextUnformatted(tr(
"Faces (vertex indices + shade)"));
356 for (
size_t i = 0; i < shape.
faces.size(); ++i) {
357 ImGui::PushID(
static_cast<int>(i));
358 ImGui::Text(tr(
"Face %zu"), i);
360 int shade = shape.
faces[i].shade;
362 if (ImGui::InputInt(tr(
"Shade##face"), &shade, 0, 0)) {
363 shape.
faces[i].shade =
static_cast<uint8_t
>(Clamp(shade, 0, 0xFF));
368 ImGui::TextUnformatted(tr(
"Vertices:"));
369 const int max_idx = shape.
vertices.empty()
371 :
static_cast<int>(shape.
vertices.size() - 1);
372 for (
size_t v = 0; v < shape.
faces[i].vertex_indices.size(); ++v) {
374 int idx = shape.
faces[i].vertex_indices[v];
377 if (ImGui::InputInt(absl::StrFormat(
"##v%zu", v).c_str(), &idx, 0, 0)) {
378 idx = Clamp(idx, 0, max_idx);
379 shape.
faces[i].vertex_indices[v] =
static_cast<uint8_t
>(idx);
393 ImVec2 plot_size = ImVec2(-1, 220);
394 ImPlotFlags flags = ImPlotFlags_NoLegend | ImPlotFlags_Equal;
395 if (ImPlot::BeginPlot(label, plot_size, flags)) {
398 ImPlot::SetupAxes(x_label, y_label, ImPlotAxisFlags_AutoFit,
399 ImPlotAxisFlags_AutoFit);
400 ImPlot::SetupAxisLimits(ImAxis_X1, -80, 80, ImGuiCond_Once);
401 ImPlot::SetupAxisLimits(ImAxis_Y1, -80, 80, ImGuiCond_Once);
403 for (
size_t i = 0; i < shape.
vertices.size(); ++i) {
420 ImVec4 color = is_selected ? kSelectedVertexColor : kVertexColor;
422 int point_id =
static_cast<int>(i * 10 +
static_cast<size_t>(plane));
423 if (ImPlot::DragPoint(point_id, &x, &y, color, 6.0f)) {
425 int rounded_x = Clamp(
static_cast<int>(std::lround(x)), -127, 127);
426 int rounded_y = Clamp(
static_cast<int>(std::lround(y)), -127, 127);
458 static float rot_x = 0.35f;
459 static float rot_y = -0.4f;
460 static float rot_z = 0.0f;
461 static float zoom = 1.0f;
463 ImGui::TextUnformatted(tr(
"Preview (orthographic)"));
465 ImGui::SliderFloat(tr(
"Rot X"), &rot_x, -3.14f, 3.14f,
"%.2f");
468 ImGui::SliderFloat(tr(
"Rot Y"), &rot_y, -3.14f, 3.14f,
"%.2f");
471 ImGui::SliderFloat(tr(
"Rot Z"), &rot_z, -3.14f, 3.14f,
"%.2f");
474 ImGui::SliderFloat(tr(
"Zoom"), &zoom, 0.5f, 3.0f,
"%.2f");
482 std::vector<RotV> rotated(shape.
vertices.size());
484 const double cx = std::cos(rot_x);
485 const double sx = std::sin(rot_x);
486 const double cy = std::cos(rot_y);
487 const double sy = std::sin(rot_y);
488 const double cz = std::cos(rot_z);
489 const double sz = std::sin(rot_z);
491 for (
size_t i = 0; i < shape.
vertices.size(); ++i) {
498 double y1 = y * cx - z * sx;
499 double z1 = y * sx + z * cx;
501 double x2 = x * cy + z1 * sy;
502 double z2 = -x * sy + z1 * cy;
504 double x3 = x2 * cz - y1 * sz;
505 double y3 = x2 * sz + y1 * cz;
507 rotated[i] = {x3 * zoom, y3 * zoom, z2 * zoom};
514 std::vector<FaceDepth> order;
515 order.reserve(shape.
faces.size());
516 for (
size_t i = 0; i < shape.
faces.size(); ++i) {
518 for (
auto idx : shape.
faces[i].vertex_indices) {
519 if (idx < rotated.size()) {
520 accum += rotated[idx].z;
524 shape.
faces[i].vertex_indices.empty()
526 : accum /
static_cast<double>(shape.
faces[i].vertex_indices.size());
527 order.push_back({avg, i});
530 std::sort(order.begin(), order.end(),
531 [](
const FaceDepth& a,
const FaceDepth& b) {
532 return a.depth < b.depth;
535 ImVec2 preview_size(-1, 260);
536 ImPlotFlags flags = ImPlotFlags_NoLegend | ImPlotFlags_Equal;
537 if (ImPlot::BeginPlot(
"PreviewXY", preview_size, flags)) {
538 ImPlot::SetupAxes(
nullptr,
nullptr, ImPlotAxisFlags_NoDecorations,
539 ImPlotAxisFlags_NoDecorations);
540 ImPlot::SetupAxisLimits(ImAxis_X1, -120, 120, ImGuiCond_Always);
541 ImPlot::SetupAxisLimits(ImAxis_Y1, -120, 120, ImGuiCond_Always);
543 ImDrawList* dl = ImPlot::GetPlotDrawList();
544 ImVec4 base_color = ImVec4(0.8f, 0.9f, 1.0f, 0.55f);
546 for (
const auto& fd : order) {
547 const auto& face = shape.
faces[fd.idx];
548 if (face.vertex_indices.size() < 3) {
552 std::vector<ImVec2> pts;
553 pts.reserve(face.vertex_indices.size());
555 for (
auto idx : face.vertex_indices) {
556 if (idx >= rotated.size()) {
559 ImVec2 p = ImPlot::PlotToPixels(rotated[idx].x, rotated[idx].y);
563 if (pts.size() < 3) {
567 ImU32 fill_col = ImGui::GetColorU32(base_color);
568 ImU32 line_col = ImGui::GetColorU32(ImVec4(0.2f, 0.4f, 0.6f, 1.0f));
569 dl->AddConvexPolyFilled(pts.data(),
static_cast<int>(pts.size()),
571 dl->AddPolyline(pts.data(),
static_cast<int>(pts.size()), line_col,
572 ImDrawFlags_Closed, 2.0f);
576 for (
size_t i = 0; i < rotated.size(); ++i) {
577 ImVec2 p = ImPlot::PlotToPixels(rotated[i].x, rotated[i].y);
578 ImU32 col = ImGui::GetColorU32(kVertexColor);
579 dl->AddCircleFilled(p, 4.0f, col);