Преглед изворни кода

Iterating on the shapes API.

Philip Rideout пре 10 година
родитељ
комит
a05a885a2c
2 измењених фајлова са 65 додато и 136 уклоњено
  1. 42 37
      par_shapes.h
  2. 23 99
      test/test_shapes.c

+ 42 - 37
par_shapes.h

@@ -1,5 +1,5 @@
 // SHAPES :: https://github.com/prideout/par
-// Mesh generator for parametric surfaces and other simple geometry.
+// Simple C library for creation and manipulation of triangle meshes.
 //
 // For our purposes, a "mesh" is a list of points and a list of triangles; the
 // former is a flattened list of three-tuples (32-bit floats) and the latter is
@@ -43,31 +43,29 @@ typedef struct par_shapes_mesh_s {
     float* tcoords;
 } par_shapes_mesh;
 
-#define PAR_SHAPES_SMOOTH_NORMALS (1 << 0)
-#define PAR_SHAPES_TEXTURE_COORDS (1 << 2)
-
 void par_shapes_free(par_shapes_mesh*);
 
 // Generators ------------------------------------------------------------------
 
-// Instance one of the pre-packaged parametric surfaces using the given
-// tessellation levels across the U and V domains.  For example, when
-// tessellating a cylinder, the "slices" control the number of pizza slices,
-// ad the "stacks" control the number of stacked rings.
-par_shapes_mesh* par_shapes_create_parametric(char const*, int slices,
-    int stacks, int flags);
+// Instance a cylinder using the given tessellation levels across the U and V
+// domains.  Think of "slices" like a number of pizza slices, and "stacks" like
+// a number of stacked rings.  Height and radius are both 1.0, but they can
+// easily be changed with par_shapes_scale.
+par_shapes_mesh* par_shapes_create_cylinder(int slices, int stacks);
 
-// List all pre-packaged parametric surfaces.
-char const * const * par_shapes_list_parametric();
+// Create a sphere with valid texture coordinates and normals, but with
+// small triangles near the poles.
+par_shapes_mesh* par_shapes_create_parametric_sphere(int slices, int stacks);
 
-// Create a parametric function from a callback function that consumes a 2D
+// Generate a sphere from a subdivided icosahedron, which produces a nice
+// distribution of triangles, but no texture coordinates.
+par_shapes_mesh* par_shapes_create_subdivided_sphere(int nsubdivisions);
+
+// Create a parametric surface from a callback function that consumes a 2D
 // point in [0,1] and produces a 3D point.
 typedef void (*par_shapes_fn)(float* const, float*);
 par_shapes_mesh* par_shapes_create_custom_parametric(par_shapes_fn, int slices,
-    int stacks, int flags);
-
-// Create a sphere from a subdivided icosahedron; it doesn't have uvs.
-par_shapes_mesh* par_shapes_create_sphere(int nsubdivisions);
+    int stacks);
 
 // Generate points for a 20-sided polyhedron that fits in the unit sphere.
 // Texture coordinates and normals are not provided.
@@ -90,7 +88,7 @@ par_shapes_mesh* par_shapes_create_rock(int seed, int nsubdivisions);
 // Generate an orientable disk shape in 3-space.  Does not include normals or
 // texture coordinates.
 par_shapes_mesh* par_shapes_create_disk(float radius, int slices,
-    float const* center, float const* normal, int flags);
+    float const* center, float const* normal);
 
 // Teaser for a TBD function.  Oooo!  Aaaa!
 par_shapes_mesh* par_shapes_create_lsystem(const char* program);
@@ -151,6 +149,8 @@ void par_shapes_compute_smooth_normals(par_shapes_mesh* m);
 #define PAR_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1))
 #define PAR_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; }
 #define PAR_PI (3.14159265359)
+#define PAR_SHAPES_SMOOTH_NORMALS (1 << 0)
+#define PAR_SHAPES_TEXTURE_COORDS (1 << 2)
 
 static void par_shapes_private_sphere(float* const uv, float* xyz);
 static void par_shapes_private_plane(float* const uv, float* xyz);
@@ -163,6 +163,9 @@ static int par_simplex_noise(int64_t seed, struct osn_context** ctx);
 static void par_simplex_noise_free(struct osn_context* ctx);
 static double par_simplex_noise2(struct osn_context* ctx, double x, double y);
 
+static par_shapes_mesh* par_shapes_create_parametric(char const* name,
+    int slices, int stacks, int flags);
+
 static par_shapes_fn par_shapes_functions[] = {
     par_shapes_private_sphere,
     par_shapes_private_plane,
@@ -228,17 +231,25 @@ static void par_shapes_add3(float* result, float const* a)
     result[2] += a[2];
 }
 
