Math-3Space

 view release on metacpan or  search on metacpan

3Space.xs  view on Meta::CPAN

				if ((field= hv_fetch(attrs, "yv", 2, 0)) && *field && SvOK(*field))
					m3s_read_vector_from_sv(SPACE_YV(space), *field, NULL, NULL);
				else
					SPACE_YV(space)[1]= 1;
				if ((field= hv_fetch(attrs, "zv", 2, 0)) && *field && SvOK(*field))
					m3s_read_vector_from_sv(SPACE_ZV(space), *field, NULL, NULL);
				else
					SPACE_ZV(space)[2]= 1;
				if ((field= hv_fetch(attrs, "origin", 6, 0)) && *field && SvOK(*field))
					m3s_read_vector_from_sv(SPACE_ORIGIN(space), *field, NULL, NULL);
				space->is_normal= -1;
			} else
				croak("Invalid source for _init");
		}

SV*
clone(obj)
	SV *obj
	INIT:
		m3s_space_t *space= m3s_get_magic_space(obj, OR_DIE), *space2;
		HV *clone_hv;
	CODE:
		if (SvTYPE(SvRV(obj)) != SVt_PVHV)
			croak("Invalid source object"); // just to be really sure before next line
		clone_hv= newHVhv((HV*)SvRV(obj));
		RETVAL= newRV_noinc((SV*)clone_hv);
		sv_bless(RETVAL, gv_stashpv(sv_reftype(SvRV(obj), 1), GV_ADD));
		space2= m3s_get_magic_space(RETVAL, AUTOCREATE);
		memcpy(space2, space, sizeof(*space2));
	OUTPUT:
		RETVAL

SV*
space(parent=NULL)
	SV *parent
	INIT:
		m3s_space_t *space;
	CODE:
		if (parent && SvOK(parent) && !m3s_get_magic_space(parent, 0))
			croak("Invalid parent, must be instance of Math::3Space");
		Newx(space, 1, m3s_space_t);
		m3s_space_init(space);
		RETVAL= m3s_wrap_space(space);
		if (parent && SvOK(parent))
			hv_store((HV*)SvRV(RETVAL), "parent", 6, newSVsv(parent), 0);
	OUTPUT:
		RETVAL

void
xv(space, x_or_vec=NULL, y=NULL, z=NULL)
	m3s_space_t *space
	SV *x_or_vec
	SV *y
	SV *z
	ALIAS:
		Math::3Space::yv = 1
		Math::3Space::zv = 2
		Math::3Space::origin = 3
	INIT:
		NV *vec= space->mat + ix * 3;
	PPCODE:
		if (x_or_vec) {
			M3S_VECLOAD(vec,x_or_vec,y,z,0);
			if (ix < 3) space->is_normal= -1;
			// leave $self on stack as return value
		} else {
			ST(0)= sv_2mortal(m3s_wrap_vector(vec));
		}
		XSRETURN(1);

bool
is_normal(space)
	m3s_space_t *space
	CODE:
		if (space->is_normal == -1)
			m3s_space_check_normal(space);
		RETVAL= space->is_normal;
	OUTPUT:
		RETVAL

int
parent_count(space)
	SV *space
	CODE:
		m3s_space_recache_parent(space);
		RETVAL= m3s_get_magic_space(space, OR_DIE)->n_parents;
	OUTPUT:
		RETVAL

void
reparent(space, parent)
	SV *space
	SV *parent
	INIT:
		m3s_space_t *sp3= m3s_get_magic_space(space, OR_DIE), *psp3=NULL, *cur;
	PPCODE:
		m3s_space_recache_parent(space);
		if (SvOK(parent)) {
			psp3= m3s_get_magic_space(parent, OR_DIE);
			m3s_space_recache_parent(parent);
			// Make sure this doesn't create a cycle
			for (cur= psp3; cur; cur= cur->parent)
				if (cur == sp3)
					croak("Attempt to create a cycle: new 'parent' is a child of this space");
		}
		m3s_space_reparent(sp3, psp3);
		hv_store((HV*) SvRV(space), "parent", 6, newSVsv(parent), 0);
		XSRETURN(1);

