Esempio n. 1
0
size_t closestPointToRay(const V3f* points, size_t nPoints,
                         const V3f& rayOrigin, const V3f& rayDirection,
                         double longitudinalScale, double* distance)
{
    const V3f T = rayDirection.normalized();
    const double f = longitudinalScale*longitudinalScale - 1;
    size_t nearestIdx = -1;
    double nearestDist2 = DBL_MAX;
    for(size_t i = 0; i < nPoints; ++i)
    {
        const V3f v = points[i] - rayOrigin; // vector from ray origin to point
        const double a = v.dot(T); // distance along ray to point of closest approach to test point
        const double r2 = v.length2() + f*a*a;

        if(r2 < nearestDist2)
        {
            // new closest angle to axis
            nearestDist2 = r2;
            nearestIdx = i;
        }
    }
    if(distance)
    {
        if(nPoints == 0)
            *distance = DBL_MAX;
        else
            *distance = sqrt(nearestDist2);
    }
    return nearestIdx;
}
Esempio n. 2
0
void renderDisk(IntegratorT& integrator, V3f N, V3f p, V3f n, float r,
                float cosConeAngle, float sinConeAngle)
{
    float dot_pn = dot(p, n);
    // Cull back-facing points.  In conjunction with the oddball composition
    // rule below, this is very important for smoothness of the result:  If we
    // don't cull the back faces, coverage will be overestimated in every
    // microbuffer pixel which contains an edge.
    if(dot_pn > 0)
        return;
    float plen2 = p.length2();
    float plen = sqrtf(plen2);
    // If solid angle of bounding sphere is greater than exactRenderAngle,
    // resolve the visibility exactly rather than using a cheap approx.
    //
    // TODO: Adjust exactRenderAngle for best results!
    const float exactRenderAngle = 0.05f;
    float origArea = M_PI*r*r;
    // Solid angle of the bound
    if(exactRenderAngle*plen2 < origArea)
    {
        // Multiplier for radius to make the cracks a bit smaller.  We
        // can't do this too much, or sharp convex edges will be
        // over occluded (TODO: Adjust for best results!  Maybe the "too
        // large" problem could be worked around using a tracing offset?)
        const float radiusMultiplier = M_SQRT2;
        // Resolve visibility of very close surfels using ray tracing.
        // This is necessary to avoid artifacts where surfaces meet.
        renderDiskExact(integrator, p, n, radiusMultiplier*r);
        return;
    }
    // Figure out which face we're on and get u,v coordinates on that face,
    MicroBuf::Face faceIndex = MicroBuf::faceIndex(p);
    int faceRes = integrator.res();
    float u = 0, v = 0;
    MicroBuf::faceCoords(faceIndex, p, u, v);
    // Compute the area of the surfel when projected onto the env face.
    // This depends on several things:
    // 1) The area of the original disk
    // 2) The angles between the disk normal n, viewing vector p, and face
    // normal.  This is the area projected onto a plane parallel to the env
    // map face, and through the centre of the disk.
    float pDotFaceN = MicroBuf::dotFaceNormal(faceIndex, p);
    float angleFactor = fabs(dot_pn/pDotFaceN);
    // 3) Ratio of distance to the surfel vs distance to projected point on
    // the face.
    float distFactor = 1.0f/(pDotFaceN*pDotFaceN);
    // Putting these together gives the projected area
    float projArea = origArea * angleFactor * distFactor;
    // Half-width of a square with area projArea
    float wOn2 = sqrtf(projArea)*0.5f;
    // Transform width and position to face raster coords.
    float rasterScale = 0.5f*faceRes;
    u = rasterScale*(u + 1.0f);
    v = rasterScale*(v + 1.0f);
    wOn2 *= rasterScale;
    // Construct square box with the correct area.  This shape isn't
    // anything like the true projection of a disk onto the raster, but
    // it's much cheaper!  Note that points which are proxies for clusters
    // of smaller points aren't going to be accurately resolved no matter
    // what we do.
    struct BoundData
    {
        MicroBuf::Face faceIndex;
        float ubegin, uend;
        float vbegin, vend;
    };
    // The current surfel can cross up to three faces.
    int nfaces = 1;
    BoundData boundData[3];
    BoundData& bd0 = boundData[0];
    bd0.faceIndex = faceIndex;
    bd0.ubegin = u - wOn2;
    bd0.uend = u + wOn2;
    bd0.vbegin = v - wOn2;
    bd0.vend = v + wOn2;
    // Detect & handle overlap onto adjacent faces
    //
    // We assume that wOn2 is the same on the adjacent face, an assumption
    // which is true when the surfel is close the the corner of the cube.
    // We also assume that a surfel touches at most three faces.  This is
    // true as long as the surfels don't have a massive solid angle; for
    // such cases the axis-aligned box isn't going to be accurate anyway and
    // the code should have branched into the renderDiskExact function instead.
    if(bd0.ubegin < 0)
    {
        // left neighbour
        BoundData& b = boundData[nfaces++];
        b.faceIndex = MicroBuf::neighbourU(faceIndex, 0);
        MicroBuf::faceCoords(b.faceIndex, p, u, v);
        u = rasterScale*(u + 1.0f);
        v = rasterScale*(v + 1.0f);
        b.ubegin = u - wOn2;
        b.uend = u + wOn2;
        b.vbegin = v - wOn2;
        b.vend = v + wOn2;
    }
    else if(bd0.uend > faceRes)
    {
        // right neighbour
        BoundData& b = boundData[nfaces++];
        b.faceIndex = MicroBuf::neighbourU(faceIndex, 1);
        MicroBuf::faceCoords(b.faceIndex, p, u, v);
        u = rasterScale*(u + 1.0f);
        v = rasterScale*(v + 1.0f);
        b.ubegin = u - wOn2;
        b.uend = u + wOn2;
        b.vbegin = v - wOn2;
        b.vend = v + wOn2;
    }
    if(bd0.vbegin < 0)
    {
        // bottom neighbour
        BoundData& b = boundData[nfaces++];
        b.faceIndex = MicroBuf::neighbourV(faceIndex, 0);
        MicroBuf::faceCoords(b.faceIndex, p, u, v);
        u = rasterScale*(u + 1.0f);
        v = rasterScale*(v + 1.0f);
        b.ubegin = u - wOn2;
        b.uend = u + wOn2;
        b.vbegin = v - wOn2;
        b.vend = v + wOn2;
    }
    else if(bd0.vend > faceRes)
    {
        // top neighbour
        BoundData& b = boundData[nfaces++];
        b.faceIndex = MicroBuf::neighbourV(faceIndex, 1);
        MicroBuf::faceCoords(b.faceIndex, p, u, v);
        u = rasterScale*(u + 1.0f);
        v = rasterScale*(v + 1.0f);
        b.ubegin = u - wOn2;
        b.uend = u + wOn2;
        b.vbegin = v - wOn2;
        b.vend = v + wOn2;
    }
    for(int iface = 0; iface < nfaces; ++iface)
    {
        BoundData& bd = boundData[iface];
        // Range of pixels which the square touches (note, exclusive end)
        int ubeginRas = Imath::clamp(int(bd.ubegin),   0, faceRes);
        int uendRas   = Imath::clamp(int(bd.uend) + 1, 0, faceRes);
        int vbeginRas = Imath::clamp(int(bd.vbegin),   0, faceRes);
        int vendRas   = Imath::clamp(int(bd.vend) + 1, 0, faceRes);
        integrator.setFace(bd.faceIndex);
        for(int iv = vbeginRas; iv < vendRas; ++iv)
            for(int iu = ubeginRas; iu < uendRas; ++iu)
            {
                // Calculate the fraction coverage of the square over the current
                // pixel for antialiasing.  This estimate is what you'd get if you
                // filtered the square representing the surfel with a 1x1 box filter.
                float urange = std::min<float>(iu+1, bd.uend) -
                               std::max<float>(iu,   bd.ubegin);
                float vrange = std::min<float>(iv+1, bd.vend) -
                               std::max<float>(iv,   bd.vbegin);
                float coverage = urange*vrange;
                integrator.addSample(iu, iv, plen, coverage);
            }
    }
}
Esempio n. 3
0
static void renderNode(IntegratorT& integrator, V3f P, V3f N, float cosConeAngle,
                       float sinConeAngle, float maxSolidAngle, int dataSize,
                       const DiffusePointOctree::Node* node)
{
    // This is an iterative traversal of the point hierarchy, since it's
    // slightly faster than a recursive traversal.
    //
    // The max required size for the explicit stack should be < 200, since
    // tree depth shouldn't be > 24, and we have a max of 8 children per node.
    const DiffusePointOctree::Node* nodeStack[200];
    nodeStack[0] = node;
    int stackSize = 1;
    while(stackSize > 0)
    {
        node = nodeStack[--stackSize];
        {
            // Examine node bound and cull if possible
            // TODO: Reinvestigate using (node->aggP - P) with spherical harmonics
            V3f c = node->center - P;
            if(sphereOutsideCone(c, c.length2(), node->boundRadius, N,
                                 cosConeAngle, sinConeAngle))
                continue;
        }
        float r = node->aggR;
        V3f p = node->aggP - P;
        float plen2 = p.length2();
        // Examine solid angle of interior node bounding sphere to see whether we
        // can render it directly or not.
        //
        // TODO: Would be nice to use dot(node->aggN, p.normalized()) in the solid
        // angle estimation.  However, we get bad artifacts if we do this naively.
        // Perhaps with spherical harmoics it'll be better.
        float solidAngle = M_PI*r*r / plen2;
        if(solidAngle < maxSolidAngle)
        {
            integrator.setPointData(reinterpret_cast<const float*>(&node->aggCol));
            renderDisk(integrator, N, p, node->aggN, r, cosConeAngle, sinConeAngle);
        }
        else
        {
            // If we get here, the solid angle of the current node was too large
            // so we must consider the children of the node.
            //
            // The render order is sorted so that points are rendered front to
            // back.  This greatly improves the correctness of the hider.
            //
            // FIXME: The sorting procedure gets things wrong sometimes!  The
            // problem is that points may stick outside the bounds of their octree
            // nodes.  Probably we need to record all the points, sort, and
            // finally render them to get this right.
            if(node->npoints != 0)
            {
                // Leaf node: simply render each child point.
                std::pair<float, int> childOrder[8];
                // INDIRECT
                assert(node->npoints <= 8);
                for(int i = 0; i < node->npoints; ++i)
                {
                    const float* data = &node->data[i*dataSize];
                    V3f p = V3f(data[0], data[1], data[2]) - P;
                    childOrder[i].first = p.length2();
                    childOrder[i].second = i;
                }
                std::sort(childOrder, childOrder + node->npoints);
                for(int i = 0; i < node->npoints; ++i)
                {
                    const float* data = &node->data[childOrder[i].second*dataSize];
                    V3f p = V3f(data[0], data[1], data[2]) - P;
                    V3f n = V3f(data[3], data[4], data[5]);
                    float r = data[6];
                    integrator.setPointData(data+7);
                    renderDisk(integrator, N, p, n, r, cosConeAngle, sinConeAngle);
                }
                continue;
            }
            else
            {
                // Interior node: render children.
                std::pair<float, const DiffusePointOctree::Node*> children[8];
                int nchildren = 0;
                for(int i = 0; i < 8; ++i)
                {
                    DiffusePointOctree::Node* child = node->children[i];
                    if(!child)
                        continue;
                    children[nchildren].first = (child->center - P).length2();
                    children[nchildren].second = child;
                    ++nchildren;
                }
                std::sort(children, children + nchildren);
                // Interior node: render each non-null child.  Nodes we want to
                // render first must go onto the stack last.
                for(int i = nchildren-1; i >= 0; --i)
                    nodeStack[stackSize++] = children[i].second;
            }
        }
    }
}
Esempio n. 4
0
static void renderDiskExact(IntegratorT& integrator, V3f p, V3f n, float r) {

    int faceRes = integrator.res();
    float plen2 = p.length2();
    if(plen2 == 0) // Sanity check
        return;
    // Angle from face normal to edge is acos(1/sqrt(3)).
    static float cosFaceAngle = 1.0f/sqrtf(3);
    static float sinFaceAngle = sqrtf(2.0f/3.0f);

    // iterate over all the faces.
    for(int iface = MicroBuf::Face_begin; iface < MicroBuf::Face_begin; ++iface) {

        // Cast this back to a Face enum.
        MicroBuf::Face face = static_cast<MicroBuf::Face>(iface);

        // Avoid rendering to the current face if the disk definitely doesn't
        // touch it.  First check the cone angle
        if(sphereOutsideCone(p, plen2, r, MicroBuf::faceNormal(face),
                             cosFaceAngle, sinFaceAngle))
            continue;
        float dot_pFaceN = MicroBuf::dotFaceNormal(face, p);
        float dot_nFaceN = MicroBuf::dotFaceNormal(face, n);
        // If the disk is behind the camera and the disk normal is relatively
        // aligned with the face normal (to within the face cone angle), the
        // disk can't contribute to the face and may be culled.
        if(dot_pFaceN < 0 && fabs(dot_nFaceN) > cosFaceAngle)
            continue;
        // Check whether disk spans the perspective divide for the current
        // face.  Note: sin^2(angle(n, faceN)) = (1 - dot_nFaceN*dot_nFaceN)
        if((1 - dot_nFaceN*dot_nFaceN)*r*r >= dot_pFaceN*dot_pFaceN)
        {
            // When the disk spans the perspective divide, the shape of the
            // disk projected onto the face is a hyperbola.  Bounding a
            // hyperbola is a pain, so the easiest thing to do is trace a ray
            // for every pixel on the face and check whether it hits the disk.
            //
            // Note that all of the tricky rasterization rubbish further down
            // could probably be replaced by the following ray tracing code if
            // I knew a way to compute the tight raster bound.
            integrator.setFace(face);
            for(int iv = 0; iv < faceRes; ++iv)
                for(int iu = 0; iu < faceRes; ++iu)
                {
                    // V = ray through the pixel
                    V3f V = integrator.rayDirection(face, iu, iv);
                    // Signed distance to plane containing disk
                    float t = dot(p, n)/dot(V, n);
                    if(t > 0 && (t*V - p).length2() < r*r)
                    {
                        // The ray hit the disk, record the hit
                        integrator.addSample(iu, iv, t, 1.0f);
                    }
                }
            continue;
        }
        // If the disk didn't span the perspective divide and is behind the
        // camera, it may be culled.
        if(dot_pFaceN < 0)
            continue;
        // Having gone through all the checks above, we know that the disk
        // doesn't span the perspective divide, and that it is in front of the
        // camera.  Therefore, the disk projected onto the current face is an
        // ellipse, and we may compute a quadratic function
        //
        //   q(u,v) = a0*u*u + b0*u*v + c0*v*v + d0*u + e0*v + f0
        //
        // such that the disk lies in the region satisfying q(u,v) < 0.  To do
        // this, start with the implicit definition of the disk on the plane,
        //
        //   norm(dot(p,n)/dot(V,n) * V - p)^2 - r^2 < 0
        //
        // and compute coefficients A,B,C such that
        //
        //   A*dot(V,V) + B*dot(V,n)*dot(p,V) + C < 0
        float dot_pn = dot(p,n);
        float A = dot_pn*dot_pn;
        float B = -2*dot_pn;
        float C = plen2 - r*r;
        // Project onto the current face to compute the coefficients a0 through
        // to f0 for q(u,v)
        V3f pp = MicroBuf::canonicalFaceCoords(face, p);
        V3f nn = MicroBuf::canonicalFaceCoords(face, n);
        float a0 = A + B*nn.x*pp.x + C*nn.x*nn.x;
        float b0 = B*(nn.x*pp.y + nn.y*pp.x) + 2*C*nn.x*nn.y;
        float c0 = A + B*nn.y*pp.y + C*nn.y*nn.y;
        float d0 = (B*(nn.x*pp.z + nn.z*pp.x) + 2*C*nn.x*nn.z);
        float e0 = (B*(nn.y*pp.z + nn.z*pp.y) + 2*C*nn.y*nn.z);
        float f0 = (A + B*nn.z*pp.z + C*nn.z*nn.z);
        // Finally, transform the coefficients so that they define the
        // quadratic function in *raster* face coordinates, (iu, iv)
        float scale = 2.0f/faceRes;
        float scale2 = scale*scale;
        float off = 0.5f*scale - 1.0f;
        float a = scale2*a0;
        float b = scale2*b0;
        float c = scale2*c0;
        float d = ((2*a0 + b0)*off + d0)*scale;
        float e = ((2*c0 + b0)*off + e0)*scale;
        float f = (a0 + b0 + c0)*off*off + (d0 + e0)*off + f0;
        // Construct a tight bound for the ellipse in raster coordinates.
        int ubegin = 0, uend = faceRes;
        int vbegin = 0, vend = faceRes;
        float det = 4*a*c - b*b;
        // Sanity check; a valid ellipse must have det > 0
        if(det <= 0)
        {
            // If we get here, the disk is probably edge on (det == 0) or we
            // have some hopefully small floating point errors (det < 0: the
            // hyperbolic case we've already ruled out).  Cull in either case.
            continue;
        }
        float ub = 0, ue = 0;
        solveQuadratic(det, 4*d*c - 2*b*e, 4*c*f - e*e, ub, ue);
        ubegin = std::max(0, Imath::ceil(ub));
        uend   = std::min(faceRes, Imath::ceil(ue));
        float vb = 0, ve = 0;
        solveQuadratic(det, 4*a*e - 2*b*d, 4*a*f - d*d, vb, ve);
        vbegin = std::max(0, Imath::ceil(vb));
        vend   = std::min(faceRes, Imath::ceil(ve));
        // By the time we get here, we've expended perhaps 120 FLOPS + 2 sqrts
        // to set up the coefficients of q(iu,iv).  The setup is expensive, but
        // the bound is optimal so it will be worthwhile vs raytracing, unless
        // the raster faces are very small.
        integrator.setFace(face);
        for(int iv = vbegin; iv < vend; ++iv)
            for(int iu = ubegin; iu < uend; ++iu)
            {
                float q = a*(iu*iu) + b*(iu*iv) + c*(iv*iv) + d*iu + e*iv + f;
                if(q < 0)
                {
                    V3f V = integrator.rayDirection(face, iu, iv);
                    // compute distance to hit point
                    float z = dot_pn/dot(V, n);
                    integrator.addSample(iu, iv, z, 1.0f);
                }
            }
    }
}