#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 ¢re) {
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