562 lines
21 KiB
C++
562 lines
21 KiB
C++
/* SPDX-FileCopyrightText: 2021-2022 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
/* MetalRT implementation of ray-scene intersection. */
|
|
|
|
#pragma once
|
|
|
|
#include "kernel/bvh/types.h"
|
|
#include "kernel/bvh/util.h"
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
/* Payload types.
|
|
*
|
|
* Best practice is to minimize the size of MetalRT payloads to avoid heavy spilling during
|
|
* intersection tests.
|
|
*/
|
|
|
|
struct MetalRTIntersectionPayload {
|
|
int self_prim;
|
|
int self_object;
|
|
};
|
|
|
|
struct MetalRTIntersectionLocalPayload_single_hit {
|
|
int self_prim;
|
|
#if defined(__METALRT_MOTION__)
|
|
int self_object;
|
|
#endif
|
|
};
|
|
|
|
struct MetalRTIntersectionLocalPayload {
|
|
int self_prim;
|
|
#if defined(__METALRT_MOTION__)
|
|
int self_object;
|
|
#endif
|
|
uint lcg_state;
|
|
uint hit_prim[LOCAL_MAX_HITS];
|
|
float hit_t[LOCAL_MAX_HITS];
|
|
float hit_u[LOCAL_MAX_HITS];
|
|
float hit_v[LOCAL_MAX_HITS];
|
|
uint max_hits : 3;
|
|
uint num_hits : 3;
|
|
uint has_lcg_state : 1;
|
|
};
|
|
static_assert(LOCAL_MAX_HITS < 8,
|
|
"MetalRTIntersectionLocalPayload max_hits & num_hits bitfields are too small");
|
|
|
|
struct MetalRTIntersectionShadowPayload {
|
|
RaySelfPrimitives self;
|
|
};
|
|
|
|
struct MetalRTIntersectionShadowAllPayload {
|
|
RaySelfPrimitives self;
|
|
int state;
|
|
float throughput;
|
|
short max_hits;
|
|
short num_hits;
|
|
short num_recorded_hits;
|
|
bool result;
|
|
};
|
|
|
|
#ifdef __HAIR__
|
|
ccl_device_forceinline bool curve_ribbon_accept(KernelGlobals kg,
|
|
const float u,
|
|
float t,
|
|
const ccl_private Ray *ray,
|
|
const int object,
|
|
const int prim,
|
|
const int type)
|
|
{
|
|
KernelCurve kcurve = kernel_data_fetch(curves, prim);
|
|
|
|
int k0 = kcurve.first_key + PRIMITIVE_UNPACK_SEGMENT(type);
|
|
int k1 = k0 + 1;
|
|
int ka = max(k0 - 1, kcurve.first_key);
|
|
int kb = min(k1 + 1, kcurve.first_key + kcurve.num_keys - 1);
|
|
|
|
/* We can ignore motion blur here because we don't need the positions, and it doesn't affect the
|
|
* radius. */
|
|
float radius[4];
|
|
radius[0] = kernel_data_fetch(curve_keys, ka).w;
|
|
radius[1] = kernel_data_fetch(curve_keys, k0).w;
|
|
radius[2] = kernel_data_fetch(curve_keys, k1).w;
|
|
radius[3] = kernel_data_fetch(curve_keys, kb).w;
|
|
const float r = metal::catmull_rom(u, radius[0], radius[1], radius[2], radius[3]);
|
|
|
|
/* MPJ TODO: Can we ignore motion and/or object transforms here? Depends on scaling? */
|
|
float3 ray_P = ray->P;
|
|
float3 ray_D = ray->D;
|
|
if (!(kernel_data_fetch(object_flag, object) & SD_OBJECT_TRANSFORM_APPLIED)) {
|
|
float3 idir;
|
|
# if defined(__METALRT_MOTION__)
|
|
bvh_instance_motion_push(nullptr, object, ray, &ray_P, &ray_D, &idir);
|
|
# else
|
|
bvh_instance_push(nullptr, object, ray, &ray_P, &ray_D, &idir);
|
|
# endif
|
|
}
|
|
|
|
/* ignore self intersections */
|
|
const float avoidance_factor = 2.0f;
|
|
return t * len(ray_D) > avoidance_factor * r;
|
|
}
|
|
|
|
ccl_device_forceinline float curve_ribbon_v(KernelGlobals kg,
|
|
const float u,
|
|
float t,
|
|
const ccl_private Ray *ray,
|
|
const int object,
|
|
const int prim,
|
|
const int type)
|
|
{
|
|
# if defined(__METALRT_MOTION__)
|
|
float time = ray->time;
|
|
# else
|
|
float time = 0.0f;
|
|
# endif
|
|
|
|
const bool is_motion = (type & PRIMITIVE_MOTION);
|
|
|
|
KernelCurve kcurve = kernel_data_fetch(curves, prim);
|
|
|
|
int k0 = kcurve.first_key + PRIMITIVE_UNPACK_SEGMENT(type);
|
|
int k1 = k0 + 1;
|
|
int ka = max(k0 - 1, kcurve.first_key);
|
|
int kb = min(k1 + 1, kcurve.first_key + kcurve.num_keys - 1);
|
|
|
|
float4 curve[4];
|
|
if (!is_motion) {
|
|
curve[0] = kernel_data_fetch(curve_keys, ka);
|
|
curve[1] = kernel_data_fetch(curve_keys, k0);
|
|
curve[2] = kernel_data_fetch(curve_keys, k1);
|
|
curve[3] = kernel_data_fetch(curve_keys, kb);
|
|
}
|
|
else {
|
|
motion_curve_keys(kg, object, time, ka, k0, k1, kb, curve);
|
|
}
|
|
|
|
float3 ray_P = ray->P;
|
|
float3 ray_D = ray->D;
|
|
if (!(kernel_data_fetch(object_flag, object) & SD_OBJECT_TRANSFORM_APPLIED)) {
|
|
float3 idir;
|
|
# if defined(__METALRT_MOTION__)
|
|
bvh_instance_motion_push(nullptr, object, ray, &ray_P, &ray_D, &idir);
|
|
# else
|
|
bvh_instance_push(nullptr, object, ray, &ray_P, &ray_D, &idir);
|
|
# endif
|
|
}
|
|
|
|
const float4 P_curve4 = metal::catmull_rom(u, curve[0], curve[1], curve[2], curve[3]);
|
|
const float r_curve = P_curve4.w;
|
|
|
|
float3 P = ray_P + ray_D * t;
|
|
const float3 P_curve = make_float3(P_curve4);
|
|
|
|
const float4 dPdu4 = metal::catmull_rom_derivative(u, curve[0], curve[1], curve[2], curve[3]);
|
|
const float3 dPdu = make_float3(dPdu4);
|
|
|
|
const float3 tangent = normalize(dPdu);
|
|
const float3 bitangent = normalize(cross(tangent, -ray_D));
|
|
|
|
float v = dot(P - P_curve, bitangent) / r_curve;
|
|
return clamp(v, -1.0, 1.0f);
|
|
}
|
|
#endif /* __HAIR__ */
|
|
|
|
/* Scene intersection. */
|
|
|
|
ccl_device_intersect bool scene_intersect(KernelGlobals kg,
|
|
const ccl_private Ray *ray,
|
|
const uint visibility,
|
|
ccl_private Intersection *isect)
|
|
{
|
|
metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax);
|
|
metalrt_intersector_type metalrt_intersect;
|
|
metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque);
|
|
metalrt_intersect.assume_geometry_type(
|
|
metal::raytracing::geometry_type::triangle |
|
|
(kernel_data.bvh.have_curves ? metal::raytracing::geometry_type::curve :
|
|
metal::raytracing::geometry_type::none) |
|
|
(kernel_data.bvh.have_points ? metal::raytracing::geometry_type::bounding_box :
|
|
metal::raytracing::geometry_type::none));
|
|
|
|
typename metalrt_intersector_type::result_type intersection;
|
|
|
|
MetalRTIntersectionPayload payload;
|
|
payload.self_prim = ray->self.prim;
|
|
payload.self_object = ray->self.object;
|
|
|
|
#if defined(__METALRT_MOTION__)
|
|
intersection = metalrt_intersect.intersect(r,
|
|
metal_ancillaries->accel_struct,
|
|
visibility,
|
|
ray->time,
|
|
metal_ancillaries->ift_default,
|
|
payload);
|
|
#else
|
|
intersection = metalrt_intersect.intersect(
|
|
r, metal_ancillaries->accel_struct, visibility, metal_ancillaries->ift_default, payload);
|
|
#endif
|
|
|
|
if (intersection.type == intersection_type::none) {
|
|
isect->t = ray->tmax;
|
|
isect->type = PRIMITIVE_NONE;
|
|
|
|
return false;
|
|
}
|
|
|
|
isect->object = intersection.instance_id;
|
|
isect->t = intersection.distance;
|
|
if (intersection.type == intersection_type::triangle) {
|
|
isect->prim = intersection.primitive_id + intersection.user_instance_id;
|
|
isect->type = kernel_data_fetch(objects, intersection.instance_id).primitive_type;
|
|
isect->u = intersection.triangle_barycentric_coord.x;
|
|
isect->v = intersection.triangle_barycentric_coord.y;
|
|
}
|
|
#ifdef __HAIR__
|
|
else if (kernel_data.bvh.have_curves && intersection.type == intersection_type::curve) {
|
|
int prim = intersection.primitive_id + intersection.user_instance_id;
|
|
const KernelCurveSegment segment = kernel_data_fetch(curve_segments, prim);
|
|
isect->prim = segment.prim;
|
|
isect->type = segment.type;
|
|
isect->u = intersection.curve_parameter;
|
|
|
|
if (segment.type & PRIMITIVE_CURVE_RIBBON) {
|
|
isect->v = curve_ribbon_v(kg,
|
|
intersection.curve_parameter,
|
|
intersection.distance,
|
|
ray,
|
|
intersection.instance_id,
|
|
segment.prim,
|
|
segment.type);
|
|
}
|
|
else {
|
|
isect->v = 0.0f;
|
|
}
|
|
}
|
|
#endif /* __HAIR__ */
|
|
#ifdef __POINTCLOUD__
|
|
else if (kernel_data.bvh.have_points && intersection.type == intersection_type::bounding_box) {
|
|
const int object = intersection.instance_id;
|
|
const uint prim = intersection.primitive_id + intersection.user_instance_id;
|
|
const int prim_type = kernel_data_fetch(objects, object).primitive_type;
|
|
|
|
if (!(kernel_data_fetch(object_flag, object) & SD_OBJECT_TRANSFORM_APPLIED)) {
|
|
float3 idir;
|
|
# if defined(__METALRT_MOTION__)
|
|
bvh_instance_motion_push(nullptr, object, ray, &r.origin, &r.direction, &idir);
|
|
# else
|
|
bvh_instance_push(nullptr, object, ray, &r.origin, &r.direction, &idir);
|
|
# endif
|
|
}
|
|
|
|
if (prim_type & PRIMITIVE_POINT) {
|
|
if (!point_intersect(nullptr,
|
|
isect,
|
|
r.origin,
|
|
r.direction,
|
|
ray->tmin,
|
|
ray->tmax,
|
|
object,
|
|
prim,
|
|
ray->time,
|
|
prim_type))
|
|
{
|
|
/* Shouldn't get here */
|
|
kernel_assert(!"Intersection mismatch");
|
|
isect->t = ray->tmax;
|
|
isect->type = PRIMITIVE_NONE;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
#endif /* __POINTCLOUD__ */
|
|
|
|
return true;
|
|
}
|
|
|
|
ccl_device_intersect bool scene_intersect_shadow(KernelGlobals kg,
|
|
const ccl_private Ray *ray,
|
|
const uint visibility)
|
|
{
|
|
metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax);
|
|
metalrt_intersector_type metalrt_intersect;
|
|
metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque);
|
|
metalrt_intersect.assume_geometry_type(
|
|
metal::raytracing::geometry_type::triangle |
|
|
(kernel_data.bvh.have_curves ? metal::raytracing::geometry_type::curve :
|
|
metal::raytracing::geometry_type::none) |
|
|
(kernel_data.bvh.have_points ? metal::raytracing::geometry_type::bounding_box :
|
|
metal::raytracing::geometry_type::none));
|
|
|
|
typename metalrt_intersector_type::result_type intersection;
|
|
|
|
metalrt_intersect.accept_any_intersection(true);
|
|
|
|
MetalRTIntersectionShadowPayload payload;
|
|
payload.self = ray->self;
|
|
|
|
#if defined(__METALRT_MOTION__)
|
|
intersection = metalrt_intersect.intersect(r,
|
|
metal_ancillaries->accel_struct,
|
|
visibility,
|
|
ray->time,
|
|
metal_ancillaries->ift_shadow,
|
|
payload);
|
|
#else
|
|
intersection = metalrt_intersect.intersect(
|
|
r, metal_ancillaries->accel_struct, visibility, metal_ancillaries->ift_shadow, payload);
|
|
#endif
|
|
return (intersection.type != intersection_type::none);
|
|
}
|
|
|
|
#ifdef __BVH_LOCAL__
|
|
template<bool single_hit = false>
|
|
ccl_device_intersect bool scene_intersect_local(KernelGlobals kg,
|
|
const ccl_private Ray *ray,
|
|
ccl_private LocalIntersection *local_isect,
|
|
const int local_object,
|
|
ccl_private uint *lcg_state,
|
|
const int max_hits)
|
|
{
|
|
uint primitive_id_offset = kernel_data_fetch(object_prim_offset, local_object);
|
|
|
|
metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax);
|
|
|
|
# if defined(__METALRT_MOTION__)
|
|
metalrt_intersector_type metalrt_intersect;
|
|
typename metalrt_intersector_type::result_type intersection;
|
|
# else
|
|
metalrt_blas_intersector_type metalrt_intersect;
|
|
typename metalrt_blas_intersector_type::result_type intersection;
|
|
|
|
if (!(kernel_data_fetch(object_flag, local_object) & SD_OBJECT_TRANSFORM_APPLIED)) {
|
|
/* Transform the ray into object's local space. */
|
|
Transform itfm = kernel_data_fetch(objects, local_object).itfm;
|
|
r.origin = transform_point(&itfm, r.origin);
|
|
r.direction = transform_direction(&itfm, r.direction);
|
|
}
|
|
# endif
|
|
|
|
metalrt_intersect.assume_geometry_type(metal::raytracing::geometry_type::triangle);
|
|
|
|
if (single_hit) {
|
|
MetalRTIntersectionLocalPayload_single_hit payload;
|
|
payload.self_prim = ray->self.prim - primitive_id_offset;
|
|
|
|
# if defined(__METALRT_MOTION__)
|
|
/* We can't skip over the top-level BVH in the motion blur case, so still need to do
|
|
* the self-object check. */
|
|
payload.self_object = local_object;
|
|
metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque);
|
|
intersection = metalrt_intersect.intersect(r,
|
|
metal_ancillaries->accel_struct,
|
|
~0,
|
|
ray->time,
|
|
metal_ancillaries->ift_local_single_hit_mblur,
|
|
payload);
|
|
# else
|
|
/* We only need custom intersection filtering (i.e. non_opaque) if we are performing a
|
|
* self-primitive intersection check. */
|
|
metalrt_intersect.force_opacity((ray->self.prim == PRIM_NONE) ?
|
|
metal::raytracing::forced_opacity::opaque :
|
|
metal::raytracing::forced_opacity::non_opaque);
|
|
intersection = metalrt_intersect.intersect(
|
|
r,
|
|
metal_ancillaries->blas_accel_structs[local_object].blas,
|
|
metal_ancillaries->ift_local_single_hit,
|
|
payload);
|
|
# endif
|
|
|
|
if (intersection.type == intersection_type::none) {
|
|
local_isect->num_hits = 0;
|
|
return false;
|
|
}
|
|
|
|
uint prim = intersection.primitive_id + primitive_id_offset;
|
|
int prim_type = kernel_data_fetch(objects, local_object).primitive_type;
|
|
|
|
local_isect->num_hits = 1;
|
|
local_isect->hits[0].prim = prim;
|
|
local_isect->hits[0].type = prim_type;
|
|
local_isect->hits[0].object = local_object;
|
|
local_isect->hits[0].u = intersection.triangle_barycentric_coord.x;
|
|
local_isect->hits[0].v = intersection.triangle_barycentric_coord.y;
|
|
local_isect->hits[0].t = intersection.distance;
|
|
|
|
const packed_uint3 tri_vindex = kernel_data_fetch(tri_vindex, prim);
|
|
const float3 tri_a = float3(kernel_data_fetch(tri_verts, tri_vindex.x));
|
|
const float3 tri_b = float3(kernel_data_fetch(tri_verts, tri_vindex.y));
|
|
const float3 tri_c = float3(kernel_data_fetch(tri_verts, tri_vindex.z));
|
|
local_isect->Ng[0] = normalize(cross(tri_b - tri_a, tri_c - tri_a));
|
|
return true;
|
|
}
|
|
else {
|
|
MetalRTIntersectionLocalPayload payload;
|
|
payload.self_prim = ray->self.prim - primitive_id_offset;
|
|
payload.max_hits = max_hits;
|
|
payload.num_hits = 0;
|
|
if (lcg_state) {
|
|
payload.has_lcg_state = 1;
|
|
payload.lcg_state = *lcg_state;
|
|
}
|
|
|
|
metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque);
|
|
|
|
# if defined(__METALRT_MOTION__)
|
|
/* We can't skip over the top-level BVH in the motion blur case, so still need to do
|
|
* the self-object check. */
|
|
payload.self_object = local_object;
|
|
intersection = metalrt_intersect.intersect(r,
|
|
metal_ancillaries->accel_struct,
|
|
~0,
|
|
ray->time,
|
|
metal_ancillaries->ift_local_mblur,
|
|
payload);
|
|
# else
|
|
intersection = metalrt_intersect.intersect(
|
|
r,
|
|
metal_ancillaries->blas_accel_structs[local_object].blas,
|
|
metal_ancillaries->ift_local,
|
|
payload);
|
|
# endif
|
|
|
|
if (max_hits == 0) {
|
|
/* Special case for when no hit information is requested, just report that something was hit
|
|
*/
|
|
return (intersection.type != intersection_type::none);
|
|
}
|
|
|
|
if (lcg_state) {
|
|
*lcg_state = payload.lcg_state;
|
|
}
|
|
|
|
const int num_hits = payload.num_hits;
|
|
if (local_isect) {
|
|
|
|
/* Record geometric normal */
|
|
int prim_type = kernel_data_fetch(objects, local_object).primitive_type;
|
|
|
|
local_isect->num_hits = num_hits;
|
|
for (int hit = 0; hit < num_hits; hit++) {
|
|
uint prim = payload.hit_prim[hit] + primitive_id_offset;
|
|
local_isect->hits[hit].prim = prim;
|
|
local_isect->hits[hit].t = payload.hit_t[hit];
|
|
local_isect->hits[hit].u = payload.hit_u[hit];
|
|
local_isect->hits[hit].v = payload.hit_v[hit];
|
|
local_isect->hits[hit].object = local_object;
|
|
local_isect->hits[hit].type = prim_type;
|
|
|
|
const packed_uint3 tri_vindex = kernel_data_fetch(tri_vindex, prim);
|
|
const float3 tri_a = float3(kernel_data_fetch(tri_verts, tri_vindex.x));
|
|
const float3 tri_b = float3(kernel_data_fetch(tri_verts, tri_vindex.y));
|
|
const float3 tri_c = float3(kernel_data_fetch(tri_verts, tri_vindex.z));
|
|
local_isect->Ng[hit] = normalize(cross(tri_b - tri_a, tri_c - tri_a));
|
|
}
|
|
}
|
|
return num_hits > 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef __SHADOW_RECORD_ALL__
|
|
ccl_device_intersect bool scene_intersect_shadow_all(KernelGlobals kg,
|
|
IntegratorShadowState state,
|
|
const ccl_private Ray *ray,
|
|
const uint visibility,
|
|
const uint max_hits,
|
|
ccl_private uint *num_recorded_hits,
|
|
ccl_private float *throughput)
|
|
{
|
|
metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax);
|
|
metalrt_intersector_type metalrt_intersect;
|
|
metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque);
|
|
metalrt_intersect.assume_geometry_type(
|
|
metal::raytracing::geometry_type::triangle |
|
|
(kernel_data.bvh.have_curves ? metal::raytracing::geometry_type::curve :
|
|
metal::raytracing::geometry_type::none) |
|
|
(kernel_data.bvh.have_points ? metal::raytracing::geometry_type::bounding_box :
|
|
metal::raytracing::geometry_type::none));
|
|
|
|
MetalRTIntersectionShadowAllPayload payload;
|
|
payload.self = ray->self;
|
|
payload.max_hits = max_hits;
|
|
payload.num_hits = 0;
|
|
payload.num_recorded_hits = 0;
|
|
payload.throughput = 1.0f;
|
|
payload.result = false;
|
|
payload.state = state;
|
|
|
|
typename metalrt_intersector_type::result_type intersection;
|
|
|
|
# if defined(__METALRT_MOTION__)
|
|
intersection = metalrt_intersect.intersect(r,
|
|
metal_ancillaries->accel_struct,
|
|
visibility,
|
|
ray->time,
|
|
metal_ancillaries->ift_shadow_all,
|
|
payload);
|
|
# else
|
|
intersection = metalrt_intersect.intersect(
|
|
r, metal_ancillaries->accel_struct, visibility, metal_ancillaries->ift_shadow_all, payload);
|
|
# endif
|
|
|
|
*num_recorded_hits = payload.num_recorded_hits;
|
|
*throughput = payload.throughput;
|
|
|
|
return payload.result;
|
|
}
|
|
#endif
|
|
|
|
#ifdef __VOLUME__
|
|
ccl_device_intersect bool scene_intersect_volume(KernelGlobals kg,
|
|
const ccl_private Ray *ray,
|
|
ccl_private Intersection *isect,
|
|
const uint visibility)
|
|
{
|
|
metal::raytracing::ray r(ray->P, ray->D, ray->tmin, ray->tmax);
|
|
metalrt_intersector_type metalrt_intersect;
|
|
metalrt_intersect.force_opacity(metal::raytracing::forced_opacity::non_opaque);
|
|
metalrt_intersect.set_geometry_cull_mode(metal::raytracing::geometry_cull_mode::bounding_box |
|
|
metal::raytracing::geometry_cull_mode::curve);
|
|
metalrt_intersect.assume_geometry_type(
|
|
metal::raytracing::geometry_type::triangle |
|
|
(kernel_data.bvh.have_curves ? metal::raytracing::geometry_type::curve :
|
|
metal::raytracing::geometry_type::none) |
|
|
(kernel_data.bvh.have_points ? metal::raytracing::geometry_type::bounding_box :
|
|
metal::raytracing::geometry_type::none));
|
|
|
|
MetalRTIntersectionShadowPayload payload;
|
|
payload.self = ray->self;
|
|
|
|
typename metalrt_intersector_type::result_type intersection;
|
|
|
|
# if defined(__METALRT_MOTION__)
|
|
intersection = metalrt_intersect.intersect(r,
|
|
metal_ancillaries->accel_struct,
|
|
visibility,
|
|
ray->time,
|
|
metal_ancillaries->ift_volume,
|
|
payload);
|
|
# else
|
|
intersection = metalrt_intersect.intersect(
|
|
r, metal_ancillaries->accel_struct, visibility, metal_ancillaries->ift_volume, payload);
|
|
# endif
|
|
|
|
if (intersection.type == intersection_type::triangle) {
|
|
isect->prim = intersection.primitive_id + intersection.user_instance_id;
|
|
isect->type = kernel_data_fetch(objects, intersection.instance_id).primitive_type;
|
|
isect->u = intersection.triangle_barycentric_coord.x;
|
|
isect->v = intersection.triangle_barycentric_coord.y;
|
|
isect->object = intersection.instance_id;
|
|
isect->t = intersection.distance;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
CCL_NAMESPACE_END
|