File Coverage

3Space.xs
Criterion Covered Total %
statement 541 762 71.0
branch 231 464 49.7
condition n/a
subroutine n/a
pod n/a
total 772 1226 62.9


line stmt bran cond sub pod time code
1             #include "EXTERN.h"
2             #include "perl.h"
3             #include "XSUB.h"
4             #include "ppport.h"
5              
6             #include
7              
8             #if !defined(__cplusplus) && defined(_MSC_VER) && _MSC_VER < 1900
9             # define inline __inline
10             #endif
11              
12             #ifndef newAV_alloc_x
13             static AV *shim_newAV_alloc_x(int n) {
14             AV *ret= newAV();
15             av_extend(ret, n-1);
16             return ret;
17             }
18             #define newAV_alloc_x shim_newAV_alloc_x
19             #endif
20              
21             /**********************************************************************************************\
22             * m3s API, defining all the math for this module.
23             \**********************************************************************************************/
24              
25             struct m3s_space;
26             typedef struct m3s_space m3s_space_t;
27             typedef struct m3s_space *m3s_space_or_null;
28              
29             #define SPACE_XV(s) ((s)->mat + 0)
30             #define SPACE_YV(s) ((s)->mat + 3)
31             #define SPACE_ZV(s) ((s)->mat + 6)
32             #define SPACE_ORIGIN(s) ((s)->mat + 9)
33             struct m3s_space {
34             NV mat[12];
35             int is_normal; // -1=UNKNOWN, 0=FALSE, 1=TRUE
36             // These pointers must be refreshed by m3s_space_recache_parent
37             // before they can be used after any control flow outside of XS.
38             struct m3s_space *parent;
39             int n_parents;
40             };
41              
42             static const m3s_space_t m3s_identity= {
43             { 1,0,0, 0,1,0, 0,0,1, 0,0,0 },
44             1,
45             NULL,
46             0,
47             };
48              
49             #define M3S_VECTYPE_VECOBJ 1
50             #define M3S_VECTYPE_ARRAY 2
51             #define M3S_VECTYPE_HASH 3
52             #define M3S_VECTYPE_PDL 4
53             #define M3S_VECTYPE_PDLMULTI 5
54              
55             #define M3S_VECLOAD(vec,x_or_vec,y,z,dflt) do { \
56             if (y) { \
57             vec[0]= SvNV(x_or_vec); \
58             vec[1]= SvNV(y); \
59             vec[2]= z? SvNV(z) : dflt; \
60             } else { \
61             m3s_read_vector_from_sv(vec, x_or_vec, NULL, NULL); \
62             } } while(0)
63              
64             typedef NV m3s_vector_t[3];
65             typedef NV *m3s_vector_p;
66              
67             struct m3s_4space_frustum_projection {
68             /* _ _
69             * | m00 0 m20 0 |
70             * | 0 m11 m21 0 |
71             * | 0 0 m22 m32 |
72             * |_ 0 0 -1 0 _|
73             */
74             double m00, m11, m20, m21, m22, m32;
75             bool centered;
76             };
77             typedef union m3s_4space_projection {
78             struct m3s_4space_frustum_projection frustum;
79             } m3s_4space_projection_t;
80              
81             static const NV NV_tolerance = 1e-14;
82             static const double double_tolerance = 1e-14;
83              
84             // Initialize to identity, known to be normal
85 150           void m3s_space_init(m3s_space_t *space) {
86 150           memcpy(space, &m3s_identity, sizeof(*space));
87 150           }
88              
89             // Vector cross product, C = A cross B
90 21           static inline void m3s_vector_cross(NV *dest, NV *vec1, NV *vec2) {
91 21           dest[0]= vec1[1]*vec2[2] - vec1[2]*vec2[1];
92 21           dest[1]= vec1[2]*vec2[0] - vec1[0]*vec2[2];
93 21           dest[2]= vec1[0]*vec2[1] - vec1[1]*vec2[0];
94 21           }
95              
96             // Vector dot Product, N = A dot B
97 43           static inline NV m3s_vector_dotprod(NV *vec1, NV *vec2) {
98 43           return vec1[0]*vec2[0] + vec1[1]*vec2[1] + vec1[2]*vec2[2];
99             }
100              
101             // Vector cosine. Same as dotprod for unit vectors.
102 3           static inline NV m3s_vector_cosine(NV *vec1, NV *vec2) {
103             NV mag, prod;
104 3           mag= m3s_vector_dotprod(vec1,vec1) * m3s_vector_dotprod(vec2,vec2);
105 3           prod= m3s_vector_dotprod(vec1,vec2);
106 3 50         if (mag < NV_tolerance)
107 0           croak("Can't calculate vector cosine of vector with length < 1e-14");
108 3 100         else if (fabs(mag - 1) > NV_tolerance)
109 2           prod /= sqrt(mag);
110 3           return prod;
111             }
112              
113             /* Check whether a space's axis vectors are unit length and orthogonal to
114             * eachother, and update the 'is_normal' flag on the space.
115             * Having this flag = 1 can optimize relative rotations later.
116             * The flag gets set to -1 any time an operation may have broken normality.
117             * Approx Cost: 4-19 load, 3-18 mul, 4-17 add, 1-2 stor
118             */
119 4           static int m3s_space_check_normal(m3s_space_t *sp) {
120             NV *vec, *pvec;
121 4           sp->is_normal= 0;
122 4 50         for (vec= sp->mat+6, pvec= sp->mat; vec > sp->mat; pvec= vec, vec -= 3) {
123 4 50         if (fabs(m3s_vector_dotprod(vec,vec) - 1) > NV_tolerance)
124 4           return 0;
125 0 0         if (m3s_vector_dotprod(vec,pvec) > NV_tolerance)
126 0           return 0;
127             }
128 0           return sp->is_normal= 1;
129             }
130              
131             /* Project a vector from the parent coordinate space into this coordinate space.
132             * The vector is modified in-place. The origin is not subtracted from a vector,
133             * as opposed to projecting a point (below)
134             * Approx Cost: 12 load, 9 mul, 6 add, 3 stor
135             */
136 36           static inline void m3s_space_project_vector(m3s_space_t *sp, NV *vec) {
137 36           NV x= vec[0], y= vec[1], z= vec[2], *mat= sp->mat;
138 36           vec[0]= x * mat[0] + y * mat[1] + z * mat[2];
139 36           vec[1]= x * mat[3] + y * mat[4] + z * mat[5];
140 36           vec[2]= x * mat[6] + y * mat[7] + z * mat[8];
141 36           }
142              
143             /* Project a point from the parent coordinate space into this coordinate space.
144             * The point is modified in-place.
145             * Approx Cost: 15 load, 9 fmul, 9 fadd, 3 stor
146             */
147 19           static inline void m3s_space_project_point(m3s_space_t *sp, NV *vec) {
148 19           NV x, y, z, *mat= sp->mat;
149 19           x= vec[0] - mat[9];
150 19           y= vec[1] - mat[10];
151 19           z= vec[2] - mat[11];
152 19           vec[0]= x * mat[0] + y * mat[1] + z * mat[2];
153 19           vec[1]= x * mat[3] + y * mat[4] + z * mat[5];
154 19           vec[2]= x * mat[6] + y * mat[7] + z * mat[8];
155 19           }
156              
157             /* Project a sibling coordinate space into this coordinate space.
158             * (sibling meaning they share the same parent coordinate space)
159             * The sibling will now be a child coordinate space.
160             * Approx Cost: 53 load, 33 fmul, 27 fadd, 14 stor
161             */
162 3           static void m3s_space_project_space(m3s_space_t *sp, m3s_space_t *peer) {
163 3           m3s_space_project_vector(sp, SPACE_XV(peer));
164 3           m3s_space_project_vector(sp, SPACE_YV(peer));
165 3           m3s_space_project_vector(sp, SPACE_ZV(peer));
166 3           m3s_space_project_point(sp, SPACE_ORIGIN(peer));
167 3           peer->parent= sp;
168 3           peer->n_parents= sp->n_parents + 1;
169 3           }
170              
171             /* Un-project a local vector of this coordinate space out to the parent
172             * coordinate space. The vector remains a directional vector, without
173             * getting the origin point added to it.
174             * Approx Cost: 12 load, 9 fmul, 6 fadd, 3 stor
175             */
176 81           static inline void m3s_space_unproject_vector(m3s_space_t *sp, NV *vec) {
177 81           NV x= vec[0], y= vec[1], z= vec[2], *mat= sp->mat;
178 81           vec[0]= x * mat[0] + y * mat[3] + z * mat[6];
179 81           vec[1]= x * mat[1] + y * mat[4] + z * mat[7];
180 81           vec[2]= x * mat[2] + y * mat[5] + z * mat[8];
181 81           }
182              
183             /* Un-project a local point of this coordinate space out to the parent
184             * coordinate space.
185             * Approx Cost: 15 load, 9 fmul, 9 fadd, 3 stor
186             */
187 24           static inline void m3s_space_unproject_point(m3s_space_t *sp, NV *vec) {
188 24           NV x= vec[0], y= vec[1], z= vec[2], *mat= sp->mat;
189 24           vec[0]= x * mat[0] + y * mat[3] + z * mat[6] + mat[9];
190 24           vec[1]= x * mat[1] + y * mat[4] + z * mat[7] + mat[10];
191 24           vec[2]= x * mat[2] + y * mat[5] + z * mat[8] + mat[11];
192 24           }
193              
194             /* Un-project a child coordinate space out of this coordinate space so that it
195             * will become a peer of this space (sharing a parent)
196             * Approx Cost: 53 load, 33 fmul, 27 fadd, 14 stor
197             */
198 16           static void m3s_space_unproject_space(m3s_space_t *sp, m3s_space_t *inner) {
199 16           m3s_space_unproject_vector(sp, SPACE_XV(inner));
200 16           m3s_space_unproject_vector(sp, SPACE_YV(inner));
201 16           m3s_space_unproject_vector(sp, SPACE_ZV(inner));
202 16           m3s_space_unproject_point(sp, SPACE_ORIGIN(inner));
203 16           inner->parent= sp->parent;
204 16           inner->n_parents= sp->n_parents;
205 16           }
206              
207             /* Assuming the ->parent pointer cache is current but the ->n_parent is not,
208             * this walks down the linked list (twice) and updates it.
209             */
210 0           static void m3s_space_recache_n_parents(m3s_space_t *space) {
211             m3s_space_t *cur;
212 0           int depth= -1;
213 0 0         for (cur= space; cur; cur= cur->parent)
214 0           ++depth;
215 0 0         for (cur= space; cur; cur= cur->parent)
216 0           cur->n_parents= depth--;
217 0 0         if (!(depth == -1)) croak("assertion failed: depth == -1");
218 0           }
219              
220             /* Given two spaces (with valid ->parent caches) project/unproject the space so that
221             * it represents the same global coordinates while now being described in terms of
222             * a new parent coordinate space. 'parent' may be NULL, to move 'space' to become a
223             * top-level space
224             * MUST call m3s_space_recache_parent on each (non null) space before calling this method!
225             */
226 4           static void m3s_space_reparent(m3s_space_t *space, m3s_space_t *parent) {
227             m3s_space_t sp_tmp, *common_parent;
228             // Short circuit for nothing to do
229 4 50         if (space->parent == parent)
230 1           return;
231             // Walk back the stack of parents until it has fewer parents than 'space'.
232             // This way space->parent has a chance to be 'common_parent'.
233 4           common_parent= parent;
234 8 100         while (common_parent && common_parent->n_parents >= space->n_parents)
    100          
235 4           common_parent= common_parent->parent;
236             // Now unproject 'space' from each of its parents until its parent is 'common_parent'.
237 18 100         while (space->n_parents && space->parent != common_parent) {
    100          
238             // Map 'space' out to be a sibling of its parent
239 14           m3s_space_unproject_space(space->parent, space);
240             // if 'space' reached the depth of common_parent+1 and the loop didn't stop,
241             // then it wasn't actually the parent they have in common, yet.
242 14 100         if (common_parent && common_parent->n_parents + 1 == space->n_parents)
    100          
243 1           common_parent= common_parent->parent;
244             }
245             // At this point, 'space' is either a root 3Space, or 'common_parent' is its parent.
246             // If the common parent is the original 'parent', then we're done.
247 4 100         if (parent == common_parent)
248 1           return;
249             // Calculate an equivalent space to 'parent' at this parent depth.
250 3 50         if (!(parent != NULL)) croak("assertion failed: parent != NULL");
251 3           memcpy(&sp_tmp, parent, sizeof(sp_tmp));
252 5 100         while (sp_tmp.parent != common_parent)
253 2           m3s_space_unproject_space(sp_tmp.parent, &sp_tmp);
254             // 'sp_tmp' is now equivalent to projecting through the chain from common_parent to parent,
255             // so just project 'space' into this temporary and we're done.
256 3           m3s_space_project_space(&sp_tmp, space);
257 3           space->parent= parent;
258 3           space->n_parents= parent->n_parents + 1;
259             // Note that any space which has 'space' as a parent will now have an invalid n_parents
260             // cache, which is why those caches need rebuilt before calling this function.
261             }
262              
263             /* Rotate the space around an arbitrary vector in the parent space.
264             * Angle is provided as direct sine and cosine factors.
265             * The axis does not need to be a normal vector.
266             * Approx Cost: 87-99 fmul, 1-2 fdiv, 56-62 fadd, 1 fabs, 1-2 sqrt
267             */
268 9           static void m3s_space_rotate(m3s_space_t *space, NV angle_sin, NV angle_cos, m3s_vector_p axis) {
269             NV mag_sq, scale, *vec, tmp0, tmp1;
270             m3s_space_t r;
271              
272             // Construct temporary coordinate space where 'zv' is 'axis'
273 9           mag_sq= m3s_vector_dotprod(axis,axis);
274 9 50         if (mag_sq == 0)
275 0           croak("Can't rotate around vector with 0 magnitude");
276 9 50         scale= (fabs(mag_sq - 1) > NV_tolerance)? 1/sqrt(mag_sq) : 1;
277 9           r.mat[6]= axis[0] * scale;
278 9           r.mat[7]= axis[1] * scale;
279 9           r.mat[8]= axis[2] * scale;
280             // set y vector to any vector not colinear with z vector
281 9           r.mat[3]= 1;
282 9           r.mat[4]= 0;
283 9           r.mat[5]= 0;
284             // x = normalize( y cross z )
285 9           m3s_vector_cross(r.mat, r.mat+3, r.mat+6);
286 9           mag_sq= m3s_vector_dotprod(r.mat,r.mat);
287 9 100         if (mag_sq < NV_tolerance) {
288             // try again with a different vector
289 1           r.mat[3]= 0;
290 1           r.mat[4]= 1;
291 1           m3s_vector_cross(r.mat, r.mat+3, r.mat+6);
292 1           mag_sq= m3s_vector_dotprod(r.mat,r.mat);
293 1 50         if (mag_sq == 0)
294 0           croak("BUG: failed to find perpendicular vector");
295             }
296 9           scale= 1 / sqrt(mag_sq);
297 9           r.mat[0] *= scale;
298 9           r.mat[1] *= scale;
299 9           r.mat[2] *= scale;
300             // y = z cross x (and should be normalized already because right angles)
301 9           m3s_vector_cross(r.mat+3, r.mat+6, r.mat+0);
302             // Now for each axis vector, project it into this space, rotate it (around Z), and project it back out
303 36 100         for (vec=space->mat + 6; vec >= space->mat; vec-= 3) {
304 27           m3s_space_project_vector(&r, vec);
305 27           tmp0= angle_cos * vec[0] - angle_sin * vec[1];
306 27           tmp1= angle_sin * vec[0] + angle_cos * vec[1];
307 27           vec[0]= tmp0;
308 27           vec[1]= tmp1;
309 27           m3s_space_unproject_vector(&r, vec);
310             }
311 9           }
312              
313             /* Rotate the space around an axis of its parent. axis_idx: 0 (xv), 1 (yv) or 2 (zv)
314             * Angle is supplied as direct sine / cosine values.
315             * Approx Cost: 12 fmul, 6 fadd
316             */
317 23           static void m2s_space_parent_axis_rotate(m3s_space_t *space, NV angle_sin, NV angle_cos, int axis_idx) {
318             int ofs1, ofs2;
319             NV tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, *matp;
320 23           switch (axis_idx) {
321 5           case 0: ofs1= 1, ofs2= 2; break;
322 4           case 1: ofs1= 2, ofs2= 0; break;
323 14           case 2: ofs1= 0, ofs2= 1; break;
324 0           default: croak("BUG: axis_idx > 2");
325             }
326 23           matp= SPACE_XV(space);
327 23           tmp1= angle_cos * matp[ofs1 ] - angle_sin * matp[ofs2 ];
328 23           tmp2= angle_sin * matp[ofs1 ] + angle_cos * matp[ofs2 ];
329 23           tmp3= angle_cos * matp[ofs1+3] - angle_sin * matp[ofs2+3];
330 23           tmp4= angle_sin * matp[ofs1+3] + angle_cos * matp[ofs2+3];
331 23           tmp5= angle_cos * matp[ofs1+6] - angle_sin * matp[ofs2+6];
332 23           tmp6= angle_sin * matp[ofs1+6] + angle_cos * matp[ofs2+6];
333 23           matp[ofs1]= tmp1;
334 23           matp[ofs2]= tmp2;
335 23           matp[ofs1+3]= tmp3;
336 23           matp[ofs2+3]= tmp4;
337 23           matp[ofs1+6]= tmp5;
338 23           matp[ofs2+6]= tmp6;
339 23           }
340              
341             /* Rotate the space around one of its own axes. axis_idx: 0 (xv), 1 (yv) or 2 (zv)
342             * Angle is supplied as direct sine / cosine values.
343             * If the space is_normal (unit-length vectors orthogonal to eachother) this uses a very
344             * efficient optimization. Else it falls back to the full m3s_space_rotate function.
345             * Approx Cost, if normal: 18 fmul, 12 fadd
346             * Approx Cost, else: 87-99 fmul, 1-2 fdiv, 56-62 fadd, 1 fabs, 1-2 sqrt
347             */
348 6           static void m3s_space_self_axis_rotate(m3s_space_t *space, NV angle_sin, NV angle_cos, int axis_idx) {
349             m3s_vector_t vec1, vec2;
350              
351 6 100         if (space->is_normal == -1)
352 3           m3s_space_check_normal(space);
353 6 100         if (!space->is_normal) {
354 3           m3s_space_rotate(space, angle_sin, angle_cos, space->mat + axis_idx*3);
355             } else {
356             // Axes are all unit vectors, orthogonal to eachother, and can skip setting up a
357             // custom rotation matrix. Just define the vectors inside the space post-rotation,
358             // then project them out of the space.
359 3 100         if (axis_idx == 0) { // around XV, Y -> Z
360 1           vec1[0]= 0; vec1[1]= angle_cos; vec1[2]= angle_sin;
361 1           m3s_space_unproject_vector(space, vec1);
362 1           vec2[0]= 0; vec2[1]= -angle_sin; vec2[2]= angle_cos;
363 1           m3s_space_unproject_vector(space, vec2);
364 1           memcpy(SPACE_YV(space), vec1, sizeof(vec1));
365 1           memcpy(SPACE_ZV(space), vec2, sizeof(vec2));
366 2 100         } else if (axis_idx == 1) { // around YV, Z -> X
367 1           vec1[0]= angle_sin; vec1[1]= 0; vec1[2]= angle_cos;
368 1           m3s_space_unproject_vector(space, vec1);
369 1           vec2[0]= angle_cos; vec2[1]= 0; vec2[2]= -angle_sin;
370 1           m3s_space_unproject_vector(space, vec2);
371 1           memcpy(SPACE_ZV(space), vec1, sizeof(vec1));
372 1           memcpy(SPACE_XV(space), vec2, sizeof(vec2));
373             } else { // around ZV, X -> Y
374 1           vec1[0]= angle_cos; vec1[1]= angle_sin; vec1[2]= 0;
375 1           m3s_space_unproject_vector(space, vec1);
376 1           vec2[0]= -angle_sin; vec2[1]= angle_cos; vec2[2]= 0;
377 1           m3s_space_unproject_vector(space, vec2);
378 1           memcpy(SPACE_XV(space), vec1, sizeof(vec1));
379 1           memcpy(SPACE_YV(space), vec2, sizeof(vec2));
380             }
381             }
382 6           }
383              
384             /**********************************************************************************************\
385             * Typemap code that converts from Perl objects to C structs and back
386             * All instances of Math::3Space have a magic-attached struct m3s_space_t
387             * All vectors objects are a blessed scalar ref aligned to hold double[3] in the PV
388             \**********************************************************************************************/
389              
390             // destructor for m3s_space_t magic
391 150           static int m3s_space_magic_free(pTHX_ SV* sv, MAGIC* mg) {
392 150 50         if (mg->mg_ptr) {
393 150           Safefree(mg->mg_ptr);
394 150           mg->mg_ptr= NULL;
395             }
396 150           return 0; // ignored anyway
397             }
398             #ifdef USE_ITHREADS
399             // If threading system needs to clone a Space, clone the struct but don't bother
400             // fixing the parent cache. That should always pass through code that calls
401             // m3s_space_recache_parent between when this happens and when ->parent gets used.
402             static int m3s_space_magic_dup(pTHX_ MAGIC *mg, CLONE_PARAMS *param) {
403             m3s_space_t *space;
404             PERL_UNUSED_VAR(param);
405             Newxz(space, 1, m3s_space_t);
406             memcpy(space, mg->mg_ptr, sizeof(m3s_space_t));
407             space->parent= NULL; // ensure no possibility of cross-thread bugs
408             mg->mg_ptr= (char*) space;
409             return 0;
410             };
411             #else
412             #define m3s_space_magic_dup NULL
413             #endif
414              
415             // magic virtual method table for m3s_space
416             // Pointer to this struct is also used as an ID for type of magic
417             static MGVTBL m3s_space_magic_vt= {
418             NULL, /* get */
419             NULL, /* write */
420             NULL, /* length */
421             NULL, /* clear */
422             m3s_space_magic_free,
423             NULL, /* copy */
424             m3s_space_magic_dup
425             #ifdef MGf_LOCAL
426             ,NULL
427             #endif
428             };
429              
430             // Return the m3s_space struct attached to a Perl object via MAGIC.
431             // The 'obj' should be a reference to a blessed SV.
432             // Use AUTOCREATE to attach magic and allocate a struct if it wasn't present.
433             // Use OR_DIE for a built-in croak() if the return value would be NULL.
434             #define AUTOCREATE 1
435             #define OR_DIE 2
436 1212           static m3s_space_t* m3s_get_magic_space(SV *obj, int flags) {
437             SV *sv;
438             MAGIC* magic;
439             m3s_space_t *space;
440 1212 50         if (!sv_isobject(obj)) {
441 0 0         if (flags & OR_DIE)
442 0           croak("Not an object");
443 0           return NULL;
444             }
445 1212           sv= SvRV(obj);
446 1212 100         if (SvMAGICAL(sv)) {
447             /* Iterate magic attached to this scalar, looking for one with our vtable */
448 1210 50         for (magic= SvMAGIC(sv); magic; magic = magic->mg_moremagic)
449 1210 50         if (magic->mg_type == PERL_MAGIC_ext && magic->mg_virtual == &m3s_space_magic_vt)
    50          
450             /* If found, the mg_ptr points to the fields structure. */
451 1210           return (m3s_space_t*) magic->mg_ptr;
452             }
453 2 50         if (flags & AUTOCREATE) {
454 2           Newx(space, 1, m3s_space_t);
455 2           m3s_space_init(space);
456 2           magic= sv_magicext(sv, NULL, PERL_MAGIC_ext, &m3s_space_magic_vt, (const char*) space, 0);
457             #ifdef USE_ITHREADS
458             magic->mg_flags |= MGf_DUP;
459             #endif
460 2           return space;
461             }
462 0 0         else if (flags & OR_DIE)
463 0           croak("Object lacks 'm3s_space_t' magic");
464 0           return NULL;
465             }
466              
467             // Create a new Math::3Space object, to become the owner of the supplied space struct.
468             // Returned SV is a reference with active refcount, which is what the typemap
469             // wants for returning a "m3s_space_t*" to perl-land
470 148           static SV* m3s_wrap_space(m3s_space_t *space) {
471             SV *obj;
472             MAGIC *magic;
473             // Since this is used in typemap, handle NULL gracefully
474 148 50         if (!space)
475 0           return &PL_sv_undef;
476             // Create a node object
477 148           obj= newRV_noinc((SV*)newHV());
478 148           sv_bless(obj, gv_stashpv("Math::3Space", GV_ADD));
479 148           magic= sv_magicext(SvRV(obj), NULL, PERL_MAGIC_ext, &m3s_space_magic_vt, (const char*) space, 0);
480             #ifdef USE_ITHREADS
481             magic->mg_flags |= MGf_DUP;
482             #else
483             (void)magic; // suppress warning
484             #endif
485 148           return obj;
486             }
487              
488             // This code assumes that 8-byte alignment is good enough even if the NV type is
489             // long double
490             #define NV_ALIGNMENT_MASK 7
491 4           static char* m3s_make_aligned_buffer(SV *buf, size_t size) {
492             char *p;
493             STRLEN len;
494              
495 4 100         if (!SvPOK(buf))
496 3           sv_setpvs(buf, "");
497 4           p= SvPV_force(buf, len);
498 4 100         if (len < size) {
499 3 50         SvGROW(buf, size);
    50          
500 3           SvCUR_set(buf, size);
501 3           p= SvPVX(buf);
502             }
503             // ensure double alignment
504 4 50         if ((intptr_t)p & NV_ALIGNMENT_MASK) {
505 0 0         SvGROW(buf, size + NV_ALIGNMENT_MASK);
    0          
506 0           p= SvPVX(buf);
507 0           sv_chop(buf, p + NV_ALIGNMENT_MASK+1 - ((intptr_t)p & NV_ALIGNMENT_MASK));
508 0           SvCUR_set(buf, size);
509             }
510 4           return p;
511             }
512              
513             // Create a new Math::3Space::Vector object, which is a blessed scalar-ref containing
514             // the aligned bytes of three NV (usually doubles)
515 137           static SV* m3s_wrap_vector(m3s_vector_p vec_array) {
516             SV *obj, *buf;
517 137           buf= newSVpvn((char*) vec_array, sizeof(NV)*3);
518 137 50         if ((intptr_t)SvPVX(buf) & NV_ALIGNMENT_MASK) {
519 0           memcpy(m3s_make_aligned_buffer(buf, sizeof(NV)*3), vec_array, sizeof(NV)*3);
520             }
521 137           obj= newRV_noinc(buf);
522 137           sv_bless(obj, gv_stashpv("Math::3Space::Vector", GV_ADD));
523 137           return obj;
524             }
525              
526             // Return a pointer to the aligned NV[3] inside the scalar ref 'vector'.
527             // These can be written directly to modify the vector's value.
528 158           static NV * m3s_vector_get_array(SV *vector) {
529 158           char *p= NULL;
530 158           STRLEN len= 0;
531 158 50         if (sv_isobject(vector) && SvPOK(SvRV(vector)))
    50          
532 158           p= SvPV(SvRV(vector), len);
533 158 50         if (len != sizeof(NV)*3 || ((intptr_t)p & NV_ALIGNMENT_MASK) != 0)
    50          
534 0           croak("Invalid or corrupt Math::3Space::Vector object");
535 158           return (NV*) p;
536             }
537              
538             // Read the values of a vector out of perl data 'in' and store them in 'vec'
539             // This should be extended to handle any sensible format a user might supply vectors.
540             // It currently supports arrayref-of-SvNV, hashref of SvNV, scalarrefs of packed doubles
541             // (i.e. vector objects), and PDL ndarrays.
542             // The return type is one of the M3S_VECTYPE_ constants. The output params 'vec', 'pdl_dims',
543             // and 'component_sv' may or may not get filled in, depending on that return type.
544             // The function 'croak's if the input is not valid.
545 57           static int m3s_read_vector_from_sv(m3s_vector_p vec, SV *in, size_t pdl_dims[3], SV *component_sv[3]) {
546 57 50         SV **el, *rv= SvROK(in)? SvRV(in) : NULL;
547             AV *vec_av;
548             HV *attrs;
549             size_t i, n;
550 57 50         if (!rv)
551 0           croak("Vector must be a reference type");
552              
553             // Given a scalar-ref to a buffer the size of 3 packed NV (could be double or long double)
554             // which is also the structure used by blessed Math::3Space::Vector, simply copy the value
555             // into 'vec'.
556 57 100         if (SvPOK(rv) && SvCUR(rv) == sizeof(NV)*3) {
    50          
557 26           memcpy(vec, SvPV_force_nolen(rv), sizeof(NV)*3);
558 26           return M3S_VECTYPE_VECOBJ;
559             }
560             // Given an array, the array must be length 2 or 3, and each element must look like a number.
561             // If it matches, the values are loaded into 'vec', and pointers to the SVs are stored in
562             // component_sv if the caller provided that.
563 31 100         else if (SvTYPE(rv) == SVt_PVAV) {
564 23           vec_av= (AV*) rv;
565 23           n= av_len(vec_av)+1;
566 23 100         if (n != 3 && n != 2)
    50          
567 0           croak("Vector arrayref must have 2 or 3 elements");
568 23           vec[2]= 0;
569 23 100         if (component_sv) component_sv[2]= NULL;
570 90 100         for (i=0; i < n; i++) {
571 67           el= av_fetch(vec_av, i, 0);
572 67 50         if (!el || !*el || !looks_like_number(*el))
    50          
    50          
573 0           croak("Vector element %d is not a number", (int)i);
574 67           vec[i]= SvNV(*el);
575 67 100         if (component_sv) component_sv[i]= *el;
576             }
577 23           return M3S_VECTYPE_ARRAY;
578             }
579             // Given a hashref, look for elements 'x', 'y', and 'z'. They default to 0 if not found.
580             // Each found element must be a number. The SV pointers are saved into component_sv if
581             // it was provided by the caller.
582 8 50         else if (SvTYPE(rv) == SVt_PVHV) {
583 8           const char *keys= "x\0y\0z";
584 8           attrs= (HV*) rv;
585 32 100         for (i=0; i < 3; i++) {
586 24 100         if ((el= hv_fetch(attrs, keys+(i<<1), 1, 0)) && *el && SvOK(*el)) {
    50          
    50          
587 16 50         if (!looks_like_number(*el))
588 0           croak("Hash element %s is not a number", keys+(i<<1));
589 16           vec[i]= SvNV(*el);
590 16 100         if (component_sv) component_sv[i]= *el;
591             } else {
592 8           vec[i]= 0;
593 8 100         if (component_sv) component_sv[i]= NULL;
594             }
595             }
596 8           return M3S_VECTYPE_HASH;
597             }
598             // Given a PDL ndarray object, check its dimensions. If the ndarray is exactly a 2x1 or 3x1
599             // array, then copy those values into 'vec'. If the ndarray has higher dimensions, let the
600             // caller know about them in the 'pdl_dims' array, and don't touch 'vec'. The caller can
601             // then decide how to handle those higher dimensions, or throw an error etc.
602 0 0         else if (sv_derived_from(in, "PDL")) {
603 0           dSP;
604 0           int count, single_dim= 0;
605             SV *ret;
606              
607 0           ENTER;
608 0           SAVETMPS;
609 0 0         PUSHMARK(SP);
610 0 0         EXTEND(SP,1);
611 0           PUSHs(in);
612 0           PUTBACK;
613 0           count= call_method("dims", G_LIST);
614 0           SPAGAIN;
615 0 0         if (pdl_dims) {
616             // return up to 3 dims; more than that doesn't change our behavior.
617 0 0         if (count > 3) SP -= (count-3);
618 0 0         pdl_dims[2]= (count > 2)? POPi : 0;
619 0 0         pdl_dims[1]= (count > 1)? POPi : 0;
620 0 0         pdl_dims[0]= (count > 0)? POPi : 0;
621 0 0         if (pdl_dims[1] == 0)
622 0           single_dim= pdl_dims[0];
623             }
624             // if caller doesn't pass pdl_dims, they expect a simple 1x3 vector.
625 0 0         else if (count == 1) {
626 0           single_dim= POPi;
627             } else {
628 0           SP -= count;
629             }
630 0           PUTBACK;
631 0 0         FREETMPS;
632 0           LEAVE;
633              
634 0 0         if (single_dim == 2 || single_dim == 3) {
    0          
635 0           ENTER;
636 0           SAVETMPS;
637 0 0         PUSHMARK(SP);
638 0 0         EXTEND(SP,1);
639 0           PUSHs(in);
640 0           PUTBACK;
641 0           count= call_method("list", G_LIST);
642 0           SPAGAIN;
643 0 0         if (count > 3) SP -= (count-3); // should never happen
644 0 0         vec[2]= (count > 2)? POPn : 0;
645 0 0         vec[1]= (count > 1)? POPn : 0;
646 0 0         vec[0]= (count > 0)? POPn : 0;
647 0           PUTBACK;
648 0 0         FREETMPS;
649 0           LEAVE;
650 0           return M3S_VECTYPE_PDL;
651             }
652 0 0         else if (!pdl_dims)
653 0           croak("Expected PDL dimensions of 2x1 or 3x1");
654             else
655 0           return M3S_VECTYPE_PDLMULTI;
656             } else
657 0           croak("Can't read vector from %s", sv_reftype(in, 1));
658             }
659              
660             // Create a new Math::3Space::Vector object, which is a blessed scalar-ref containing
661             // the aligned bytes of three NV (usually doubles)
662 5           static SV* m3s_wrap_projection(m3s_4space_projection_t *p, const char* pkg) {
663             SV *obj, *buf;
664 5           buf= newSVpvn((char*) p, sizeof(m3s_4space_projection_t));
665 5 50         if ((intptr_t)SvPVX(buf) & NV_ALIGNMENT_MASK) {
666 0           memcpy(m3s_make_aligned_buffer(buf, sizeof(m3s_4space_projection_t)), p, sizeof(m3s_4space_projection_t));
667             }
668 5           obj= newRV_noinc(buf);
669 5           sv_bless(obj, gv_stashpv(pkg, GV_ADD));
670 5           return obj;
671             }
672              
673             // Return a pointer to the aligned NV[3] inside the scalar ref 'vector'.
674             // These can be written directly to modify the vector's value.
675 11           static m3s_4space_projection_t * m3s_projection_get(SV *vector) {
676 11           char *p= NULL;
677 11           STRLEN len= 0;
678 11 50         if (sv_isobject(vector) && SvPOK(SvRV(vector)))
    50          
679 11           p= SvPV(SvRV(vector), len);
680 11 50         if (len != sizeof(m3s_4space_projection_t) || ((intptr_t)p & NV_ALIGNMENT_MASK) != 0)
    50          
681 0           croak("Invalid or corrupt Math::3Space::Projection object");
682 11           return (m3s_4space_projection_t*) p;
683             }
684              
685             // Walk the perl-side chain of $space->parent->parent->... and update the C-side
686             // parent pointers and n_parents counters. This needs called any time we come back
687             // from perl-land because scripts might update these references at any time, and
688             // it would require too much magic to update the C pointers as that happened.
689             // So, just let them get out of sync, then re-cache them here.
690 22           static void m3s_space_recache_parent(SV *space_sv) {
691 22           m3s_space_t *space= NULL, *prev= NULL;
692 22           SV **field, *cur= space_sv;
693 22           HV *seen= NULL;
694 22           int depth= 0;
695 869 100         while (cur && SvOK(cur)) {
    100          
696 847           prev= space;
697 1694           if (!(
698 847 50         SvROK(cur) && SvTYPE(SvRV(cur)) == SVt_PVHV
    50          
699 847 50         && (space= m3s_get_magic_space(cur, 0))
700             ))
701 0           croak("'parent' is not a Math::3Space : %s", SvPV_nolen(cur));
702 847           space->parent= NULL;
703 847 100         if (prev) {
704 825           prev->parent= space;
705 825 50         if (++depth > 964) { // Check for cycles in the graph
706 0 0         if (!seen) seen= (HV*) sv_2mortal((SV*)newHV()); // hash will auto-garbage-collect
707 0           field= hv_fetch(seen, (char*)&space, sizeof(space), 1); // use pointer value as key
708 0 0         if (!field || !*field) croak("BUG");
    0          
709 0 0         if (SvOK(*field))
710 0           croak("Cycle detected in space->parent graph");
711 0           sv_setsv(*field, &PL_sv_yes); // signal that we've been here with any pointer value
712             }
713             }
714 847           field= hv_fetch((HV*) SvRV(cur), "parent", 6, 0);
715 847 100         cur= field? *field : NULL;
716             }
717 869 100         for (space= m3s_get_magic_space(space_sv, OR_DIE); space; space= space->parent)
718 847           space->n_parents= depth--;
719 22           }
720              
721             // TODO: find a way to tap into PDL without compile-time dependency...
722 0           static SV* m3s_pdl_vector(m3s_vector_p vec, int dim) {
723             SV *ret;
724             int count;
725 0           dSP;
726 0           ENTER;
727 0           SAVETMPS;
728 0 0         PUSHMARK(SP);
729 0 0         EXTEND(SP, 3);
730 0           PUSHs(sv_2mortal(newSVnv(vec[0])));
731 0           PUSHs(sv_2mortal(newSVnv(vec[1])));
732 0 0         if (dim == 3) PUSHs(sv_2mortal(newSVnv(vec[2])));
733 0           PUTBACK;
734 0           count= call_pv("PDL::Core::pdl", G_SCALAR);
735 0 0         if (count != 1) croak("call to PDL::Core::pdl did not return an ndarray");
736 0           SPAGAIN;
737 0           ret= POPs;
738 0           SvREFCNT_inc(ret); // protect from FREETMPS
739 0           PUTBACK;
740 0 0         FREETMPS;
741 0           LEAVE;
742 0           return ret;
743             }
744 0           static SV* m3s_pdl_matrix(NV *mat, bool transpose) {
745             AV *av;
746             SV *ret;
747             int i, count;
748 0           dSP;
749 0           ENTER;
750 0           SAVETMPS;
751 0 0         PUSHMARK(SP);
752 0 0         EXTEND(SP, 3);
753 0 0         for (i= 0; i < 3; i++) {
754 0           av= newAV_alloc_x(3);
755 0 0         av_push(av, newSVnv(mat[transpose? i+0 : (i*3)+0]));
756 0 0         av_push(av, newSVnv(mat[transpose? i+3 : (i*3)+1]));
757 0 0         av_push(av, newSVnv(mat[transpose? i+6 : (i*3)+2]));
758 0           PUSHs(sv_2mortal((SV*)newRV_noinc((SV*)av)));
759             }
760 0           PUTBACK;
761 0           count= call_pv("PDL::Core::pdl", G_SCALAR);
762 0 0         if (count != 1) croak("call to PDL::Core::pdl did not return an ndarray");
763 0           SPAGAIN;
764 0           ret= POPs;
765 0           SvREFCNT_inc(ret); // protect from FREETMPS
766 0           PUTBACK;
767 0 0         FREETMPS;
768 0           LEAVE;
769 0           return ret;
770             }
771              
772             /**********************************************************************************************\
773             * Math::3Space Public API
774             \**********************************************************************************************/
775             MODULE = Math::3Space PACKAGE = Math::3Space
776              
777             void
778             _init(obj, source=NULL)
779             SV *obj
780             SV *source
781             INIT:
782 0           m3s_space_t *space= m3s_get_magic_space(obj, AUTOCREATE);
783             m3s_space_t *src_space;
784             HV *attrs;
785             SV **field;
786             CODE:
787 0 0         if (source) {
788 0 0         if (sv_isobject(source)) {
789 0           src_space= m3s_get_magic_space(source, OR_DIE);
790 0           memcpy(space, src_space, sizeof(*space));
791 0 0         } else if (SvROK(source) && SvTYPE(source) == SVt_PVHV) {
    0          
792 0           attrs= (HV*) SvRV(source);
793 0 0         if ((field= hv_fetch(attrs, "xv", 2, 0)) && *field && SvOK(*field))
    0          
    0          
794 0           m3s_read_vector_from_sv(SPACE_XV(space), *field, NULL, NULL);
795             else
796 0           SPACE_XV(space)[0]= 1;
797 0 0         if ((field= hv_fetch(attrs, "yv", 2, 0)) && *field && SvOK(*field))
    0          
    0          
798 0           m3s_read_vector_from_sv(SPACE_YV(space), *field, NULL, NULL);
799             else
800 0           SPACE_YV(space)[1]= 1;
801 0 0         if ((field= hv_fetch(attrs, "zv", 2, 0)) && *field && SvOK(*field))
    0          
    0          
802 0           m3s_read_vector_from_sv(SPACE_ZV(space), *field, NULL, NULL);
803             else
804 0           SPACE_ZV(space)[2]= 1;
805 0 0         if ((field= hv_fetch(attrs, "origin", 6, 0)) && *field && SvOK(*field))
    0          
    0          
806 0           m3s_read_vector_from_sv(SPACE_ORIGIN(space), *field, NULL, NULL);
807 0           space->is_normal= -1;
808             } else
809 0           croak("Invalid source for _init");
810             }
811              
812             SV*
813             clone(obj)
814             SV *obj
815             INIT:
816 2           m3s_space_t *space= m3s_get_magic_space(obj, OR_DIE), *space2;
817             HV *clone_hv;
818             CODE:
819 2 50         if (SvTYPE(SvRV(obj)) != SVt_PVHV)
820 0           croak("Invalid source object"); // just to be really sure before next line
821 2           clone_hv= newHVhv((HV*)SvRV(obj));
822 2           RETVAL= newRV_noinc((SV*)clone_hv);
823 2           sv_bless(RETVAL, gv_stashpv(sv_reftype(SvRV(obj), 1), GV_ADD));
824 2           space2= m3s_get_magic_space(RETVAL, AUTOCREATE);
825 2           memcpy(space2, space, sizeof(*space2));
826             OUTPUT:
827             RETVAL
828              
829             SV*
830             space(parent=NULL)
831             SV *parent
832             INIT:
833             m3s_space_t *space;
834             CODE:
835 148 100         if (parent && SvOK(parent) && !m3s_get_magic_space(parent, 0))
    50          
    50          
