All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.mihosoft.vrl.v3d.Edge Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package eu.mihosoft.vrl.v3d;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import eu.mihosoft.vrl.v3d.ext.org.poly2tri.PolygonUtil;

/**
 * The Class Edge.
 *
 * @author miho
 */
public class Edge {

    /** The p1. */
    private final Vertex p1;
    
    /** The p2. */
    private final Vertex p2;
    
    /** The direction. */
    private final Vector3d direction;

    /**
     * Instantiates a new edge.
     *
     * @param p1 the p1
     * @param p2 the p2
     */
    public Edge(Vertex p1, Vertex p2) {
        this.p1 = p1;
        this.p2 = p2;

        direction = p2.pos.minus(p1.pos).normalized();
    }

    /**
     * Gets the p1.
     *
     * @return the p1
     */
    public Vertex getP1() {
        return p1;
    }

//    /**
//     * @param p1 the p1 to set
//     */
//    public void setP1(Vertex p1) {
//        this.p1 = p1;
//    }
    /**
 * Gets the p2.
 *
 * @return the p2
 */
    public Vertex getP2() {
        return p2;
    }

//    /**
//     * @param p2 the p2 to set
//     */
//    public void setP2(Vertex p2) {
//        this.p2 = p2;
/**
 * From polygon.
 *
 * @param poly the poly
 * @return the list
 */
//    }
    public static List fromPolygon(Polygon poly) {
        List result = new ArrayList<>();

        for (int i = 0; i < poly.vertices.size(); i++) {
            Edge e = new Edge(poly.vertices.get(i), poly.vertices.get((i + 1) % poly.vertices.size()));

            result.add(e);
        }

        return result;
    }

    /**
     * To vertices.
     *
     * @param edges the edges
     * @return the list
     */
    public static List toVertices(List edges) {
        return edges.stream().map(e -> e.p1).collect(Collectors.toList());
    }

    /**
     * To points.
     *
     * @param edges the edges
     * @return the list
     */
    public static List toPoints(List edges) {
        return edges.stream().map(e -> e.p1.pos).collect(Collectors.toList());
    }

    /**
     * To polygon.
     *
     * @param points the points
     * @param plane the plane
     * @return the polygon
     */
    public static Polygon toPolygon(List points, Plane plane) {

//        List points = edges.stream().().map(e -> e.p1.pos).
//                collect(Collectors.toList());
        Polygon p = Polygon.fromPoints(points);

        p.vertices.stream().forEachOrdered((vertex) -> {
            vertex.normal = plane.normal.clone();
        });

//        // we try to detect wrong orientation by comparing normals
//        if (p.plane.normal.angle(plane.normal) > 0.1) {
//            p.flip();
//        }
        return p;
    }

    /**
     * To polygons.
     *
     * @param boundaryEdges the boundary edges
     * @param plane the plane
     * @return the list
     */
    public static List toPolygons(List boundaryEdges, Plane plane) {

        List boundaryPath = new ArrayList<>();

        boolean[] used = new boolean[boundaryEdges.size()];
        Edge edge = boundaryEdges.get(0);
        used[0] = true;
        while (true) {
            Edge finalEdge = edge;

            boundaryPath.add(finalEdge.p1.pos);

            int nextEdgeIndex = boundaryEdges.indexOf(boundaryEdges.stream().
                    filter(e -> finalEdge.p2.equals(e.p1)).findFirst().get());

            if (used[nextEdgeIndex]) {
//                System.out.println("nexIndex: " + nextEdgeIndex);
                break;
            }
//            System.out.print("edge: " + edge.p2.pos);
            edge = boundaryEdges.get(nextEdgeIndex);
//            System.out.println("-> edge: " + edge.p1.pos);
            used[nextEdgeIndex] = true;
        }

        List result = new ArrayList<>();

        System.out.println("#bnd-path-length: " + boundaryPath.size());

        result.add(toPolygon(boundaryPath, plane));

        return result;
    }

    /**
     * The Class Node.
     *
     * @param  the generic type
     */
    private static class Node {

        /** The parent. */
        private Node parent;
        
        /** The children. */
        private final List children = new ArrayList<>();
        
        /** The index. */
        private final int index;
        
        /** The value. */
        private final T value;
        
        /** The is hole. */
        private boolean isHole;

        /**
         * Instantiates a new node.
         *
         * @param index the index
         * @param value the value
         */
        public Node(int index, T value) {
            this.index = index;
            this.value = value;
        }

        /**
         * Adds the child.
         *
         * @param index the index
         * @param value the value
         */
        public void addChild(int index, T value) {
            children.add(new Node(index, value));
        }

