OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / shared / blender_mesh_primitives.h

923 lines · c ref:
#ifndef PROC_NODES_BLENDER_MESH_PRIMITIVES_H
#define PROC_NODES_BLENDER_MESH_PRIMITIVES_H

// BLENDER'S MESH PRIMITIVES, PORTED FROM BLENDER'S SOURCE.
//
// From `source/blender/geometry/intern/mesh_primitive_{cuboid,cylinder_cone,
// uv_sphere,line}.cc`, `nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc`
// and `bmesh/operators/bmo_primitive.cc` (the icosphere's vertex table), Blender
// source commit 61a36e08, BLENDER_VERSION 502.
//
// WHY. Measured with audit_mesh_primitives.py: only the grid had Blender's
// vertex count. Ours split vertices per face where Blender welds -- 24 against 8
// for a cube -- and Mesh Line produced nothing at all. That is not a coordinate
// frame, it is a different mesh, and it matters to a graph rather than only to
// a renderer: `Position` is evaluated per POINT, so a cube with 24 points and a
// cube with 8 give different answers to every field in the graph below it.
//
// These generate VERTEX POSITIONS in Blender's order and Blender's frame. They
// are used when a graph is marked BLENDER; a Godot-authored graph keeps the
// layouts it has always had, so nothing already on disk moves.
//
// WHAT IS NOT REPRODUCED, and is reported rather than hidden:
//
//   FACES. Blender's primitives are quads and ngons; ProcMesh carries
//   triangles, so the triangulation is ours. Vertex positions and order are
//   what a field sees, and those match.
//
//   FLAT SHADING. Blender stores it as a per-face attribute over WELDED
//   vertices; ProcMesh has per-vertex normals, so a welded cube gets averaged
//   normals. Splitting to keep facets is exactly what made the counts wrong.
//
//   ICO SPHERE above 1 subdivision. Blender subdivides the base icosahedron
//   through a BMesh edge-subdivide with `use_sphere`, whose vertex ORDER is a
//   property of that operator. Subdivision 1 -- the base icosahedron -- is
//   exact; higher ones are not attempted.

#include <godot_cpp/variant/packed_vector3_array.hpp>
#include <godot_cpp/variant/vector3.hpp>

#include <cmath>

namespace godot {
namespace bprim {

// mesh_primitive_cuboid.cc: calculate_positions. Bottom and top are filled
// solid; the rings between carry only their border, which is why this is not a
// plain triple loop.
static inline PackedVector3Array cuboid(const Vector3 &size, int verts_x, int verts_y,
		int verts_z) {
	verts_x = MAX(verts_x, 2);
	verts_y = MAX(verts_y, 2);
	verts_z = MAX(verts_z, 2);
	const int edges_x = verts_x - 1, edges_y = verts_y - 1, edges_z = verts_z - 1;
	const float z_bottom = -size.z / 2.0f, z_delta = size.z / edges_z;
	const float x_left = -size.x / 2.0f, x_delta = size.x / edges_x;
	const float y_front = -size.y / 2.0f, y_delta = size.y / edges_y;

	PackedVector3Array out;
	for (int z = 0; z < verts_z; z++) {
		if (z == 0 || z == edges_z) {
			const float z_pos = z_bottom + z_delta * z;
			for (int y = 0; y < verts_y; y++) {
				const float y_pos = y_front + y_delta * y;
				for (int x = 0; x < verts_x; x++) {
					out.push_back(Vector3(x_left + x_delta * x, y_pos, z_pos));
				}
			}
		} else {
			for (int y = 0; y < verts_y; y++) {
				const float z_pos = z_bottom + z_delta * z;
				const float y_pos = y_front + y_delta * y;
				if (y == 0 || y == edges_y) {
					for (int x = 0; x < verts_x; x++) {
						out.push_back(Vector3(x_left + x_delta * x, y_pos, z_pos));
					}
				} else {
					out.push_back(Vector3(x_left, y_pos, z_pos));
					out.push_back(Vector3(x_left + x_delta * edges_x, y_pos, z_pos));
				}
			}
		}
	}
	return out;
}

// node_geo_mesh_primitive_circle.cc. The ring only -- Blender's default fill is
// NONE, and a fan or ngon fill adds no vertices either way.
static inline PackedVector3Array circle(int verts_num, float radius) {
	verts_num = MAX(verts_num, 3);
	const float angle_delta = 2.0f * ((float)Math_PI / (float)verts_num);
	PackedVector3Array out;
	for (int i = 0; i < verts_num; i++) {
		const float angle = i * angle_delta;
		out.push_back(Vector3(cosf(angle) * radius, sinf(angle) * radius, 0.0f));
	}
	return out;
}

// mesh_primitive_cylinder_cone.cc: calculate_cone_verts. `height` is HALF the
// depth. A radius of zero at either end makes that end a point, which removes
// its fill ring and adds a tip vertex instead.
// mesh_primitive_cylinder_cone.cc: ConeConfig. Everything about a cylinder's
// layout follows from these, and the one that matters most is
// `*_has_center_vert`: Blender ties it to the FILL TYPE, not to the number of
// fill segments. This file used to say `fill_segments > 1`, which puts a centre
// vertex in an N-gon-filled cylinder that Blender does not have -- invisible
// while fill_type could not cross the bridge at all.
//   fill_type: 0 = None, 1 = N-gon, 2 = Triangle Fan
struct ConeConfig {
	int circle_segments, side_segments, fill_segments, fill_type;
	bool top_is_point, bottom_is_point, top_has_center_vert, bottom_has_center_vert;
	int tot_quad_rings = 0, tot_verts = 0, tot_corners = 0;
	int first_ring_verts_start = 0, last_ring_verts_start = 0, last_vert = 0;
	int top_faces_start = 0, top_faces_len = 0;
	int side_faces_start = 0, side_faces_len = 0;
	int bottom_faces_start = 0, bottom_faces_len = 0;