836 0           croak("Invalid parent, must be instance of Math::3Space");
837 148           Newx(space, 1, m3s_space_t);
838 148           m3s_space_init(space);
839 148           RETVAL= m3s_wrap_space(space);
840 148 100         if (parent && SvOK(parent))
    50          
841 113           hv_store((HV*)SvRV(RETVAL), "parent", 6, newSVsv(parent), 0);
842             OUTPUT:
843             RETVAL
844              
845             void
846             xv(space, x_or_vec=NULL, y=NULL, z=NULL)
847             m3s_space_t *space
848             SV *x_or_vec
849             SV *y
850             SV *z
851             ALIAS:
852             Math::3Space::yv = 1
853             Math::3Space::zv = 2
854             Math::3Space::origin = 3
855             INIT:
856 103           NV *vec= space->mat + ix * 3;
857             PPCODE:
858 103 100         if (x_or_vec) {
859 14 100         M3S_VECLOAD(vec,x_or_vec,y,z,0);
    50          
860 14 100         if (ix < 3) space->is_normal= -1;
861             // leave $self on stack as return value
862             } else {
863 89           ST(0)= sv_2mortal(m3s_wrap_vector(vec));
864             }
865 103           XSRETURN(1);
866              
867             bool
868             is_normal(space)
869             m3s_space_t *space
870             CODE:
871 13 100         if (space->is_normal == -1)
872 1           m3s_space_check_normal(space);
873 13 100         RETVAL= space->is_normal;
874             OUTPUT:
875             RETVAL
876              
877             int
878             parent_count(space)
879             SV *space
880             CODE:
881 11           m3s_space_recache_parent(space);
882 11 100         RETVAL= m3s_get_magic_space(space, OR_DIE)->n_parents;
883             OUTPUT:
884             RETVAL
885              
886             void
887             reparent(space, parent)
888             SV *space
889             SV *parent
890             INIT:
891 6           m3s_space_t *sp3= m3s_get_magic_space(space, OR_DIE), *psp3=NULL, *cur;
892             PPCODE:
893 6           m3s_space_recache_parent(space);
894 6 100         if (SvOK(parent)) {
895 5           psp3= m3s_get_magic_space(parent, OR_DIE);
896 5           m3s_space_recache_parent(parent);
897             // Make sure this doesn't create a cycle
898 112 100         for (cur= psp3; cur; cur= cur->parent)
899 109 100         if (cur == sp3)
900 2           croak("Attempt to create a cycle: new 'parent' is a child of this space");
901             }
902 4           m3s_space_reparent(sp3, psp3);
903 4           hv_store((HV*) SvRV(space), "parent", 6, newSVsv(parent), 0);
904 4           XSRETURN(1);
905              
906             void
907             translate(space, x_or_vec, y=NULL, z=NULL)
908             m3s_space_t *space
909             SV *x_or_vec
910             SV *y
911             SV *z
912             ALIAS:
913             Math::3Space::tr = 0
914             Math::3Space::travel = 1
915             Math::3Space::go = 1
916             INIT:
917             NV vec[3], *matp;
918             PPCODE:
919 11 100         M3S_VECLOAD(vec,x_or_vec,y,z,0);
    50          
920 11 100         if (ix) {
921 2           matp= space->mat;
922 2           matp[9] += vec[0] * matp[0] + vec[1] * matp[3] + vec[2] * matp[6];
923 2           ++matp;
924 2           matp[9] += vec[0] * matp[0] + vec[1] * matp[3] + vec[2] * matp[6];
925 2           ++matp;
926 2           matp[9] += vec[0] * matp[0] + vec[1] * matp[3] + vec[2] * matp[6];
927             } else {
928 9           matp= SPACE_ORIGIN(space);
929 9           *matp++ += vec[0];
930 9           *matp++ += vec[1];
931 9           *matp++ += vec[2];
932             }
933 11           XSRETURN(1);
934              
935             void
936             scale(space, xscale_or_vec, yscale=NULL, zscale=NULL)
937             m3s_space_t *space
938             SV *xscale_or_vec
939             SV *yscale
940             SV *zscale
941             ALIAS:
942             Math::3Space::set_scale = 1
943             INIT:
944 6           NV vec[3], s, m, *matp= SPACE_XV(space);
945             size_t i;
946             PPCODE:
947 6 50         if (SvROK(xscale_or_vec) && yscale == NULL) {
    0          
948 0           m3s_read_vector_from_sv(vec, xscale_or_vec, NULL, NULL);
949             } else {
950 6           vec[0]= SvNV(xscale_or_vec);
951 6 100         vec[1]= yscale? SvNV(yscale) : vec[0];
952 6 100         vec[2]= zscale? SvNV(zscale) : vec[0];
953             }
954 24 100         for (i= 0; i < 3; i++) {
955 18           s= vec[i];
956 18 50         if (ix == 1) {
957 0           m= sqrt(m3s_vector_dotprod(matp,matp));
958 0 0         if (m > 0)
959 0           s /= m;
960             else
961 0           warn("can't scale magnitude=0 vector");
962             }
963 18           *matp++ *= s;
964 18           *matp++ *= s;
965 18           *matp++ *= s;
966             }
967 6           space->is_normal= -1;
968 6           XSRETURN(1);
969              
970             void
971             rotate(space, angle, x_or_vec, y=NULL, z=NULL)
972             m3s_space_t *space
973             NV angle
974             SV *x_or_vec
975             SV *y
976             SV *z
977             INIT:
978             m3s_vector_t vec;
979             ALIAS:
980             Math::3Space::rot = 0
981             PPCODE:
982 6 50         if (y) {
983 0 0         if (!z) croak("Missing z coordinate in space->rotate(angle, x, y, z)");
984 0           vec[0]= SvNV(x_or_vec);
985 0           vec[1]= SvNV(y);
986 0           vec[2]= SvNV(z);
987             } else {
988 6           m3s_read_vector_from_sv(vec, x_or_vec, NULL, NULL);
989             }
990 6           m3s_space_rotate(space, sin(angle * 2 * M_PI), cos(angle * 2 * M_PI), vec);
991             // return $self
992 6           XSRETURN(1);
993              
994             void
995             rot_x(space, angle)
996             m3s_space_t *space
997             NV angle
998             ALIAS:
999             Math::3Space::rot_y = 1
1000             Math::3Space::rot_z = 2
1001             Math::3Space::rot_xv = 3
1002             Math::3Space::rot_yv = 4
1003             Math::3Space::rot_zv = 5
1004             INIT:
1005             NV *matp, tmp1, tmp2;
1006             size_t ofs1, ofs2;
1007 29           NV s= sin(angle * 2 * M_PI), c= cos(angle * 2 * M_PI);
1008             PPCODE:
1009 29 100         if (ix < 3) // Rotate around axis of parent
1010 23           m2s_space_parent_axis_rotate(space, s, c, ix);
1011             else
1012 6           m3s_space_self_axis_rotate(space, s, c, ix - 3);
1013 29           XSRETURN(1);
1014              
1015             void
1016             project_vector(space, ...)
1017             m3s_space_t *space
1018             INIT:
1019             m3s_vector_t vec;
1020             int i, vectype, count;
1021             AV *vec_av;
1022             HV *vec_hv;
1023 7           SV *pdl_origin= NULL, *pdl_matrix= NULL;
1024             size_t pdl_dims[3];
1025             ALIAS:
1026             Math::3Space::project = 1
1027             Math::3Space::unproject_vector = 2
1028             Math::3Space::unproject = 3
1029             PPCODE:
1030 14 100         for (i= 1; i < items; i++) {
1031 7           vectype= m3s_read_vector_from_sv(vec, ST(i), pdl_dims, NULL);
1032 7 50         if (vectype == M3S_VECTYPE_PDLMULTI) {
1033 0           dSP;
1034 0 0         if (!pdl_origin) {
1035 0           pdl_origin= sv_2mortal(m3s_pdl_vector(SPACE_ORIGIN(space), 3));
1036 0           pdl_matrix= sv_2mortal(m3s_pdl_matrix(SPACE_XV(space), !(ix&2) /*transpose bool*/));
1037             }
1038 0           ENTER;
1039 0           SAVETMPS;
1040 0 0         PUSHMARK(SP);
1041             // first, clone the input. Then modify it in place.
1042 0 0         EXTEND(SP, 4);
1043 0           PUSHs(ST(i));
1044 0           PUTBACK;
1045 0           count= call_method("copy", G_SCALAR);
1046 0           SPAGAIN;
1047 0 0         if (count != 1) croak("PDL->copy failed?");
1048             // project point subtracts origin
1049 0 0         PUSHs(ix == 3? pdl_origin : &PL_sv_undef);
1050 0           PUSHs(pdl_matrix);
1051             // unproject point adds origin
1052 0 0         PUSHs(ix == 1? pdl_origin : &PL_sv_undef);
1053 0           PUTBACK;
1054 0           count= call_pv("Math::3Space::_pdl_project_inplace", G_DISCARD);
1055            
1056 0 0         FREETMPS;
1057 0           LEAVE;
1058 0           ST(i-1)= ST(i);
1059             }
1060             else {
1061 7           switch (ix) {
1062 0           case 0: m3s_space_project_vector(space, vec); break;
1063 6           case 1: m3s_space_project_point(space, vec); break;
1064 0           case 2: m3s_space_unproject_vector(space, vec); break;
1065 1           default: m3s_space_unproject_point(space, vec);
1066             }
1067 7           switch (vectype) {
1068 2           case M3S_VECTYPE_ARRAY:
1069 2           vec_av= newAV();
1070 2           av_extend(vec_av, 2);
1071 2           av_push(vec_av, newSVnv(vec[0]));
1072 2           av_push(vec_av, newSVnv(vec[1]));
1073 2           av_push(vec_av, newSVnv(vec[2]));
1074 2           ST(i-1)= sv_2mortal(newRV_noinc((SV*)vec_av));
1075 2           break;
1076 2           case M3S_VECTYPE_HASH:
1077 2           vec_hv= newHV();
1078 2           hv_stores(vec_hv, "x", newSVnv(vec[0]));
1079 2           hv_stores(vec_hv, "y", newSVnv(vec[1]));
1080 2           hv_stores(vec_hv, "z", newSVnv(vec[2]));
1081 2           ST(i-1)= sv_2mortal(newRV_noinc((SV*)vec_hv));
1082 2           break;
1083 0           case M3S_VECTYPE_PDL:
1084 0           ST(i-1)= sv_2mortal(m3s_pdl_vector(vec, pdl_dims[0]));
1085 0           break;
1086 3           default:
1087 3           ST(i-1)= sv_2mortal(m3s_wrap_vector(vec));
1088             }
1089             }
1090             }
1091 7           XSRETURN(items-1);
1092              
1093             void
1094             project_vector_inplace(space, ...)
1095             m3s_space_t *space
1096             INIT:
1097             m3s_vector_t vec;
1098             m3s_vector_p vecp;
1099             size_t i, j, n;
1100             int vectype;
1101             AV *vec_av;
1102 17           SV **item, *x, *y, *z, *pdl_origin= NULL, *pdl_matrix= NULL;
1103             size_t pdl_dims[3];
1104             SV *component_sv[3];
1105             ALIAS:
1106             Math::3Space::project_inplace = 1
1107             Math::3Space::unproject_vector_inplace = 2
1108             Math::3Space::unproject_inplace = 3
1109             PPCODE:
1110 34 100         for (i= 1; i < items; i++) {
1111 17 50         if (!SvROK(ST(i)))
1112 0           croak("Expected vector at $_[%d]", (int)(i-1));
1113 17           vectype= m3s_read_vector_from_sv(vec, ST(i), pdl_dims, component_sv);
1114 17           switch (vectype) {
1115 13           case M3S_VECTYPE_VECOBJ:
1116 13           vecp= m3s_vector_get_array(ST(i));
1117 13           switch (ix) {
1118 0           case 0: m3s_space_project_vector(space, vecp); break;
1119 6           case 1: m3s_space_project_point(space, vecp); break;
1120 0           case 2: m3s_space_unproject_vector(space, vecp); break;
1121 7           default: m3s_space_unproject_point(space, vecp);
1122             }
1123 13           break;
1124 4           case M3S_VECTYPE_ARRAY:
1125             case M3S_VECTYPE_HASH:
1126 4           switch (ix) {
1127 0           case 0: m3s_space_project_vector(space, vec); break;
1128 4           case 1: m3s_space_project_point(space, vec); break;
1129 0           case 2: m3s_space_unproject_vector(space, vec); break;
1130 0           default: m3s_space_unproject_point(space, vec);
1131             }
1132 16 100         for (j=0; j < 3; j++)
1133 12 100         if (component_sv[j])
1134 10           sv_setnv(component_sv[j], vec[j]);
1135 4           break;
1136 0           case M3S_VECTYPE_PDL:
1137             case M3S_VECTYPE_PDLMULTI:
1138             {
1139             int count;
1140 0           dSP;
1141 0 0         if (!pdl_origin) {
1142 0           pdl_origin= sv_2mortal(m3s_pdl_vector(SPACE_ORIGIN(space), 3));
1143 0           pdl_matrix= sv_2mortal(m3s_pdl_matrix(SPACE_XV(space), !(ix&2) /*transpose bool*/));
1144             }
1145 0           ENTER;
1146 0           SAVETMPS;
1147 0 0         PUSHMARK(SP);
1148 0 0         EXTEND(SP, 4);
1149 0           PUSHs(ST(i));
1150             // project point subtracts origin
1151 0 0         PUSHs(ix == 3? pdl_origin : &PL_sv_undef);
1152 0           PUSHs(pdl_matrix);
1153             // unproject point adds origin
1154 0 0         PUSHs(ix == 1? pdl_origin : &PL_sv_undef);
1155 0           PUTBACK;
1156 0           count= call_pv("Math::3Space::_pdl_project_inplace", G_DISCARD);
1157            
1158 0 0         FREETMPS;
1159 0           LEAVE;
1160             }
1161 0           break;
1162 0           default:
1163 0           croak("bug: unhandled vec type");
1164             }
1165             }
1166             // return $self
1167 17           XSRETURN(1);
1168              
1169             void
1170             get_gl_matrix(space, buffer=NULL)
1171             m3s_space_t *space
1172             SV *buffer
1173             INIT:
1174             NV *src;
1175             double *dst;
1176             PPCODE:
1177 6 100         if (buffer) {
1178 2           dst= (double*) m3s_make_aligned_buffer(buffer, sizeof(double)*16);
1179 2           src= space->mat;
1180 2           dst[ 0] = src[ 0]; dst[ 1] = src[ 1]; dst[ 2] = src[ 2]; dst[ 3] = 0;
1181 2           dst[ 4] = src[ 3]; dst[ 5] = src[ 4]; dst[ 6] = src[ 5]; dst[ 7] = 0;
1182 2           dst[ 8] = src[ 6]; dst[ 9] = src[ 7]; dst[10] = src[ 8]; dst[11] = 0;
1183 2           dst[12] = src[ 9]; dst[13] = src[10]; dst[14] = src[11]; dst[15] = 1;
1184 2           XSRETURN(0);
1185             } else {
1186 4 50         EXTEND(SP, 16);
1187 4           mPUSHn(SPACE_XV(space)[0]); mPUSHn(SPACE_XV(space)[1]); mPUSHn(SPACE_XV(space)[2]); mPUSHn(0);
1188 4           mPUSHn(SPACE_YV(space)[0]); mPUSHn(SPACE_YV(space)[1]); mPUSHn(SPACE_YV(space)[2]); mPUSHn(0);
1189 4           mPUSHn(SPACE_ZV(space)[0]); mPUSHn(SPACE_ZV(space)[1]); mPUSHn(SPACE_ZV(space)[2]); mPUSHn(0);
1190 4           mPUSHn(SPACE_ORIGIN(space)[0]); mPUSHn(SPACE_ORIGIN(space)[1]); mPUSHn(SPACE_ORIGIN(space)[2]); mPUSHn(1);
1191 4           XSRETURN(16);
1192             }
1193              
1194             #**********************************************************************************************
1195             # Math::3Space::Projection
1196             #**********************************************************************************************
1197             MODULE = Math::3Space PACKAGE = Math::3Space::Projection
1198              
1199             SV *
1200             _frustum(left, right, bottom, top, near_z, far_z)
1201             double left
1202             double right
1203             double bottom
1204             double top
1205             double near_z
1206             double far_z
1207             INIT:
1208             m3s_4space_projection_t proj;
1209             double w, h, d, w_1, h_1, d_1;
1210             CODE:
1211 2           w= right - left;
1212 2           h= top - bottom;
1213 2           d= far_z - near_z;
1214 2 50         if (fabs(w) < double_tolerance || fabs(h) < double_tolerance || fabs(d) < double_tolerance)
    50          
    50          
1215 0           croak("Described frustum has a zero-sized dimension");
1216              
1217 2           w_1= 1/w;
1218 2           h_1= 1/h;
1219 2           d_1= 1/d;
1220 2           proj.frustum.m00= near_z * 2 * w_1;
1221 2           proj.frustum.m11= near_z * 2 * h_1;
1222 2           proj.frustum.m20= (right+left) * w_1;
1223 2           proj.frustum.m21= (top+bottom) * h_1;
1224 2           proj.frustum.m22= -(near_z+far_z) * d_1;
1225 2           proj.frustum.m32= -2 * near_z * far_z * d_1;
1226             // use optimized version if m20 and m21 are zero
1227 2 100         proj.frustum.centered= fabs(proj.frustum.m20) < double_tolerance && fabs(proj.frustum.m21) < double_tolerance;
    50          
1228 2           RETVAL= m3s_wrap_projection(&proj,
1229             "Math::3Space::Projection::Frustum"
1230             );
1231             OUTPUT:
1232             RETVAL
1233              
1234             SV *
1235             _perspective(vertical_field_of_view, aspect, near_z, far_z)
1236             double vertical_field_of_view
1237             double aspect
1238             double near_z
1239             double far_z
1240             INIT:
1241             m3s_4space_projection_t proj;
1242 3           double f= tan(M_PI_2 - vertical_field_of_view * M_PI),
1243 3           neg_inv_range_z= -1 / (far_z - near_z);
1244             CODE:
1245 3           proj.frustum.m00= f / aspect;
1246 3           proj.frustum.m11= f;
1247 3           proj.frustum.m20= 0;
1248 3           proj.frustum.m21= 0;
1249 3           proj.frustum.m22= (near_z+far_z) * neg_inv_range_z;
1250 3           proj.frustum.m32= 2 * near_z * far_z * neg_inv_range_z;
1251 3           proj.frustum.centered= true;
1252 3           RETVAL= m3s_wrap_projection(&proj, "Math::3Space::Projection::Frustum");
1253             OUTPUT:
1254             RETVAL
1255              
1256             MODULE = Math::3Space PACKAGE = Math::3Space::Projection::Frustum
1257              
1258             # This is an optimized matrix multiplication taking advantage of all the
1259             # zeroes and ones in both the 3Space matrix and the projection matrix.
1260              
1261             void
1262             matrix_colmajor(proj, space=NULL)
1263             m3s_4space_projection_t *proj
1264             m3s_space_t *space
1265             ALIAS:
1266             get_gl_matrix = 0
1267             matrix_pack_float = 1
1268             matrix_pack_double = 2
1269             INIT:
1270             double dst[16];
1271 11           struct m3s_4space_frustum_projection *f= &proj->frustum;
1272             PPCODE:
1273 11 100         if (!space) { /* user just wants the matrix itself */
1274 5           dst[ 0]= f->m00; dst[ 4]= 0; dst[ 8]= f->m20; dst[12]= 0;
1275 5           dst[ 1]= 0; dst[ 5]= f->m11; dst[ 9]= f->m21; dst[13]= 0;
1276 5           dst[ 2]= 0; dst[ 6]= 0; dst[10]= f->m22; dst[14]= f->m32;
1277 5           dst[ 3]= 0; dst[ 7]= 0; dst[11]= -1; dst[15]= 0;
1278             }
1279 6 100         else if (proj->frustum.centered) { /* centered frustum, optimize by assuming m20 and m21 are zero */
1280 5           dst[ 0]= f->m00 * SPACE_XV(space)[0];
1281 5           dst[ 1]= f->m11 * SPACE_XV(space)[1];
1282 5           dst[ 2]= f->m22 * SPACE_XV(space)[2];
1283 5           dst[ 3]= -SPACE_XV(space)[2];
1284 5           dst[ 4]= f->m00 * SPACE_YV(space)[0];
1285 5           dst[ 5]= f->m11 * SPACE_YV(space)[1];
1286 5           dst[ 6]= f->m22 * SPACE_YV(space)[2];
1287 5           dst[ 7]= -SPACE_YV(space)[2];
1288 5           dst[ 8]= f->m00 * SPACE_ZV(space)[0];
1289 5           dst[ 9]= f->m11 * SPACE_ZV(space)[1];
1290 5           dst[10]= f->m22 * SPACE_ZV(space)[2];
1291 5           dst[11]= -SPACE_ZV(space)[2];
1292 5           dst[12]= f->m00 * SPACE_ORIGIN(space)[0];
1293 5           dst[13]= f->m11 * SPACE_ORIGIN(space)[1];
1294 5           dst[14]= f->m22 * SPACE_ORIGIN(space)[2] + f->m32;
1295 5           dst[15]= -SPACE_ORIGIN(space)[2];
1296             } else {
1297 1           dst[ 0]= f->m00 * SPACE_XV(space)[0] + f->m20 * SPACE_XV(space)[2];
1298 1           dst[ 1]= f->m11 * SPACE_XV(space)[1] + f->m21 * SPACE_XV(space)[2];
1299 1           dst[ 2]= f->m22 * SPACE_XV(space)[2];
1300 1           dst[ 3]= -SPACE_XV(space)[2];
1301 1           dst[ 4]= f->m00 * SPACE_YV(space)[0] + f->m20 * SPACE_YV(space)[2];
1302 1           dst[ 5]= f->m11 * SPACE_YV(space)[1] + f->m21 * SPACE_YV(space)[2];
1303 1           dst[ 6]= f->m22 * SPACE_YV(space)[2];
1304 1           dst[ 7]= -SPACE_YV(space)[2];
1305 1           dst[ 8]= f->m00 * SPACE_ZV(space)[0] + f->m20 * SPACE_ZV(space)[2];
1306 1           dst[ 9]= f->m11 * SPACE_ZV(space)[1] + f->m21 * SPACE_ZV(space)[2];
1307 1           dst[10]= f->m22 * SPACE_ZV(space)[2];
1308 1           dst[11]= -SPACE_ZV(space)[2];
1309 1           dst[12]= f->m00 * SPACE_ORIGIN(space)[0] + f->m20 * SPACE_ORIGIN(space)[2];
1310 1           dst[13]= f->m11 * SPACE_ORIGIN(space)[1] + f->m21 * SPACE_ORIGIN(space)[2];
1311 1           dst[14]= f->m22 * SPACE_ORIGIN(space)[2] + f->m32;
1312 1           dst[15]= -SPACE_ORIGIN(space)[2];
1313             }
1314 11 100         if (ix & 3) { /* packed something */
1315 2           ST(0)= sv_newmortal();
1316 2 100         if (ix & 1) { /* packed floats */
1317             float *buf;
1318             int i;
1319 1           buf= (float*) m3s_make_aligned_buffer(ST(0), sizeof(float)*16);
1320 17 100         for (i= 0; i < 16; i++) buf[i]= (float) dst[i];
1321             } else {
1322 1           memcpy(m3s_make_aligned_buffer(ST(0), sizeof(double)*16), dst, sizeof(double)*16);
1323             }
1324 2           XSRETURN(1);
1325             } else {
1326             int i;
1327 9 50         EXTEND(SP, 16);
1328 153 100         for (i= 0; i < 16; i++)
1329 144           mPUSHn(dst[i]);
1330 9           XSRETURN(16);
1331             }
1332              
1333             #**********************************************************************************************
1334             # Math::3Space::Vector
1335             #**********************************************************************************************
1336             MODULE = Math::3Space PACKAGE = Math::3Space::Vector
1337              
1338             m3s_vector_p
1339             vec3(vec_or_x, y=NULL, z=NULL)
1340             SV* vec_or_x
1341             SV* y
1342             SV* z
1343             INIT:
1344             m3s_vector_t vec;
1345             CODE:
1346 37 100         M3S_VECLOAD(vec,vec_or_x,y,z,0);
    50          
1347 37           RETVAL = vec;
1348             OUTPUT:
1349             RETVAL
1350              
1351             m3s_vector_p
1352             new(pkg, ...)
1353             SV *pkg
1354             INIT:
1355 7           m3s_vector_t vec= { 0, 0, 0 };
1356             const char *key;
1357             IV i, ofs;
1358             CODE:
1359 7 100         if (items == 2 && SvROK(ST(1))) {
    50          
1360 3           m3s_read_vector_from_sv(vec, ST(1), NULL, NULL);
1361             }
1362 4 50         else if (items & 1) {
1363 7 100         for (i= 1; i < items; i+= 2) {
1364 3 50         key= SvOK(ST(i))? SvPV_nolen(ST(i)) : "";
1365 3 100         if (strcmp(key, "x") == 0) ofs= 0;
1366 2 100         else if (strcmp(key, "y") == 0) ofs= 1;
1367 1 50         else if (strcmp(key, "z") == 0) ofs= 2;
1368 0           else croak("Unknown attribute '%s'", key);
1369            
1370 3 50         if (!looks_like_number(ST(i+1)))
1371 0           croak("Expected attribute '%s' value, but got '%s'", SvPV_nolen(ST(i)), SvPV_nolen(ST(i+1)));
1372 3           vec[ofs]= SvNV(ST(i+1));
1373             }
1374             }
1375             else
1376 0           croak("Expected hashref, arrayref, or even-length list of attribute/value pairs");
1377 7           RETVAL = vec;
1378             OUTPUT:
1379             RETVAL
1380              
1381             void
1382             x(vec, newval=NULL)
1383             m3s_vector_p vec
1384             SV *newval
1385             ALIAS:
1386             Math::3Space::Vector::y = 1
1387             Math::3Space::Vector::z = 2
1388             PPCODE:
1389 2 50         if (newval) {
1390 2           vec[ix]= SvNV(newval);
1391             } else {
1392 0           ST(0)= sv_2mortal(newSVnv(vec[ix]));
1393             }
1394 2           XSRETURN(1);
1395              
1396             void
1397             xyz(vec)
1398             m3s_vector_p vec
1399             PPCODE:
1400 117 50         EXTEND(SP, 3);
1401 117           PUSHs(sv_2mortal(newSVnv(vec[0])));
1402 117           PUSHs(sv_2mortal(newSVnv(vec[1])));
1403 117           PUSHs(sv_2mortal(newSVnv(vec[2])));
1404              
1405             void
1406             magnitude(vec, scale=NULL)
1407             m3s_vector_p vec
1408             SV *scale
1409             INIT:
1410 5           NV s, m= sqrt(m3s_vector_dotprod(vec,vec));
1411             PPCODE:
1412 5 100         if (scale) {
1413 1 50         if (m > 0) {
1414 1           s= SvNV(scale) / m;
1415 1           vec[0] *= s;
1416 1           vec[1] *= s;
1417 1           vec[2] *= s;
1418             } else
1419 0           warn("can't scale magnitude=0 vector");
1420             // return $self
1421             } else {
1422 4           ST(0)= sv_2mortal(newSVnv(m));
1423             }
1424 5           XSRETURN(1);
1425              
1426             void
1427             set(vec1, vec2_or_x, y=NULL, z=NULL)
1428             m3s_vector_p vec1
1429             SV *vec2_or_x
1430             SV *y
1431             SV *z
1432             ALIAS:
1433             Math::3Space::Vector::add = 1
1434             Math::3Space::Vector::sub = 2
1435             INIT:
1436             NV vec2[3];
1437             PPCODE:
1438 6 100         M3S_VECLOAD(vec2,vec2_or_x,y,z,0);
    100          