        /**
         * Gets the children.
         *
         * @return the children
         */
        public List getChildren() {
            return this.children;
        }

        /**
         * Gets the parent.
         *
         * @return the parent
         */
        public Node getParent() {
            return parent;
        }

        /**
         * Gets the index.
         *
         * @return the index
         */
        public int getIndex() {
            return index;
        }

        /**
         * Gets the value.
         *
         * @return the value
         */
        public T getValue() {
            return value;
        }

        /* (non-Javadoc)
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode() {
            int hash = 7;
            hash = 67 * hash + this.index;
            return hash;
        }

        /* (non-Javadoc)
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final Node other = (Node) obj;
            if (this.index != other.index) {
                return false;
            }
            return true;
        }

        /**
         * Distance to root.
         *
         * @return the int
         */
        public int distanceToRoot() {
            int dist = 0;

            Node pNode = getParent();

            while (pNode != null) {
                dist++;
                pNode = getParent();
            }

            return dist;
        }

        /**
         * Checks if is checks if is hole.
         *
         * @return the isHole
         */
        public boolean isIsHole() {
            return isHole;
        }

        /**
         * Sets the checks if is hole.
         *
         * @param isHole the isHole to set
         */
        public void setIsHole(boolean isHole) {
            this.isHole = isHole;
        }

    }

    /** The Constant KEY_POLYGON_HOLES. */
    public static final String KEY_POLYGON_HOLES = "jcsg:edge:polygon-holes";

    /**
     * Boundary paths with holes.
     *
     * @param boundaryPaths the boundary paths
     * @return the list
     */
    public static List boundaryPathsWithHoles(List boundaryPaths) {

        List result = boundaryPaths.stream().
                map(p -> p.clone()).collect(Collectors.toList());

        List> parents = new ArrayList<>();
        boolean[] isHole = new boolean[result.size()];

        for (int i = 0; i < result.size(); i++) {
            Polygon p1 = result.get(i);
            List parentsOfI = new ArrayList<>();
            parents.add(parentsOfI);
            for (int j = 0; j < result.size(); j++) {
                Polygon p2 = result.get(j);
                if (i != j) {
                    if (p2.contains(p1)) {
                        parentsOfI.add(j);
                    }
                }
            }
            isHole[i] = parentsOfI.size() % 2 != 0;
        }

        int[] parent = new int[result.size()];

        for (int i = 0; i < parent.length; i++) {
            parent[i] = -1;
        }

        for (int i = 0; i < parents.size(); i++) {
            List par = parents.get(i);

            int max = 0;
            int maxIndex = 0;
            for (int pIndex : par) {

                int pSize = parents.get(pIndex).size();

                if (max < pSize) {
                    max = pSize;
                    maxIndex = pIndex;
                }
            }

            parent[i] = maxIndex;

            if (!isHole[maxIndex] && isHole[i]) {

                List holes;

                Optional> holesOpt = result.get(maxIndex).
                        getStorage().getValue(KEY_POLYGON_HOLES);

                if (holesOpt.isPresent()) {
                    holes = holesOpt.get();
                } else {
                    holes = new ArrayList<>();
                    result.get(maxIndex).getStorage().
                            set(KEY_POLYGON_HOLES, holes);
                }
                
                holes.add(result.get(i));
            }
        }

        return result;
    }

    /**
     * Returns a list of all boundary paths.
     *
     * @param boundaryEdges boundary edges (all paths must be closed)
     * @return the list
     */
    public static List boundaryPaths(List boundaryEdges) {
        List result = new ArrayList<>();

        boolean[] used = new boolean[boundaryEdges.size()];
        int startIndex = 0;
        Edge edge = boundaryEdges.get(startIndex);
        used[startIndex] = true;

        startIndex = 1;

        while (startIndex > 0) {
            List boundaryPath = new ArrayList<>();

            while (true) {
                Edge finalEdge = edge;

                boundaryPath.add(finalEdge.p1.pos);

//                System.out.print("edge: " + edge.p2.pos);

                Optional nextEdgeResult = boundaryEdges.stream().
                        filter(e -> finalEdge.p2.equals(e.p1)).findFirst();

                if (!nextEdgeResult.isPresent()) {
//                    System.out.println("ERROR: unclosed path:"
//                            + " no edge found with " + finalEdge.p2);
                    break;
                }

                Edge nextEdge = nextEdgeResult.get();

                int nextEdgeIndex = boundaryEdges.indexOf(nextEdge);

                if (used[nextEdgeIndex]) {
                    break;
                }

                edge = nextEdge;
//                System.out.println("-> edge: " + edge.p1.pos);
                used[nextEdgeIndex] = true;
            }

            if (boundaryPath.size() < 3) {
                break;
            }

            result.add(Polygon.fromPoints(boundaryPath));
            startIndex = nextUnused(used);

            if (startIndex > 0) {
                edge = boundaryEdges.get(startIndex);
                used[startIndex] = true;
            }

        }
//
//        System.out.println("paths: " + result.size());

        return result;
    }

