////////////////////////////////////////////////////////////////////////////////////////
//
//  Copyright 2023 OVITO GmbH, Germany
//
//  This file is part of OVITO (Open Visualization Tool).
//
//  OVITO is free software; you can redistribute it and/or modify it either under the
//  terms of the GNU General Public License version 3 as published by the Free Software
//  Foundation (the "GPL") or, at your option, under the terms of the MIT License.
//  If you do not alter this notice, a recipient may use your version of this
//  file under either the GPL or the MIT License.
//
//  You should have received a copy of the GPL along with this program in a
//  file LICENSE.GPL.txt.  You should have received a copy of the MIT License along
//  with this program in a file LICENSE.MIT.txt
//
//  This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
//  either express or implied. See the GPL or the MIT License for the specific language
//  governing rights and limitations.
//
////////////////////////////////////////////////////////////////////////////////////////

#include <ovito/crystalanalysis/CrystalAnalysis.h>
#include <ovito/core/utilities/concurrent/Task.h>
#include "DislocationTracer.h"
#include "InterfaceMesh.h"

namespace Ovito {

/******************************************************************************
* Allocates a new BurgersCircuit instance.
******************************************************************************/
BurgersCircuit* DislocationTracer::allocateCircuit()
{
    if(_unusedCircuit == nullptr)
        return _circuitPool.construct();
    else {
        BurgersCircuit* circuit = _unusedCircuit;
        _unusedCircuit = nullptr;
        return circuit;
    }
}

/******************************************************************************
* Discards a previously allocated BurgersCircuit instance.
******************************************************************************/
void DislocationTracer::discardCircuit(BurgersCircuit* circuit)
{
    OVITO_ASSERT(_unusedCircuit == nullptr);
    _unusedCircuit = circuit;
}

/******************************************************************************
* Performs a dislocation search on the interface mesh by generating
* trial Burgers circuits. Identified dislocation segments are converted to
* a continuous line representation.
******************************************************************************/
bool DislocationTracer::traceDislocationSegments(ProgressingTask& operation)
{
    if(_maxBurgersCircuitSize < 3 || _maxBurgersCircuitSize > _maxExtendedBurgersCircuitSize)
        throw Exception("Invalid maximum circuit size parameter(s).");

    // Set up progress indicator.
    // The estimated runtime of each sub-step scales quadratically with the Burgers circuit search depth.
    std::vector<int> subStepWeights(_maxExtendedBurgersCircuitSize - 2);
    for(int i = 0; i < subStepWeights.size(); i++)
        subStepWeights[i] = (i+3)*(i+3);
    operation.beginProgressSubStepsWithWeights(subStepWeights);

    mesh().clearFaceFlag(0);

    // Incrementally extend search radius for new Burgers circuits and extend existing segments by enlarging
    // the maximum circuit size until segments meet at a junction.
    size_t numJunctions = 0;
    for(int circuitLength = 3; circuitLength <= _maxExtendedBurgersCircuitSize; circuitLength++) {

        // Extend existing segments with dangling ends.
        for(DislocationNode* node : danglingNodes()) {
            OVITO_ASSERT(node->circuit->isDangling);
            OVITO_ASSERT(node->circuit->countEdges() == node->circuit->edgeCount);

            // Trace segment a bit further.
            traceSegment(*node->segment, *node, circuitLength, circuitLength <= _maxBurgersCircuitSize);
        }

        // Find dislocation segments by generating trial Burgers circuits on the interface mesh
        // and then moving them in both directions along the dislocation segment.
        if(circuitLength <= _maxBurgersCircuitSize && (circuitLength % 2) != 0) {
            if(!findPrimarySegments(circuitLength, operation))
                return false;
        }

        // Join segments forming dislocation junctions.
        numJunctions += joinSegments(circuitLength);

        // Store circuits of dangling ends.
        if(circuitLength >= _maxBurgersCircuitSize) {
            for(DislocationNode* node : danglingNodes()) {
                OVITO_ASSERT(node->circuit->isDangling);
                OVITO_ASSERT(node->isDangling());
                if(node->circuit->segmentMeshCap.empty()) {
                    node->circuit->storeCircuit();
                    node->circuit->numPreliminaryPoints = 0;
                }
            }
        }

        if(circuitLength < _maxExtendedBurgersCircuitSize)
            operation.nextProgressSubStep();
    }

    //qDebug() << "Number of dislocation segments:" << network()->segments().size();
    //qDebug() << "Number of dislocation junctions:" << numJunctions;

    operation.endProgressSubSteps();
    return !operation.isCanceled();
}

/******************************************************************************
* After dislocation segments have been extracted, this method trims
* dangling lines.
******************************************************************************/
void DislocationTracer::finishDislocationSegments(int crystalStructure)
{
    // Remove extra line points from segments that do not end in a junction.
    // Also assign consecutive IDs to final segments.
    for(int segmentIndex = 0; segmentIndex < network()->segments().size(); segmentIndex++) {
        DislocationSegment* segment = network()->segments()[segmentIndex];
        std::deque<Point3>& line = segment->line;
        std::deque<int>& coreSize = segment->coreSize;
        segment->id = segmentIndex;
        OVITO_ASSERT(coreSize.size() == line.size());
        OVITO_ASSERT(segment->backwardNode().circuit->numPreliminaryPoints + segment->forwardNode().circuit->numPreliminaryPoints <= line.size());
        line.erase(line.begin(), line.begin() + segment->backwardNode().circuit->numPreliminaryPoints);
        line.erase(line.end() - segment->forwardNode().circuit->numPreliminaryPoints, line.end());
        coreSize.erase(coreSize.begin(), coreSize.begin() + segment->backwardNode().circuit->numPreliminaryPoints);
        coreSize.erase(coreSize.end() - segment->forwardNode().circuit->numPreliminaryPoints, coreSize.end());
    }

    // Express Burgers vectors of dislocations in a proper lattice frame whenever possible.
    for(DislocationSegment* segment : network()->segments()) {
        Cluster* originalCluster = segment->burgersVector.cluster();
        if(originalCluster->structure != crystalStructure) {
            for(ClusterTransition* t = originalCluster->transitions; t != nullptr && t->distance <= 1; t = t->next) {
                if(t->cluster2->structure == crystalStructure) {
                    segment->burgersVector = ClusterVector(t->transform(segment->burgersVector.localVec()), t->cluster2);
                    break;
                }
            }
        }
    }

    // Align dislocations.
    for(DislocationSegment* segment : network()->segments()) {
        std::deque<Point3>& line = segment->line;
        OVITO_ASSERT(line.size() >= 2);

        // Change sense of all dislocations for backward compatibility with OVITO 2.9.0:
        segment->burgersVector = -segment->burgersVector;

        Vector3 dir = line.back() - line.front();
        if(dir.isZero(CA_ATOM_VECTOR_EPSILON))
            continue;

        if(std::abs(dir.x()) > std::abs(dir.y())) {
            if(std::abs(dir.x()) > std::abs(dir.z())) {
                if(dir.x() >= 0.0) continue;
            }
            else {
                if(dir.z() >= 0.0) continue;
            }
        }
        else {
            if(std::abs(dir.y()) > std::abs(dir.z())) {
                if(dir.y() >= 0.0) continue;
            }
            else {
                if(dir.z() >= 0.0) continue;
            }
        }

        segment->flipOrientation();
    }
}

/**
 * This data structure is used for the recursive generation of
 * trial Burgers circuits on the interface mesh.
 */
struct BurgersCircuitSearchStruct
{
    /// The current mesh node.
    InterfaceMesh::Vertex* node;