void
translate(space, x_or_vec, y=NULL, z=NULL)
	m3s_space_t *space
	SV *x_or_vec
	SV *y
	SV *z
	ALIAS:
		Math::3Space::tr = 0
		Math::3Space::travel = 1
		Math::3Space::go = 1
	INIT:
		NV vec[3], *matp;
	PPCODE:
		M3S_VECLOAD(vec,x_or_vec,y,z,0);
		if (ix) {
			matp= space->mat;
			matp[9] += vec[0] * matp[0] + vec[1] * matp[3] + vec[2] * matp[6];
			++matp;
			matp[9] += vec[0] * matp[0] + vec[1] * matp[3] + vec[2] * matp[6];
			++matp;
			matp[9] += vec[0] * matp[0] + vec[1] * matp[3] + vec[2] * matp[6];
		} else {
			matp= SPACE_ORIGIN(space);
			*matp++ += vec[0];
			*matp++ += vec[1];
			*matp++ += vec[2];
		}
		XSRETURN(1);

void
scale(space, xscale_or_vec, yscale=NULL, zscale=NULL)
	m3s_space_t *space
	SV *xscale_or_vec
	SV *yscale
	SV *zscale
	ALIAS:
		Math::3Space::set_scale = 1
	INIT:
		NV vec[3], s, m, *matp= SPACE_XV(space);
		size_t i;
	PPCODE:
		if (SvROK(xscale_or_vec) && yscale == NULL) {
			m3s_read_vector_from_sv(vec, xscale_or_vec, NULL, NULL);
		} else {
			vec[0]= SvNV(xscale_or_vec);
			vec[1]= yscale? SvNV(yscale) : vec[0];
			vec[2]= zscale? SvNV(zscale) : vec[0];
		}
		for (i= 0; i < 3; i++) {
			s= vec[i];
			if (ix == 1) {
				m= sqrt(m3s_vector_dotprod(matp,matp));
				if (m > 0)
					s /= m;
				else
					warn("can't scale magnitude=0 vector");
			}
			*matp++ *= s;
			*matp++ *= s;
			*matp++ *= s;
		}
		space->is_normal= -1;
		XSRETURN(1);

void
rotate(space, angle, x_or_vec, y=NULL, z=NULL)
	m3s_space_t *space
	NV angle
	SV *x_or_vec
	SV *y
	SV *z
	INIT:
		m3s_vector_t vec;
	ALIAS:
		Math::3Space::rot = 0
	PPCODE:
		if (y) {
			if (!z) croak("Missing z coordinate in space->rotate(angle, x, y, z)");
			vec[0]= SvNV(x_or_vec);
			vec[1]= SvNV(y);
			vec[2]= SvNV(z);
		} else {
			m3s_read_vector_from_sv(vec, x_or_vec, NULL, NULL);
		}
		m3s_space_rotate(space, sin(angle * 2 * M_PI), cos(angle * 2 * M_PI), vec);
		// return $self
		XSRETURN(1);

void
rot_x(space, angle)
	m3s_space_t *space
	NV angle
	ALIAS:
		Math::3Space::rot_y = 1
		Math::3Space::rot_z = 2
		Math::3Space::rot_xv = 3
		Math::3Space::rot_yv = 4
		Math::3Space::rot_zv = 5
	INIT:
		NV *matp, tmp1, tmp2;
		size_t ofs1, ofs2;
		NV s= sin(angle * 2 * M_PI), c= cos(angle * 2 * M_PI);
	PPCODE:
		if (ix < 3) // Rotate around axis of parent
			m2s_space_parent_axis_rotate(space, s, c, ix);
		else
			m3s_space_self_axis_rotate(space, s, c, ix - 3);
		XSRETURN(1);

void
project_vector(space, ...)
	m3s_space_t *space
	INIT:
		m3s_vector_t vec;
		int i, vectype, count;
		AV *vec_av;
		HV *vec_hv;
		SV *pdl_origin= NULL, *pdl_matrix= NULL;
		size_t pdl_dims[3];
	ALIAS:
		Math::3Space::project = 1
		Math::3Space::unproject_vector = 2
		Math::3Space::unproject = 3
	PPCODE:
		for (i= 1; i < items; i++) {
			vectype= m3s_read_vector_from_sv(vec, ST(i), pdl_dims, NULL);
			if (vectype == M3S_VECTYPE_PDLMULTI) {
				dSP;
				if (!pdl_origin) {
					pdl_origin= sv_2mortal(m3s_pdl_vector(SPACE_ORIGIN(space), 3));
					pdl_matrix= sv_2mortal(m3s_pdl_matrix(SPACE_XV(space), !(ix&2) /*transpose bool*/));
				}
				ENTER;
				SAVETMPS;
				PUSHMARK(SP);
				// first, clone the input.  Then modify it in place.
				EXTEND(SP, 4);
				PUSHs(ST(i));
				PUTBACK;
				count= call_method("copy", G_SCALAR);
				SPAGAIN;
				if (count != 1) croak("PDL->copy failed?");
				// project point subtracts origin
				PUSHs(ix == 3? pdl_origin : &PL_sv_undef);
				PUSHs(pdl_matrix);
				// unproject point adds origin
				PUSHs(ix == 1? pdl_origin : &PL_sv_undef);
				PUTBACK;
				count= call_pv("Math::3Space::_pdl_project_inplace", G_DISCARD);
				
				FREETMPS;
				LEAVE;
				ST(i-1)= ST(i);
			}
			else {
				switch (ix) {
				case 0: m3s_space_project_vector(space, vec); break;
				case 1: m3s_space_project_point(space, vec); break;
				case 2: m3s_space_unproject_vector(space, vec); break;
				default: m3s_space_unproject_point(space, vec);
				}
				switch (vectype) {
				case M3S_VECTYPE_ARRAY:
					vec_av= newAV();
					av_extend(vec_av, 2);
					av_push(vec_av, newSVnv(vec[0]));
					av_push(vec_av, newSVnv(vec[1]));
					av_push(vec_av, newSVnv(vec[2]));
					ST(i-1)= sv_2mortal(newRV_noinc((SV*)vec_av));
					break;
				case M3S_VECTYPE_HASH:
					vec_hv= newHV();
					hv_stores(vec_hv, "x", newSVnv(vec[0]));
					hv_stores(vec_hv, "y", newSVnv(vec[1]));
					hv_stores(vec_hv, "z", newSVnv(vec[2]));
					ST(i-1)= sv_2mortal(newRV_noinc((SV*)vec_hv));
					break;
				case M3S_VECTYPE_PDL:
					ST(i-1)= sv_2mortal(m3s_pdl_vector(vec, pdl_dims[0]));
					break;
				default:
					ST(i-1)= sv_2mortal(m3s_wrap_vector(vec));
				}
			}
		}
		XSRETURN(items-1);

void
project_vector_inplace(space, ...)
	m3s_space_t *space
	INIT:
		m3s_vector_t vec;
		m3s_vector_p vecp;
		size_t i, j, n;
		int vectype;
		AV *vec_av;
		SV **item, *x, *y, *z, *pdl_origin= NULL, *pdl_matrix= NULL;
		size_t pdl_dims[3];
		SV *component_sv[3];
	ALIAS:
		Math::3Space::project_inplace = 1
		Math::3Space::unproject_vector_inplace = 2
		Math::3Space::unproject_inplace = 3
	PPCODE:
		for (i= 1; i < items; i++) {
			if (!SvROK(ST(i)))
				croak("Expected vector at $_[%d]", (int)(i-1));
			vectype= m3s_read_vector_from_sv(vec, ST(i), pdl_dims, component_sv);
			switch (vectype) {
			case M3S_VECTYPE_VECOBJ:
				vecp= m3s_vector_get_array(ST(i));
				switch (ix) {
				case 0: m3s_space_project_vector(space, vecp); break;
				case 1: m3s_space_project_point(space, vecp); break;
				case 2: m3s_space_unproject_vector(space, vecp); break;
				default: m3s_space_unproject_point(space, vecp);
				}
				break;
			case M3S_VECTYPE_ARRAY:
			case M3S_VECTYPE_HASH:
				switch (ix) {
				case 0: m3s_space_project_vector(space, vec); break;
				case 1: m3s_space_project_point(space, vec); break;
				case 2: m3s_space_unproject_vector(space, vec); break;
				default: m3s_space_unproject_point(space, vec);
				}
				for (j=0; j < 3; j++)
					if (component_sv[j])
						sv_setnv(component_sv[j], vec[j]);
				break;
			case M3S_VECTYPE_PDL:
			case M3S_VECTYPE_PDLMULTI:
				{
					int count;
					dSP;
					if (!pdl_origin) {
						pdl_origin= sv_2mortal(m3s_pdl_vector(SPACE_ORIGIN(space), 3));
						pdl_matrix= sv_2mortal(m3s_pdl_matrix(SPACE_XV(space), !(ix&2) /*transpose bool*/));
					}
					ENTER;
					SAVETMPS;
					PUSHMARK(SP);
					EXTEND(SP, 4);
					PUSHs(ST(i));
					// project point subtracts origin
					PUSHs(ix == 3? pdl_origin : &PL_sv_undef);
					PUSHs(pdl_matrix);
					// unproject point adds origin
					PUSHs(ix == 1? pdl_origin : &PL_sv_undef);
					PUTBACK;
					count= call_pv("Math::3Space::_pdl_project_inplace", G_DISCARD);
					
					FREETMPS;
					LEAVE;
				}
				break;
			default:
				croak("bug: unhandled vec type");
			}
		}
		// return $self
		XSRETURN(1);