    /**
     * Returns the next unused index as specified in the given boolean array.
     *
     * @param usage the usage array
     * @return the next unused index or a value < 0 if all indices are used
     */
    private static int nextUnused(boolean[] usage) {
        for (int i = 0; i < usage.length; i++) {
            if (usage[i] == false) {
                return i;
            }
        }

        return -1;
    }

    /**
     * _to polygons.
     *
     * @param boundaryEdges the boundary edges
     * @param plane the plane
     * @return the list
     */
    public static List _toPolygons(List boundaryEdges, Plane plane) {

        List boundaryPath = new ArrayList<>();

        boolean[] used = new boolean[boundaryEdges.size()];
        Edge edge = boundaryEdges.get(0);
        used[0] = true;
        while (true) {
            Edge finalEdge = edge;

            boundaryPath.add(finalEdge.p1.pos);

            int nextEdgeIndex = boundaryEdges.indexOf(boundaryEdges.stream().
                    filter(e -> finalEdge.p2.equals(e.p1)).findFirst().get());

            if (used[nextEdgeIndex]) {
//                System.out.println("nexIndex: " + nextEdgeIndex);
                break;
            }
//            System.out.print("edge: " + edge.p2.pos);
            edge = boundaryEdges.get(nextEdgeIndex);
//            System.out.println("-> edge: " + edge.p1.pos);
            used[nextEdgeIndex] = true;
        }

        List result = new ArrayList<>();

        System.out.println("#bnd-path-length: " + boundaryPath.size());

        result.add(toPolygon(boundaryPath, plane));

        return result;
    }
    /**
     * Determines whether the specified point is colinear
     *
     * @param p point to check
     * @return true if the specified point lies on this line
     * segment; false otherwise
     */
    public boolean colinear(Vector3d p) {
    	return colinear(p,Plane.EPSILON_Point);
    }
    public boolean colinear(Vector3d p, double TOL) {

        double x = p.x;
        double x1 = this.p1.pos.x;
        double x2 = this.p2.pos.x;

        double y = p.y;
        double y1 = this.p1.pos.y;
        double y2 = this.p2.pos.y;

        double z = p.z;
        double z1 = this.p1.pos.z;
        double z2 = this.p2.pos.z;

        double slopeSelfxy = (x1-x2)/(y1-y2);
        double slopeSelfxz = (x1-x2)/(z1-z2);
        double slopeSelfyz = (y1-y2)/(z1-z2);
        
        
        double slopeTestxy = (x-x2)/(y-y2);
        double slopeTestxz = (x-x2)/(z-z2);
        double slopeTestyz = (y-y2)/(z-z2);
        
        
        return Math.abs(slopeSelfxy - slopeTestxy) < TOL &&
        		Math.abs(slopeSelfxz - slopeTestxz) < TOL&&
        		Math.abs(slopeSelfyz - slopeTestyz) < TOL;
    }
    /**
     * Determines whether the specified point lies on tthis edge.
     *
     * @param p point to check
     * @param TOL tolerance
     * @return true if the specified point lies on this line
     * segment; false otherwise
     */
    public boolean contains(Vector3d p, double TOL) {

        double x = p.x;
        double x1 = this.p1.pos.x;
        double x2 = this.p2.pos.x;

        double y = p.y;
        double y1 = this.p1.pos.y;
        double y2 = this.p2.pos.y;

        double z = p.z;
        double z1 = this.p1.pos.z;
        double z2 = this.p2.pos.z;

        double AB = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) + (z2 - z1) * (z2 - z1));
        double AP = Math.sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1) + (z - z1) * (z - z1));
        double PB = Math.sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y) + (z2 - z) * (z2 - z));

        return Math.abs(AB - (AP + PB)) < TOL;
    }

    /**
     * Determines whether the specified point lies on tthis edge.
     *
     * @param p point to check
     * @return true if the specified point lies on this line
     * segment; false otherwise
     */
    public boolean contains(Vector3d p) {
        return contains(p, Plane.EPSILON);
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        int hash = 7;
        hash = 71 * hash + Objects.hashCode(this.p1);
        hash = 71 * hash + Objects.hashCode(this.p2);
        return hash;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Edge other = (Edge) obj;
        if(this.p1.pos.test( other.p1.pos,Plane.EPSILON_Point) && this.p2.pos.test( other.p2.pos,Plane.EPSILON_Point)) {
        	return true;
        }
        if(this.p1.pos.test( other.p2.pos,Plane.EPSILON_Point) && this.p2.pos.test( other.p1.pos,Plane.EPSILON_Point)) {
        	return true;
        }
        if (!(Objects.equals(this.p1, other.p1) || Objects.equals(this.p2, other.p1))) {
            return false;
        }
        if (!(Objects.equals(this.p2, other.p2) || Objects.equals(this.p1, other.p2))) {
            return false;
        }
        return true;
    }
    
    public boolean isThisPointOneOfMine(Vertex test) {
    	return p1.pos.test(test.pos, Plane.EPSILON_Point)||p2.pos.test(test.pos, Plane.EPSILON_Point);
    }

    @Override
    public String toString()
    {
        return "[[" + this.p1.getX() + ", " + this.p1.getY() + ", " + this.p1.getZ() + "]" +
                ", [" + this.p2.getX() + ", " + this.p2.getY() + ", " + this.p2.getZ() + "]]";
    }

    /**
     * Gets the direction.
     *
     * @return the direction
     */
    public Vector3d getDirection() {
        return direction;
    }

    /**
     * Returns the the point of this edge that is closest to the specified edge.
     *
     *  NOTE:  returns an empty optional if the edges are parallel
     *
     * @param e the edge to check
     * @return the the point of this edge that is closest to the specified edge
     */
    public Optional getClosestPoint(Edge e) {

        // algorithm from:
        // org.apache.commons.math3.geometry.euclidean.threed/Line.java.html
        Vector3d ourDir = getDirection();

        double cos = ourDir.dot(e.getDirection());
        double n = 1 - cos * cos;

        if (n < Plane.EPSILON) {
            // the lines are parallel
            return Optional.empty();
        }

        final Vector3d thisDelta = p2.pos.minus(p1.pos);
        final double norm2This = thisDelta.magnitudeSq();

        final Vector3d eDelta = e.p2.pos.minus(e.p1.pos);
        final double norm2E = eDelta.magnitudeSq();

        // line points above the origin
        Vector3d thisZero = p1.pos.plus(thisDelta.times(-p1.pos.dot(thisDelta) / norm2This));
        Vector3d eZero = e.p1.pos.plus(eDelta.times(-e.p1.pos.dot(eDelta) / norm2E));

        final Vector3d delta0 = eZero.minus(thisZero);
        final double a = delta0.dot(direction);
        final double b = delta0.dot(e.direction);

        Vector3d closestP = thisZero.plus(direction.times((a - b * cos) / n));

        if (!contains(closestP)) {
            if (closestP.minus(p1.pos).magnitudeSq()
                    < closestP.minus(p2.pos).magnitudeSq()) {
                return Optional.of(p1.pos);
            } else {
                return Optional.of(p2.pos);
            }
        }

        return Optional.of(closestP);
    }

    /**
     * Returns the intersection point between this edge and the specified edge.
     *
     *  NOTE:  returns an empty optional if the edges are parallel or if
     * the intersection point is not inside the specified edge segment
     *
     * @param e edge to intersect
     * @return the intersection point between this edge and the specified edge
     */
    public Optional getIntersection(Edge e) {
        Optional closestPOpt = getClosestPoint(e);

        if (!closestPOpt.isPresent()) {
            // edges are parallel
            return Optional.empty();
        }

        Vector3d closestP = closestPOpt.get();

        if (e.contains(closestP)) {
            return closestPOpt;
        } else {
            // intersection point outside of segment
            return Optional.empty();
        }
    }

    /**
     * Boundary polygons.
     *
     * @param csg the csg
     * @return the list
     */
    public static List boundaryPolygons(CSG csg) {
        List result = new ArrayList<>();

        for (List polygonGroup : searchPlaneGroups(csg.getPolygons())) {
            result.addAll(boundaryPolygonsOfPlaneGroup(polygonGroup));
        }

        return result;
    }

    /**
     * Boundary edges of plane group.
     *
     * @param planeGroup the plane group
     * @return the list
     */
    public static List boundaryEdgesOfPlaneGroup(List planeGroup) {
        List edges = new ArrayList<>();

        Stream pStream;

        if (planeGroup.size() > 200) {
            pStream = planeGroup.parallelStream();
        } else {
            pStream = planeGroup.stream();
        }

        pStream.map((p) -> Edge.fromPolygon(p)).forEach((pEdges) -> {
            edges.addAll(pEdges);
        });

        Stream edgeStream;

        if (edges.size() > 200) {
            edgeStream = edges.parallelStream();
        } else {
            edgeStream = edges.stream();
        }

        // find potential boundary edges, i.e., edges that occur once (freq=1)
        List potentialBoundaryEdges = new ArrayList<>();
        edgeStream.forEachOrdered((e) -> {
            int count = Collections.frequency(edges, e);
            if (count == 1) {
                potentialBoundaryEdges.add(e);
            }
        });

        // now find "false boundary" edges end remove them from the 
        // boundary-edge-list
        // 
        // thanks to Susanne Höllbacher for the idea :)
        Stream bndEdgeStream;

        if (potentialBoundaryEdges.size() > 200) {
            bndEdgeStream = potentialBoundaryEdges.parallelStream();
        } else {
            bndEdgeStream = potentialBoundaryEdges.stream();
        }

        List realBndEdges = bndEdgeStream.
                filter(be -> edges.stream().filter(
                                e -> falseBoundaryEdgeSharedWithOtherEdge(be, e)
                        ).count() == 0).collect(Collectors.toList());

        //
//        System.out.println("#bnd-edges: " + realBndEdges.size()
//                + ",#edges: " + edges.size()
//                + ", #del-bnd-edges: " + (boundaryEdges.size() - realBndEdges.size()));
        return realBndEdges;
    }

    /**
     * Boundary polygons of plane group.
     *
     * @param planeGroup the plane group
     * @return the list
     */
    private static List boundaryPolygonsOfPlaneGroup(
            List planeGroup) {

        List polygons = boundaryPathsWithHoles(
                boundaryPaths(boundaryEdgesOfPlaneGroup(planeGroup)));

        System.out.println("polygons: " + polygons.size());

        List result = new ArrayList<>(polygons.size());

        for (Polygon p : polygons) {

            Optional> holesOfPresult
                    = p.
                    getStorage().getValue(Edge.KEY_POLYGON_HOLES);

            if (!holesOfPresult.isPresent()) {
                result.add(p);
            } else {
                result.addAll(PolygonUtil.concaveToConvex(p));
            }
        }

        return result;
    }

    /**
     * False boundary edge shared with other edge.
     *
     * @param fbe the fbe
     * @param e the e
     * @return true, if successful
     */
    public static boolean falseBoundaryEdgeSharedWithOtherEdge(Edge fbe, Edge e) {

        // we don't consider edges with shared end-points since we are only
        // interested in "false-boundary-edge"-cases
        boolean sharedEndPoints = e.getP1().pos.equals(fbe.getP1().pos)
                || e.getP1().pos.equals(fbe.getP2().pos)
                || e.getP2().pos.equals(fbe.getP1().pos)
                || e.getP2().pos.equals(fbe.getP2().pos);

        if (sharedEndPoints) {
            return false;
        }

        return fbe.contains(e.getP1().pos) || fbe.contains(e.getP2().pos);
    }

    /**
     * Search plane groups.
     *
     * @param polygons the polygons
     * @return the list
     */
    private static List> searchPlaneGroups(List polygons) {
        List> planeGroups = new ArrayList<>();
        boolean[] used = new boolean[polygons.size()];
        System.out.println("#polys: " + polygons.size());
        for (int pOuterI = 0; pOuterI < polygons.size(); pOuterI++) {

            if (used[pOuterI]) {
                continue;
            }

            Polygon pOuter = polygons.get(pOuterI);

            List otherPolysInPlane = new ArrayList<>();

            otherPolysInPlane.add(pOuter);

            for (int pInnerI = 0; pInnerI < polygons.size(); pInnerI++) {

                Polygon pInner = polygons.get(pInnerI);

                if (pOuter.equals(pInner)) {
                    continue;
                }

                Vector3d nOuter = pOuter.plane.normal;
                Vector3d nInner = pInner.plane.normal;

                double angle = nOuter.angle(nInner);

//                System.out.println("angle: " + angle + " between " + pOuterI+" -> " + pInnerI);
                if (angle < 0.01 /*&& abs(pOuter.plane.dist - pInner.plane.dist) < 0.1*/) {
                    otherPolysInPlane.add(pInner);
                    used[pInnerI] = true;
                    System.out.println("used: " + pOuterI + " -> " + pInnerI);
                }
            }

            if (!otherPolysInPlane.isEmpty()) {
                planeGroups.add(otherPolysInPlane);
            }
        }
        return planeGroups;
    }

	public double length() {
		// TODO Auto-generated method stub
		return p1.pos.minus(p2.pos).length();
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy