通过消除重叠或“填充”缺口来“连接”角色。碰撞检测

时间:2018-07-16 08:27:41

标签: python vtk

我正在尝试使用VTK版本8.1.0和Python可视化具有空心圆柱横截面的结构框架(稍后我想对横​​截面进行泛化,但现在可以了)。免责声明,我对VTK和Python编程都是陌生的。

问题是,老实说,我的演员重叠了。如图所示 Figure 1.

我想摆脱这一点,并实际上“连接”相邻的演员。我在想,首先,我需要某种形式的碰撞检测。然后,我考虑了在另一个内的一个演员外推,然后通过使用一个希望可以从这些演员之间的交点创建的平面来“切割”剩余部分,如本示例中所述:intersection between actors.

我还认为实际上可以“挤出到表面”,但是我不知道这在VTK中是否可行,但是我正在寻找尽可能多的示例。

那么,首先,我想到的是现实的想法吗?如果挤出的东西可行,谁能给我一些入门的提示?

如果没有一个可用的想法,那么你们中的每个人是否还有另一个想法来实现这一目标?

我创建了一个最小的代码段,其中显示了我要修复的问题:

import vtk

def main():
    colors = vtk.vtkNamedColors()

    disk = vtk.vtkDiskSource()
    disk.SetCircumferentialResolution(128)
    disk.SetRadialResolution(1)
    disk.SetOuterRadius(2)
    disk.SetInnerRadius(2 - 0.1)
    extrude_disk = vtk.vtkLinearExtrusionFilter()
    extrude_disk.SetInputConnection(disk.GetOutputPort())
    extrude_disk.SetExtrusionTypeToNormalExtrusion()
    extrude_disk.SetVector(0, 0, 1)
    extrude_disk.SetScaleFactor(1)
    extrude_disk.Update()

    disk2 = vtk.vtkDiskSource()
    disk2.SetCircumferentialResolution(128)
    disk2.SetRadialResolution(1)
    disk2.SetOuterRadius(1.5)
    disk2.SetInnerRadius(1.5 - 0.1)
    extrude_disk2 = vtk.vtkLinearExtrusionFilter()
    extrude_disk2.SetInputConnection(disk2.GetOutputPort())
    extrude_disk2.SetExtrusionTypeToNormalExtrusion()
    extrude_disk2.SetVector(0, 0, 1)
    extrude_disk2.SetScaleFactor(1)
    extrude_disk2.Update()

    start_point = [0] * 3
    start_point2 = [-10, 0, 0]
    end_point = [0, 0, 10]
    end_point2 = [0, 0, 7]

    # Computing a basis
    normalized_x = [0] * 3
    normalized_x2 = [0] * 3
    normalized_y = [0] * 3
    normalized_y2 = [0] * 3
    normalized_z = [0] * 3
    normalized_z2 = [0] * 3

    # The X axis is a vector from start to end
    vtk.vtkMath.Subtract(end_point, start_point, normalized_x)
    vtk.vtkMath.Subtract(end_point2, start_point2, normalized_x2)
    length = vtk.vtkMath.Norm(normalized_x)
    length2 = vtk.vtkMath.Norm(normalized_x2)
    vtk.vtkMath.Normalize(normalized_x)
    vtk.vtkMath.Normalize(normalized_x2)

    # The Z axis is an arbitrary vector cross X
    rng = vtk.vtkMinimalStandardRandomSequence()
    rng.SetSeed(8775070)  # For testing.
    arbitrary = [0] * 3
    for i in range(0, 3):
        rng.Next()
        arbitrary[i] = rng.GetRangeValue(-10, 10)
    vtk.vtkMath.Cross(normalized_x, arbitrary, normalized_z)
    vtk.vtkMath.Cross(normalized_x2, arbitrary, normalized_z2)
    vtk.vtkMath.Normalize(normalized_z)
    vtk.vtkMath.Normalize(normalized_z2)

    # The Y axis is Z cross X
    vtk.vtkMath.Cross(normalized_z, normalized_x, normalized_y)
    vtk.vtkMath.Cross(normalized_z2, normalized_x2, normalized_y2)
    matrix = vtk.vtkMatrix4x4()
    matrix2 = vtk.vtkMatrix4x4()

    # Create the direction cosine matrix
    matrix.Identity()
    matrix2.Identity()
    for i in range(3):
        matrix.SetElement(i, 0, normalized_x[i])
        matrix2.SetElement(i, 0, normalized_x2[i])
        matrix.SetElement(i, 1, normalized_y[i])
        matrix2.SetElement(i, 1, normalized_y2[i])
        matrix.SetElement(i, 2, normalized_z[i])
        matrix2.SetElement(i, 2, normalized_z2[i])

    # Apply the transforms
    transform = vtk.vtkTransform()
    transform.Translate(start_point)  # translate to starting point
    transform.Concatenate(matrix)  # apply direction cosines
    transform.RotateY(90.0)  # align cylinder
    transform.Scale(1.0, 1.0, length)  # scale along the height vector

    transform2 = vtk.vtkTransform()
    transform2.Translate(start_point2)  # translate to starting point
    transform2.Concatenate(matrix2)  # apply direction cosines
    transform2.RotateY(90.0)  # align cylinder
    transform2.Scale(1.0, 1.0, length2)  # scale along the height vector

    # Create a mapper and actor for the disks
    mapper = vtk.vtkPolyDataMapper()
    mapper.SetInputConnection(extrude_disk.GetOutputPort())
    actor = vtk.vtkActor()
    actor.SetUserMatrix(transform.GetMatrix())
    actor.SetMapper(mapper)
    actor.GetProperty().SetColor(colors.GetColor3d("yellow"))

    mapper2 = vtk.vtkPolyDataMapper()
    mapper2.SetInputConnection(extrude_disk2.GetOutputPort())
    actor2 = vtk.vtkActor()
    actor2.SetUserMatrix(transform2.GetMatrix())
    actor2.SetMapper(mapper2)
    actor2.GetProperty().SetColor(colors.GetColor3d("yellow"))

    # Create a renderer, render window, and interactor
    renderer = vtk.vtkRenderer()
    render_window = vtk.vtkRenderWindow()
    render_window.AddRenderer(renderer)
    render_window.SetWindowName("Overlapping cylinders example")
    render_window_interactor = vtk.vtkRenderWindowInteractor()
    render_window_interactor.SetRenderWindow(render_window)

    # Add the actors to the scene
    renderer.AddActor(actor)
    renderer.AddActor(actor2)
    renderer.SetBackground(colors.GetColor3d("BkgColor"))

    # Render and interact
    render_window.Render()
    render_window_interactor.Start()

if __name__ == '__main__':
    main()

This is the end result I am looking for!,只是在没有演员相交/交叉/碰撞的情况下,我不知道我道歉的行为的正确用语,就像他们在此处的示例中所做的那样。

我希望有人对这个问题有一些想法。预先感谢!

最好的问候, 马丁

1 个答案:

答案 0 :(得分:1)

让我从一点澄清开始。 VTK中的角色是简单的容器对象,结合了几何数据和可视化属性以进行渲染。 VTK在参与者方面没有提供太多功能。如果要检测碰撞对象,则需要针对几何形状(vtkPolyData)解决此问题。

VTK中没有通用的碰撞检测或身体交叉点引擎。在VTK中,您可以执行布尔运算。但是,要实现强健性并不容易。主要有两种方法:

方法A:对网格进行布尔运算

使用[vtkBooleanOperationPolyDataFilter][1]将布尔运算直接应用于网格。有关示例,请参见here。不幸的是,由于表面的网格属性,这将失败(在RenderWindow中查看表面以检查网格的 wireframe 时,按下键W)。如果网格的三角形很小并且具有合适的条件编号,即三角形不太尖锐,则vtkBooleanOperationPolyDataFilter的效果最佳。 (有关某些三角形的度量,请参见the manual of the Verdict toolbox。)挤出磁盘的网格由非常长的尖锐三角形组成。您需要做的是首先重新整理表面。 VTK不提供开箱即用的重新整理功能,但是相关的工具箱(例如VMTK)可以提供。

正确地确定一般几何形状的方法(A)是很棘手的,因为您最终可能会遇到非流形或渗漏的表面。另外,已知vtkBooleanOperationPolyDataFilter有一些错误(请参阅herehere)。希望有一天能解决这些问题。

方法B:对隐式函数的布尔运算

第二种方法是使用隐式函数。例如,您可以将管表示为隐式圆柱,并使用[vtkImplicitBoolean][7]将其相交。有关示例,请参见here。这种方法的问题在于,您需要转换对象的隐式表示形式以产生最终的表面网格。 VTK中的行进多维数据集算法相当慢,因此您需要等待很长时间才能获得高分辨率。而且不可能保留锋利的边缘。另一方面,它更强大且更易于处理。

示例代码

下面的代码演示了两种情况。我在这里无法与您分享重新整理功能,因此只有隐式布尔型才起作用。屏幕截图显示了结果的样子。 (黄色:输入表面,红色:结果)

enter image description here

有关术语和替代问题表述的更多详细信息,请参见Ming和Gottschalk于1999年发表的"Collision detection between geometric models: a survey"

对隐式函数的布尔运算

# This code has been written by normanius under the CC BY-SA 4.0 license.
# License: https://creativecommons.org/licenses/by-sa/4.0/
# Author:  https://stackoverflow.com/users/3388962/normanius
# Date:    July 2018

import vtk
import numpy as np

def compute_transform(start, end):
    # Better compute the matrix in numpy!

    normalized_x = [0]*3
    normalized_y = [0]*3
    normalized_z = [0]*3

    # The X axis is a vector from start to end
    vtk.vtkMath.Subtract(end, start, normalized_x)
    length = vtk.vtkMath.Norm(normalized_x)
    vtk.vtkMath.Normalize(normalized_x)
    # The Z axis is an arbitrary vector cross X
    rng = vtk.vtkMinimalStandardRandomSequence()
    rng.SetSeed(8775070)  # For testing.
    arbitrary = [0]*3
    for i in range(0, 3):
        rng.Next()
        arbitrary[i] = rng.GetRangeValue(-10, 10)
    vtk.vtkMath.Cross(normalized_x, arbitrary, normalized_z)
    vtk.vtkMath.Normalize(normalized_z)
    # The Y axis is Z cross X
    vtk.vtkMath.Cross(normalized_z, normalized_x, normalized_y)
    matrix = vtk.vtkMatrix4x4()
    # Create the direction cosine matrix
    matrix.Identity()
    for i in range(3):
        matrix.SetElement(i, 0, normalized_x[i])
        matrix.SetElement(i, 1, normalized_y[i])
        matrix.SetElement(i, 2, normalized_z[i])

    transform = vtk.vtkTransform()
    transform.Translate(start)          # translate to starting point
    transform.Concatenate(matrix)       # apply direction cosines
    transform.RotateY(90.0)             # align cylinder
    # Don't scale! This changes mesh properties (e.g. aspect ratio)
    #transform.Scale(1.0, 1.0, length)   # scale along the height vector
    return transform

def transform_item(item, transform):
    transformed = vtk.vtkTransformPolyDataFilter()
    transformed.SetInputConnection(item.GetOutputPort())
    transformed.SetTransform(transform)
    transformed.Update()
    return transformed

def create_pipe(radius, thickness, height):
    # This type of pipe is not suited for remeshing, because remeshing does not
    # preserve (feature-) edges. See create_pipe2
    assert(radius>thickness)
    disk = vtk.vtkDiskSource()
    disk.SetCircumferentialResolution(128)
    disk.SetRadialResolution(1)
    disk.SetOuterRadius(radius)
    disk.SetInnerRadius(radius - thickness)
    pipe = vtk.vtkLinearExtrusionFilter()
    pipe.SetInputConnection(disk.GetOutputPort())
    pipe.SetExtrusionTypeToNormalExtrusion()
    pipe.SetVector(0, 0, 1)
    pipe.SetScaleFactor(height)
    pipe.Update()
    return pipe

def create_pipe_implicit(radius, thickness, height):
    center = np.array([0,0,0])
    axis = np.array([0,0,1])
    centerTop = center + height*axis
    centerBottom = center

    # Outer cylinder.
    outer = vtk.vtkCylinder()
    outer.SetCenter(center)
    outer.SetAxis(axis)
    outer.SetRadius(radius)
    # Inner cylinder.
    inner = vtk.vtkCylinder()
    inner.SetCenter(center)
    inner.SetAxis(axis)
    inner.SetRadius(radius-thickness)
    # Top face.
    plane1 = vtk.vtkPlane()
    plane1.SetOrigin(centerTop)
    plane1.SetNormal(np.array(outer.GetAxis()))
    # Bottom face.
    plane2 = vtk.vtkPlane()
    plane2.SetOrigin(centerBottom)
    plane2.SetNormal(-np.array(outer.GetAxis()))
    # Put things together.
    difference = vtk.vtkImplicitBoolean()
    difference.AddFunction(outer)
    difference.AddFunction(inner)
    difference.SetOperationTypeToDifference()
    intersection = vtk.vtkImplicitBoolean()
    intersection.AddFunction(difference)
    intersection.AddFunction(plane1)
    intersection.AddFunction(plane2)
    intersection.SetOperationTypeToIntersection()
    pipe = intersection
    # Also return inner and outer cylinder.
    intersection = vtk.vtkImplicitBoolean()
    intersection.AddFunction(inner)
    intersection.AddFunction(plane1)
    intersection.AddFunction(plane2)
    intersection.SetOperationTypeToIntersection()
    inner = intersection
    intersection = vtk.vtkImplicitBoolean()
    intersection.AddFunction(outer)
    intersection.AddFunction(plane1)
    intersection.AddFunction(plane2)
    intersection.SetOperationTypeToIntersection()
    outer = intersection
    return pipe, inner, outer

def add_to_renderer(renderer, item, color, opacity=1., translate=None):
    colors = vtk.vtkNamedColors()
    mapper = vtk.vtkPolyDataMapper()
    mapper.SetScalarVisibility(False)
    mapper.SetInputConnection(item.GetOutputPort())
    actor = vtk.vtkActor()
    actor.SetMapper(mapper)
    actor.GetProperty().SetColor(colors.GetColor3d(color))
    actor.GetProperty().SetOpacity(opacity)
    if translate:
        trafo = vtk.vtkTransform()
        trafo.Translate(translate)
        actor.SetUserTransform(trafo)
    renderer.AddActor(actor)
    return mapper, actor

def evaluate_implicit(implicit_function, resolution, bounds):
    sampled = vtk.vtkSampleFunction()
    sampled.SetSampleDimensions(resolution, resolution, resolution)
    sampled.SetModelBounds(bounds)
    sampled.SetImplicitFunction(implicit_function)
    iso = vtk.vtkMarchingCubes()
    iso.SetValue(0,0.)
    iso.SetInputConnection(sampled.GetOutputPort())
    iso.Update()
    return iso

def main():
    colors = vtk.vtkNamedColors()

    # Params.
    radius = 2.
    thickness = 0.5
    start_point = np.array([0] * 3)
    end_point = np.array([0, 0, 10])
    length = np.linalg.norm(start_point-end_point)

    radius2 = 2.
    thickness2 = 0.5
    start_point2 = np.array([-10, 0, 0])
    end_point2 = np.array([0, 0, 7])
    length2 = np.linalg.norm(start_point2-end_point2)

    # Compute transforms.
    transform = compute_transform(start_point, end_point)
    transform2 = compute_transform(start_point2, end_point2)

    ############################################################################
    # BOOLEAN OPERATIONS ON MESHES
    ############################################################################
    if False:
        pipe, inner, outer = create_pipe2(radius=radius, thickness=thickness, height=length)
        pipe2, inner2, outer2 = create_pipe2(radius=radius2, thickness=thickness2, height=length2)
        # Apply the transforms.
        pipe = transform_item(pipe, transform)
        inner = transform_item(inner, transform)
        outer = transform_item(outer, transform)
        pipe2 = transform_item(pipe2, transform2)
        inner2 = transform_item(inner2, transform2)
        outer2 = transform_item(outer2, transform2)

        #pipe_2m1 = boolean_combine(pipe2, pipe, 'difference')
        pipe_2m1 = boolean_combine(pipe2, pipe, 'union') # Ugly! There is a bug in vtk!
        result_bool = pipe_2m1
        #result_bool = boolean_combine(pipe, pipe_2m1, 'union')
        #result_bool = remeshSurface(result_bool, targetArea=.1, iterations=10)

        # Add items to renderer.
        renderer = vtk.vtkRenderer()
        opacity=1.0
        #add_to_renderer(renderer=renderer, item=pipe, color='yellow', opacity=opacity)
        #add_to_renderer(renderer=renderer, item=pipe2, color='yellow', opacity=opacity)
        add_to_renderer(renderer=renderer, item=result_bool, color='red')

    ############################################################################
    # IMPLICIT BOOLEAN
    ############################################################################
    else:
        # We need to know the domain where the implicit function will be
        # evaulated. There is certainly other ways to achieve this. Here,
        # we simply get the bounds from the meshes. Also, we add a margin
        # to avoid artifacts close to the domain boundary.
        pipe = create_pipe(radius=radius, thickness=thickness, height=length)
        pipe2 = create_pipe(radius=radius2, thickness=thickness2, height=length2)
        pipe = transform_item(pipe, transform)
        pipe2 = transform_item(pipe2, transform2)
        bounds = pipe.GetOutput().GetBounds()
        bounds2 = pipe2.GetOutput().GetBounds()

        def applyMargin(bounds, margin):
            extent = [ bounds[1]-bounds[0],
                       bounds[3]-bounds[2],
                       bounds[5]-bounds[4] ]
            bounds = [ bounds[0]-extent[0]*margin, bounds[1]+extent[0]*margin,
                       bounds[2]-extent[1]*margin, bounds[3]+extent[1]*margin,
                       bounds[4]-extent[2]*margin, bounds[5]+extent[2]*margin ]
            return bounds
        bounds = applyMargin(bounds, margin=0.1)
        bounds2 = applyMargin(bounds2, margin=0.1)

        # The bounds of the combined object pipe+pipe2
        boundsCombo = [min(bounds[0], bounds2[0]),
                       max(bounds[1], bounds2[1]),
                       min(bounds[2], bounds2[2]),
                       max(bounds[3], bounds2[3]),
                       min(bounds[4], bounds2[4]),
                       max(bounds[5], bounds2[5])]

        # Let's create implicit functions for the pipes.
        pipeImp, innerImp, outerImp = create_pipe_implicit(radius=radius, thickness=thickness, height=length)
        pipeImp2, innerImp2, outerImp2 = create_pipe_implicit(radius=radius2, thickness=thickness2, height=length2)
        pipeImp.SetTransform(transform.GetInverse())
        pipeImp2.SetTransform(transform2.GetInverse())
        innerImp.SetTransform(transform.GetInverse())
        innerImp2.SetTransform(transform2.GetInverse())
        outerImp.SetTransform(transform.GetInverse())
        outerImp2.SetTransform(transform2.GetInverse())

        # Apply the intersection.
        difference = vtk.vtkImplicitBoolean()
        difference.AddFunction(pipeImp2)
        difference.AddFunction(outerImp)
        difference.SetOperationTypeToDifference()
        union = vtk.vtkImplicitBoolean()
        union.AddFunction(difference)
        union.AddFunction(pipeImp)
        union.SetOperationTypeToUnion()
        # This last operation is required to "cut through" the first pipe.
        difference = vtk.vtkImplicitBoolean()
        difference.AddFunction(union)
        difference.AddFunction(innerImp2)
        difference.SetOperationTypeToDifference()

        # Convert the implicit functions into surfaces.
        pipe = evaluate_implicit(implicit_function=pipeImp,
                                 resolution=100,
                                 bounds=bounds)
        pipe2 = evaluate_implicit(implicit_function=pipeImp2,
                                  resolution=100,
                                  bounds=bounds2)
        result = evaluate_implicit(implicit_function=difference,
                                  resolution=100,
                                  bounds=boundsCombo)

        # Add items to renderer.
        renderer = vtk.vtkRenderer()
        opacity=1.
        add_to_renderer(renderer=renderer, item=pipe, color='yellow', opacity=opacity, translate=[0,5,0])
        add_to_renderer(renderer=renderer, item=pipe2, color='yellow', opacity=opacity, translate=[0,5,0])
        add_to_renderer(renderer=renderer, item=result, color='red')

    # Create a renderer, render window, and interactor.
    render_window = vtk.vtkRenderWindow()
    render_window.AddRenderer(renderer)
    render_window.SetWindowName("Overlapping cylinders example")
    render_window.SetSize(1000,1000)
    render_window_interactor = vtk.vtkRenderWindowInteractor()
    render_window_interactor.SetRenderWindow(render_window)
    # Add the actors to the scene.
    renderer.SetBackground(colors.GetColor3d("Gray"))
    # Render and interact.
    render_window.Render()
    render_window_interactor.Start()

if __name__ == '__main__':
    main()

对网格的布尔运算

# This code has been written by normanius under the CC BY-SA 4.0 license.
# License: https://creativecommons.org/licenses/by-sa/4.0/
# Author:  https://stackoverflow.com/users/3388962/normanius
# Date:    July 2018

import vtk
import numpy as np

try:
    # Remesher based on VMTK. Sorry, cannot share this with you.
    from geometry.remesher import remeshSurface
    from vtkutils.misc import extractEdges
    from geometry.capper import capSurface
except:
    remeshSurface = None
    extractEdges = None
    capSurface = None

def compute_transform(start, end):
    # Better compute the matrix in numpy!

    normalized_x = [0]*3
    normalized_y = [0]*3
    normalized_z = [0]*3

    # The X axis is a vector from start to end
    vtk.vtkMath.Subtract(end, start, normalized_x)
    length = vtk.vtkMath.Norm(normalized_x)
    vtk.vtkMath.Normalize(normalized_x)
    # The Z axis is an arbitrary vector cross X
    rng = vtk.vtkMinimalStandardRandomSequence()
    rng.SetSeed(8775070)  # For testing.
    arbitrary = [0]*3
    for i in range(0, 3):
        rng.Next()
        arbitrary[i] = rng.GetRangeValue(-10, 10)
    vtk.vtkMath.Cross(normalized_x, arbitrary, normalized_z)
    vtk.vtkMath.Normalize(normalized_z)
    # The Y axis is Z cross X
    vtk.vtkMath.Cross(normalized_z, normalized_x, normalized_y)
    matrix = vtk.vtkMatrix4x4()
    # Create the direction cosine matrix
    matrix.Identity()
    for i in range(3):
        matrix.SetElement(i, 0, normalized_x[i])
        matrix.SetElement(i, 1, normalized_y[i])
        matrix.SetElement(i, 2, normalized_z[i])

    transform = vtk.vtkTransform()
    transform.Translate(start)          # translate to starting point
    transform.Concatenate(matrix)       # apply direction cosines
    transform.RotateY(90.0)             # align cylinder
    # Don't scale! This changes mesh properties (e.g. aspect ratio)
    #transform.Scale(1.0, 1.0, length)   # scale along the height vector
    return transform

def transform_item(item, transform):
    transformed = vtk.vtkTransformPolyDataFilter()
    transformed.SetInputConnection(item.GetOutputPort())
    transformed.SetTransform(transform)
    transformed.Update()
    return transformed

def create_pipe(radius, thickness, height):
    # This type of pipe is not suited for remeshing, because remeshing does not
    # preserve (feature-) edges. See create_pipe2
    assert(radius>thickness)
    disk = vtk.vtkDiskSource()
    disk.SetCircumferentialResolution(128)
    disk.SetRadialResolution(1)
    disk.SetOuterRadius(radius)
    disk.SetInnerRadius(radius - thickness)
    pipe = vtk.vtkLinearExtrusionFilter()
    pipe.SetInputConnection(disk.GetOutputPort())
    pipe.SetExtrusionTypeToNormalExtrusion()
    pipe.SetVector(0, 0, 1)
    pipe.SetScaleFactor(height)
    pipe.Update()
    return pipe