void
get_gl_matrix(space, buffer=NULL)
	m3s_space_t *space
	SV *buffer
	INIT:
		NV *src;
		double *dst;
	PPCODE:
		if (buffer) {
			dst= (double*) m3s_make_aligned_buffer(buffer, sizeof(double)*16);
			src= space->mat;
			dst[ 0] = src[ 0]; dst[ 1] = src[ 1]; dst[ 2] = src[ 2]; dst[ 3] = 0;
			dst[ 4] = src[ 3]; dst[ 5] = src[ 4]; dst[ 6] = src[ 5]; dst[ 7] = 0;
			dst[ 8] = src[ 6]; dst[ 9] = src[ 7]; dst[10] = src[ 8]; dst[11] = 0;
			dst[12] = src[ 9]; dst[13] = src[10]; dst[14] = src[11]; dst[15] = 1;
			XSRETURN(0);
		} else {
			EXTEND(SP, 16);
			mPUSHn(SPACE_XV(space)[0]); mPUSHn(SPACE_XV(space)[1]); mPUSHn(SPACE_XV(space)[2]); mPUSHn(0);
			mPUSHn(SPACE_YV(space)[0]); mPUSHn(SPACE_YV(space)[1]); mPUSHn(SPACE_YV(space)[2]); mPUSHn(0);
			mPUSHn(SPACE_ZV(space)[0]); mPUSHn(SPACE_ZV(space)[1]); mPUSHn(SPACE_ZV(space)[2]); mPUSHn(0);
			mPUSHn(SPACE_ORIGIN(space)[0]); mPUSHn(SPACE_ORIGIN(space)[1]); mPUSHn(SPACE_ORIGIN(space)[2]); mPUSHn(1);
			XSRETURN(16);
		}

#**********************************************************************************************
# Math::3Space::Projection
#**********************************************************************************************
MODULE = Math::3Space              PACKAGE = Math::3Space::Projection

SV *
_frustum(left, right, bottom, top, near_z, far_z)
	double left
	double right
	double bottom
	double top
	double near_z
	double far_z
	INIT:
		m3s_4space_projection_t proj;
		double w, h, d, w_1, h_1, d_1;
	CODE:
		w= right - left;
		h= top - bottom;
		d= far_z - near_z;
		if (fabs(w) < double_tolerance || fabs(h) < double_tolerance || fabs(d) < double_tolerance)
			croak("Described frustum has a zero-sized dimension");

		w_1= 1/w;
		h_1= 1/h;
		d_1= 1/d;
		proj.frustum.m00= near_z * 2 * w_1;
		proj.frustum.m11= near_z * 2 * h_1;
		proj.frustum.m20= (right+left) * w_1;
		proj.frustum.m21= (top+bottom) * h_1;
		proj.frustum.m22= -(near_z+far_z) * d_1;
		proj.frustum.m32= -2 * near_z * far_z * d_1;
		// use optimized version if m20 and m21 are zero
		proj.frustum.centered= fabs(proj.frustum.m20) < double_tolerance && fabs(proj.frustum.m21) < double_tolerance;
		RETVAL= m3s_wrap_projection(&proj,
			"Math::3Space::Projection::Frustum"
		);
	OUTPUT:
		RETVAL

