as i can see your function returns the first face that was intersected… but the built-in method returns the closest one. if you continue search intersected faces and finally find the closest hit, it will change the performance.
not so, it returns the closest, this line tests to see if it’s closer and if not continue searching
if(first && a > at)
continue;
just run this from the listener…
outray = meshop.rayintersect $Plane01 (ray $Point01.pos $Point01.dir)
point pos:outray.pos dir:outray.dir
outray = meshop.rayintersect $Plane01 (ray $Point02.pos $Point02.dir)
point pos:outray.pos dir:outray.dir
gives the following result
if you can do without the barycoords you can replace
bry = mesh->BaryCoords(f, p);
if (bry.x < 0.0f || bry.x > 1.0f || bry.y < 0.0f || bry.y > 1.0f || bry.z < 0.0f || bry.z > 1.0f)
continue;
if (Fabs(bry.x + bry.y + bry.z - 1.0f) > EPSILON)
continue;
with a call to a PointInTriangle routine and it will improve the performance on the alway hitting from ~2-4x to 4~6x. I my test scene it was working out to 100 times on a 80,000 face object in about 0.5s which equates to 16000000 fps
it is quite odd coding, it comes from the surfwrap project in the sdk (conform wsmod). As I understand it, the code tests the ray against each face as a plane, discards backfacing and non intersections, then if it is closer than previous test and the intersection pt is within the face bounds thats the new current face point and normal continue testing. Where it wins is it can invalidate and discard faces much more efficiently than the “native” max version.
i’ve found a time to play with new Ray Intersect method…
first of all i slightly modified the code to support Extended return data (ray, face index, bary).
here is a code:
#define EPSILON 0.0001f
int RayIntersect(Ray& ray, float& at, Point3& norm, int& fi, Point3& bary, Mesh *mesh)
{
Face *face = mesh->faces;
Point3* verts = mesh->verts;
Point3 n, p, bry;
float d, rn, a;
BOOL first = FALSE;
int nfaces = mesh->getNumFaces();
for(int i = 0; i < nfaces ; ++i, ++face)
{
n = mesh->getFaceNormal(i);
rn = DotProd(ray.dir,n);
if (rn > -EPSILON)
continue;
d = DotProd(mesh->verts[face->v[0]],n);
a = (d - DotProd(ray.p,n)) / rn;
if (a < 0.0f)
continue;
if(first && a > at)
continue;
p = ray.p + a*ray.dir;
bry = mesh->BaryCoords(i,p);
if (bry.x < 0.0f || bry.x > 1.0f || bry.y < 0.0f || bry.y > 1.0f || bry.z < 0.0f || bry.z > 1.0f)
continue;
if (fabs(bry.x + bry.y + bry.z - 1.0f) > EPSILON)
continue;
first = TRUE;
at = a;
norm = n;
fi = i;
bary = bry;
}
return first;
}
def_visible_primitive(rayMeshIntersect, "rayMeshIntersect");
Value* rayMeshIntersect_cf(Value** arg_list, int count)
{
float at;
Point3 pt, normal, bary;
Ray ray, os_ray;
GeomObject* obj;
int fi;
check_arg_count_with_keys(rayintersect, 2, count);
INode* node = arg_list[0]->to_node();
BOOL dataArray = key_arg_or_default(dataArray, &false_value)->to_bool();
// Get the object from the node
ObjectState os = node->EvalWorldState(MAXScript_time());
if (os.obj->SuperClassID() == GEOMOBJECT_CLASS_ID)
{
obj = (GeomObject*)os.obj;
if (obj->ClassID() != GetEditTriObjDesc()->ClassID() && obj->ClassID() != Class_ID(TRIOBJ_CLASS_ID, 0))
return &undefined;
}
else
return &undefined;
// Back transform the ray into object space.
Matrix3 obtm = node->GetObjectTM(MAXScript_time());
Matrix3 iobtm = Inverse(obtm);
ray = arg_list[1]->to_ray();
os_ray.p = iobtm * ray.p;
os_ray.dir = VectorTransform(iobtm, ray.dir);
// See if we hit the object
Mesh& mesh = ((TriObject*)obj)->GetMesh();
if(!mesh.normalsBuilt)
mesh.buildNormals();
if(RayIntersect(os_ray, at, normal, fi, bary, &mesh))
{
// Calculate the hit point
pt = os_ray.p + os_ray.dir * at;
// transform back into world space & build result ray
pt = pt * obtm;
normal = Normalize(VectorTransform(obtm, normal));
RayValue* ray = new RayValue(pt, normal);
if (dataArray)
{
Array* data = new Array(3);
data->append(ray);
data->append(Integer::intern(fi+1));
data->append(new Point3Value(bary));
return data;
}
else
return ray;
}
else
return &undefined;
}
here is a test scene and comparison methods:
delete objects
(
s = plane name:"source" width:100 length:100 widthsegs:40 lengthsegs:40 pos:[0,0,10]
t = teapot name:"target" radius:50 segsments:100 scale:[1,1,0.1]
converttomesh #(s, t)
--converttomesh #(s)
num = 10
gc()
data = undefined
missed = undefined
t0 = timestamp()
for k=1 to num do
(
missed = 0
data = for v=1 to s.numverts collect
(
p = getvert s v
r = ray [p.x,p.y,100] -z_axis
d = intersectRay t r
if d == undefined do missed += 1
d
)
)
format "intersect ray >> time:% count:% missed:%
>> %
" (timestamp() - t0) s.numverts missed data
gc()
data = undefined
missed = undefined
t0 = timestamp()
m0 = heapfree
for k=1 to num do
(
missed = 0
data = for v=1 to s.numverts collect
(
p = getvert s v
r = ray [p.x,p.y,100] -z_axis
d = rayMeshIntersect t r dataArray:off
if d == undefined do missed += 1
d
)
)
format "ray intersect >> time:% count:% missed:%
>> %
" (timestamp() - t0) s.numverts missed data
gc()
data = undefined
missed = undefined
t0 = timestamp()
for k=1 to num do
(
missed = 0
data = for v=1 to s.numverts collect
(
p = getvert s v
r = ray [p.x,p.y,100] -z_axis
d = intersectRayEx t r
if d == undefined do missed += 1
d
)
)
format "intersect ray +> time:% count:% missed:%
>> %
" (timestamp() - t0) s.numverts missed data
gc()
data = undefined
missed = undefined
t0 = timestamp()
m0 = heapfree
for k=1 to num do
(
missed = 0
data = for v=1 to s.numverts collect
(
p = getvert s v
r = ray [p.x,p.y,100] -z_axis
d = rayMeshIntersect t r dataArray:on
if d == undefined do missed += 1
d
)
)
format "ray intersect +> time:% count:% missed:%
>> %
" (timestamp() - t0) s.numverts missed data
)
you can see no big difference. the only real improvement in the way how new Ray Intersect method takes Mesh.
native max intersectRay method does do it the bad way. this issue almost fixed in intersectRayEx, but as the comparison shows could be solved better
you can change the line
converttomesh #(s, t)
to
converttomesh #(s)
and see what i mean.
seems pretty conclusive to me running your script on my function gives the following
intersect ray >> time:5715 count:1681 missed:496 ray intersect >> time:886 count:1681 missed:468 intersect ray +> time:5702 count:1681 missed:496
with the converttomesh #(s) ?
intersect ray >> time:5688 count:1681 missed:496
ray intersect >> time:838 count:1681 missed:468
intersect ray +> time:5756 count:1681 missed:496
~6.5 times faster ! not sure whats happening with the missed discrepancy but when you use the routine in anger it doesn't seem to error.
… i’m probably going off my head. i have absolutely different numbers. absolutely… i will show them tomorrow
i’m not quiet sure but this morning for both methods i had time == ~90
ok. i had to reset xform for teapot to make methods work right. these are the numbers:
intersect ray >> time:1052 count:1681 missed:492
ray intersect >> time:879 count:1681 missed:492
intersect ray +> time:1061 count:1681 missed:492
ray intersect +> time:893 count:1681 missed:492
we can see the difference, but my thought it’s only because of using a better way of getting the mesh.
for most of my functions where i need to do any not deform operations (bbox, intersection, search) i pass already a mesh (instead of node) to avoid conversion on every method’s call.
but my thought it's only because of using a better way of getting the mesh.
the get mesh method was ripped straight out of the code for intersectRayEx function so there’s no difference. I have to ask, as I get much greater performance differential have you compiled in release ?
yes. i have a release.
max 2014 (machine is not good)
please add to my compare code
resetxform t
before conversion teapot to mesh
it seems like native max methods doesn’t take into account node scale