    /// The coordinates of this node in the unstrained reference crystal it was mapped to.
    Point3 latticeCoord;

    /// The matrix that transforms local lattice vectors to the reference frame of the start node.
    Matrix3 tm;

    /// Number of steps between this node and the start node of the recursive walk.
    int recursiveDepth;

    /// The previous edge in the path to this node.
    InterfaceMesh::Edge* predecessorEdge;

    /// Linked list pointer.
    BurgersCircuitSearchStruct* nextToProcess;
};

/******************************************************************************
* Generates all possible trial circuits on the interface mesh until it finds
* one with a non-zero Burgers vector.
* Then moves the Burgers circuit in both directions along the dislocation
* segment until the maximum circuit size has been reached.
******************************************************************************/
bool DislocationTracer::findPrimarySegments(int maxBurgersCircuitSize, ProgressingTask& operation)
{
    int searchDepth =  (maxBurgersCircuitSize - 1) / 2;
    OVITO_ASSERT(searchDepth >= 1);

    MemoryPool<BurgersCircuitSearchStruct> structPool;

    operation.setProgressMaximum(mesh().vertexCount());
    int progressCounter = 0;

    // Find an appropriate start node for the recursive search.
    for(InterfaceMesh::Vertex& startNode : mesh().vertices()) {
        OVITO_ASSERT(startNode.edges() != nullptr);
        OVITO_ASSERT(startNode.burgersSearchStruct == nullptr);

        // Update progress indicator.
        if(!operation.setProgressValueIntermittent(progressCounter++))
            return false;

        // The first node is the seed of our recursive walk.
        // It is mapped to the origin of the reference lattice.
        BurgersCircuitSearchStruct* start = structPool.construct();
        start->latticeCoord = Point3::Origin();
        start->predecessorEdge = nullptr;
        start->recursiveDepth = 0;
        start->nextToProcess = nullptr;
        start->tm.setIdentity();
        start->node = &startNode;
        startNode.burgersSearchStruct = start;

        // This is the cluster we work in.
        OVITO_ASSERT(startNode.edges()->clusterTransition);
        Cluster* cluster = startNode.edges()->clusterTransition->cluster1;
        OVITO_ASSERT(cluster && cluster->id != 0);

        bool foundBurgersCircuit = false;
        BurgersCircuitSearchStruct* end_of_queue = start;

        // Process nodes from the queue until it becomes empty or until a valid Burgers circuit has been found.
        for(BurgersCircuitSearchStruct* current = start; current != nullptr && foundBurgersCircuit == false; current = current->nextToProcess) {
            InterfaceMesh::Vertex* currentNode = current->node;
            for(InterfaceMesh::Edge* edge = currentNode->edges(); edge != nullptr; edge = edge->nextVertexEdge()) {

                OVITO_ASSERT((edge->circuit == nullptr) == (edge->nextCircuitEdge == nullptr));
                OVITO_ASSERT((edge->oppositeEdge()->circuit == nullptr) == (edge->oppositeEdge()->nextCircuitEdge == nullptr));
                OVITO_ASSERT(edge->face() != nullptr);

                // Skip edges which are, or have already been, part of a Burgers circuit.
                if(edge->nextCircuitEdge != nullptr || edge->oppositeEdge()->nextCircuitEdge != nullptr)
                    continue;

                // Skip edges that border an existing Burgers circuit.
                if(edge->face()->circuit != nullptr)
                    continue;

                // Get the neighbor node.
                InterfaceMesh::Vertex* neighbor = edge->vertex2();

                // Calculate reference lattice coordinates of the neighboring vertex.
                Point3 neighborCoord = current->latticeCoord;
                neighborCoord += current->tm * edge->clusterVector;

                // If this neighbor has been assigned reference lattice coordinates before,
                // then perform the Burgers circuit test now by comparing the previous to the new coordinates.
                BurgersCircuitSearchStruct* neighborStruct = neighbor->burgersSearchStruct;
                if(neighborStruct != nullptr) {

                    // Compute Burgers vector of the current circuit.
                    Vector3 burgersVector = neighborStruct->latticeCoord - neighborCoord;
                    if(burgersVector.isZero(CA_LATTICE_VECTOR_EPSILON) == false) {
                        // Found circuit with non-zero Burgers vector.
                        // Check if circuit encloses disclination.
                        Matrix3 frankRotation = current->tm * edge->clusterTransition->reverse->tm;
                        if(frankRotation.equals(neighborStruct->tm, CA_TRANSITION_MATRIX_EPSILON)) {
                            // Stop as soon as a valid Burgers circuit has been found.
                            if(createBurgersCircuit(edge, maxBurgersCircuitSize)) {
                                foundBurgersCircuit = true;
                                break;
                            }
                        }
                    }
                }
                else if(current->recursiveDepth < searchDepth) {
                    // This neighbor has not been visited before. Put it at the end of the queue.
                    neighborStruct = structPool.construct();
                    neighborStruct->node = neighbor;
                    neighborStruct->latticeCoord = neighborCoord;
                    neighborStruct->predecessorEdge = edge;
                    neighborStruct->recursiveDepth = current->recursiveDepth + 1;
                    if(edge->clusterTransition->isSelfTransition())
                        neighborStruct->tm = current->tm;
                    else
                        neighborStruct->tm = current->tm * edge->clusterTransition->reverse->tm;
                    neighborStruct->nextToProcess = nullptr;
                    neighbor->burgersSearchStruct = neighborStruct;
                    OVITO_ASSERT(end_of_queue->nextToProcess == nullptr);
                    end_of_queue->nextToProcess = neighborStruct;
                    end_of_queue = neighborStruct;
                }
            }
        }

        // Clear the pointers of the nodes that have been visited during the last pass.
        for(BurgersCircuitSearchStruct* s = start; s != nullptr; s = s->nextToProcess) {
            s->node->burgersSearchStruct = nullptr;
            s->node->visited = false;
        }
        structPool.clear(true);
    }

    return !operation.isCanceled();
}

/******************************************************************************
* Creates a dislocation segment and a pair of Burgers circuits.
******************************************************************************/
bool DislocationTracer::createBurgersCircuit(InterfaceMesh::Edge* edge, int maxBurgersCircuitSize)
{
    OVITO_ASSERT(edge->circuit == nullptr);

    InterfaceMesh::Vertex* currentNode = edge->vertex1();
    InterfaceMesh::Vertex* neighborNode = edge->vertex2();
    BurgersCircuitSearchStruct* currentStruct = currentNode->burgersSearchStruct;
    BurgersCircuitSearchStruct* neighborStruct = neighborNode->burgersSearchStruct;
    OVITO_ASSERT(currentStruct != neighborStruct);

    // Reconstruct the Burgers circuit from the path we took along the mesh edges.
    BurgersCircuit* forwardCircuit = allocateCircuit();
    forwardCircuit->edgeCount = 1;
    forwardCircuit->firstEdge = forwardCircuit->lastEdge = edge->oppositeEdge();
    OVITO_ASSERT(forwardCircuit->firstEdge->circuit == nullptr);
    forwardCircuit->firstEdge->circuit = forwardCircuit;

    // Clear flags of nodes on the second branch of the recursive walk.
    for(BurgersCircuitSearchStruct* a = neighborStruct; ; a = a->predecessorEdge->vertex1()->burgersSearchStruct) {
        a->node->visited = false;
        if(a->predecessorEdge == nullptr) break;
        OVITO_ASSERT(a->predecessorEdge->circuit == nullptr);
        OVITO_ASSERT(a->predecessorEdge->oppositeEdge()->circuit == nullptr);
    }

    // Mark all nodes on the first branch of the recursive walk.
    for(BurgersCircuitSearchStruct* a = currentStruct; ; a = a->predecessorEdge->vertex1()->burgersSearchStruct) {
        a->node->visited = true;
        if(a->predecessorEdge == nullptr) break;
        OVITO_ASSERT(a->predecessorEdge->circuit == nullptr);
        OVITO_ASSERT(a->predecessorEdge->oppositeEdge()->circuit == nullptr);
    }

    // Then walk on the second branch again until we hit the first branch.
    for(BurgersCircuitSearchStruct* a = neighborStruct; ; a = a->predecessorEdge->vertex1()->burgersSearchStruct) {
        if(a->node->visited) {
            a->node->visited = false;
            break;
        }
        OVITO_ASSERT(a->predecessorEdge != nullptr);
        OVITO_ASSERT(a->predecessorEdge->circuit == nullptr);
        OVITO_ASSERT(a->predecessorEdge->oppositeEdge()->circuit == nullptr);
        // Insert edge into the circuit.
        a->predecessorEdge->nextCircuitEdge = forwardCircuit->firstEdge;
        forwardCircuit->firstEdge = a->predecessorEdge;
        forwardCircuit->edgeCount++;
        forwardCircuit->firstEdge->circuit = forwardCircuit;
    }

    // Walk along the first branch again until the second branch is hit.
    for(BurgersCircuitSearchStruct* a = currentStruct; a->node->visited == true; a = a->predecessorEdge->vertex1()->burgersSearchStruct) {
        OVITO_ASSERT(a->predecessorEdge != nullptr);
        OVITO_ASSERT(a->predecessorEdge->circuit == nullptr);
        OVITO_ASSERT(a->predecessorEdge->oppositeEdge()->circuit == nullptr);
        // Insert edge into the circuit.
        forwardCircuit->lastEdge->nextCircuitEdge = a->predecessorEdge->oppositeEdge();
        forwardCircuit->lastEdge = forwardCircuit->lastEdge->nextCircuitEdge;
        forwardCircuit->edgeCount++;
        forwardCircuit->lastEdge->circuit = forwardCircuit;
        a->node->visited = false;
    }

    // Close circuit.
    forwardCircuit->lastEdge->nextCircuitEdge = forwardCircuit->firstEdge;
    OVITO_ASSERT(forwardCircuit->firstEdge != forwardCircuit->firstEdge->nextCircuitEdge);
    OVITO_ASSERT(forwardCircuit->countEdges() == forwardCircuit->edgeCount);
    OVITO_ASSERT(forwardCircuit->edgeCount >= 3);

    // Make sure the circuit is not infinite, spanning periodic boundaries.
    // This can be checked by summing up the atom-to-atom vectors of the circuit's edges.
    // The sum should be zero for valid closed circuits.
    InterfaceMesh::Edge* e = forwardCircuit->firstEdge;
    Vector3 edgeSum = Vector3::Zero();
    Matrix3 frankRotation = Matrix3::Identity();
    Vector3 b = Vector3::Zero();
    do {
        edgeSum += e->physicalVector;
        b += frankRotation * e->clusterVector;
        if(e->clusterTransition->isSelfTransition() == false)
            frankRotation = frankRotation * e->clusterTransition->reverse->tm;
        e = e->nextCircuitEdge;
    }
    while(e != forwardCircuit->firstEdge);
    OVITO_ASSERT(frankRotation.equals(Matrix3::Identity(), CA_TRANSITION_MATRIX_EPSILON));

    // Make sure new circuit does not intersect other circuits.
    bool intersects = intersectsOtherCircuits(forwardCircuit);
    if(b.isZero(CA_LATTICE_VECTOR_EPSILON) || edgeSum.isZero(CA_ATOM_VECTOR_EPSILON) == false || intersects) {
        // Reset edges.
        InterfaceMesh::Edge* e = forwardCircuit->firstEdge;
        do {
            InterfaceMesh::Edge* nextEdge = e->nextCircuitEdge;
            OVITO_ASSERT(e->circuit == forwardCircuit);
            e->nextCircuitEdge = nullptr;
            e->circuit = nullptr;
            e = nextEdge;
        }
        while(e != forwardCircuit->firstEdge);

#ifdef OVITO_DEBUG
        for(BurgersCircuitSearchStruct* a = neighborStruct; a->predecessorEdge != nullptr; a = a->predecessorEdge->vertex1()->burgersSearchStruct) {
            OVITO_ASSERT(a->predecessorEdge->circuit == nullptr);
            OVITO_ASSERT(a->predecessorEdge->oppositeEdge()->circuit == nullptr);
        }
        for(BurgersCircuitSearchStruct* a = currentStruct; a->predecessorEdge != nullptr; a = a->predecessorEdge->vertex1()->burgersSearchStruct) {
            OVITO_ASSERT(a->predecessorEdge->circuit == nullptr);
            OVITO_ASSERT(a->predecessorEdge->oppositeEdge()->circuit == nullptr);
        }
        OVITO_ASSERT(edge->circuit == nullptr);
        OVITO_ASSERT(edge->oppositeEdge()->circuit == nullptr);
#endif

        discardCircuit(forwardCircuit);
        return intersects;
    }

    OVITO_ASSERT(!forwardCircuit->calculateBurgersVector().localVec().isZero(CA_LATTICE_VECTOR_EPSILON));
    OVITO_ASSERT(!b.isZero(CA_LATTICE_VECTOR_EPSILON));
    createAndTraceSegment(ClusterVector(b, forwardCircuit->firstEdge->clusterTransition->cluster1), forwardCircuit, maxBurgersCircuitSize);

    return true;
}

/******************************************************************************
* Creates a reverse Burgers circuit, allocates a new DislocationSegment,
* and traces it in both directions.
******************************************************************************/
void DislocationTracer::createAndTraceSegment(const ClusterVector& burgersVector, BurgersCircuit* forwardCircuit, int maxCircuitLength)
{
    // Generate the reverse circuit.
    BurgersCircuit* backwardCircuit = buildReverseCircuit(forwardCircuit);

    // Create new dislocation segment.
    DislocationSegment* segment = network()->createSegment(burgersVector);
    segment->forwardNode().circuit = forwardCircuit;
    segment->backwardNode().circuit = backwardCircuit;
    forwardCircuit->dislocationNode = &segment->forwardNode();
    backwardCircuit->dislocationNode = &segment->backwardNode();
    _danglingNodes.push_back(&segment->forwardNode());
    _danglingNodes.push_back(&segment->backwardNode());

    // Add the first point to the line.
    segment->line.push_back(backwardCircuit->calculateCenter());
    segment->coreSize.push_back(backwardCircuit->countEdges());
    // Add a second point to the line.
    appendLinePoint(segment->forwardNode());

    // Trace the segment in the forward direction.
    traceSegment(*segment, segment->forwardNode(), maxCircuitLength, true);

    // Trace the segment in the backward direction.
    traceSegment(*segment, segment->backwardNode(), maxCircuitLength, true);
}

/******************************************************************************
* Tests whether the given circuit intersection any other existing circuit.
******************************************************************************/
bool DislocationTracer::intersectsOtherCircuits(BurgersCircuit* circuit)
{
    InterfaceMesh::Edge* edge1 = circuit->firstEdge;
    do {
        InterfaceMesh::Edge* edge2 = edge1->nextCircuitEdge;
        if(edge1 != edge2->oppositeEdge()) {
            InterfaceMesh::Edge* currentEdge = edge1->oppositeEdge();
            do {
                InterfaceMesh::Edge* nextEdge = currentEdge->prevFaceEdge();
                OVITO_ASSERT(nextEdge != edge2);
                if(nextEdge != edge2 && nextEdge->circuit != nullptr) {
                    OVITO_ASSERT(nextEdge->circuit == nextEdge->nextCircuitEdge->circuit);
                    int goingOutside = 0, goingInside = 0;
                    circuitCircuitIntersection(edge2->oppositeEdge(), edge1->oppositeEdge(), nextEdge, nextEdge->nextCircuitEdge, goingOutside, goingInside);
                    OVITO_ASSERT(goingInside == 0);
                    if(goingOutside)
                        return true;
                }
                currentEdge = nextEdge->oppositeEdge();
            }
            while(currentEdge != edge2);
        }
        edge1 = edge2;
    }
    while(edge1 != circuit->firstEdge);
    return false;
}

/******************************************************************************
* Given some Burgers circuit, this function generates a reverse circuit.
******************************************************************************/
BurgersCircuit* DislocationTracer::buildReverseCircuit(BurgersCircuit* forwardCircuit)
{
    BurgersCircuit* backwardCircuit = allocateCircuit();

    // Build the backward circuit along inner outline.
    backwardCircuit->edgeCount = 0;
    backwardCircuit->firstEdge = nullptr;
    backwardCircuit->lastEdge = nullptr;
    InterfaceMesh::Edge* edge1 = forwardCircuit->firstEdge;
    do {
        InterfaceMesh::Edge* edge2 = edge1->nextCircuitEdge;
        InterfaceMesh::Edge* oppositeEdge1 = edge1->oppositeEdge();
        InterfaceMesh::Edge* oppositeEdge2 = edge2->oppositeEdge();
        InterfaceMesh::Face* facet1 = oppositeEdge1->face();
        InterfaceMesh::Face* facet2 = oppositeEdge2->face();
        OVITO_ASSERT(facet1 != nullptr && facet2 != nullptr);
        OVITO_ASSERT(facet1->circuit == nullptr || facet1->circuit == backwardCircuit);
        OVITO_ASSERT(facet2->circuit == nullptr || facet2->circuit == backwardCircuit);
        OVITO_ASSERT(edge1->vertex2() == edge2->vertex1());
        OVITO_ASSERT((edge1->clusterVector + oppositeEdge1->clusterTransition->tm * oppositeEdge1->clusterVector).isZero(CA_LATTICE_VECTOR_EPSILON));
        OVITO_ASSERT((edge2->clusterVector + oppositeEdge2->clusterTransition->tm * oppositeEdge2->clusterVector).isZero(CA_LATTICE_VECTOR_EPSILON));

        if(facet1 != facet2) {
            InterfaceMesh::Edge* outerEdge1 = oppositeEdge1->nextFaceEdge()->oppositeEdge();
            InterfaceMesh::Edge* innerEdge1 = oppositeEdge1->prevFaceEdge()->oppositeEdge();
            InterfaceMesh::Edge* outerEdge2 = oppositeEdge2->prevFaceEdge()->oppositeEdge();
            InterfaceMesh::Edge* innerEdge2 = oppositeEdge2->nextFaceEdge()->oppositeEdge();
            OVITO_ASSERT(innerEdge1 != nullptr && innerEdge2 != nullptr);
            OVITO_ASSERT(innerEdge1->vertex1() == edge1->vertex2());
            OVITO_ASSERT(innerEdge2->vertex2() == edge1->vertex2());
            OVITO_ASSERT(innerEdge1->vertex1() == innerEdge2->vertex2());
            OVITO_ASSERT(innerEdge1->circuit == nullptr || innerEdge1->circuit == backwardCircuit);
            OVITO_ASSERT(innerEdge2->circuit == nullptr || innerEdge2->circuit == backwardCircuit);
            facet1->setFlag(1);
            facet1->circuit = backwardCircuit;
            facet2->setFlag(1);
            facet2->circuit = backwardCircuit;
            innerEdge1->circuit = backwardCircuit;
            innerEdge2->circuit = backwardCircuit;
            innerEdge2->nextCircuitEdge = innerEdge1;
            if(backwardCircuit->lastEdge == nullptr) {
                OVITO_ASSERT(backwardCircuit->firstEdge == nullptr);
                OVITO_ASSERT(innerEdge1->nextCircuitEdge == nullptr);
                backwardCircuit->lastEdge = innerEdge1;
                backwardCircuit->firstEdge = innerEdge2;
                backwardCircuit->edgeCount += 2;
            }
            else if(backwardCircuit->lastEdge != innerEdge2) {
                if(innerEdge1 != backwardCircuit->firstEdge) {
                    innerEdge1->nextCircuitEdge = backwardCircuit->firstEdge;
                    backwardCircuit->edgeCount += 2;
                }
                else {
                    backwardCircuit->edgeCount += 1;
                }
                backwardCircuit->firstEdge = innerEdge2;
            }
            else if(backwardCircuit->firstEdge != innerEdge1) {
                innerEdge1->nextCircuitEdge = backwardCircuit->firstEdge;
                backwardCircuit->firstEdge = innerEdge1;
                backwardCircuit->edgeCount += 1;
            }
            OVITO_ASSERT(innerEdge1->vertex1() != innerEdge1->vertex2());
            OVITO_ASSERT(innerEdge2->vertex1() != innerEdge2->vertex2());
        }

        edge1 = edge2;
    }
    while(edge1 != forwardCircuit->firstEdge);
    OVITO_ASSERT(backwardCircuit->lastEdge->vertex2() == backwardCircuit->firstEdge->vertex1());
    OVITO_ASSERT(backwardCircuit->lastEdge->nextCircuitEdge == nullptr || backwardCircuit->lastEdge->nextCircuitEdge == backwardCircuit->firstEdge);

    // Close circuit.
    backwardCircuit->lastEdge->nextCircuitEdge = backwardCircuit->firstEdge;

    OVITO_ASSERT(backwardCircuit->firstEdge != backwardCircuit->firstEdge->nextCircuitEdge);
    OVITO_ASSERT(backwardCircuit->countEdges() == backwardCircuit->edgeCount);
    OVITO_ASSERT(backwardCircuit->edgeCount >= 3);
    OVITO_ASSERT(!backwardCircuit->calculateBurgersVector().localVec().isZero(CA_LATTICE_VECTOR_EPSILON));

    return backwardCircuit;
}

/******************************************************************************
* Traces a dislocation segment in one direction by advancing the Burgers circuit.
******************************************************************************/
void DislocationTracer::traceSegment(DislocationSegment& segment, DislocationNode& node, int maxCircuitLength, bool isPrimarySegment)
{
    BurgersCircuit& circuit = *node.circuit;
    OVITO_ASSERT(circuit.countEdges() == circuit.edgeCount);
    OVITO_ASSERT(circuit.isDangling);

    // Advance circuit as far as possible.
    for(;;) {

        // During each iteration, first shorten circuit as much as possible.
        // Pick a random start edge to distribute the removal of edges
        // over the whole circuit.
        int edgeIndex = std::uniform_int_distribution<>(0, circuit.edgeCount - 1)(_rng);
        InterfaceMesh::Edge* firstEdge = circuit.getEdge(edgeIndex);

        InterfaceMesh::Edge* edge0 = firstEdge;
        InterfaceMesh::Edge* edge1 = edge0->nextCircuitEdge;
        InterfaceMesh::Edge* edge2 = edge1->nextCircuitEdge;
        OVITO_ASSERT(edge1->circuit == &circuit);
        int counter = 0;
        do {
            // Check Burgers circuit.
            OVITO_ASSERT(circuit.edgeCount >= 3);
            OVITO_ASSERT(!circuit.calculateBurgersVector().localVec().isZero(CA_LATTICE_VECTOR_EPSILON));
            OVITO_ASSERT(circuit.countEdges() == circuit.edgeCount);
            OVITO_ASSERT(edge0->circuit == &circuit && edge1->circuit == &circuit && edge2->circuit == &circuit);

            bool wasShortened = false;
            if(tryRemoveTwoCircuitEdges(edge0, edge1, edge2))
                wasShortened = true;
            else if(tryRemoveThreeCircuitEdges(edge0, edge1, edge2, isPrimarySegment))
                wasShortened = true;
            else if(tryRemoveOneCircuitEdge(edge0, edge1, edge2, isPrimarySegment))
                wasShortened = true;
            else if(trySweepTwoFacets(edge0, edge1, edge2, isPrimarySegment))
                wasShortened = true;

            if(wasShortened) {
                appendLinePoint(node);
                counter = -1;
            }

            edge0 = edge1;
            edge1 = edge2;
            edge2 = edge2->nextCircuitEdge;
            counter++;
        }
        while(counter <= circuit.edgeCount);
        OVITO_ASSERT(circuit.edgeCount >= 3);
        OVITO_ASSERT(circuit.countEdges() == circuit.edgeCount);

        if(circuit.edgeCount >= maxCircuitLength)
            break;

        // In the second step, extend circuit by inserting an edge if possible.
        bool wasExtended = false;

        // Pick a random start edge to distribute the insertion of new edges
        // over the whole circuit.
        edgeIndex = std::uniform_int_distribution<>(0, circuit.edgeCount - 1)(_rng);
        firstEdge = circuit.getEdge(edgeIndex);

        edge0 = firstEdge;
        edge1 = firstEdge->nextCircuitEdge;
        do {
            if(tryInsertOneCircuitEdge(edge0, edge1, isPrimarySegment)) {
                wasExtended = true;
                appendLinePoint(node);
                break;
            }

            edge0 = edge1;
            edge1 = edge1->nextCircuitEdge;
        }
        while(edge0 != firstEdge);
        if(!wasExtended) break;
    }
}

/******************************************************************************
* Eliminates two edges from a Burgers circuit if they are opposite halfedges.
******************************************************************************/
bool DislocationTracer::tryRemoveTwoCircuitEdges(InterfaceMesh::Edge*& edge0, InterfaceMesh::Edge*& edge1, InterfaceMesh::Edge*& edge2)
{
    if(edge1 != edge2->oppositeEdge())
        return false;

    BurgersCircuit* circuit = edge0->circuit;
    OVITO_ASSERT(circuit->edgeCount >= 4);
    edge0->nextCircuitEdge = edge2->nextCircuitEdge;
    if(edge0 == circuit->lastEdge) {
        circuit->firstEdge = circuit->lastEdge->nextCircuitEdge;
    }
    else if(edge1 == circuit->lastEdge) {
        circuit->lastEdge = edge0;
        circuit->firstEdge = edge0->nextCircuitEdge;
    }
    else if(edge2 == circuit->lastEdge) {
        circuit->lastEdge = edge0;
    }
    circuit->edgeCount -= 2;
    OVITO_ASSERT(circuit->edgeCount >= 0);

    edge1 = edge0->nextCircuitEdge;
    edge2 = edge1->nextCircuitEdge;
    return true;
}

/******************************************************************************
* Eliminates three edges from a Burgers circuit if they border a triangle.
******************************************************************************/
bool DislocationTracer::tryRemoveThreeCircuitEdges(InterfaceMesh::Edge*& edge0, InterfaceMesh::Edge*& edge1, InterfaceMesh::Edge*& edge2, bool isPrimarySegment)
{
    InterfaceMesh::Face* facet1 = edge1->face();
    InterfaceMesh::Face* facet2 = edge2->face();

    if(facet2 != facet1 || facet1->circuit != nullptr)
        return false;

    BurgersCircuit* circuit = edge0->circuit;
    OVITO_ASSERT(circuit->edgeCount > 2);
    InterfaceMesh::Edge* edge3 = edge2->nextCircuitEdge;

    if(edge3->face() != facet1) return false;
    OVITO_ASSERT(circuit->edgeCount > 4);

    edge0->nextCircuitEdge = edge3->nextCircuitEdge;

    if(edge2 == circuit->firstEdge || edge3 == circuit->firstEdge) {
        circuit->firstEdge = edge3->nextCircuitEdge;
        circuit->lastEdge = edge0;
    }
    else if(edge1 == circuit->firstEdge) {
        circuit->firstEdge = edge3->nextCircuitEdge;
        OVITO_ASSERT(circuit->lastEdge == edge0);
    }
    else if(edge3 == circuit->lastEdge) {
        circuit->lastEdge = edge0;
    }
    circuit->edgeCount -= 3;
    edge1 = edge3->nextCircuitEdge;
    edge2 = edge1->nextCircuitEdge;

    facet1->circuit = circuit;
    if(isPrimarySegment)
        facet1->setFlag(1);

    return true;
}

/******************************************************************************
* Eliminates one edge from a Burgers circuit by replacing two edges with one.
******************************************************************************/
bool DislocationTracer::tryRemoveOneCircuitEdge(InterfaceMesh::Edge*& edge0, InterfaceMesh::Edge*& edge1, InterfaceMesh::Edge*& edge2, bool isPrimarySegment)
{
    InterfaceMesh::Face* facet1 = edge1->face();
    InterfaceMesh::Face* facet2 = edge2->face();
    if(facet2 != facet1 || facet1->circuit != nullptr) return false;

    BurgersCircuit* circuit = edge0->circuit;
    OVITO_ASSERT(circuit->edgeCount > 2);

    if(edge0->face() == facet1) return false;

    InterfaceMesh::Edge* shortEdge = edge1->prevFaceEdge()->oppositeEdge();
    OVITO_ASSERT(shortEdge->vertex1() == edge1->vertex1());
    OVITO_ASSERT(shortEdge->vertex2() == edge2->vertex2());

    if(shortEdge->circuit != nullptr)
        return false;

    OVITO_ASSERT(shortEdge->nextCircuitEdge == nullptr);
    shortEdge->nextCircuitEdge = edge2->nextCircuitEdge;
    OVITO_ASSERT(shortEdge != edge2->nextCircuitEdge->oppositeEdge());
    OVITO_ASSERT(shortEdge != edge0->oppositeEdge());
    edge0->nextCircuitEdge = shortEdge;
    if(edge0 == circuit->lastEdge) {
        OVITO_ASSERT(circuit->lastEdge != edge2);
        OVITO_ASSERT(circuit->firstEdge == edge1);
        OVITO_ASSERT(shortEdge != circuit->lastEdge->oppositeEdge());
        circuit->firstEdge = shortEdge;
    }

    if(edge2 == circuit->lastEdge) {
        circuit->lastEdge = shortEdge;
    }
    else if(edge2 == circuit->firstEdge) {
        circuit->firstEdge = shortEdge->nextCircuitEdge;
        circuit->lastEdge = shortEdge;
    }
    circuit->edgeCount -= 1;
    edge1 = shortEdge;
    edge2 = shortEdge->nextCircuitEdge;
    shortEdge->circuit = circuit;

    facet1->circuit = circuit;
    if(isPrimarySegment)
        facet1->setFlag(1);

    return true;
}

/******************************************************************************
* Advances a Burgers circuit by skipping two facets.
******************************************************************************/
bool DislocationTracer::trySweepTwoFacets(InterfaceMesh::Edge*& edge0, InterfaceMesh::Edge*& edge1, InterfaceMesh::Edge*& edge2, bool isPrimarySegment)
{
    InterfaceMesh::Face* facet1 = edge1->face();
    InterfaceMesh::Face* facet2 = edge2->face();

    if(facet1->circuit != nullptr || facet2->circuit != nullptr) return false;

    BurgersCircuit* circuit = edge0->circuit;
    if(facet1 == facet2 || circuit->edgeCount <= 2) return false;

    InterfaceMesh::Edge* outerEdge1 = edge1->prevFaceEdge()->oppositeEdge();
    InterfaceMesh::Edge* innerEdge1 = edge1->nextFaceEdge();
    InterfaceMesh::Edge* outerEdge2 = edge2->nextFaceEdge()->oppositeEdge();
    InterfaceMesh::Edge* innerEdge2 = edge2->prevFaceEdge();

    if(innerEdge1 != innerEdge2->oppositeEdge() || outerEdge1->circuit != nullptr || outerEdge2->circuit != nullptr)
        return false;

    OVITO_ASSERT(outerEdge1->nextCircuitEdge == nullptr);
    OVITO_ASSERT(outerEdge2->nextCircuitEdge == nullptr);
    outerEdge1->nextCircuitEdge = outerEdge2;
    outerEdge2->nextCircuitEdge = edge2->nextCircuitEdge;
    edge0->nextCircuitEdge = outerEdge1;
    if(edge0 == circuit->lastEdge) {
        circuit->firstEdge = outerEdge1;
    }
    else if(edge1 == circuit->lastEdge) {
        circuit->lastEdge = outerEdge1;
        circuit->firstEdge = outerEdge2;
    }
    else if(edge2 == circuit->lastEdge) {
        circuit->lastEdge = outerEdge2;
    }
    outerEdge1->circuit = circuit;
    outerEdge2->circuit = circuit;

    facet1->circuit = circuit;
    facet2->circuit = circuit;
    if(isPrimarySegment) {
        facet1->setFlag(1);
        facet2->setFlag(1);
    }

    edge0 = outerEdge1;
    edge1 = outerEdge2;
    edge2 = edge1->nextCircuitEdge;

    return true;
}

/******************************************************************************
* Advances a Burgers circuit by skipping one facet and inserting an
* additional edge.
******************************************************************************/
bool DislocationTracer::tryInsertOneCircuitEdge(InterfaceMesh::Edge*& edge0, InterfaceMesh::Edge*& edge1, bool isPrimarySegment)
{
    OVITO_ASSERT(edge0 != edge1->oppositeEdge());

    InterfaceMesh::Face* facet = edge1->face();
    if(facet->circuit != nullptr)
        return false;

    InterfaceMesh::Edge* insertEdge1 = edge1->prevFaceEdge()->oppositeEdge();
    if(insertEdge1->circuit != nullptr)
        return false;

    InterfaceMesh::Edge* insertEdge2 = edge1->nextFaceEdge()->oppositeEdge();
    if(insertEdge2->circuit != nullptr)
        return false;

    OVITO_ASSERT(insertEdge1->nextCircuitEdge == nullptr);
    OVITO_ASSERT(insertEdge2->nextCircuitEdge == nullptr);
    BurgersCircuit* circuit = edge0->circuit;
    insertEdge1->nextCircuitEdge = insertEdge2;
    insertEdge2->nextCircuitEdge = edge1->nextCircuitEdge;
    edge0->nextCircuitEdge = insertEdge1;
    if(edge0 == circuit->lastEdge) {
        circuit->firstEdge = insertEdge1;
    }
    else if(edge1 == circuit->lastEdge) {
        circuit->lastEdge = insertEdge2;
    }
    insertEdge1->circuit = circuit;
    insertEdge2->circuit = circuit;
    circuit->edgeCount++;

    // Check Burgers circuit.
    OVITO_ASSERT(circuit->countEdges() == circuit->edgeCount);

    facet->circuit = circuit;
    if(isPrimarySegment)
        facet->setFlag(1);

    return true;
}

/******************************************************************************
* Appends another point to the curve at one end of a dislocation segment.
******************************************************************************/
void DislocationTracer::appendLinePoint(DislocationNode& node)
{
    DislocationSegment& segment = *node.segment;
    OVITO_ASSERT(!segment.line.empty());

    // Get size of dislocation core.
    int coreSize = node.circuit->edgeCount;

    // Make sure the line is not wrapped at periodic boundaries.
    const Point3& lastPoint = node.isForwardNode() ? segment.line.back() : segment.line.front();
    Point3 newPoint = lastPoint + mesh().wrapVector(node.circuit->calculateCenter() - lastPoint);

    if(node.isForwardNode()) {
        // Add a new point to end the line.
        segment.line.push_back(newPoint);
        segment.coreSize.push_back(coreSize);
    }
    else {
        // Add a new point to start the line.
        segment.line.push_front(newPoint);
        segment.coreSize.push_front(coreSize);
    }
    node.circuit->numPreliminaryPoints++;
}

/******************************************************************************
* Determines whether two Burgers circuits intersect.
******************************************************************************/
void DislocationTracer::circuitCircuitIntersection(InterfaceMesh::Edge* circuitAEdge1, InterfaceMesh::Edge* circuitAEdge2, InterfaceMesh::Edge* circuitBEdge1, InterfaceMesh::Edge* circuitBEdge2, int& goingOutside, int& goingInside)
{
    OVITO_ASSERT(circuitAEdge2->vertex1() == circuitBEdge2->vertex1());
    OVITO_ASSERT(circuitAEdge1->vertex2() == circuitBEdge2->vertex1());
    OVITO_ASSERT(circuitBEdge1->vertex2() == circuitBEdge2->vertex1());

    // Iterate over interior facet edges.
    InterfaceMesh::Edge* edge = circuitBEdge2;
    bool contour1inside = false;
    bool contour2inside = false;
    for(;;) {
        InterfaceMesh::Edge* oppositeEdge = edge->oppositeEdge();
        if(oppositeEdge == circuitBEdge1) break;
        if(edge != circuitBEdge2) {
            if(oppositeEdge == circuitAEdge1) contour1inside = true;
            if(edge == circuitAEdge2) contour2inside = true;
        }
        edge = oppositeEdge->nextFaceEdge();
        OVITO_ASSERT(edge->vertex1() == circuitBEdge2->vertex1());
        OVITO_ASSERT(edge != circuitBEdge2);
    }
    OVITO_ASSERT(circuitAEdge2 != circuitBEdge2 || contour2inside == false);

    // Iterate over exterior facet edges.
    bool contour1outside = false;
    bool contour2outside = false;
    edge = circuitBEdge1;
    for(;;) {
        InterfaceMesh::Edge* nextEdge = edge->nextFaceEdge();
        if(nextEdge == circuitBEdge2) break;
        InterfaceMesh::Edge* oppositeEdge = nextEdge->oppositeEdge();
        OVITO_ASSERT(oppositeEdge->vertex2() == circuitBEdge2->vertex1());
        edge = oppositeEdge;
        if(edge == circuitAEdge1) contour1outside = true;
        if(nextEdge == circuitAEdge2) contour2outside = true;
    }

    OVITO_ASSERT(contour1outside == false || contour1inside == false);
    OVITO_ASSERT(contour2outside == false || contour2inside == false);

    if(contour2outside == true && contour1outside == false) {
        goingOutside += 1;
    }
    else if(contour2inside == true && contour1inside == false) {
        goingInside += 1;
    }
}

/******************************************************************************
* Look for dislocation segments whose circuits touch each other.
******************************************************************************/
size_t DislocationTracer::joinSegments(int maxCircuitLength)
{
    // First iteration over all dangling circuits.
    // Try to create secondary dislocation segments in the adjacent regions of the
    // interface mesh.
    for(size_t nodeIndex = 0; nodeIndex < danglingNodes().size(); nodeIndex++) {
        DislocationNode* node = danglingNodes()[nodeIndex];
        BurgersCircuit* circuit = node->circuit;
        OVITO_ASSERT(circuit->isDangling);

        // Go around the circuit to find an unvisited region on the interface mesh.
        InterfaceMesh::Edge* edge = circuit->firstEdge;
        do {
            OVITO_ASSERT(edge->circuit == circuit);
            BurgersCircuit* oppositeCircuit = edge->oppositeEdge()->circuit;
            if(oppositeCircuit == nullptr) {
                OVITO_ASSERT(edge->oppositeEdge()->nextCircuitEdge == nullptr);

                // Try to create a new circuit inside the unvisited region.
                createSecondarySegment(edge, circuit, maxCircuitLength);

                // Skip edges to the end of the unvisited interval.
                while(edge->oppositeEdge()->circuit == nullptr && edge != circuit->firstEdge)
                    edge = edge->nextCircuitEdge;
            }
            else edge = edge->nextCircuitEdge;
        }
        while(edge != circuit->firstEdge);
    }

    // Second pass over all dangling nodes.
    // Mark circuits that are completely blocked by other circuits.
    // They are candidates for the formation of junctions.
    for(DislocationNode* node : danglingNodes()) {
        BurgersCircuit* circuit = node->circuit;
        OVITO_ASSERT(circuit->isDangling);

        // Go around the circuit to see whether it is completely surrounded by other circuits.
        // Put it into one ring with the adjacent circuits.
        circuit->isCompletelyBlocked = true;
        InterfaceMesh::Edge* edge = circuit->firstEdge;
        do {
            OVITO_ASSERT(edge->circuit == circuit);
            BurgersCircuit* adjacentCircuit = edge->oppositeEdge()->circuit;
            if(adjacentCircuit == nullptr) {
                // Found a section of the circuit, which is not blocked
                // by some other circuit.
                circuit->isCompletelyBlocked = false;
                break;
            }
            else if(adjacentCircuit != circuit) {
                OVITO_ASSERT(adjacentCircuit->isDangling);
                DislocationNode* adjacentNode = adjacentCircuit->dislocationNode;
                if(node->formsJunctionWith(adjacentNode) == false) {
                    node->connectNodes(adjacentNode);
                }
            }
            edge = edge->nextCircuitEdge;
        }
        while(edge != circuit->firstEdge);
    }

    // Count number of created dislocation junctions.
    size_t numJunctions = 0;

    // Actually create junctions for completely blocked circuits.
    for(DislocationNode* node : danglingNodes()) {
        BurgersCircuit* circuit = node->circuit;

        // Skip circuits which have already become part of a junction.
        if(circuit->isDangling == false) continue;
        // Skip dangling circuits, which are not completely blocked by other circuits;
        if(!circuit->isCompletelyBlocked) {
            node->dissolveJunction();
            continue;
        }
        // Junctions must consist of at least two dislocation segments.
        if(node->junctionRing == node) continue;

        OVITO_ASSERT(node->segment->replacedWith == nullptr);

        // Compute center of mass of junction node.
        Vector3 centerOfMassVector = Vector3::Zero();
        Point3 basePoint = node->position();
        int armCount = 1;
        bool allCircuitsCompletelyBlocked = true;
        DislocationNode* armNode = node->junctionRing;
        while(armNode != node) {
            OVITO_ASSERT(armNode->segment->replacedWith == nullptr);
            OVITO_ASSERT(armNode->circuit->isDangling);
            if(armNode->circuit->isCompletelyBlocked == false) {
                allCircuitsCompletelyBlocked = false;
                break;
            }
            armCount++;
            centerOfMassVector += mesh().wrapVector(armNode->position() - basePoint);
            armNode = armNode->junctionRing;
        }

        // All circuits of the junction must be fully blocked by other circuits.
        if(!allCircuitsCompletelyBlocked) {
            node->dissolveJunction();
            continue;
        }

        // Junctions must consist of at least two dislocation segments.
        OVITO_ASSERT(armCount >= 2);

        // Only create a real junction for three or more segments.
        if(armCount >= 3) {
            Point3 centerOfMass = basePoint + centerOfMassVector / armCount;

            // Iterate over all arms of the new junction.
            armNode = node;
            do {
                // Mark this node as no longer dangling.
                armNode->circuit->isDangling = false;
                OVITO_ASSERT(armNode != armNode->junctionRing);

                // Extend arm to junction's exact center point.
                std::deque<Point3>& line = armNode->segment->line;
                if(armNode->isForwardNode()) {
                    line.push_back(line.back() + mesh().wrapVector(centerOfMass - line.back()));
                    armNode->segment->coreSize.push_back(armNode->segment->coreSize.back());
                }
                else {
                    line.push_front(line.front() + mesh().wrapVector(centerOfMass - line.front()));
                    armNode->segment->coreSize.push_front(armNode->segment->coreSize.front());
                }
                armNode->circuit->numPreliminaryPoints = 0;
                armNode = armNode->junctionRing;
            }
            while(armNode != node);
            numJunctions++;
        }
        else {
            // For a two-armed junction, just merge the two segments into one.
            DislocationNode* node1 = node;
            DislocationNode* node2 = node->junctionRing;
            OVITO_ASSERT(node1 != node2);
            OVITO_ASSERT(node2->junctionRing == node1);
            OVITO_ASSERT(node1->junctionRing == node2);

            BurgersCircuit* circuit1 = node1->circuit;
            BurgersCircuit* circuit2 = node2->circuit;
            circuit1->isDangling = false;
            circuit2->isDangling = false;
            circuit1->numPreliminaryPoints = 0;
            circuit2->numPreliminaryPoints = 0;

            // Check if this is a closed dislocation loop.
            if(node1->oppositeNode == node2) {
                OVITO_ASSERT(node1->segment == node2->segment);
                DislocationSegment* loop = node1->segment;
                OVITO_ASSERT(loop->isClosedLoop());

                // Make both ends of the segment coincide by adding an extra point if necessary.
                if(!mesh().wrapVector(node1->position() - node2->position()).isZero(CA_ATOM_VECTOR_EPSILON)) {
                    loop->line.push_back(loop->line.back() + mesh().wrapVector(loop->line.front() - loop->line.back()));
                    OVITO_ASSERT(mesh().wrapVector(node1->position() - node2->position()).isZero(CA_ATOM_VECTOR_EPSILON));
                    loop->coreSize.push_back(loop->coreSize.back());
                }

                // Loop segment should not be degenerate.
                OVITO_ASSERT(loop->line.size() >= 3);
            }
            else {
                // If not a closed loop, merge the two segments into a single line.

                OVITO_ASSERT(node1->segment != node2->segment);

                DislocationNode* farEnd1 = node1->oppositeNode;
                DislocationNode* farEnd2 = node2->oppositeNode;
                DislocationSegment* segment1 = node1->segment;
                DislocationSegment* segment2 = node2->segment;
                if(node1->isBackwardNode()) {
                    segment1->nodes[1] = farEnd2;
                    Vector3 shiftVector;
                    if(node2->isBackwardNode()) {
                        shiftVector = calculateShiftVector(segment1->line.front(), segment2->line.front());
                        segment1->line.insert(segment1->line.begin(), segment2->line.rbegin(), segment2->line.rend() - 1);
                        segment1->coreSize.insert(segment1->coreSize.begin(), segment2->coreSize.rbegin(), segment2->coreSize.rend() - 1);
                    }
                    else {
                        shiftVector = calculateShiftVector(segment1->line.front(), segment2->line.back());
                        segment1->line.insert(segment1->line.begin(), segment2->line.begin(), segment2->line.end() - 1);
                        segment1->coreSize.insert(segment1->coreSize.begin(), segment2->coreSize.begin(), segment2->coreSize.end() - 1);
                    }
                    if(shiftVector != Vector3::Zero()) {
                        for(auto p = segment1->line.begin(); p != segment1->line.begin() + segment2->line.size() - 1; ++p)
                            *p -= shiftVector;
                    }
                }
                else {
                    segment1->nodes[0] = farEnd2;
                    Vector3 shiftVector;
                    if(node2->isBackwardNode()) {
                        shiftVector = calculateShiftVector(segment1->line.back(), segment2->line.front());
                        segment1->line.insert(segment1->line.end(), segment2->line.begin() + 1, segment2->line.end());
                        segment1->coreSize.insert(segment1->coreSize.end(), segment2->coreSize.begin() + 1, segment2->coreSize.end());
                    }
                    else {
                        shiftVector = calculateShiftVector(segment1->line.back(), segment2->line.back());
                        segment1->line.insert(segment1->line.end(), segment2->line.rbegin() + 1, segment2->line.rend());
                        segment1->coreSize.insert(segment1->coreSize.end(), segment2->coreSize.rbegin() + 1, segment2->coreSize.rend());
                    }
                    if(shiftVector != Vector3::Zero()) {
                        for(auto p = segment1->line.end() - segment2->line.size() + 1; p != segment1->line.end(); ++p)
                            *p -= shiftVector;
                    }
                }
                farEnd2->segment = segment1;
                farEnd2->oppositeNode = farEnd1;
                farEnd1->oppositeNode = farEnd2;
                node1->oppositeNode = node2;
                node2->oppositeNode = node1;
                segment2->replacedWith = segment1;
                network()->discardSegment(segment2);
            }
        }
    }

    // Clean up list of dangling nodes. Remove joined nodes.
    _danglingNodes.erase(std::remove_if(_danglingNodes.begin(), _danglingNodes.end(),
            [](DislocationNode* node) { return !node->isDangling(); }), _danglingNodes.end());

    return numJunctions;
}

/******************************************************************************
* Creates a new dislocation segment at an incomplete junction.
******************************************************************************/
void DislocationTracer::createSecondarySegment(InterfaceMesh::Edge* firstEdge, BurgersCircuit* outerCircuit, int maxCircuitLength)
{
    OVITO_ASSERT(firstEdge->circuit == outerCircuit);

    // Create circuit along the border of the hole.
    int edgeCount = 1;
    Vector3 burgersVector = Vector3::Zero();
    Vector3 edgeSum = Vector3::Zero();
    Cluster* baseCluster = nullptr;
    Matrix3 frankRotation = Matrix3::Identity();
    int numCircuits = 1;
    InterfaceMesh::Edge* circuitStart = firstEdge->oppositeEdge();
    InterfaceMesh::Edge* circuitEnd = circuitStart;
    InterfaceMesh::Edge* edge = circuitStart;
    for(;;) {
        for(;;) {
            OVITO_ASSERT(edge->circuit == nullptr);
            InterfaceMesh::Edge* oppositeEdge = edge->oppositeEdge();
            InterfaceMesh::Face* oppositeFacet = oppositeEdge->face();
            InterfaceMesh::Edge* nextEdge = oppositeEdge->prevFaceEdge();
            OVITO_ASSERT(nextEdge->vertex2() == oppositeEdge->vertex1());
            OVITO_ASSERT(nextEdge->vertex2() == edge->vertex2());
            if(nextEdge->circuit != nullptr) {
                if(nextEdge->circuit != outerCircuit) {
                    outerCircuit = nextEdge->circuit;
                    numCircuits++;
                }
                edge = nextEdge->oppositeEdge();
                break;
            }
            edge = nextEdge;
        }

        circuitEnd->nextCircuitEdge = edge;
        edgeSum += edge->physicalVector;
        burgersVector += frankRotation * edge->clusterVector;
        if(!baseCluster) baseCluster = edge->clusterTransition->cluster1;
        if(!edge->clusterTransition->isSelfTransition())
            frankRotation = frankRotation * edge->clusterTransition->reverse->tm;
        if(edge == circuitStart)
            break;
        circuitEnd = edge;
        edgeCount++;

        if(edgeCount > maxCircuitLength)
            break;
    }

    // Create secondary segment only for dislocations (b != 0) and small enough dislocation cores.
    if(numCircuits == 1
            || edgeCount > maxCircuitLength
            || burgersVector.isZero(CA_LATTICE_VECTOR_EPSILON)
            || edgeSum.isZero(CA_ATOM_VECTOR_EPSILON) == false
            || !frankRotation.equals(Matrix3::Identity(), CA_TRANSITION_MATRIX_EPSILON)) {

        // Discard unused circuit.
        edge = circuitStart;
        for(;;) {
            OVITO_ASSERT(edge->circuit == nullptr);
            InterfaceMesh::Edge* nextEdge = edge->nextCircuitEdge;
            edge->nextCircuitEdge = nullptr;
            if(edge == circuitEnd) break;
            edge = nextEdge;
        }
        return;
    }
    OVITO_ASSERT(circuitStart != circuitEnd);

    // Create forward circuit.
    BurgersCircuit* forwardCircuit = allocateCircuit();
    forwardCircuit->firstEdge = circuitStart;
    forwardCircuit->lastEdge = circuitEnd;
    forwardCircuit->edgeCount = edgeCount;
    edge = circuitStart;
    do {
        OVITO_ASSERT(edge->circuit == nullptr);
        edge->circuit = forwardCircuit;
        edge = edge->nextCircuitEdge;
    }
    while(edge != circuitStart);
    OVITO_ASSERT(forwardCircuit->countEdges() == forwardCircuit->edgeCount);

    // Do all the rest.
    createAndTraceSegment(ClusterVector(burgersVector, baseCluster), forwardCircuit, maxCircuitLength);
}

}   // End of namespace