-char const * const * par_shapes_list_parametric()
+par_shapes_mesh* par_shapes_create_cylinder(int slices, int stacks)
 {
-    return par_shapes_names;
+    if (slices < 3 || stacks < 1) {
+        return 0;
+    }
+    return par_shapes_create_parametric("cylinder", slices, stacks, 0);
 }
 
-par_shapes_mesh* par_shapes_create_parametric(char const* name,
-    int slices, int stacks, int flags)
+par_shapes_mesh* par_shapes_create_parametric_sphere(int slices, int stacks)
 {
     if (slices < 3 || stacks < 3) {
         return 0;
     }
+    return par_shapes_create_parametric("sphere", slices, stacks, 0);
+}
+
+static par_shapes_mesh* par_shapes_create_parametric(char const* name,
+    int slices, int stacks, int flags)
+{
     char const * const * list = par_shapes_names;
     int shape_index = 0;
     while (*list) {
@@ -524,12 +535,8 @@ void par_shapes_merge(par_shapes_mesh* dst, par_shapes_mesh const* src)
 }
 
 par_shapes_mesh* par_shapes_create_disk(float radius, int slices,
-    float const* center, float const* normal, int flags)
+    float const* center, float const* normal)
 {
-    if (flags & PAR_SHAPES_TEXTURE_COORDS) {
-        // No texture coordinates since this doesn't have a 2D domain.
-        return 0;
-    }
     par_shapes_mesh* mesh = (par_shapes_mesh*)
         calloc(sizeof(par_shapes_mesh), 1);
     mesh->npoints = slices + 1;
@@ -546,14 +553,12 @@ par_shapes_mesh* par_shapes_create_disk(float radius, int slices,
     }
     float nnormal[3] = {normal[0], normal[1], normal[2]};
     par_shapes_normalize3(nnormal);
-    if (flags & PAR_SHAPES_SMOOTH_NORMALS) {
-        mesh->normals = PAR_MALLOC(float, 3 * mesh->npoints);
-        float* norms = mesh->normals;
-        for (int i = 0; i < mesh->npoints; i++) {
-            *norms++ = nnormal[0];
-            *norms++ = nnormal[1];
-            *norms++ = nnormal[2];
-        }
+    mesh->normals = PAR_MALLOC(float, 3 * mesh->npoints);
+    float* norms = mesh->normals;
+    for (int i = 0; i < mesh->npoints; i++) {
+        *norms++ = nnormal[0];
+        *norms++ = nnormal[1];
+        *norms++ = nnormal[2];
     }
     mesh->ntriangles = slices;
     mesh->triangles = (uint16_t*)
@@ -852,7 +857,7 @@ static void par_shapes_subdivide(par_shapes_mesh* mesh)
     mesh->ntriangles = ntriangles;
 }
 
-par_shapes_mesh* par_shapes_create_sphere(int nsubd)
+par_shapes_mesh* par_shapes_create_subdivided_sphere(int nsubd)
 {
     par_shapes_mesh* mesh = par_shapes_create_icosahedron();
     par_shapes_unweld(mesh, false);
@@ -873,7 +878,7 @@ par_shapes_mesh* par_shapes_create_sphere(int nsubd)
 
 par_shapes_mesh* par_shapes_create_rock(int seed, int subd)
 {
-    par_shapes_mesh* mesh = par_shapes_create_sphere(subd);
+    par_shapes_mesh* mesh = par_shapes_create_subdivided_sphere(subd);
     struct osn_context* ctx;
     par_simplex_noise(seed, &ctx);
     for (int p = 0; p < mesh->npoints; p++) {

+ 23 - 99
test/test_shapes.c

@@ -15,41 +15,18 @@ static int fileexists(const char* filename)
 
 int main()
 {
-    describe("par_shapes_list_parametric") {
-        it("should return several reasonably-sized strings") {
-            char const * const * list = par_shapes_list_parametric();
-            int nshapes = 0;
-            while (*list) {
-                const char* name = *list;
-                assert(strlen(name) > 0 && strlen(name) < 128);
-                nshapes++;
-                list++;
-            }
-            assert(nshapes == 5);
-        }
-    }
-
-    describe("par_shapes_create_parametric") {
-        it("should fail when stacks or slices are less than 3") {
-            par_shapes_mesh* bad1 = par_shapes_create_parametric(
-                "sphere", 2, 3, 0);
-            par_shapes_mesh* bad2 = par_shapes_create_parametric(
-                "sphere", 3, 2, 0);
-            par_shapes_mesh* good = par_shapes_create_parametric(
-                "sphere", 3, 3, 0);
+    describe("par_shapes_create_cylinder") {
+        it("should fail when the number of stacks or slices is invalid") {
+            par_shapes_mesh* bad1 = par_shapes_create_cylinder(1, 1);
+            par_shapes_mesh* bad2 = par_shapes_create_cylinder(1, 3);
+            par_shapes_mesh* good = par_shapes_create_cylinder(3, 1);
             assert_null(bad1);
             assert_null(bad2);
             assert_ok(good);
             par_shapes_free(good);
         }
-        it("should fail with a bogus string") {
-            par_shapes_mesh const* bad = par_shapes_create_parametric(
-                "bogus", 3, 3, 0);
-            assert_null(bad);
-        }
         it("should generate correct number of faces and vertices") {
-            par_shapes_mesh* m = par_shapes_create_parametric(
-                "sphere", 5, 6, 0);
+            par_shapes_mesh* m = par_shapes_create_cylinder(5, 6);
             assert_equal(m->npoints, 42);
             assert_equal(m->ntriangles, 60);
             par_shapes_free(m);
@@ -59,7 +36,11 @@ int main()
     describe("par_shapes_export") {
         it("should generate an OBJ file") {
             par_shapes_mesh* m;
-            m = par_shapes_create_parametric("sphere", 5, 6, 0);
+            m = par_shapes_create_cylinder(5, 20);
+            par_shapes_export(m, "build/test_shapes_cylinder.obj");
+            assert_ok(fileexists("build/test_shapes_cylinder.obj"));
+            par_shapes_free(m);
+            m = par_shapes_create_parametric_sphere(5, 6);
             par_shapes_export(m, "build/test_shapes_sphere.obj");
             assert_ok(fileexists("build/test_shapes_sphere.obj"));
             par_shapes_free(m);
@@ -67,10 +48,6 @@ int main()
             par_shapes_export(m, "build/test_shapes_plane.obj");
             assert_ok(fileexists("build/test_shapes_plane.obj"));
             par_shapes_free(m);
-            m = par_shapes_create_parametric("cylinder", 5, 20, 0);
-            par_shapes_export(m, "build/test_shapes_cylinder.obj");
-            assert_ok(fileexists("build/test_shapes_cylinder.obj"));
-            par_shapes_free(m);
             m = par_shapes_create_parametric("torus", 7, 10, 0);
             par_shapes_export(m, "build/test_shapes_torus.obj");
             assert_ok(fileexists("build/test_shapes_torus.obj"));
@@ -101,8 +78,8 @@ int main()
     describe("transforms") {
         it("should support translation") {
             par_shapes_mesh* a, *b;
-            a = par_shapes_create_parametric("cylinder", 20, 3, 0);
-            b = par_shapes_create_parametric("cylinder", 4, 3, 0);
+            a = par_shapes_create_cylinder(20, 3);
+            b = par_shapes_create_cylinder(4, 3);
             par_shapes_translate(a, 0.5, 0.5, 0.25);
             par_shapes_merge(a, b);
             par_shapes_export(a, "build/test_shapes_translation.obj");
@@ -111,8 +88,8 @@ int main()
         }
         it("should support rotation") {
             par_shapes_mesh* a, *b;
-            a = par_shapes_create_parametric("cylinder", 20, 3, 0);
-            b = par_shapes_create_parametric("cylinder", 4, 3, 0);
+            a = par_shapes_create_cylinder(20, 3);
+            b = par_shapes_create_cylinder(4, 3);
             float axis1[3] = {0, 1, 0};
             float axis2[3] = {0, 0, 1};
             par_shapes_rotate(a, PAR_PI * 0.5, axis1);
@@ -124,7 +101,7 @@ int main()
         }
         it("should support non-uniform scale") {
             par_shapes_mesh* a;
-            a = par_shapes_create_parametric("cylinder", 15, 3, 0);
+            a = par_shapes_create_cylinder(15, 3);
             par_shapes_scale(a, 1, 1, 5);
             par_shapes_export(a, "build/test_shapes_scale.obj");
             par_shapes_free(a);
@@ -138,11 +115,11 @@ int main()
             float anormal[3] = {0, 0, 1};
             float acenter[3] = {0, 0, 0};
             par_shapes_mesh* a, *b;
-            a = par_shapes_create_disk(aradius, slices, acenter, anormal, 0);
+            a = par_shapes_create_disk(aradius, slices, acenter, anormal);
             float bradius = 0.2;
             float bcenter[3] = {0, 0, 0.2};
             float bnormal[3] = {0, 1, 0};
-            b = par_shapes_create_disk(bradius, slices, bcenter, bnormal, 0);
+            b = par_shapes_create_disk(bradius, slices, bcenter, bnormal);
             par_shapes_merge(a, b);
             par_shapes_export(a, "build/test_shapes_disks.obj");
             par_shapes_free(a);
@@ -154,7 +131,7 @@ int main()
             float normal[3] = {0, 1, 0};
             float center[3] = {0, 0, 0};
             par_shapes_mesh* a, *b;
-            a = par_shapes_create_disk(radius, slices, center, normal, 0);
+            a = par_shapes_create_disk(radius, slices, center, normal);
             b = par_shapes_create_rock(1, 2);
             float aabb[6];
             par_shapes_compute_aabb(b, aabb);
@@ -170,7 +147,7 @@ int main()
             float normal[3] = {0, 1, 0};
             float center[3] = {0, 0, 0};
             par_shapes_mesh* a, *b;
-            a = par_shapes_create_disk(radius, slices, center, normal, 0);
+            a = par_shapes_create_disk(radius, slices, center, normal);
             b = par_shapes_create_dodecahedron();
             par_shapes_translate(b, 0, 0.934, 0);
             par_shapes_merge(a, b);
@@ -186,10 +163,10 @@ int main()
             const float top_center[3] = {0, 1.2, 0};
             const int tess = 30;
             par_shapes_mesh *a, *b, *c, *d;
-            a = par_shapes_create_disk(2.5, tess, O, J, 0);
-            b = par_shapes_create_parametric("cylinder", tess, 3, 0);
+            a = par_shapes_create_disk(2.5, tess, O, J);
+            b = par_shapes_create_cylinder(tess, 3);
             c = par_shapes_create_parametric("torus", 15, tess, 0);
-            d = par_shapes_create_disk(1, tess, top_center, J, 0);
+            d = par_shapes_create_disk(1, tess, top_center, J);
             par_shapes_rotate(c, PAR_PI / tess, K);
             par_shapes_translate(c, 0, 0, 1);
             par_shapes_scale(b, 1.2, 1.2, 1);
@@ -206,58 +183,5 @@ int main()
         }
     }
 
-    describe("flags") {
-        it("parametric surfaces should support normals and/or uvs") {
-            par_shapes_mesh* m;
-            int flags = PAR_SHAPES_SMOOTH_NORMALS;
-            m = par_shapes_create_parametric("sphere", 5, 6, flags);
-            par_shapes_export(m, "build/test_shapes_n.obj");
-            assert_ok(m->normals);
-            par_shapes_free(m);
-            flags = PAR_SHAPES_TEXTURE_COORDS;
-            m = par_shapes_create_parametric("sphere", 5, 6, flags);
-            par_shapes_export(m, "build/test_shapes_tc.obj");
-            assert_ok(m->tcoords);
-            par_shapes_free(m);
-            flags |= PAR_SHAPES_SMOOTH_NORMALS;
-            m = par_shapes_create_parametric("sphere", 5, 6, flags);
-            assert_ok(m->tcoords && m->normals);
-            par_shapes_export(m, "build/test_shapes_tcn.obj");
-            par_shapes_free(m);
-        }
-        it("disk should support normals but not uvs") {
-            float normal[3] = {0, 0, 1};
-            float center[3] = {0, 0, 0};
-            int flags = PAR_SHAPES_SMOOTH_NORMALS;
-            par_shapes_mesh* m;
-            m = par_shapes_create_disk(1, 32, center, normal, flags);
-            assert_ok(m->normals);
-            par_shapes_export(m, "build/test_shapes_diskn.obj");
-            par_shapes_free(m);
-            flags |= PAR_SHAPES_TEXTURE_COORDS;
-            m = par_shapes_create_disk(1, 32, center, normal, flags);
-            assert_null(m);
-        }
-        it("meshes with heterogeneous flags should be mergeable") {
-            float normal[3] = {0, 0, 1};
-            float center[3] = {0, 0, 0};
-            int flags = PAR_SHAPES_SMOOTH_NORMALS;
-            par_shapes_mesh* a, *b;
-            a = par_shapes_create_disk(1, 32, center, normal, flags);
-            assert_null(a->tcoords);
-            flags |= PAR_SHAPES_TEXTURE_COORDS;
-            b = par_shapes_create_parametric("klein", 30, 40, flags);
-            par_shapes_translate(b, 0, 0, 10);
-            par_shapes_scale(b, 0.08, 0.08, 0.08);
-            par_shapes_merge(a, b);
-            assert_ok(a->normals && a->tcoords);
-            float axis[3] = {1, 0, 0};
-            par_shapes_rotate(a, -PAR_PI * 0.5, axis);
-            par_shapes_export(a, "build/test_shapes_heterogeneous.obj");
-            par_shapes_free(a);
-            par_shapes_free(b);
-        }
-    }
-
     return assert_failures();
 }