1439 6 100         if (ix == 0) {
1440 1           vec1[0]= vec2[0];
1441 1           vec1[1]= vec2[1];
1442 1           vec1[2]= vec2[2];
1443 5 100         } else if (ix == 1) {
1444 4           vec1[0]+= vec2[0];
1445 4           vec1[1]+= vec2[1];
1446 4           vec1[2]+= vec2[2];
1447             } else {
1448 1           vec1[0]-= vec2[0];
1449 1           vec1[1]-= vec2[1];
1450 1           vec1[2]-= vec2[2];
1451             }
1452 6           XSRETURN(1);
1453              
1454             void
1455             scale(vec1, vec2_or_x, y=NULL, z=NULL)
1456             m3s_vector_p vec1
1457             SV *vec2_or_x
1458             SV *y
1459             SV *z
1460             INIT:
1461             NV vec2[3];
1462             PPCODE:
1463             // single value should be treated as ($x,$x,$x) instead of ($x,0,0)
1464 4 100         if (looks_like_number(vec2_or_x)) {
1465 3           vec2[0]= SvNV(vec2_or_x);
1466 3 100         vec2[1]= y? SvNV(y) : vec2[0];
1467 3 100         vec2[2]= z? SvNV(z) : y? 1 : vec2[0];
    100          
1468             }
1469             else {
1470 1           m3s_read_vector_from_sv(vec2, vec2_or_x, NULL, NULL);
1471             }
1472 4           vec1[0]*= vec2[0];
1473 4           vec1[1]*= vec2[1];
1474 4           vec1[2]*= vec2[2];
1475 4           XSRETURN(1);
1476              
1477             NV
1478             dot(vec1, vec2_or_x, y=NULL, z=NULL)
1479             m3s_vector_p vec1
1480             SV *vec2_or_x
1481             SV *y
1482             SV *z
1483             INIT:
1484             NV vec2[3];
1485             CODE:
1486 6 100         M3S_VECLOAD(vec2,vec2_or_x,y,z,0);
    50          
