OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / graph / domain_adapt.cpp

320 lines · cpp ref:
#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