SV *
_perspective(vertical_field_of_view, aspect, near_z, far_z)
	double vertical_field_of_view
	double aspect
	double near_z
	double far_z
	INIT:
		m3s_4space_projection_t proj;
		double f= tan(M_PI_2 - vertical_field_of_view * M_PI),
		       neg_inv_range_z= -1 / (far_z - near_z);
	CODE:
		proj.frustum.m00= f / aspect;
		proj.frustum.m11= f;
		proj.frustum.m20= 0;
		proj.frustum.m21= 0;
		proj.frustum.m22= (near_z+far_z) * neg_inv_range_z;
		proj.frustum.m32= 2 * near_z * far_z * neg_inv_range_z;
		proj.frustum.centered= true;
		RETVAL= m3s_wrap_projection(&proj, "Math::3Space::Projection::Frustum");
	OUTPUT:
		RETVAL

MODULE = Math::3Space              PACKAGE = Math::3Space::Projection::Frustum

# This is an optimized matrix multiplication taking advantage of all the
# zeroes and ones in both the 3Space matrix and the projection matrix.

void
matrix_colmajor(proj, space=NULL)
	m3s_4space_projection_t *proj
	m3s_space_t *space
	ALIAS:
		get_gl_matrix      = 0
		matrix_pack_float  = 1
		matrix_pack_double = 2
	INIT:
		double dst[16];
		struct m3s_4space_frustum_projection *f= &proj->frustum;
	PPCODE:
		if (!space) { /* user just wants the matrix itself */
			dst[ 0]= f->m00; dst[ 4]= 0;      dst[ 8]= f->m20;  dst[12]= 0;
			dst[ 1]= 0;      dst[ 5]= f->m11; dst[ 9]= f->m21;  dst[13]= 0;
			dst[ 2]= 0;      dst[ 6]= 0;      dst[10]= f->m22;  dst[14]= f->m32;
			dst[ 3]= 0;      dst[ 7]= 0;      dst[11]= -1;      dst[15]= 0;
		}
		else if (proj->frustum.centered) { /* centered frustum, optimize by assuming m20 and m21 are zero */
			dst[ 0]= f->m00 * SPACE_XV(space)[0];
			dst[ 1]= f->m11 * SPACE_XV(space)[1];
			dst[ 2]= f->m22 * SPACE_XV(space)[2];
			dst[ 3]=         -SPACE_XV(space)[2];
			dst[ 4]= f->m00 * SPACE_YV(space)[0];
			dst[ 5]= f->m11 * SPACE_YV(space)[1];
			dst[ 6]= f->m22 * SPACE_YV(space)[2];
			dst[ 7]=         -SPACE_YV(space)[2];
			dst[ 8]= f->m00 * SPACE_ZV(space)[0];
			dst[ 9]= f->m11 * SPACE_ZV(space)[1];
			dst[10]= f->m22 * SPACE_ZV(space)[2];
			dst[11]=         -SPACE_ZV(space)[2];
			dst[12]= f->m00 * SPACE_ORIGIN(space)[0];
			dst[13]= f->m11 * SPACE_ORIGIN(space)[1];
			dst[14]= f->m22 * SPACE_ORIGIN(space)[2] + f->m32;
			dst[15]=         -SPACE_ORIGIN(space)[2];
		} else {
			dst[ 0]= f->m00 * SPACE_XV(space)[0] +                               f->m20 * SPACE_XV(space)[2];
			dst[ 1]=                               f->m11 * SPACE_XV(space)[1] + f->m21 * SPACE_XV(space)[2];
			dst[ 2]=                                                             f->m22 * SPACE_XV(space)[2];
			dst[ 3]=                                                                     -SPACE_XV(space)[2];
			dst[ 4]= f->m00 * SPACE_YV(space)[0] +                               f->m20 * SPACE_YV(space)[2];
			dst[ 5]=                               f->m11 * SPACE_YV(space)[1] + f->m21 * SPACE_YV(space)[2];
			dst[ 6]=                                                             f->m22 * SPACE_YV(space)[2];
			dst[ 7]=                                                                     -SPACE_YV(space)[2];
			dst[ 8]= f->m00 * SPACE_ZV(space)[0] +                               f->m20 * SPACE_ZV(space)[2];
			dst[ 9]=                               f->m11 * SPACE_ZV(space)[1] + f->m21 * SPACE_ZV(space)[2];
			dst[10]=                                                             f->m22 * SPACE_ZV(space)[2];
			dst[11]=                                                                     -SPACE_ZV(space)[2];
			dst[12]= f->m00 * SPACE_ORIGIN(space)[0]                           + f->m20 * SPACE_ORIGIN(space)[2];
			dst[13]=                           f->m11 * SPACE_ORIGIN(space)[1] + f->m21 * SPACE_ORIGIN(space)[2];
			dst[14]=                                                             f->m22 * SPACE_ORIGIN(space)[2] + f->m32;
			dst[15]=                                                                     -SPACE_ORIGIN(space)[2];
		}
		if (ix & 3) { /* packed something */
			ST(0)= sv_newmortal();
			if (ix & 1) { /* packed floats */
				float *buf;
				int i;
				buf= (float*) m3s_make_aligned_buffer(ST(0), sizeof(float)*16);
				for (i= 0; i < 16; i++) buf[i]= (float) dst[i];
			} else {
				memcpy(m3s_make_aligned_buffer(ST(0), sizeof(double)*16), dst, sizeof(double)*16);
			}
			XSRETURN(1);
		} else {
			int i;
			EXTEND(SP, 16);
			for (i= 0; i < 16; i++)
				mPUSHn(dst[i]);
			XSRETURN(16);
		}