def create_pipe2(radius, thickness, height):
    # Create pipes with decently meshed surfaces, if remeshSurface is
    # availaable.

    # Align the cylinder in the same way as create_pipe() does.
    transform = vtk.vtkTransform()
    transform.RotateX(90.0)
    transform.Translate(0,height/2,0)

    outer = vtk.vtkCylinderSource()
    outer.SetRadius(radius)
    outer.SetResolution(128)
    outer.SetHeight(height)
    outer.CappingOff()
    outer.Update()
    outer = transform_item(outer, transform)

    inner = vtk.vtkCylinderSource()
    inner.SetRadius(radius-thickness)
    inner.SetResolution(128)
    inner.SetHeight(height)
    inner.CappingOff()
    inner.Update()
    inner = transform_item(inner, transform)

    # remeshSurface, extractEdges and capSurface are not available, sorry!
    if remeshSurface:
        outer = remeshSurface(outer, targetArea=.1, iterations=10, smoothing=False)
        inner = remeshSurface(inner, targetArea=.1, iterations=10, smoothing=False)

        # So far, we have two concentric cylinders.
        # Close the upper and lower caps using vtkContourTriangulator.
        result = combine_polydata(outer, inner)
        edges1 = extractEdges(outer, mode='separated')
        edges2 = extractEdges(inner, mode='separated')
        assert(len(edges1)==len(edges2)==2)
        for i in range(2):
            edgesBottom = combine_polydata(edges1[i], edges2[i])
            bottom = vtk.vtkContourTriangulator()
            bottom.SetInputConnection(edgesBottom.GetOutputPort())
            bottom.Update()
            result = combine_polydata(result, bottom)

        # Return also the inner and outer cylinders.
        #return result, inner, outer
        inner = capSurface(inner, remesh=True, returnCaps=False)
        outer = capSurface(outer, remesh=True, returnCaps=False)
    return result, inner, outer

def clean_mesh(source):
    clean = vtk.vtkCleanPolyData()
    #clean.ConvertPolysToLinesOff()
    clean.SetInputData(source.GetOutput())
    clean.Update()
    return clean

def fill_holes(source):
    fill = vtk.vtkFillHolesFilter()
    fill.SetInputConnection(source.GetOutputPort())
    fill.SetHoleSize(100)
    fill.Update()
    return fill

def combine_polydata(source1, source2):
    if source2 is None:
        return source1
    if source1 is None:
        return source2
    combo = vtk.vtkAppendPolyData()
    combo.AddInputData(source1.GetOutput())
    combo.AddInputData(source2.GetOutput())
    combo.Update()
    return clean_mesh(combo)

def boolean_combine(source1, source2, method='union'):
    assert(method.lower() in ['union', 'or',
                              'intersection', 'and',
                              'difference', 'subtract', 'minus'])
    source1 = source1 if source1 is not None else None
    source2 = source2 if source2 is not None else None
    method = method.lower()
    if source1 is None and source2 is None:
        return None
    # vtkBooleanOperationPolyDataFilter cannot handle empty sources!
    if source2 is None or source2.GetOutput().GetNumberOfPoints() == 0:
        return source1
    if source1 is None or source1.GetOutput().GetNumberOfPoints() == 0:
        return source2
    boolean = vtk.vtkBooleanOperationPolyDataFilter()
    if method in ['union', 'or']:
        boolean.SetOperationToUnion()
    elif method in ['intersection', 'and']:
        boolean.SetOperationToIntersection()
    elif method in ['difference', 'subtract', 'minus']:
        boolean.SetOperationToDifference()
    boolean.SetInputData(0, source1.GetOutput())
    boolean.SetInputData(1, source2.GetOutput())
    boolean.Update()
    result = boolean
    if result.GetOutput().GetNumberOfPoints() == 0:
        # No intersection betweeen source1 and source2.
        if method in ['union', 'or']:
            result = combine_polydata(source1, source2)
        elif method in ['intersection', 'and']:
            result = vtk.vtkPolyData()
        elif method in ['difference', 'subtract', 'minus']:
            result = vtk.vtkPolyData()
            result.DeepCopy(source1.GetOutput())
            pt = vtk.vtkPassThroughFilter()
            pt.SetInputData(result)
            pt.Update()
            result = pt
    return clean_mesh(result)

