Commit f5529511 authored by Ondřej Šimeček's avatar Ondřej Šimeček Committed by Radek Ošlejšek
Browse files

Resolve "Introduce shadow-casting glyphs"

parent 8410844e
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
@@ -13,6 +13,9 @@ import cz.fidentis.analyst.feature.services.FeaturePointImportService;
import cz.fidentis.analyst.kdtree.KdTree;
import cz.fidentis.analyst.mesh.BoundingBox;
import cz.fidentis.analyst.mesh.CurvatureCalculator;
import cz.fidentis.analyst.mesh.GlyphCalculator;
import cz.fidentis.analyst.mesh.core.Glyph;
import cz.fidentis.analyst.mesh.core.MeshFacet;
import cz.fidentis.analyst.mesh.core.MeshModel;
import cz.fidentis.analyst.mesh.io.MeshObjLoader;
import cz.fidentis.analyst.octree.Octree;
@@ -74,6 +77,8 @@ public class HumanFace implements Serializable {
    
    private final transient SurfaceMask surfaceMask = new SurfaceMask();

    private List<Glyph> glyphs;

    /**
     * Fast (de)serialization handler
     */
@@ -509,6 +514,31 @@ public class HumanFace implements Serializable {
        return new RelationToMesh(visitor.getDistance(), visitor.getNearestPoints());
    }

    /**
     * Gets the glyphs of the face; can be null.
     *
     * @return list of glyphs
     */
    public List<Glyph> getGlyphSamples() {
        return glyphs;
    }

    /**
     * Computes sample points and information used for shadow-casting glyphs
     *
     * @param force recompute the glyphs even if they were computed before
     * @param maxGlyphs maximum amount of glyphs to compute; used for sampling.
     */
    public void computeGlyphs(boolean force, int maxGlyphs) {
        if (force || glyphs == null) {
            GlyphCalculator glyphCalculator = new GlyphCalculator();
            for (MeshFacet facet : meshModel.getFacets()) {
                glyphCalculator.visitMeshFacet(facet);
            }
            glyphs = glyphCalculator.calculateGlyphSamples(maxGlyphs);
        }
    }

    @Override
    public int hashCode() {
        int hash = 7;
+147 −0
Original line number Diff line number Diff line
package cz.fidentis.analyst.mesh;

import cz.fidentis.analyst.mesh.core.*;
import cz.fidentis.analyst.sampling.PoissonDiskSubSampling;

import javax.vecmath.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Visitor, which chooses positions of glyphs via subsampling and then calculates the principle directions of these points.
 *
 * @author Ondrej Simecek
 */
public class GlyphCalculator extends MeshVisitor {
    private final List<MeshFacet> facets = new ArrayList<>();
    private final double NEIGHBOURHOOD_DISTANCE = 100; //squared for efficiency

    /**
     * Constructor
     */
    public GlyphCalculator() {
    }

    @Override
    public boolean isThreadSafe() {
        return false;
    }

    @Override
    public void visitMeshFacet(MeshFacet facet) {
        facets.add(facet);
    }

    /**
     * Chooses vertices to become glyphs and calculates their principal curvatures.
     * Based on
     * <a href="https://ieeexplore.ieee.org/document/597794">V. Interrante et al.: Conveying the 3D shape of smoothly curving transparent surfaces via texture</a>,
     * 1997, doi: 10.1109/2945.597794
     *
     * @param maxSamples maximum number of glyphs to be calculated, passed to a subsampler.
     * @return list of {@code Glyph}
     */
    public List<Glyph> calculateGlyphSamples(int maxSamples) {
        List<Glyph> glyphs = new ArrayList<>();

        List<MeshPoint> samples = new ArrayList<>();
        PoissonDiskSubSampling sampler = new PoissonDiskSubSampling(maxSamples, MeshTriangle.Smoothing.NONE);
        for (MeshFacet facet : facets) {
            sampler.visitMeshFacet(facet);
            samples.addAll(sampler.getSamples());
        }

        for (MeshPoint sample : samples) {
            PrincipalCurvature curvature = calculatePrincipalCurvatures(sample);
            if (curvature != null) {
                glyphs.add(new Glyph(sample.getPosition(), sample.getNormal(), curvature.maxCurvatureDir(),
                        curvature.minCurvatureDir()));
            } else {
                Vector3d temp = new Vector3d(1, 0, 0);
                if (sample.getNormal().y == 0 && sample.getNormal().z == 0) {
                    temp = new Vector3d(0, 1, 0);
                }
                Vector3d base3 = new Vector3d(sample.getNormal());
                base3.normalize();
                Vector3d base1 = new Vector3d();
                Vector3d base2 = new Vector3d();
                base1.cross(base3, temp);
                base2.cross(base3, base1);

                glyphs.add(new Glyph(sample.getPosition(), sample.getNormal(), base1, base2));
            }
        }
        return glyphs;
    }

    /**
     * Calculates principal curvatures and principal directions. If the calculation fails returns null.
     *
     * @param point of a surface the curvature is calculated of
     * @return a @PrincipalCurvature
     */
    public PrincipalCurvature calculatePrincipalCurvatures(MeshPoint point) {
        Vector3d temp = new Vector3d(1, 0, 0);
        if (point.getNormal().y == 0 && point.getNormal().z == 0) {
            temp = new Vector3d(0, 1, 0);
        }
        Vector3d base3 = new Vector3d(point.getNormal());
        base3.normalize();
        Vector3d base1 = new Vector3d();
        Vector3d base2 = new Vector3d();
        base1.cross(base3, temp);
        base2.cross(base3, base1);

        Vector4d secondFundamentalForm = new Vector4d(); //second fundamental form
        int facesCounted = 0;
        for (MeshFacet facet : facets) {
            List<MeshTriangle> triangles = facet.getTriangles();

            for (MeshTriangle triangle : triangles) {
                Vector4d planeCurvature = triangle.getCurvatureOfTrianglePlane(point.getPosition(),
                        NEIGHBOURHOOD_DISTANCE, base1, base2);
                if (planeCurvature != null) {
                    secondFundamentalForm.add(planeCurvature);
                    facesCounted++;
                }
            }
        }
        secondFundamentalForm.scale(1 / (double) facesCounted);
        double b = -secondFundamentalForm.w - secondFundamentalForm.x;
        double discriminant = b * b + 4 * (secondFundamentalForm.x * secondFundamentalForm.w -
                secondFundamentalForm.y * secondFundamentalForm.z);
        double eigenValue1;
        double eigenValue2;
        if (discriminant >= 0) {
            eigenValue1 = (-b + Math.sqrt(discriminant)) / 2;
            eigenValue2 = (-b - Math.sqrt(discriminant)) / 2;
            if (Math.abs(eigenValue1) < Math.abs(eigenValue2)) {
                double tempEigen = eigenValue1;
                eigenValue1 = eigenValue2;
                eigenValue2 = tempEigen;
            }
        } else {
            return null;
        }
        Vector4d sFFminusEigen = new Vector4d(secondFundamentalForm.x - eigenValue1, secondFundamentalForm.y,
                secondFundamentalForm.z, secondFundamentalForm.w - eigenValue1);
        double y = (sFFminusEigen.x + sFFminusEigen.z) / -(sFFminusEigen.y + sFFminusEigen.w);
        Vector2d eigenVector1 = new Vector2d(1, y);
        eigenVector1.normalize();

        //rotate base
        Vector3d tempBase1 = new Vector3d(base1);
        Vector3d tempBase2 = new Vector3d(base2);
        tempBase1.scale(eigenVector1.x);
        tempBase2.scale(eigenVector1.y);
        tempBase1.add(tempBase2);
        tempBase1.normalize();

        Vector3d maxCurvatureDir = new Vector3d(tempBase1);
        Vector3d minCurvatureDir = new Vector3d();
        minCurvatureDir.cross(maxCurvatureDir, point.getNormal());

        return new PrincipalCurvature(eigenValue1, eigenValue2, maxCurvatureDir, minCurvatureDir);
    }

}
+15 −0
Original line number Diff line number Diff line
package cz.fidentis.analyst.mesh.core;

import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

/**
 * Information about a point selected to be a glyph. Needed for rendering shadow-casting glyphs.
 * @param samplePoint     position of the sample
 * @param sampleNormal    normal of the sample
 * @param maxCurvatureDir the maximal principal curvature at the sample point
 * @param minCurvatureDir the minimal principal curvature at the sample point
 *
 * @author Ondrej Simecek
 */
public record Glyph(Point3d samplePoint, Vector3d sampleNormal, Vector3d maxCurvatureDir, Vector3d minCurvatureDir) {}
+54 −2
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ import cz.fidentis.analyst.shapes.Ray;
import javax.vecmath.Point3d;
import javax.vecmath.Tuple3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector4d;
import java.io.Serial;
import java.io.Serializable;
import java.util.*;
@@ -228,7 +229,7 @@ public class MeshTriangle implements Iterable<MeshPoint>, Serializable {

    /**
     * Computes the point laying on the triangle which is closest to
     * given 3D point. Return point is either one of the tringle's vertices,
     * given 3D point. Return point is either one of the triangle's vertices,
     * a point laying on triangles edge, or a point laying on the plane of
     * the triangle inside the triangle boundaries.
     *
@@ -328,7 +329,7 @@ public class MeshTriangle implements Iterable<MeshPoint>, Serializable {

    /**
     * Return a center of circumcircle. This point represents the point
     * of Voronoi area used for Delaunay triangulation, for instence.
     * of Voronoi area used for Delaunay triangulation, for instance.
     *
     * @return the center of circumcircle
     */
@@ -804,4 +805,55 @@ public class MeshTriangle implements Iterable<MeshPoint>, Serializable {
        double t = oa.dot(v) + ab.dot(v) * s;
        return (t >= 0);
    }

    /**
     * Function tests if the mesh triangle is closer to the sample point than a certain distance and if is, calculates
     * the second fundamental form of the sample point regarding the plane defined by the mesh triangle, otherwise
     * returns null.
     * <p>
     *     For more details, see:
     *     <a href="https://ieeexplore.ieee.org/document/597794">V. Interrante et al.: Conveying the 3D shape of smoothly curving transparent surfaces via texture</a>,
     *     1997, doi: 10.1109/2945.597794
     * </p>
     *
     * @param samplePosition position of a point on model's surface selected to be a glyph
     * @param distanceAccepted squared distance; the triangle needs to be closer to the sample point than this distance
     *                         to be taken into account
     * @param base1 first vector of orthogonal base; the base consists of three vectors: the normal of the sample point
     *              and two vectors(base1 and base2) defining the tangent plane of the model at the sample point
     * @param base2 second vector of orthogonal base; the base consists of three vectors: the normal of the sample point
     *              and two vectors(base1 and base2) defining the tangent plane of the model at the sample point
     * @return curvature of the sample point represented by the rate the surface normal tips in the directions of
     *         orthogonal base
     */
    public Vector4d getCurvatureOfTrianglePlane(Point3d samplePosition, double distanceAccepted, Vector3d base1, Vector3d base2) {
        if (getVertex1().distanceSquared(samplePosition) < distanceAccepted
                || getVertex2().distanceSquared(samplePosition) < distanceAccepted
                || getVertex3().distanceSquared(samplePosition) < distanceAccepted) {

            Vector3d normal = new Vector3d();
            normal.add(getPoint1().getNormal());
            normal.add(getPoint2().getNormal());
            normal.add(getPoint3().getNormal());
            normal.normalize();

            Vector3d middlePoint = new Vector3d();
            middlePoint.add(getPoint1().getPosition());
            middlePoint.add(getPoint2().getPosition());
            middlePoint.add(getPoint3().getPosition());
            middlePoint.scale(1 / (3.0f));

            Vector3d samplePoint = new Vector3d(samplePosition);
            Vector3d sampleToMiddle = new Vector3d();
            sampleToMiddle.sub(middlePoint, samplePoint);

            double omega11 = base1.dot(normal) / base1.dot(sampleToMiddle);
            double omega21 = base2.dot(normal) / base1.dot(sampleToMiddle);
            double omega12 = base1.dot(normal) / base2.dot(sampleToMiddle);
            double omega22 = base2.dot(normal) / base2.dot(sampleToMiddle);

            return new Vector4d(omega11, omega21, omega12, omega22);
        }
        return null;
    }
}
+14 −0
Original line number Diff line number Diff line
package cz.fidentis.analyst.mesh.core;

import javax.vecmath.Vector3d;

/**
 * Information about the curvature of a surface.
 *
 * @param maxCurvature maximum value of curvature of a surface point
 * @param minCurvature minimum value of curvature of a surface point
 * @param maxCurvatureDir direction of the maximum principal curvature
 * @param minCurvatureDir direction of the minimum principal curvature
 */
public record PrincipalCurvature(double maxCurvature, double minCurvature, Vector3d maxCurvatureDir, Vector3d minCurvatureDir) {
}
Loading