#include "domain_adapt.h"
#include <godot_cpp/core/math.hpp>
#include <godot_cpp/templates/hash_map.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
namespace godot {
namespace {
// The topology every adaptation needs, built once per call. Edge identity is
// ProcMesh::compute_edges()' own ordering -- keys sorted as (min << 32) | max --
// so an adapted edge field indexes the same edges the edge domain does.
struct MeshTopo {
int n_point = 0, n_edge = 0, n_face = 0, n_corner = 0;
const int32_t *corner_vert = nullptr; // ProcMesh::indices
PackedInt32Array edges; // 2 vertex indices per edge
Vector<int> corner_face; // face owning each corner
Vector<int> corner_next; // next corner within the same face
Vector<int> corner_prev; // previous corner within the same face
Vector<int> corner_edge; // edge from this corner to the next
Vector<int> face_begin, face_size;
bool build(const Ref<ProcMesh> &p_mesh) {
const ProcMesh &m = *p_mesh.ptr();
n_point = m.vertex_count();
n_corner = m.corner_count();
n_face = m.face_count();
if (n_corner <= 0 || n_face <= 0 || m.indices.size() < n_corner) {
return false;
}
corner_vert = m.indices.ptr();
corner_face.resize(n_corner);
corner_next.resize(n_corner);
corner_prev.resize(n_corner);
corner_edge.resize(n_corner);
face_begin.resize(n_face);
face_size.resize(n_face);
for (int f = 0; f < n_face; f++) {
const int begin = m.face_corner_begin(f);
const int n = m.face_size(f);
face_begin.write[f] = begin;
face_size.write[f] = n;
for (int k = 0; k < n; k++) {
const int c = begin + k;
if (c >= n_corner) {
return false;
}
corner_face.write[c] = f;
corner_next.write[c] = begin + (k + 1) % n;
corner_prev.write[c] = begin + (k + n - 1) % n;
}
}
// Edge indices, matching compute_edges()' ordering exactly.
edges = m.compute_edges();
n_edge = edges.size() / 2;
HashMap<int64_t, int> edge_of_pair;
for (int e = 0; e < n_edge; e++) {
int a = edges[e * 2], b = edges[e * 2 + 1];
edge_of_pair.insert(((int64_t)a << 32) | (uint32_t)b, e);
}
for (int c = 0; c < n_corner; c++) {
int a = corner_vert[c];
int b = corner_vert[corner_next[c]];
if (a > b) {
const int t = a;
a = b;
b = t;
}
const HashMap<int64_t, int>::ConstIterator it =
edge_of_pair.find(((int64_t)a << 32) | (uint32_t)b);
corner_edge.write[c] = it ? it->value : -1;
}
return true;
}
};
int domain_count(const MeshTopo &t, AttrDomain d) {
switch (d) {
case AttrDomain::Point: return t.n_point;
case AttrDomain::Edge: return t.n_edge;
case AttrDomain::Face: return t.n_face;
case AttrDomain::Corner: return t.n_corner;
default: return 0;
}
}
// Which source elements feed each destination element. Every direction is a
// list; a gather or a broadcast is simply a list of one.
bool build_contributions(const MeshTopo &t, AttrDomain from, AttrDomain to,
Vector<Vector<int>> &r_contrib) {
const int n_dst = domain_count(t, to);
if (n_dst <= 0) {
return false;
}
r_contrib.resize(n_dst);
auto add = [&](int dst, int src) {
if (dst >= 0 && dst < n_dst && src >= 0) {
r_contrib.write[dst].push_back(src);
}
};
if (from == AttrDomain::Point) {
switch (to) {
case AttrDomain::Corner:
for (int c = 0; c < t.n_corner; c++) add(c, t.corner_vert[c]);
return true;
case AttrDomain::Face:
for (int c = 0; c < t.n_corner; c++) add(t.corner_face[c], t.corner_vert[c]);
return true;
case AttrDomain::Edge:
for (int e = 0; e < t.n_edge; e++) {
add(e, t.edges[e * 2]);
add(e, t.edges[e * 2 + 1]);
}
return true;
default: return false;
}
}
if (from == AttrDomain::Corner) {
switch (to) {
case AttrDomain::Point:
for (int c = 0; c < t.n_corner; c++) add(t.corner_vert[c], c);
return true;
case AttrDomain::Face:
for (int c = 0; c < t.n_corner; c++) add(t.corner_face[c], c);
return true;
case AttrDomain::Edge:
// The corner that OWNS the edge, and the next corner -- the two
// corners sitting at the edge's ends inside that face.
for (int c = 0; c < t.n_corner; c++) {
const int e = t.corner_edge[c];
add(e, c);
add(e, t.corner_next[c]);
}
return true;
default: return false;
}
}
if (from == AttrDomain::Face) {
switch (to) {
case AttrDomain::Corner:
for (int c = 0; c < t.n_corner; c++) add(c, t.corner_face[c]);
return true;
case AttrDomain::Point:
for (int c = 0; c < t.n_corner; c++) add(t.corner_vert[c], t.corner_face[c]);
return true;
case AttrDomain::Edge:
for (int c = 0; c < t.n_corner; c++) add(t.corner_edge[c], t.corner_face[c]);
return true;
default: return false;
}
}
if (from == AttrDomain::Edge) {
switch (to) {
case AttrDomain::Point:
// Walk the EDGES, not the corners: an interior edge is spanned
// by one corner in each adjacent face, so going through corners
// would count it twice and bias the mean.
for (int e = 0; e < t.n_edge; e++) {
add(t.edges[e * 2], e);
add(t.edges[e * 2 + 1], e);
}
return true;
case AttrDomain::Face:
for (int c = 0; c < t.n_corner; c++) add(t.corner_face[c], t.corner_edge[c]);
return true;
case AttrDomain::Corner:
for (int c = 0; c < t.n_corner; c++) {
add(c, t.corner_edge[c]);
add(c, t.corner_edge[t.corner_prev[c]]);
}
return true;
default: return false;
}
}
return false;
}
// Measured: ALL everywhere except FROM FACE, and edge -> point.
bool bool_rule_is_any(AttrDomain from, AttrDomain to) {
if (from == AttrDomain::Face) {
return true;
}
return from == AttrDomain::Edge && to == AttrDomain::Point;
}
} // namespace
SocketValue adapt_field_to_domain(const SocketValue &p_val, AttrDomain p_from,
AttrDomain p_to, const Ref<ProcMesh> &p_mesh, bool *r_ok) {
if (r_ok) {
*r_ok = true;
}
if (!p_val.is_field || p_from == p_to) {
return p_val;
}
if (p_mesh.is_null()) {
if (r_ok) *r_ok = false;
return p_val;
}
MeshTopo topo;
if (!topo.build(p_mesh)) {
if (r_ok) *r_ok = false;
return p_val;
}
Vector<Vector<int>> contrib;
if (!build_contributions(topo, p_from, p_to, contrib)) {
if (r_ok) *r_ok = false;
return p_val;
}
const int n_dst = contrib.size();
SocketValue out;
out.type = p_val.type;
out.is_field = true;
out.domain = p_to;
out.field_size = n_dst;
const bool as_any = bool_rule_is_any(p_from, p_to);
if (p_val.type == SocketType::Bool) {
out.int_array.resize(n_dst);
int32_t *w = out.int_array.ptrw();
for (int d = 0; d < n_dst; d++) {
const Vector<int> &src = contrib[d];
if (src.is_empty()) {
w[d] = 0;
continue;
}
bool acc = !as_any;
for (int k = 0; k < src.size(); k++) {
const bool v = p_val.bool_at(src[k]);
acc = as_any ? (acc || v) : (acc && v);
}
w[d] = acc ? 1 : 0;
}
return out;
}
if (p_val.type == SocketType::Int) {
out.int_array.resize(n_dst);
int32_t *w = out.int_array.ptrw();
for (int d = 0; d < n_dst; d++) {
const Vector<int> &src = contrib[d];
if (src.is_empty()) {
w[d] = 0;
continue;
}
double sum = 0.0;
for (int k = 0; k < src.size(); k++) {
sum += (double)p_val.int_at(src[k]);
}
// Rounded half UP: measured 4.5 -> 5, which rules out the
// round-half-to-even a naive cast or std::rint would give.
w[d] = (int32_t)Math::floor(sum / (double)src.size() + 0.5);
}
return out;
}
if (p_val.type == SocketType::Vector || p_val.type == SocketType::Rotation) {
out.type = SocketType::Vector;
out.vec_array.resize(n_dst);
Vector3 *w = out.vec_array.ptrw();
for (int d = 0; d < n_dst; d++) {
const Vector<int> &src = contrib[d];
if (src.is_empty()) {
w[d] = Vector3();
continue;
}
Vector3 sum;
for (int k = 0; k < src.size(); k++) {
sum += p_val.vec_at(src[k]);
}
w[d] = sum / (float)src.size();
}
return out;
}
if (p_val.type == SocketType::Color) {
out.color_array.resize(n_dst);
Color *w = out.color_array.ptrw();
for (int d = 0; d < n_dst; d++) {
const Vector<int> &src = contrib[d];
if (src.is_empty()) {
w[d] = Color(0, 0, 0, 0);
continue;
}
Color sum(0, 0, 0, 0);
for (int k = 0; k < src.size(); k++) {
const Color c = p_val.color_at(src[k]);
sum.r += c.r; sum.g += c.g; sum.b += c.b; sum.a += c.a;
}
const float inv = 1.0f / (float)src.size();
w[d] = Color(sum.r * inv, sum.g * inv, sum.b * inv, sum.a * inv);
}
return out;
}
// Float, and anything a float can carry.
out.float_array.resize(n_dst);
float *w = out.float_array.ptrw();
for (int d = 0; d < n_dst; d++) {
const Vector<int> &src = contrib[d];
if (src.is_empty()) {
w[d] = 0.0f;
continue;
}
double sum = 0.0;
for (int k = 0; k < src.size(); k++) {
sum += (double)p_val.float_at(src[k]);
}
w[d] = (float)(sum / (double)src.size());
}
return out;
}
} // namespace godot