#**********************************************************************************************
# Math::3Space::Vector
#**********************************************************************************************
MODULE = Math::3Space              PACKAGE = Math::3Space::Vector

m3s_vector_p
vec3(vec_or_x, y=NULL, z=NULL)
	SV* vec_or_x
	SV* y
	SV* z
	INIT:
		m3s_vector_t vec;
	CODE:
		M3S_VECLOAD(vec,vec_or_x,y,z,0);
		RETVAL = vec;
	OUTPUT:
		RETVAL

m3s_vector_p
new(pkg, ...)
	SV *pkg
	INIT:
		m3s_vector_t vec= { 0, 0, 0 };
		const char *key;
		IV i, ofs;
	CODE:
		if (items == 2 && SvROK(ST(1))) {
			m3s_read_vector_from_sv(vec, ST(1), NULL, NULL);
		}
		else if (items & 1) {
			for (i= 1; i < items; i+= 2) {
				key= SvOK(ST(i))? SvPV_nolen(ST(i)) : "";
				if (strcmp(key, "x") == 0) ofs= 0;
				else if (strcmp(key, "y") == 0) ofs= 1;
				else if (strcmp(key, "z") == 0) ofs= 2;
				else croak("Unknown attribute '%s'", key);
				
				if (!looks_like_number(ST(i+1)))
					croak("Expected attribute '%s' value, but got '%s'", SvPV_nolen(ST(i)), SvPV_nolen(ST(i+1)));
				vec[ofs]= SvNV(ST(i+1));
			}
		}
		else
			croak("Expected hashref, arrayref, or even-length list of attribute/value pairs");
		RETVAL = vec;
	OUTPUT:
		RETVAL

void
x(vec, newval=NULL)
	m3s_vector_p vec
	SV *newval
	ALIAS:
		Math::3Space::Vector::y = 1
		Math::3Space::Vector::z = 2
	PPCODE:
		if (newval) {
			vec[ix]= SvNV(newval);
		} else {
			ST(0)= sv_2mortal(newSVnv(vec[ix]));
		}
		XSRETURN(1);

void
xyz(vec)
	m3s_vector_p vec
	PPCODE:
		EXTEND(SP, 3);
		PUSHs(sv_2mortal(newSVnv(vec[0])));
		PUSHs(sv_2mortal(newSVnv(vec[1])));
		PUSHs(sv_2mortal(newSVnv(vec[2])));

void
magnitude(vec, scale=NULL)
	m3s_vector_p vec
	SV *scale
	INIT:
		NV s, m= sqrt(m3s_vector_dotprod(vec,vec));
	PPCODE:
		if (scale) {
			if (m > 0) {
				s= SvNV(scale) / m;
				vec[0] *= s;
				vec[1] *= s;
				vec[2] *= s;
			} else
				warn("can't scale magnitude=0 vector");
			// return $self
		} else {
			ST(0)= sv_2mortal(newSVnv(m));
		}
		XSRETURN(1);

void
set(vec1, vec2_or_x, y=NULL, z=NULL)
	m3s_vector_p vec1
	SV *vec2_or_x
	SV *y
	SV *z
	ALIAS:
		Math::3Space::Vector::add = 1
		Math::3Space::Vector::sub = 2
	INIT:
		NV vec2[3];
	PPCODE:
		M3S_VECLOAD(vec2,vec2_or_x,y,z,0);
		if (ix == 0) {
			vec1[0]= vec2[0];
			vec1[1]= vec2[1];
			vec1[2]= vec2[2];
		} else if (ix == 1) {
			vec1[0]+= vec2[0];
			vec1[1]+= vec2[1];
			vec1[2]+= vec2[2];
		} else {
			vec1[0]-= vec2[0];
			vec1[1]-= vec2[1];
			vec1[2]-= vec2[2];
		}
		XSRETURN(1);

void
scale(vec1, vec2_or_x, y=NULL, z=NULL)
	m3s_vector_p vec1
	SV *vec2_or_x
	SV *y
	SV *z
	INIT:
		NV vec2[3];
	PPCODE:
		// single value should be treated as ($x,$x,$x) instead of ($x,0,0)
		if (looks_like_number(vec2_or_x)) {
			vec2[0]= SvNV(vec2_or_x);
			vec2[1]= y? SvNV(y) : vec2[0];
			vec2[2]= z? SvNV(z) : y? 1 : vec2[0];
		}
		else {
			m3s_read_vector_from_sv(vec2, vec2_or_x, NULL, NULL);
		}
		vec1[0]*= vec2[0];
		vec1[1]*= vec2[1];
		vec1[2]*= vec2[2];
		XSRETURN(1);

NV
dot(vec1, vec2_or_x, y=NULL, z=NULL)
	m3s_vector_p vec1
	SV *vec2_or_x
	SV *y
	SV *z
	INIT:
		NV vec2[3];
	CODE:
		M3S_VECLOAD(vec2,vec2_or_x,y,z,0);
		RETVAL= m3s_vector_dotprod(vec1, vec2);
	OUTPUT:
		RETVAL

NV
cos(vec1, vec2_or_x, y=NULL, z=NULL)
	m3s_vector_p vec1
	SV *vec2_or_x
	SV *y
	SV *z
	INIT:
		NV vec2[3];
	CODE:
		M3S_VECLOAD(vec2,vec2_or_x,y,z,0);
		RETVAL= m3s_vector_cosine(vec1, vec2);
	OUTPUT:
		RETVAL

void
cross(vec1, vec2_or_x, vec3_or_y=NULL, z=NULL)
	m3s_vector_p vec1
	SV *vec2_or_x
	SV *vec3_or_y
	SV *z
	INIT:
		m3s_vector_t vec2, vec3;
	PPCODE:
		if (!vec3_or_y) { // RET = vec1->cross(vec2)
			m3s_read_vector_from_sv(vec2, vec2_or_x, NULL, NULL);
			m3s_vector_cross(vec3, vec1, vec2);
			ST(0)= sv_2mortal(m3s_wrap_vector(vec3));
		} else if (z || !SvROK(vec2_or_x) || looks_like_number(vec2_or_x)) { // RET = vec1->cross(x,y,z)
			vec2[0]= SvNV(vec2_or_x);
			vec2[1]= SvNV(vec3_or_y);
			vec2[2]= z? SvNV(z) : 0;
			m3s_vector_cross(vec3, vec1, vec2);
			ST(0)= sv_2mortal(m3s_wrap_vector(vec3));
		} else {
			m3s_read_vector_from_sv(vec2, vec2_or_x, NULL, NULL);
			m3s_read_vector_from_sv(vec3, vec3_or_y, NULL, NULL);
			m3s_vector_cross(vec1, vec2, vec3);
			// leave $self on stack
		}
		XSRETURN(1);

BOOT:
	HV *inc= get_hv("INC", GV_ADD);
	AV *isa;
	hv_stores(inc, "Math::3Space::Projection",                  newSVpvs("Math/3Space.pm"));
	hv_stores(inc, "Math::3Space::Projection::Frustum",         newSVpvs("Math/3Space.pm"));
	isa= get_av("Math::3Space::Projection::Frustum::ISA", GV_ADD);
	av_push(isa, newSVpvs("Math::3Space::Projection"));



( run in 0.822 second using v1.01-cache-2.11-cpan-71847e10f99 )