1487 6           RETVAL= m3s_vector_dotprod(vec1, vec2);
1488             OUTPUT:
1489             RETVAL
1490              
1491             NV
1492             cos(vec1, vec2_or_x, y=NULL, z=NULL)
1493             m3s_vector_p vec1
1494             SV *vec2_or_x
1495             SV *y
1496             SV *z
1497             INIT:
1498             NV vec2[3];
1499             CODE:
1500 3 50         M3S_VECLOAD(vec2,vec2_or_x,y,z,0);
    50          
1501 3           RETVAL= m3s_vector_cosine(vec1, vec2);
1502             OUTPUT:
1503             RETVAL
1504              
1505             void
1506             cross(vec1, vec2_or_x, vec3_or_y=NULL, z=NULL)
1507             m3s_vector_p vec1
1508             SV *vec2_or_x
1509             SV *vec3_or_y
1510             SV *z
1511             INIT:
1512             m3s_vector_t vec2, vec3;
1513             PPCODE:
1514 2 100         if (!vec3_or_y) { // RET = vec1->cross(vec2)
1515 1           m3s_read_vector_from_sv(vec2, vec2_or_x, NULL, NULL);
1516 1           m3s_vector_cross(vec3, vec1, vec2);
1517 1           ST(0)= sv_2mortal(m3s_wrap_vector(vec3));
1518 1 50         } else if (z || !SvROK(vec2_or_x) || looks_like_number(vec2_or_x)) { // RET = vec1->cross(x,y,z)
    50          
    50          
1519 0           vec2[0]= SvNV(vec2_or_x);
1520 0           vec2[1]= SvNV(vec3_or_y);
1521 0 0         vec2[2]= z? SvNV(z) : 0;
1522 0           m3s_vector_cross(vec3, vec1, vec2);
1523 0           ST(0)= sv_2mortal(m3s_wrap_vector(vec3));
1524             } else {
1525 1           m3s_read_vector_from_sv(vec2, vec2_or_x, NULL, NULL);
1526 1           m3s_read_vector_from_sv(vec3, vec3_or_y, NULL, NULL);
1527 1           m3s_vector_cross(vec1, vec2, vec3);
1528             // leave $self on stack
1529             }
1530 2           XSRETURN(1);
1531              
1532             BOOT:
1533 8           HV *inc= get_hv("INC", GV_ADD);
1534             AV *isa;
1535 8           hv_stores(inc, "Math::3Space::Projection", newSVpvs("Math/3Space.pm"));
1536 8           hv_stores(inc, "Math::3Space::Projection::Frustum", newSVpvs("Math/3Space.pm"));
1537 8           isa= get_av("Math::3Space::Projection::Frustum::ISA", GV_ADD);
1538 8           av_push(isa, newSVpvs("Math::3Space::Projection"));