def add_to_renderer(renderer, item, color, opacity=1., translate=None):
    colors = vtk.vtkNamedColors()
    mapper = vtk.vtkPolyDataMapper()
    mapper.SetScalarVisibility(False)
    mapper.SetInputConnection(item.GetOutputPort())
    actor = vtk.vtkActor()
    actor.SetMapper(mapper)
    actor.GetProperty().SetColor(colors.GetColor3d(color))
    actor.GetProperty().SetOpacity(opacity)
    if translate:
        trafo = vtk.vtkTransform()
        trafo.Translate(translate)
        actor.SetUserTransform(trafo)
    renderer.AddActor(actor)
    return mapper, actor

def main():
    colors = vtk.vtkNamedColors()

    # Params.
    radius = 2.
    thickness = 0.5
    start_point = np.array([0] * 3)
    end_point = np.array([0, 0, 10])
    length = np.linalg.norm(start_point-end_point)

    radius2 = 2.
    thickness2 = 0.5
    start_point2 = np.array([-10, 0, 0])
    end_point2 = np.array([0, 0, 7])
    length2 = np.linalg.norm(start_point2-end_point2)

    # Compute transforms.
    transform = compute_transform(start_point, end_point)
    transform2 = compute_transform(start_point2, end_point2)

    ############################################################################
    # BOOLEAN OPERATIONS ON MESHES
    ############################################################################
    if remeshSurface and False:
        pipe, inner, outer = create_pipe2(radius=radius,
                                          thickness=thickness,
                                          height=length)
        pipe2, inner2, outer2 = create_pipe2(radius=radius2,
                                             thickness=thickness2,
                                             height=length2)
        # Apply the transforms.
        pipe = transform_item(pipe, transform)
        inner = transform_item(inner, transform)
        outer = transform_item(outer, transform)
        pipe2 = transform_item(pipe2, transform2)
        inner2 = transform_item(inner2, transform2)
        outer2 = transform_item(outer2, transform2)

        # Ugly! There is a bug in vtk!
        result_bool = boolean_combine(pipe2, pipe, 'union')
    else:
        pipe = create_pipe(radius=radius, thickness=thickness, height=length)
        pipe2 = create_pipe(radius=radius2, thickness=thickness2, height=length2)
        pipe = transform_item(pipe, transform)
        pipe2 = transform_item(pipe2, transform2)

        # A warning is printed: "No Intersection between objects"...
        # This has something to do with the mesh properties.
        result_bool = boolean_combine(pipe2, pipe, 'difference')

    # Add items to renderer.
    renderer = vtk.vtkRenderer()
    opacity=1.0
    add_to_renderer(renderer=renderer, item=pipe, color='yellow', opacity=opacity, translate=[0,5,0])
    add_to_renderer(renderer=renderer, item=pipe2, color='yellow', opacity=opacity, translate=[0,5,0])
    add_to_renderer(renderer=renderer, item=result_bool, color='red')

    # Create a renderer, render window, and interactor.
    render_window = vtk.vtkRenderWindow()
    render_window.AddRenderer(renderer)
    render_window.SetWindowName("Overlapping cylinders example")
    render_window.SetSize(1000,1000)
    render_window_interactor = vtk.vtkRenderWindowInteractor()
    render_window_interactor.SetRenderWindow(render_window)
    # Add the actors to the scene.
    renderer.SetBackground(colors.GetColor3d("Gray"))
    # Render and interact.
    render_window.Render()
    render_window_interactor.Start()

if __name__ == '__main__':
    main()