	ConeConfig(int p_circle_segments, int p_side_segments, int p_fill_segments,
			float radius_top, float radius_bottom, int p_fill_type) {
		circle_segments = MAX(p_circle_segments, 3);
		side_segments = MAX(p_side_segments, 1);
		fill_segments = MAX(p_fill_segments, 1);
		fill_type = p_fill_type;
		top_is_point = (radius_top == 0.0f);
		bottom_is_point = (radius_bottom == 0.0f);
		top_has_center_vert = top_is_point || fill_type == 2;
		bottom_has_center_vert = bottom_is_point || fill_type == 2;

		if (top_is_point && bottom_is_point) {
			tot_quad_rings = 0;
			tot_verts = side_segments + 1;
			tot_corners = 0;
		} else {
			tot_quad_rings = 0;
			if (!top_is_point) tot_quad_rings += fill_segments - 1;
			tot_quad_rings += (!top_is_point && !bottom_is_point) ? side_segments
															     : (side_segments - 1);
			if (!bottom_is_point) tot_quad_rings += fill_segments - 1;

			tot_verts = 0;
			if (top_has_center_vert) tot_verts++;
			if (!top_is_point) tot_verts += circle_segments * fill_segments;
			tot_verts += circle_segments * (side_segments - 1);
			if (!bottom_is_point) tot_verts += circle_segments * fill_segments;
			if (bottom_has_center_vert) tot_verts++;

			tot_corners = 0;
			if (top_has_center_vert) tot_corners += circle_segments * 3;
			else if (!top_is_point && fill_type == 1) tot_corners += circle_segments;
			tot_corners += tot_quad_rings * circle_segments * 4;
			if (bottom_has_center_vert) tot_corners += circle_segments * 3;
			else if (!bottom_is_point && fill_type == 1) tot_corners += circle_segments;
		}
		first_ring_verts_start = top_has_center_vert ? 1 : 0;
		last_vert = tot_verts - 1;
		last_ring_verts_start = last_vert - circle_segments;

		top_faces_start = 0;
		if (!top_is_point) {
			top_faces_len = (fill_segments - 1) * circle_segments;
			top_faces_len += top_has_center_vert ? circle_segments : 0;
			top_faces_len += (fill_type == 1) ? 1 : 0;
		}
		side_faces_start = top_faces_len;
		side_faces_len = (top_is_point && bottom_is_point) ? 0
														   : side_segments * circle_segments;
		if (!bottom_is_point) {
			bottom_faces_len = (fill_segments - 1) * circle_segments;
			bottom_faces_len += bottom_has_center_vert ? circle_segments : 0;
			bottom_faces_len += (fill_type == 1) ? 1 : 0;
		}
		bottom_faces_start = side_faces_start + side_faces_len;
	}
};

// mesh_primitive_cylinder_cone.cc: calculate_cone_faces. A triangle fan at each
// capped end when the fill is Triangles (or the end is a point), one n-gon when
// it is N-gon, nothing when it is None, and QUAD RINGS between -- including the
// concentric rings inside a multi-segment fill. The bottom n-gon runs BACKWARDS,
// which is what reverses its normal.
static inline void cone_faces(const ConeConfig &c, PackedInt32Array &r_corners,
		PackedInt32Array &r_face_offsets) {
	r_corners.clear();
	r_face_offsets.clear();
	if (c.top_is_point && c.bottom_is_point) {
		return;
	}
	auto begin_face = [&]() { r_face_offsets.push_back(r_corners.size()); };

	if (c.top_has_center_vert) {
		for (int i = 0; i < c.circle_segments; i++) {
			begin_face();
			r_corners.push_back(c.first_ring_verts_start + i);
			r_corners.push_back(c.first_ring_verts_start + ((i + 1) % c.circle_segments));
			r_corners.push_back(0);
		}
	} else if (c.fill_type == 1 && !c.top_is_point) {
		begin_face();
		for (int i = 0; i < c.circle_segments; i++) {
			r_corners.push_back(i);
		}
	}

	for (int i = 0; i < c.tot_quad_rings; i++) {
		const int this_ring_vert_start = c.first_ring_verts_start + i * c.circle_segments;
		const int next_ring_vert_start = this_ring_vert_start + c.circle_segments;
		for (int j = 0; j < c.circle_segments; j++) {
			const int j_next = (j + 1) % c.circle_segments;
			begin_face();
			r_corners.push_back(this_ring_vert_start + j);
			r_corners.push_back(next_ring_vert_start + j);
			r_corners.push_back(next_ring_vert_start + j_next);
			r_corners.push_back(this_ring_vert_start + j_next);
		}
	}

	if (c.bottom_has_center_vert) {
		for (int i = 0; i < c.circle_segments; i++) {
			begin_face();
			r_corners.push_back(c.last_ring_verts_start + i);
			r_corners.push_back(c.last_vert);
			r_corners.push_back(c.last_ring_verts_start + ((i + 1) % c.circle_segments));
		}
	} else if (c.fill_type == 1 && !c.bottom_is_point) {
		begin_face();
		for (int i = 0; i < c.circle_segments; i++) {
			r_corners.push_back(c.last_vert - i);
		}
	}
	r_face_offsets.push_back(r_corners.size());
}

// mesh_primitive_cylinder_cone.cc: calculate_cone_uvs. Two discs and a
// rectangle: the top fill unwraps into a circle of radius 0.225 at (0.25, 0.25),
// the bottom into one at (0.75, 0.25), and the side into the strip above them --
// which is why the side starts at v = 0.5 when there is a fill and at 0 when
// there is none.
// mesh_primitive_cylinder_cone.cc: calculate_selection_outputs. Top, Side and
// Bottom are selections over FACES when the end has a fill, and over POINTS when
// it is a cone tip or unfilled -- a domain that DEPENDS ON THE FILL, which is
// why they cannot be a plain per-face array. r_domain says which.
//   which: 0 = Top, 1 = Side, 2 = Bottom
static inline PackedInt32Array cone_selection(const ConeConfig &c, int which,
		bool &r_is_face, int &r_size) {
	const int face_count = c.top_faces_len + c.side_faces_len + c.bottom_faces_len;
	PackedInt32Array sel;
	if (which == 1) {
		r_is_face = true;
		r_size = face_count;
		sel.resize(face_count);
		for (int i = 0; i < face_count; i++) {
			sel.set(i, (i >= c.side_faces_start && i < c.side_faces_start + c.side_faces_len)
							  ? 1 : 0);
		}
		return sel;
	}
	const bool is_point = (which == 0) ? c.top_is_point : c.bottom_is_point;
	const bool face = !is_point && c.fill_type != 0;
	r_is_face = face;
	r_size = face ? face_count : c.tot_verts;
	sel.resize(r_size);
	for (int i = 0; i < r_size; i++) {
		sel.set(i, 0);
	}
	if (which == 0) {
		if (c.top_is_point) {
			sel.set(0, 1);
		} else {
			const int n = face ? c.top_faces_len : c.circle_segments;
			for (int i = 0; i < n && i < r_size; i++) {
				sel.set(i, 1);
			}
		}
	} else {
		if (c.bottom_is_point) {
			sel.set(c.last_vert, 1);
		} else if (face) {
			for (int i = 0; i < c.bottom_faces_len; i++) {
				const int f = c.bottom_faces_start + i;
				if (f < r_size) sel.set(f, 1);
			}
		} else {
			for (int i = 0; i < c.circle_segments; i++) {
				const int v = c.last_ring_verts_start + 1 + i;
				if (v < r_size) sel.set(v, 1);
			}
		}
	}
	return sel;
}

static inline PackedVector3Array cone_uvs(const ConeConfig &c) {
	PackedVector3Array uv;
	auto put = [&](float u, float v) { uv.push_back(Vector3(u, v, 0.0f)); };

	Vector<Vector2> circle;
	circle.resize(c.circle_segments);
	{
		float angle = 0.0f;
		const float angle_delta = 2.0f * (float)Math_PI / (float)c.circle_segments;
		for (int i = 0; i < c.circle_segments; i++) {
			circle.write[i] = Vector2(cosf(angle) * 0.225f, sinf(angle) * 0.225f);
			angle += angle_delta;
		}
	}
	auto disc = [&](float factor, int i, const Vector2 &centre) {
		const Vector2 p = circle[i] * factor + centre;
		put(p.x, p.y);
	};

	if (c.top_is_point || c.fill_type != 0) {
		const Vector2 centre_left(0.25f, 0.25f);
		const float rf_delta = 1.0f / (float)(c.top_is_point ? c.side_segments : c.fill_segments);
		const int left_count = c.top_is_point ? c.side_segments : c.fill_segments;
		if (c.top_has_center_vert) {
			for (int i = 0; i < c.circle_segments; i++) {
				disc(rf_delta, i, centre_left);
				disc(rf_delta, (i + 1) % c.circle_segments, centre_left);
				put(centre_left.x, centre_left.y);
			}
		} else if (!c.top_is_point && c.fill_type == 1) {
			for (int i = 0; i < c.circle_segments; i++) {
				disc(rf_delta, i, centre_left);
			}
		}
		for (int i = 1; i < left_count; i++) {
			const float inner = i * rf_delta;
			const float outer = (i + 1) * rf_delta;
			for (int j = 0; j < c.circle_segments; j++) {
				const int j_next = (j + 1) % c.circle_segments;
				disc(inner, j, centre_left);
				disc(outer, j, centre_left);
				disc(outer, j_next, centre_left);
				disc(inner, j_next, centre_left);
			}
		}
	}

	if (!c.top_is_point && !c.bottom_is_point) {
		const float bottom = (c.fill_type == 0) ? 0.0f : 0.5f;
		const float x_delta = 1.0f / (float)c.circle_segments;
		const float y_delta = (1.0f - bottom) / (float)c.side_segments;
		for (int i = 0; i < c.side_segments; i++) {
			for (int j = 0; j < c.circle_segments; j++) {
				put(j * x_delta, i * y_delta + bottom);
				put(j * x_delta, (i + 1) * y_delta + bottom);
				put((j + 1) * x_delta, (i + 1) * y_delta + bottom);
				put((j + 1) * x_delta, i * y_delta + bottom);
			}
		}
	}

	if (c.bottom_is_point || c.fill_type != 0) {
		const Vector2 centre_right(0.75f, 0.25f);
		const float rf_delta = 1.0f / (float)(c.bottom_is_point ? c.side_segments : c.fill_segments);
		const int right_count = c.bottom_is_point ? c.side_segments : c.fill_segments;
		// OUTSIDE IN, to match the corner order the bottom fill is emitted in.
		for (int i = 0; i < right_count - 1; i++) {
			const float outer = 1.0f - i * rf_delta;
			const float inner = 1.0f - (i + 1) * rf_delta;
			for (int j = 0; j < c.circle_segments; j++) {
				const int j_next = (j + 1) % c.circle_segments;
				disc(outer, j, centre_right);
				disc(inner, j, centre_right);
				disc(inner, j_next, centre_right);
				disc(outer, j_next, centre_right);
			}
		}
		if (c.bottom_has_center_vert) {
			for (int i = 0; i < c.circle_segments; i++) {
				disc(rf_delta, i, centre_right);
				put(centre_right.x, centre_right.y);
				disc(rf_delta, (i + 1) % c.circle_segments, centre_right);
			}
		} else if (!c.bottom_is_point && c.fill_type == 1) {
			for (int i = 0; i < c.circle_segments; i++) {
				disc(rf_delta, c.circle_segments - 1 - i, centre_right);
			}
		}
	}
	return uv;
}

static inline PackedVector3Array cone(int circle_segments, int side_segments,
		int fill_segments, float radius_top, float radius_bottom, float height,
		int fill_type = 2) {
	const ConeConfig cfg(circle_segments, side_segments, fill_segments,
			radius_top, radius_bottom, fill_type);
	circle_segments = cfg.circle_segments;
	side_segments = cfg.side_segments;
	fill_segments = cfg.fill_segments;

	const bool top_is_point = cfg.top_is_point;
	const bool bottom_is_point = cfg.bottom_is_point;
	const bool top_has_center_vert = cfg.top_has_center_vert;
	const bool bottom_has_center_vert = cfg.bottom_has_center_vert;

	PackedVector3Array circle_pts;
	{
		const float angle_delta = 2.0f * ((float)Math_PI / (float)circle_segments);
		float angle = 0.0f;
		for (int i = 0; i < circle_segments; i++) {
			circle_pts.push_back(Vector3(cosf(angle), sinf(angle), 0.0f));
			angle += angle_delta;
		}
	}

	PackedVector3Array out;
	if (top_has_center_vert) {
		out.push_back(Vector3(0.0f, 0.0f, height));
	}
	if (!top_is_point) {
		const float d = radius_top / (float)fill_segments;
		for (int i = 0; i < fill_segments; i++) {
			const float r = d * (i + 1);
			for (int j = 0; j < circle_segments; j++) {
				out.push_back(Vector3(circle_pts[j].x * r, circle_pts[j].y * r, height));
			}
		}
	}
	const float side_radius_delta = (radius_bottom - radius_top) / (float)side_segments;
	const float height_delta = 2.0f * height / (float)side_segments;
	for (int i = 0; i < side_segments - 1; i++) {
		const float r = radius_top + side_radius_delta * (i + 1);
		const float h = height - height_delta * (i + 1);
		for (int j = 0; j < circle_segments; j++) {
			out.push_back(Vector3(circle_pts[j].x * r, circle_pts[j].y * r, h));
		}
	}
	if (!bottom_is_point) {
		const float d = radius_bottom / (float)fill_segments;
		for (int i = 0; i < fill_segments; i++) {
			const float r = radius_bottom - i * d;
			for (int j = 0; j < circle_segments; j++) {
				out.push_back(Vector3(circle_pts[j].x * r, circle_pts[j].y * r, -height));
			}
		}
	}
	if (bottom_has_center_vert) {
		out.push_back(Vector3(0.0f, 0.0f, -height));
	}
	return out;
}

// mesh_primitive_uv_sphere.cc: calculate_sphere_vertex_data. Note the segment
// loop runs 1..segments, so the first ring vertex is at phi = delta, NOT at
// phi = 0 -- the sphere is rotated by one segment against the obvious loop.
static inline PackedVector3Array uv_sphere(int segments, int rings, float radius) {
	segments = MAX(segments, 3);
	rings = MAX(rings, 2);
	const float delta_theta = (float)Math_PI / (float)rings;
	const float delta_phi = (2.0f * (float)Math_PI) / (float)segments;

	PackedVector3Array out;
	out.push_back(Vector3(0.0f, 0.0f, radius));
	for (int ring = 1; ring <= rings - 1; ring++) {
		const float theta = ring * delta_theta;
		const float sin_theta = sinf(theta);
		const float z = cosf(theta);
		for (int segment = 1; segment <= segments; segment++) {
			const float phi = segment * delta_phi;
			out.push_back(Vector3(sin_theta * cosf(phi), sin_theta * sinf(phi), z) * radius);
		}
	}
	out.push_back(Vector3(0.0f, 0.0f, -radius));
	return out;
}

// bmo_primitive.cc: icovert, scaled by radius/200. Subdivision 1 only.
static inline PackedVector3Array ico_sphere_base(float radius) {
	static const float icovert[12][3] = {
		{ 0.0f, 0.0f, -200.0f },
		{ 144.72f, -105.144f, -89.443f },
		{ -55.277f, -170.128f, -89.443f },
		{ -178.885f, 0.0f, -89.443f },
		{ -55.277f, 170.128f, -89.443f },
		{ 144.72f, 105.144f, -89.443f },
		{ 55.277f, -170.128f, 89.443f },
		{ -144.72f, -105.144f, 89.443f },
		{ -144.72f, 105.144f, 89.443f },
		{ 55.277f, 170.128f, 89.443f },
		{ 178.885f, 0.0f, 89.443f },
		{ 0.0f, 0.0f, 200.0f },
	};
	const float rad_div = radius / 200.0f;
	PackedVector3Array out;
	for (int a = 0; a < 12; a++) {
		out.push_back(Vector3(rad_div * icovert[a][0], rad_div * icovert[a][1],
				rad_div * icovert[a][2]));
	}
	return out;
}

// mesh_primitive_line.cc: start plus a constant offset, `count` times.
static inline PackedVector3Array line(const Vector3 &start, const Vector3 &offset, int count) {
	PackedVector3Array out;
	for (int i = 0; i < MAX(count, 0); i++) {
		out.push_back(start + offset * (float)i);
	}
	return out;
}

// The 20 faces of the base icosahedron, bmo_primitive.cc: icoface.
// bmo_primitive.cc: the `icouvs` table. THE ICO SPHERE'S UV IS NOT A PROJECTION.
// A spherical unwrap (u = 0.5 + atan2(y,x)/2pi) is what BMesh uses for the UV
// SPHERE, and it is wrong here -- measured, it gives corner 0 u = 0.300001 where
// Blender gives 0.181819. Blender ships a hand-made net for the icosahedron
// instead: 60 corner uvs, in the same face order the base icosahedron is built
// in, with u stepping in elevenths and v in three rows of 0.157461.
//
// It is a TABLE, so it covers subdivision 1 only -- which is the same limit the
// mesh itself has, since a subdivided ico sphere is a BMesh edge-subdivide whose
// vertex order is a property of that operator.
static const float ICO_UVS[60][2] = {
	{ 0.181819f, 0.000000f }, { 0.272728f, 0.157461f }, { 0.090910f, 0.157461f },
	{ 0.272728f, 0.157461f }, { 0.363637f, 0.000000f }, { 0.454546f, 0.157461f },
	{ 0.909091f, 0.000000f }, { 1.000000f, 0.157461f }, { 0.818182f, 0.157461f },
	{ 0.727273f, 0.000000f }, { 0.818182f, 0.157461f }, { 0.636364f, 0.157461f },
	{ 0.545455f, 0.000000f }, { 0.636364f, 0.157461f }, { 0.454546f, 0.157461f },
	{ 0.272728f, 0.157461f }, { 0.454546f, 0.157461f }, { 0.363637f, 0.314921f },
	{ 0.090910f, 0.157461f }, { 0.272728f, 0.157461f }, { 0.181819f, 0.314921f },
	{ 0.818182f, 0.157461f }, { 1.000000f, 0.157461f }, { 0.909091f, 0.314921f },
	{ 0.636364f, 0.157461f }, { 0.818182f, 0.157461f }, { 0.727273f, 0.314921f },
	{ 0.454546f, 0.157461f }, { 0.636364f, 0.157461f }, { 0.545455f, 0.314921f },
	{ 0.272728f, 0.157461f }, { 0.363637f, 0.314921f }, { 0.181819f, 0.314921f },
	{ 0.090910f, 0.157461f }, { 0.181819f, 0.314921f }, { 0.000000f, 0.314921f },
	{ 0.818182f, 0.157461f }, { 0.909091f, 0.314921f }, { 0.727273f, 0.314921f },
	{ 0.636364f, 0.157461f }, { 0.727273f, 0.314921f }, { 0.545455f, 0.314921f },
	{ 0.454546f, 0.157461f }, { 0.545455f, 0.314921f }, { 0.363637f, 0.314921f },
	{ 0.181819f, 0.314921f }, { 0.363637f, 0.314921f }, { 0.272728f, 0.472382f },
	{ 0.000000f, 0.314921f }, { 0.181819f, 0.314921f }, { 0.090910f, 0.472382f },
	{ 0.727273f, 0.314921f }, { 0.909091f, 0.314921f }, { 0.818182f, 0.472382f },
	{ 0.545455f, 0.314921f }, { 0.727273f, 0.314921f }, { 0.636364f, 0.472382f },
	{ 0.363637f, 0.314921f }, { 0.545455f, 0.314921f }, { 0.454546f, 0.472382f },
};

static inline PackedVector3Array ico_sphere_uvs() {
	PackedVector3Array uv;
	uv.resize(60);
	Vector3 *w = uv.ptrw();
	for (int i = 0; i < 60; i++) {
		w[i] = Vector3(ICO_UVS[i][0], ICO_UVS[i][1], 0.0f);
	}
	return uv;
}

static inline PackedInt32Array ico_sphere_base_indices() {
	static const int icoface[20][3] = {
		{ 0, 1, 2 }, { 1, 0, 5 }, { 0, 2, 3 }, { 0, 3, 4 }, { 0, 4, 5 },
		{ 1, 5, 10 }, { 2, 1, 6 }, { 3, 2, 7 }, { 4, 3, 8 }, { 5, 4, 9 },
		{ 1, 10, 6 }, { 2, 6, 7 }, { 3, 7, 8 }, { 4, 8, 9 }, { 5, 9, 10 },
		{ 6, 10, 11 }, { 7, 6, 11 }, { 8, 7, 11 }, { 9, 8, 11 }, { 10, 9, 11 },
	};
	PackedInt32Array out;
	for (int f = 0; f < 20; f++) {
		out.push_back(icoface[f][0]);
		out.push_back(icoface[f][1]);
		out.push_back(icoface[f][2]);
	}
	return out;
}

// --- triangulations over the vertex orders above ---
//
// ProcMesh is triangles, so these are ours, not Blender's quads and ngons. The
// vertex positions and their ORDER are Blender's, which is what every field in
// the graph is evaluated over.

// The 2x2x2 cuboid, whose vertex order from cuboid() is
// (-,-,-) (+,-,-) (-,+,-) (+,+,-) (-,-,+) (+,-,+) (-,+,+) (+,+,+).
// mesh_primitive_cuboid.cc: calculate_corner_verts, ported verbatim. EVERY face
// is a quad, in Blender's order -- bottom, front, top, back, left, right -- and
// the left/right sides carry the index arithmetic that a shell layout forces:
// the rings between bottom and top hold only their border, so a vertex two rows
// up is not `verts_x` away.
//
// Returns the CORNER array, 4 corners per face; the face count is
// 2 * (ex*ey + ey*ez + ez*ex).
static inline PackedInt32Array cuboid_corner_verts(int verts_x, int verts_y, int verts_z) {
	verts_x = MAX(verts_x, 2);
	verts_y = MAX(verts_y, 2);
	verts_z = MAX(verts_z, 2);
	const int edges_x = verts_x - 1, edges_y = verts_y - 1, edges_z = verts_z - 1;
	PackedInt32Array c;

	const int xy_cross_section_vert_count =
			verts_x * verts_y - (verts_x - 2) * (verts_y - 2);

	auto quad = [&](int v1, int v2, int v3, int v4) {
		c.push_back(v1); c.push_back(v2); c.push_back(v3); c.push_back(v4);
	};

	// Bottom.
	int vert_1_start = 0;
	for (int y = 0; y < edges_y; y++) {
		for (int x = 0; x < edges_x; x++) {
			const int vert_1 = vert_1_start + x;
			const int vert_2 = vert_1_start + verts_x + x;
			quad(vert_1, vert_2, vert_2 + 1, vert_1 + 1);
		}
		vert_1_start += verts_x;
	}

	// Front.
	vert_1_start = 0;
	int vert_2_start = verts_x * verts_y;
	for (int z = 0; z < edges_z; z++) {
		for (int x = 0; x < edges_x; x++) {
			quad(vert_1_start + x, vert_1_start + x + 1,
					vert_2_start + x + 1, vert_2_start + x);
		}
		vert_1_start = vert_2_start;
		vert_2_start += verts_x * verts_y - (verts_x - 2) * (verts_y - 2);
	}

	// Top.
	vert_1_start = verts_x * verts_y +
			(verts_z - 2) * (verts_x * verts_y - (verts_x - 2) * (verts_y - 2));
	vert_2_start = vert_1_start + verts_x;
	for (int y = 0; y < edges_y; y++) {
		for (int x = 0; x < edges_x; x++) {
			quad(vert_1_start + x, vert_1_start + x + 1,
					vert_2_start + x + 1, vert_2_start + x);
		}
		vert_2_start += verts_x;
		vert_1_start += verts_x;
	}

	// Back.
	vert_1_start = verts_x * edges_y;
	vert_2_start = vert_1_start + xy_cross_section_vert_count;
	for (int z = 0; z < edges_z; z++) {
		if (z == edges_z - 1) {
			vert_2_start += (verts_x - 2) * (verts_y - 2);
		}
		for (int x = 0; x < edges_x; x++) {
			quad(vert_1_start + x, vert_2_start + x,
					vert_2_start + x + 1, vert_1_start + x + 1);
		}
		vert_2_start += xy_cross_section_vert_count;
		vert_1_start += xy_cross_section_vert_count;
	}

	// Left.
	vert_1_start = 0;
	vert_2_start = verts_x * verts_y;
	for (int z = 0; z < edges_z; z++) {
		for (int y = 0; y < edges_y; y++) {
			int vert_1, vert_2, vert_3, vert_4;
			if (z == 0 || y == 0) {
				vert_1 = vert_1_start + verts_x * y;
				vert_4 = vert_1 + verts_x;
			} else {
				vert_1 = vert_1_start + 2 * y;
				vert_1 += verts_x - 2;
				vert_4 = vert_1 + 2;
			}
			if (y == 0 || z == edges_z - 1) {
				vert_2 = vert_2_start + verts_x * y;
				vert_3 = vert_2 + verts_x;
			} else {
				vert_2 = vert_2_start + 2 * y;
				vert_2 += verts_x - 2;
				vert_3 = vert_2 + 2;
			}
			quad(vert_1, vert_2, vert_3, vert_4);
		}
		vert_1_start += (z == 0) ? verts_x * verts_y : xy_cross_section_vert_count;
		vert_2_start += xy_cross_section_vert_count;
	}

	// Right.
	vert_1_start = edges_x;
	vert_2_start = vert_1_start + verts_x * verts_y;
	for (int z = 0; z < edges_z; z++) {
		for (int y = 0; y < edges_y; y++) {
			int vert_1, vert_2, vert_3, vert_4;
			if (z == 0) {
				vert_1 = vert_1_start + verts_x * y;
				vert_4 = vert_1 + verts_x;
			} else {
				vert_1 = vert_1_start + 2 * y;
				vert_4 = vert_1 + 2;
			}
			if (z == edges_z - 1) {
				vert_2 = vert_2_start + verts_x * y;
				vert_3 = vert_2 + verts_x;
			} else {
				vert_2 = vert_2_start + 2 * y;
				vert_3 = vert_2 + 2;
			}
			if (y == edges_y - 1) {
				vert_3 = vert_2 + verts_x;
				vert_4 = vert_1 + verts_x;
			}
			quad(vert_1, vert_4, vert_3, vert_2);
		}
		vert_1_start += (z == 0) ? verts_x * verts_y : xy_cross_section_vert_count;
		vert_2_start += xy_cross_section_vert_count;
	}
	return c;
}

static inline PackedInt32Array cuboid_2x2x2_indices() {
	static const int tri[12][3] = {
		{ 0, 2, 1 }, { 1, 2, 3 }, // -Z
		{ 4, 5, 6 }, { 5, 7, 6 }, // +Z
		{ 0, 1, 4 }, { 1, 5, 4 }, // -Y
		{ 2, 6, 3 }, { 3, 6, 7 }, // +Y
		{ 0, 4, 2 }, { 2, 4, 6 }, // -X
		{ 1, 3, 5 }, { 3, 7, 5 }, // +X
	};
	PackedInt32Array out;
	for (int f = 0; f < 12; f++) {
		for (int k = 0; k < 3; k++) {
			out.push_back(tri[f][k]);
		}
	}
	return out;
}

// A fan over a ring of `n` vertices starting at `base`, anchored on `base`.
// mesh_primitive_cuboid.cc: calculate_uvs. The cross layout, per CORNER, in the
// same order cuboid_corner_verts emits the faces -- bottom, front, top, back,
// left, right. The deltas are quarter-of-a-unit divided by the subdivision, so
// a subdivided cube keeps the same cross and fills it with more quads.
static inline PackedVector3Array cuboid_uvs(int verts_x, int verts_y, int verts_z) {
	verts_x = MAX(verts_x, 2);
	verts_y = MAX(verts_y, 2);
	verts_z = MAX(verts_z, 2);
	const int edges_x = verts_x - 1, edges_y = verts_y - 1, edges_z = verts_z - 1;
	const float x_delta = 0.25f / (float)edges_x;
	const float y_delta = 0.25f / (float)edges_y;
	const float z_delta = 0.25f / (float)edges_z;
	PackedVector3Array uv;
	auto put = [&](float u, float v) { uv.push_back(Vector3(u, v, 0.0f)); };

	for (int y = 0; y < edges_y; y++) {          // bottom
		for (int x = 0; x < edges_x; x++) {
			put(0.25f + x * x_delta, 0.375f - y * y_delta);
			put(0.25f + x * x_delta, 0.375f - (y + 1) * y_delta);
			put(0.25f + (x + 1) * x_delta, 0.375f - (y + 1) * y_delta);
			put(0.25f + (x + 1) * x_delta, 0.375f - y * y_delta);
		}
	}
	for (int z = 0; z < edges_z; z++) {          // front
		for (int x = 0; x < edges_x; x++) {
			put(0.25f + x * x_delta, 0.375f + z * z_delta);
			put(0.25f + (x + 1) * x_delta, 0.375f + z * z_delta);
			put(0.25f + (x + 1) * x_delta, 0.375f + (z + 1) * z_delta);
			put(0.25f + x * x_delta, 0.375f + (z + 1) * z_delta);
		}
	}
	for (int y = 0; y < edges_y; y++) {          // top
		for (int x = 0; x < edges_x; x++) {
			put(0.25f + x * x_delta, 0.625f + y * y_delta);
			put(0.25f + (x + 1) * x_delta, 0.625f + y * y_delta);
			put(0.25f + (x + 1) * x_delta, 0.625f + (y + 1) * y_delta);
			put(0.25f + x * x_delta, 0.625f + (y + 1) * y_delta);
		}
	}
	for (int z = 0; z < edges_z; z++) {          // back
		for (int x = 0; x < edges_x; x++) {
			put(1.0f - x * x_delta, 0.375f + z * z_delta);
			put(1.0f - x * x_delta, 0.375f + (z + 1) * z_delta);
			put(1.0f - (x + 1) * x_delta, 0.375f + (z + 1) * z_delta);
			put(1.0f - (x + 1) * x_delta, 0.375f + z * z_delta);
		}
	}
	for (int z = 0; z < edges_z; z++) {          // left
		for (int y = 0; y < edges_y; y++) {
			put(0.25f - y * y_delta, 0.375f + z * z_delta);
			put(0.25f - y * y_delta, 0.375f + (z + 1) * z_delta);
			put(0.25f - (y + 1) * y_delta, 0.375f + (z + 1) * z_delta);
			put(0.25f - (y + 1) * y_delta, 0.375f + z * z_delta);
		}
	}
	for (int z = 0; z < edges_z; z++) {          // right
		for (int y = 0; y < edges_y; y++) {
			put(0.50f + y * y_delta, 0.375f + z * z_delta);
			put(0.50f + (y + 1) * y_delta, 0.375f + z * z_delta);
			put(0.50f + (y + 1) * y_delta, 0.375f + (z + 1) * z_delta);
			put(0.50f + y * y_delta, 0.375f + (z + 1) * z_delta);
		}
	}
	return uv;
}

// node_geo_mesh_primitive_circle.cc: the fill types, which are a PROPERTY and
// not a socket, and which change the VERTEX COUNT -- a triangle fan appends the
// centre as the LAST vertex. r_face_offsets is empty for NONE, since a circle
// with no fill is an edge ring with no faces at all.
//   0 = None, 1 = N-gon, 2 = Triangle Fan (Blender's own enum values)
static inline void circle_faces(int verts_num, int fill_type, PackedInt32Array &r_corners,
		PackedInt32Array &r_face_offsets) {
	r_corners.clear();
	r_face_offsets.clear();
	if (fill_type == 1) {
		for (int i = 0; i < verts_num; i++) {
			r_corners.push_back(i);
		}
		r_face_offsets.push_back(0);
		r_face_offsets.push_back(verts_num);
	} else if (fill_type == 2) {
		// corner order (i, i+1, centre) -- the CENTRE IS LAST, and it is vertex
		// verts_num because the fan appends it.
		for (int i = 0; i < verts_num; i++) {
			r_corners.push_back(i);
			r_corners.push_back((i + 1) % verts_num);
			r_corners.push_back(verts_num);
			r_face_offsets.push_back(i * 3);
		}
		r_face_offsets.push_back(verts_num * 3);
	}
}

// mesh_primitive_uv_sphere.cc: calculate_sphere_faces + calculate_sphere_corners.
// A triangle fan at each pole and QUADS between, which is why our all-triangle
// sphere had 16 faces where Blender has 12 at 4x3.
static inline void uv_sphere_faces(int segments, int rings, PackedInt32Array &r_corners,
		PackedInt32Array &r_face_offsets) {
	r_corners.clear();
	r_face_offsets.clear();
	const int vert_total = segments * (rings - 1) + 2;
	auto next_or_first = [&](int segment) { return segment == segments - 1 ? 0 : segment + 1; };

	// Top fan.
	for (int segment = 0; segment < segments; segment++) {
		r_face_offsets.push_back(r_corners.size());
		r_corners.push_back(0);
		r_corners.push_back(1 + segment);
		r_corners.push_back(1 + next_or_first(segment));
	}
	// Middle quads.
	for (int ring = 1; ring <= rings - 2; ring++) {
		const int ring_vert_start = 1 + (ring - 1) * segments;
		const int next_ring_vert_start = ring_vert_start + segments;
		for (int segment = 0; segment < segments; segment++) {
			const int segment_next = next_or_first(segment);
			r_face_offsets.push_back(r_corners.size());
			r_corners.push_back(ring_vert_start + segment);
			r_corners.push_back(next_ring_vert_start + segment);
			r_corners.push_back(next_ring_vert_start + segment_next);
			r_corners.push_back(ring_vert_start + segment_next);
		}
	}
	// Bottom fan.
	const int last_vert_index = vert_total - 1;
	const int last_vert_ring_start = last_vert_index - segments;
	for (int segment = 0; segment < segments; segment++) {
		const int segment_next = next_or_first(segment);
		r_face_offsets.push_back(r_corners.size());
		r_corners.push_back(last_vert_index);
		r_corners.push_back(last_vert_ring_start + segment_next);
		r_corners.push_back(last_vert_ring_start + segment);
	}
	r_face_offsets.push_back(r_corners.size());
}

// mesh_primitive_uv_sphere.cc: calculate_sphere_uvs. Per CORNER, in the same
// order the corners are generated above, and the poles are NOT a single point in
// uv space -- each pole triangle gets its own u at the seam.
static inline PackedVector3Array uv_sphere_uvs(int segments, int rings) {
	PackedVector3Array uv;
	const float dy = 1.0f / (float)rings;
	const float seg_inv = 1.0f / (float)segments;
	for (int i = 0; i < segments; i++) {
		const float s = (float)i;
		uv.push_back(Vector3((s + 0.5f) * seg_inv, 0.0f, 0.0f));
		uv.push_back(Vector3(s * seg_inv, dy, 0.0f));
		uv.push_back(Vector3((s + 1.0f) * seg_inv, dy, 0.0f));
	}
	for (int ring = 1; ring <= rings - 2; ring++) {
		const float r = (float)ring;
		for (int i = 0; i < segments; i++) {
			const float s = (float)i;
			uv.push_back(Vector3(s * seg_inv, r / (float)rings, 0.0f));
			uv.push_back(Vector3(s * seg_inv, (r + 1.0f) / (float)rings, 0.0f));
			uv.push_back(Vector3((s + 1.0f) * seg_inv, (r + 1.0f) / (float)rings, 0.0f));
			uv.push_back(Vector3((s + 1.0f) * seg_inv, r / (float)rings, 0.0f));
		}
	}
	for (int i = 0; i < segments; i++) {
		const float s = (float)i;
		uv.push_back(Vector3((s + 0.5f) * seg_inv, 1.0f, 0.0f));
		uv.push_back(Vector3((s + 1.0f) * seg_inv, 1.0f - dy, 0.0f));
		uv.push_back(Vector3(s * seg_inv, 1.0f - dy, 0.0f));
	}
	return uv;
}

static inline void fan_indices(PackedInt32Array &out, int base, int n, bool flip) {
	for (int i = 1; i + 1 < n; i++) {
		if (flip) {
			out.push_back(base);
			out.push_back(base + i + 1);
			out.push_back(base + i);
		} else {
			out.push_back(base);
			out.push_back(base + i);
			out.push_back(base + i + 1);
		}
	}
}

// Two rings of `n` vertices joined into a band of quads, each split in two.
static inline void band_indices(PackedInt32Array &out, int a_base, int b_base, int n) {
	for (int i = 0; i < n; i++) {
		const int i2 = (i + 1) % n;
		out.push_back(a_base + i);
		out.push_back(b_base + i);
		out.push_back(b_base + i2);
		out.push_back(a_base + i);
		out.push_back(b_base + i2);
		out.push_back(a_base + i2);
	}
}

} // namespace bprim
} // namespace godot

#endif // PROC_NODES_BLENDER_MESH_PRIMITIVES_H