578 lines
21 KiB
C
578 lines
21 KiB
C
// OCTASPHERE :: https://prideout.net/blog/octasphere
|
|
// Tiny malloc-free library that generates triangle meshes for spheres, rounded boxes, and capsules.
|
|
//
|
|
// This library proffers the following functions:
|
|
//
|
|
// - par_octasphere_get_counts
|
|
// - par_octasphere_populate
|
|
//
|
|
// Usage example:
|
|
//
|
|
// /* Specify a 100x100x20 rounded box. */
|
|
// const par_octasphere_config cfg = {
|
|
// .corner_radius = 5,
|
|
// .width = 100,
|
|
// .height = 100,
|
|
// .depth = 20,
|
|
// .num_subdivisions = 3,
|
|
// };
|
|
//
|
|
// /* Allocate memory for the mesh and opt-out of normals. */
|
|
// uint32_t num_indices;
|
|
// uint32_t num_vertices;
|
|
// par_octasphere_get_counts(&cfg, &num_indices, &num_vertices);
|
|
// par_octasphere_mesh mesh = {
|
|
// .positions = malloc(sizeof(float) * 3 * num_vertices),
|
|
// .normals = NULL,
|
|
// .texcoords = malloc(sizeof(float) * 2 * num_vertices),
|
|
// .indices = malloc(sizeof(uint16_t) * num_indices),
|
|
// };
|
|
//
|
|
// /* Generate vertex coordinates, UV's, and triangle indices. */
|
|
// par_octasphere_populate(&cfg, &mesh);
|
|
//
|
|
// To generate a sphere: set width, height, and depth to 0 in your configuration.
|
|
// To generate a capsule shape: set only two of these dimensions to 0.
|
|
//
|
|
// Distributed under the MIT License, see bottom of file.
|
|
|
|
#ifndef PAR_OCTASPHERE_H
|
|
#define PAR_OCTASPHERE_H
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define PAR_OCTASPHERE_MAX_SUBDIVISIONS 5
|
|
|
|
typedef enum {
|
|
PAR_OCTASPHERE_UV_LATLONG,
|
|
} par_octasphere_uv_mode;
|
|
|
|
typedef enum {
|
|
PAR_OCTASPHERE_NORMALS_SMOOTH,
|
|
} par_octasphere_normals_mode;
|
|
|
|
typedef struct {
|
|
float corner_radius;
|
|
float width;
|
|
float height;
|
|
float depth;
|
|
int num_subdivisions;
|
|
par_octasphere_uv_mode uv_mode;
|
|
par_octasphere_normals_mode normals_mode;
|
|
} par_octasphere_config;
|
|
|
|
typedef struct {
|
|
float* positions;
|
|
float* normals;
|
|
float* texcoords;
|
|
uint16_t* indices;
|
|
uint32_t num_indices;
|
|
uint32_t num_vertices;
|
|
} par_octasphere_mesh;
|
|
|
|
// Computes the maximum possible number of indices and vertices for the given octasphere config.
|
|
void par_octasphere_get_counts(const par_octasphere_config* config, uint32_t* num_indices,
|
|
uint32_t* num_vertices);
|
|
|
|
// Populates a pre-allocated mesh structure with indices and vertices.
|
|
void par_octasphere_populate(const par_octasphere_config* config, par_octasphere_mesh* mesh);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// END PUBLIC API
|
|
// -----------------------------------------------------------------------------
|
|
#ifdef PAR_OCTASPHERE_IMPLEMENTATION
|
|
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <memory.h> // for memcpy
|
|
|
|
#define PARO_PI (3.14159265359)
|
|
#define PARO_MIN(a, b) (a > b ? b : a)
|
|
#define PARO_MAX(a, b) (a > b ? a : b)
|
|
#define PARO_CLAMP(v, lo, hi) PARO_MAX(lo, PARO_MIN(hi, v))
|
|
#define PARO_MAX_BOUNDARY_LENGTH ((1 << PAR_OCTASPHERE_MAX_SUBDIVISIONS) + 1)
|
|
|
|
#ifndef PARO_CONSTANT_TOPOLOGY
|
|
#define PARO_CONSTANT_TOPOLOGY 1
|
|
#endif
|
|
|
|
static uint16_t* paro_write_quad(uint16_t* dst, uint16_t a, uint16_t b, uint16_t c, uint16_t d) {
|
|
*dst++ = a;
|
|
*dst++ = b;
|
|
*dst++ = c;
|
|
*dst++ = c;
|
|
*dst++ = d;
|
|
*dst++ = a;
|
|
return dst;
|
|
}
|
|
|
|
static void paro_write_ui3(uint16_t* dst, int index, uint16_t a, uint16_t b, uint16_t c) {
|
|
dst[index * 3 + 0] = a;
|
|
dst[index * 3 + 1] = b;
|
|
dst[index * 3 + 2] = c;
|
|
}
|
|
|
|
static float* paro_write_f3(float* dst, const float src[3]) {
|
|
dst[0] = src[0];
|
|
dst[1] = src[1];
|
|
dst[2] = src[2];
|
|
return dst + 3;
|
|
}
|
|
|
|
static void paro_copy(float dst[3], const float src[3]) {
|
|
dst[0] = src[0];
|
|
dst[1] = src[1];
|
|
dst[2] = src[2];
|
|
}
|
|
|
|
static float paro_dot(const float a[3], const float b[3]) {
|
|
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
}
|
|
|
|
static void paro_add(float result[3], float const a[3], float const b[3]) {
|
|
result[0] = a[0] + b[0];
|
|
result[1] = a[1] + b[1];
|
|
result[2] = a[2] + b[2];
|
|
}
|
|
|
|
static void paro_normalize(float v[3]) {
|
|
float lsqr = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
|
if (lsqr > 0) {
|
|
v[0] /= lsqr;
|
|
v[1] /= lsqr;
|
|
v[2] /= lsqr;
|
|
}
|
|
}
|
|
|
|
static void paro_cross(float result[3], float const a[3], float const b[3]) {
|
|
float x = (a[1] * b[2]) - (a[2] * b[1]);
|
|
float y = (a[2] * b[0]) - (a[0] * b[2]);
|
|
float z = (a[0] * b[1]) - (a[1] * b[0]);
|
|
result[0] = x;
|
|
result[1] = y;
|
|
result[2] = z;
|
|
}
|
|
|
|
static void paro_scale(float dst[3], float v) {
|
|
dst[0] *= v;
|
|
dst[1] *= v;
|
|
dst[2] *= v;
|
|
}
|
|
|
|
static void paro_scaled(float dst[3], const float src[3], float v) {
|
|
dst[0] = src[0] * v;
|
|
dst[1] = src[1] * v;
|
|
dst[2] = src[2] * v;
|
|
}
|
|
|
|
static void paro_quat_from_rotation(float quat[4], const float axis[3], float radians) {
|
|
paro_copy(quat, axis);
|
|
paro_normalize(quat);
|
|
paro_scale(quat, sin(0.5 * radians));
|
|
quat[3] = cos(0.5 * radians);
|
|
}
|
|
|
|
static void paro_quat_from_eulers(float quat[4], const float eulers[3]) {
|
|
const float roll = eulers[0];
|
|
const float pitch = eulers[1];
|
|
const float yaw = eulers[2];
|
|
const float halfRoll = roll * 0.5;
|
|
const float sR = sin(halfRoll);
|
|
const float cR = cos(halfRoll);
|
|
const float halfPitch = pitch * 0.5;
|
|
const float sP = sin(halfPitch);
|
|
const float cP = cos(halfPitch);
|
|
const float halfYaw = yaw * 0.5;
|
|
const float sY = sin(halfYaw);
|
|
const float cY = cos(halfYaw);
|
|
quat[0] = (sR * cP * cY) + (cR * sP * sY);
|
|
quat[1] = (cR * sP * cY) - (sR * cP * sY);
|
|
quat[2] = (cR * cP * sY) + (sR * sP * cY);
|
|
quat[3] = (cR * cP * cY) - (sR * sP * sY);
|
|
}
|
|
|
|
static void paro_quat_rotate_vector(float dst[3], const float quat[4], const float src[3]) {
|
|
float t[3];
|
|
paro_cross(t, quat, src);
|
|
paro_scale(t, 2.0);
|
|
|
|
float p[3];
|
|
paro_cross(p, quat, t);
|
|
|
|
paro_scaled(dst, t, quat[3]);
|
|
paro_add(dst, dst, src);
|
|
paro_add(dst, dst, p);
|
|
}
|
|
|
|
static float* paro_write_geodesic(float* dst, const float point_a[3], const float point_b[3],
|
|
int num_segments) {
|
|
dst = paro_write_f3(dst, point_a);
|
|
if (num_segments == 0) {
|
|
return dst;
|
|
}
|
|
const float angle_between_endpoints = acos(paro_dot(point_a, point_b));
|
|
const float dtheta = angle_between_endpoints / num_segments;
|
|
float rotation_axis[3], quat[4];
|
|
paro_cross(rotation_axis, point_a, point_b);
|
|
paro_normalize(rotation_axis);
|
|
for (int point_index = 1; point_index < num_segments; point_index++, dst += 3) {
|
|
paro_quat_from_rotation(quat, rotation_axis, dtheta * point_index);
|
|
paro_quat_rotate_vector(dst, quat, point_a);
|
|
}
|
|
return paro_write_f3(dst, point_b);
|
|
}
|
|
|
|
void paro_add_quads(const par_octasphere_config* config, par_octasphere_mesh* mesh) {
|
|
const int ndivisions = PARO_CLAMP(config->num_subdivisions, 0, PAR_OCTASPHERE_MAX_SUBDIVISIONS);
|
|
const int n = (1 << ndivisions) + 1;
|
|
const int verts_per_patch = n * (n + 1) / 2;
|
|
const float r2 = config->corner_radius * 2;
|
|
const float w = PARO_MAX(config->width, r2);
|
|
const float h = PARO_MAX(config->height, r2);
|
|
const float d = PARO_MAX(config->depth, r2);
|
|
const float tx = (w - r2) / 2, ty = (h - r2) / 2, tz = (d - r2) / 2;
|
|
|
|
// Find the vertex indices along each of the patch's 3 edges.
|
|
uint16_t boundaries[3][PARO_MAX_BOUNDARY_LENGTH];
|
|
int a = 0, b = 0, c = 0, row;
|
|
uint16_t j0 = 0;
|
|
for (int col_index = 0; col_index < n - 1; col_index++) {
|
|
int col_height = n - 1 - col_index;
|
|
uint16_t j1 = j0 + 1;
|
|
boundaries[0][a++] = j0;
|
|
for (row = 0; row < col_height - 1; row++) {
|
|
if (col_height == n - 1) {
|
|
boundaries[2][c++] = j0 + row;
|
|
}
|
|
}
|
|
if (col_height == n - 1) {
|
|
boundaries[2][c++] = j0 + row;
|
|
boundaries[2][c++] = j1 + row;
|
|
}
|
|
boundaries[1][b++] = j1 + row;
|
|
j0 += col_height + 1;
|
|
}
|
|
boundaries[0][a] = boundaries[1][b] = j0 + row;
|
|
|
|
// If there is no rounding (i.e. this is a plain box), then clobber the existing indices.
|
|
if (!PARO_CONSTANT_TOPOLOGY && config->corner_radius == 0) {
|
|
mesh->num_indices = 0;
|
|
}
|
|
uint16_t* write_ptr = mesh->indices + mesh->num_indices;
|
|
const uint16_t* begin_ptr = write_ptr;
|
|
|
|
if (PARO_CONSTANT_TOPOLOGY || config->corner_radius > 0) {
|
|
// Go around the top half.
|
|
for (int patch = 0; patch < 4; patch++) {
|
|
if (!PARO_CONSTANT_TOPOLOGY && (patch % 2) == 0 && tz == 0) continue;
|
|
if (!PARO_CONSTANT_TOPOLOGY && (patch % 2) == 1 && tx == 0) continue;
|
|
const int next_patch = (patch + 1) % 4;
|
|
const uint16_t* boundary_a = boundaries[1];
|
|
const uint16_t* boundary_b = boundaries[0];
|
|
const uint16_t offset_a = verts_per_patch * patch;
|
|
const uint16_t offset_b = verts_per_patch * next_patch;
|
|
for (int i = 0; i < n - 1; i++) {
|
|
const uint16_t a = boundary_a[i] + offset_a;
|
|
const uint16_t b = boundary_b[i] + offset_b;
|
|
const uint16_t c = boundary_a[i + 1] + offset_a;
|
|
const uint16_t d = boundary_b[i + 1] + offset_b;
|
|
write_ptr = paro_write_quad(write_ptr, a, b, d, c);
|
|
}
|
|
}
|
|
// Go around the bottom half.
|
|
for (int patch = 4; patch < 8; patch++) {
|
|
if (!PARO_CONSTANT_TOPOLOGY && (patch % 2) == 0 && tx == 0) continue;
|
|
if (!PARO_CONSTANT_TOPOLOGY && (patch % 2) == 1 && tz == 0) continue;
|
|
const int next_patch = 4 + (patch + 1) % 4;
|
|
const uint16_t* boundary_a = boundaries[0];
|
|
const uint16_t* boundary_b = boundaries[2];
|
|
const uint16_t offset_a = verts_per_patch * patch;
|
|
const uint16_t offset_b = verts_per_patch * next_patch;
|
|
for (int i = 0; i < n - 1; i++) {
|
|
const uint16_t a = boundary_a[i] + offset_a;
|
|
const uint16_t b = boundary_b[i] + offset_b;
|
|
const uint16_t c = boundary_a[i + 1] + offset_a;
|
|
const uint16_t d = boundary_b[i + 1] + offset_b;
|
|
write_ptr = paro_write_quad(write_ptr, d, b, a, c);
|
|
}
|
|
}
|
|
// Connect the top and bottom halves.
|
|
if (PARO_CONSTANT_TOPOLOGY || ty > 0) {
|
|
for (int patch = 0; patch < 4; patch++) {
|
|
const int next_patch = 4 + (4 - patch) % 4;
|
|
const uint16_t* boundary_a = boundaries[2];
|
|
const uint16_t* boundary_b = boundaries[1];
|
|
const uint16_t offset_a = verts_per_patch * patch;
|
|
const uint16_t offset_b = verts_per_patch * next_patch;
|
|
for (int i = 0; i < n - 1; i++) {
|
|
const uint16_t a = boundary_a[i] + offset_a;
|
|
const uint16_t b = boundary_b[n - 1 - i] + offset_b;
|
|
const uint16_t c = boundary_a[i + 1] + offset_a;
|
|
const uint16_t d = boundary_b[n - 1 - i - 1] + offset_b;
|
|
write_ptr = paro_write_quad(write_ptr, a, b, d, c);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fill in the top and bottom holes.
|
|
if (PARO_CONSTANT_TOPOLOGY || tx > 0 || ty > 0) {
|
|
uint16_t a, b, c, d;
|
|
a = boundaries[0][n - 1];
|
|
b = a + verts_per_patch;
|
|
c = b + verts_per_patch;
|
|
d = c + verts_per_patch;
|
|
write_ptr = paro_write_quad(write_ptr, a, b, c, d);
|
|
a = boundaries[2][0] + verts_per_patch * 4;
|
|
b = a + verts_per_patch;
|
|
c = b + verts_per_patch;
|
|
d = c + verts_per_patch;
|
|
write_ptr = paro_write_quad(write_ptr, a, b, c, d);
|
|
}
|
|
|
|
// Fill in the side holes.
|
|
if (PARO_CONSTANT_TOPOLOGY || ty > 0) {
|
|
const int sides[4][2] = {{7, 0}, {1, 2}, {3, 4}, {5, 6}};
|
|
for (int side = 0; side < 4; side++) {
|
|
int patch_index, patch, next_patch;
|
|
uint16_t *boundary_a, *boundary_b;
|
|
uint16_t offset_a, offset_b;
|
|
|
|
uint16_t a, b;
|
|
patch_index = sides[side][0];
|
|
patch = patch_index / 2;
|
|
next_patch = 4 + (4 - patch) % 4;
|
|
offset_a = verts_per_patch * patch;
|
|
offset_b = verts_per_patch * next_patch;
|
|
boundary_a = boundaries[2];
|
|
boundary_b = boundaries[1];
|
|
if (patch_index % 2 == 0) {
|
|
a = boundary_a[0] + offset_a;
|
|
b = boundary_b[n - 1] + offset_b;
|
|
} else {
|
|
a = boundary_a[n - 1] + offset_a;
|
|
b = boundary_b[0] + offset_b;
|
|
}
|
|
|
|
uint16_t c, d;
|
|
patch_index = sides[side][1];
|
|
patch = patch_index / 2;
|
|
next_patch = 4 + (4 - patch) % 4;
|
|
offset_a = verts_per_patch * patch;
|
|
offset_b = verts_per_patch * next_patch;
|
|
boundary_a = boundaries[2];
|
|
boundary_b = boundaries[1];
|
|
if (patch_index % 2 == 0) {
|
|
c = boundary_a[0] + offset_a;
|
|
d = boundary_b[n - 1] + offset_b;
|
|
} else {
|
|
c = boundary_a[n - 1] + offset_a;
|
|
d = boundary_b[0] + offset_b;
|
|
}
|
|
write_ptr = paro_write_quad(write_ptr, a, b, d, c);
|
|
}
|
|
}
|
|
|
|
mesh->num_indices += write_ptr - begin_ptr;
|
|
|
|
#ifndef NDEBUG
|
|
uint32_t expected_indices;
|
|
uint32_t expected_vertices;
|
|
par_octasphere_get_counts(config, &expected_indices, &expected_vertices);
|
|
assert(mesh->num_indices <= expected_indices);
|
|
#endif
|
|
}
|
|
|
|
void par_octasphere_get_counts(const par_octasphere_config* config, uint32_t* num_indices,
|
|
uint32_t* num_vertices) {
|
|
const int ndivisions = PARO_CLAMP(config->num_subdivisions, 0, PAR_OCTASPHERE_MAX_SUBDIVISIONS);
|
|
const int n = (1 << ndivisions) + 1;
|
|
const int verts_per_patch = n * (n + 1) / 2;
|
|
const float r2 = config->corner_radius * 2;
|
|
const float w = PARO_MAX(config->width, r2);
|
|
const float h = PARO_MAX(config->height, r2);
|
|
const float d = PARO_MAX(config->depth, r2);
|
|
const float tx = (w - r2) / 2, ty = (h - r2) / 2, tz = (d - r2) / 2;
|
|
const int triangles_per_patch = (n - 2) * (n - 1) + n - 1;
|
|
|
|
// If this is a sphere, return early.
|
|
if (tx == 0 && ty == 0 && tz == 0) {
|
|
*num_indices = triangles_per_patch * 8 * 3;
|
|
*num_vertices = verts_per_patch * 8;
|
|
return;
|
|
}
|
|
|
|
// This is a cuboid, so account for the maximum number of possible quads.
|
|
// - 4*(n-1) quads between the 4 top patches.
|
|
// - 4*(n-1) quads between the 4 bottom patches.
|
|
// - 4*(n-1) quads between the top and bottom patches.
|
|
// - 6 quads to fill "holes" in each cuboid face.
|
|
const int num_connection_quads = (4 + 4 + 4) * (n - 1) + 6;
|
|
|
|
*num_indices = (triangles_per_patch * 8 + num_connection_quads * 2) * 3;
|
|
*num_vertices = verts_per_patch * 8;
|
|
}
|
|
|
|
void par_octasphere_populate(const par_octasphere_config* config, par_octasphere_mesh* mesh) {
|
|
const int ndivisions = PARO_CLAMP(config->num_subdivisions, 0, PAR_OCTASPHERE_MAX_SUBDIVISIONS);
|
|
const int n = (1 << ndivisions) + 1;
|
|
const int verts_per_patch = n * (n + 1) / 2;
|
|
const float r2 = config->corner_radius * 2;
|
|
const float w = PARO_MAX(config->width, r2);
|
|
const float h = PARO_MAX(config->height, r2);
|
|
const float d = PARO_MAX(config->depth, r2);
|
|
const float tx = (w - r2) / 2, ty = (h - r2) / 2, tz = (d - r2) / 2;
|
|
const int triangles_per_patch = (n - 2) * (n - 1) + n - 1;
|
|
const int total_vertices = verts_per_patch * 8;
|
|
|
|
// START TESSELLATION OF SINGLE PATCH (one-eighth of the octasphere)
|
|
float* write_ptr = mesh->positions;
|
|
for (int i = 0; i < n; i++) {
|
|
const float theta = PARO_PI * 0.5 * i / (n - 1);
|
|
const float point_a[] = {0, sinf(theta), cosf(theta)};
|
|
const float point_b[] = {cosf(theta), sinf(theta), 0};
|
|
const int num_segments = n - 1 - i;
|
|
write_ptr = paro_write_geodesic(write_ptr, point_a, point_b, num_segments);
|
|
}
|
|
int f = 0, j0 = 0;
|
|
uint16_t* faces = mesh->indices;
|
|
for (int col_index = 0; col_index < n - 1; col_index++) {
|
|
const int col_height = n - 1 - col_index;
|
|
const int j1 = j0 + 1;
|
|
const int j2 = j0 + col_height + 1;
|
|
const int j3 = j0 + col_height + 2;
|
|
for (int row = 0; row < col_height - 1; row++) {
|
|
paro_write_ui3(faces, f++, j0 + row, j1 + row, j2 + row);
|
|
paro_write_ui3(faces, f++, j2 + row, j1 + row, j3 + row);
|
|
}
|
|
const int row = col_height - 1;
|
|
paro_write_ui3(faces, f++, j0 + row, j1 + row, j2 + row);
|
|
j0 = j2;
|
|
}
|
|
// END TESSELLATION OF SINGLE PATCH
|
|
|
|
// START 8-WAY CLONE OF PATCH
|
|
// clang-format off
|
|
float euler_angles[8][3] = {
|
|
{0, 0, 0}, {0, 1, 0}, {0, 2, 0}, {0, 3, 0},
|
|
{1, 0, 0}, {1, 0, 1}, {1, 0, 2}, {1, 0, 3},
|
|
};
|
|
// clang-format on
|
|
for (int octant = 1; octant < 8; octant++) {
|
|
paro_scale(euler_angles[octant], PARO_PI * 0.5);
|
|
float quat[4];
|
|
paro_quat_from_eulers(quat, euler_angles[octant]);
|
|
float* dst = mesh->positions + octant * verts_per_patch * 3;
|
|
const float* src = mesh->positions;
|
|
for (int vindex = 0; vindex < verts_per_patch; vindex++, dst += 3, src += 3) {
|
|
paro_quat_rotate_vector(dst, quat, src);
|
|
}
|
|
}
|
|
for (int octant = 1; octant < 8; octant++) {
|
|
const int indices_per_patch = triangles_per_patch * 3;
|
|
uint16_t* dst = mesh->indices + octant * indices_per_patch;
|
|
const uint16_t* src = mesh->indices;
|
|
const uint16_t offset = verts_per_patch * octant;
|
|
for (int iindex = 0; iindex < indices_per_patch; ++iindex) {
|
|
dst[iindex] = src[iindex] + offset;
|
|
}
|
|
}
|
|
// END 8-WAY CLONE OF PATCH
|
|
|
|
if (mesh->texcoords && config->uv_mode == PAR_OCTASPHERE_UV_LATLONG) {
|
|
for (int i = 0; i < total_vertices; i++) {
|
|
const int octant = i / verts_per_patch;
|
|
const int relative_index = i % verts_per_patch;
|
|
float* uv = mesh->texcoords + i * 2;
|
|
const float* xyz = mesh->positions + i * 3;
|
|
const float x = xyz[0], y = xyz[1], z = xyz[2];
|
|
const float phi = -atan2(z, x);
|
|
const float theta = acos(y);
|
|
uv[0] = 0.5 * (phi / PARO_PI + 1.0);
|
|
uv[1] = theta / PARO_PI;
|
|
// Special case for the north pole.
|
|
if (octant < 4 && relative_index == verts_per_patch - 1) {
|
|
uv[0] = fmod(0.375 + 0.25 * octant, 1.0);
|
|
uv[1] = 0;
|
|
}
|
|
// Special case for the south pole.
|
|
if (octant >= 4 && relative_index == 0) {
|
|
uv[0] = 0.375 - 0.25 * (octant - 4);
|
|
uv[0] = uv[0] + uv[0] < 0 ? 1.0 : 0.0;
|
|
uv[1] = 1.0;
|
|
}
|
|
// Adjust the prime meridian for proper wrapping.
|
|
if ((octant == 2 || octant == 6) && uv[0] < 0.5) {
|
|
uv[0] += 1.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mesh->normals && config->normals_mode == PAR_OCTASPHERE_NORMALS_SMOOTH) {
|
|
memcpy(mesh->normals, mesh->positions, sizeof(float) * 3 * total_vertices);
|
|
}
|
|
|
|
if (config->corner_radius != 1.0) {
|
|
for (int i = 0; i < total_vertices; i++) {
|
|
float* xyz = mesh->positions + i * 3;
|
|
xyz[0] *= config->corner_radius;
|
|
xyz[1] *= config->corner_radius;
|
|
xyz[2] *= config->corner_radius;
|
|
}
|
|
}
|
|
|
|
mesh->num_indices = triangles_per_patch * 8 * 3;
|
|
mesh->num_vertices = total_vertices;
|
|
|
|
if (tx == 0 && ty == 0 && tz == 0) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < total_vertices; i++) {
|
|
float* xyz = mesh->positions + i * 3;
|
|
const int octant = i / verts_per_patch;
|
|
const float sx = (octant < 2 || octant == 4 || octant == 7) ? +1 : -1;
|
|
const float sy = octant < 4 ? +1 : -1;
|
|
const float sz = (octant == 0 || octant == 3 || octant == 4 || octant == 5) ? +1 : -1;
|
|
xyz[0] += tx * sx;
|
|
xyz[1] += ty * sy;
|
|
xyz[2] += tz * sz;
|
|
}
|
|
|
|
paro_add_quads(config, mesh);
|
|
}
|
|
|
|
#endif // PAR_OCTASPHERE_IMPLEMENTATION
|
|
#endif // PAR_OCTASPHERE_H
|
|
|
|
// par_octasphere is distributed under the MIT license:
|
|
//
|
|
// Copyright (c) 2019 Philip Rideout
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|