@rdeits unfortunately rdeits/ConcreteInterfaces.jl is not working with a recent version of Julia. In any case, I have made a test with FunctionWrapper
by hand instead of using the @interface
macro, which looks very handy.
I have defined a partial interface to Shapes
with only two methods (inside and extent) to start with.
#---Define shape interfaces (aka C++ virtual table)------------------------------------------------
struct ShapeInterface{T<:AbstractFloat} <: AbstractInterface
self::AbstractShape{T}
inside::FunctionWrapper{Int64, Tuple{Point3{T}}}
extent::FunctionWrapper{Tuple{Point3{T},Point3{T}},Tuple{}}
ShapeInterface{T}(self::AbstractShape{T}) where T = new(self, (p::Point3{T} -> inside(self, p)), (()->extent(self)))
end
import Geom4hep: inside, extent
inside(i::ShapeInterface{T}, p::Point3{T}) = i.inside(p)
extent(i::ShapeInterface{T}) = i.extent()
then I define the new BooleanShape
like this:
struct NBooleanShape{T<:AbstractFloat, OP} <: AbstractShape{T}
left::ShapeInterface{T} # the mother (or left) volume A in unplaced form
right::ShapeInterface{T} # (or right) volume B in placed form, acting on A with a boolean operation
transformation::Transformation3D{T} # placement of "right" with respect of "left"
end
The NBooleanShape has good properties (type stability, no allocations at runtime, etc.) but the performance is not better. With a nested boolean shape you get a factor 2 worst performance.
with the original code (as in the head of repository)
62.709 ns (0 allocations: 0 bytes)
with the new BooleanShape
110.988 ns (0 allocations: 0 bytes)
full example
using Revise
using Geom4hep
using FunctionWrappers
import FunctionWrappers: FunctionWrapper
abstract type AbstractInterface end
#---Define shape interfaces (aka C++ virtual table)------------------------------------------------
struct ShapeInterface{T<:AbstractFloat} <: AbstractInterface
self::AbstractShape{T}
inside::FunctionWrapper{Int64, Tuple{Point3{T}}}
extent::FunctionWrapper{Tuple{Point3{T},Point3{T}},Tuple{}}
ShapeInterface{T}(self::AbstractShape{T}) where T = new(self, (p::Point3{T} -> inside(self, p)), (()->extent(self)))
end
import Geom4hep: inside, extent
inside(i::ShapeInterface{T}, p::Point3{T}) = i.inside(p)
extent(i::ShapeInterface{T}) = i.extent()
#---New definition of Boolean Shapes--------------------------------------------------------------------
struct NBooleanShape{T<:AbstractFloat, OP} <: AbstractShape{T}
left::ShapeInterface{T} # the mother (or left) volume A in unplaced form
right::ShapeInterface{T} # (or right) volume B in placed form, acting on A with a boolean operation
transformation::Transformation3D{T} # placement of "right" with respect of "left"
end
const NBooleanUnion{T} = NBooleanShape{T, :union}
const NBooleanSubtraction{T} = NBooleanShape{T, :subtraction}
const NBooleanIntersection{T} = NBooleanShape{T, :intersection}
#---Constructor--------------------------------------------------------------------------------------
function NBooleanSubtraction(left::AbstractShape{T}, right::AbstractShape{T}, place::Transformation3D{T}=one(Transformation3D{T})) where T<:AbstractFloat
NBooleanSubtraction{T}(ShapeInterface{T}(left), ShapeInterface{T}(right), place)
end
function inside(shape::NBooleanSubtraction{T}, point::Point3{T})::Int64
(; left, right, transformation) = shape
lpoint = transformation * point
positionA = inside(left, point)
positionA == kOutside && return kOutside
positionB = inside(right, lpoint)
if positionA == kInside && positionB == kOutside
return kInside;
elseif positionA == kInside && positionB == kSurface ||
positionB == kOutside && positionA == kSurface
return kSurface
else
return kOutside
end
end
function extent(shape::NBooleanSubtraction{T})::Tuple{Point3{T},Point3{T}} where {T}
extent(shape.left)
end
const T = Float64
#---New boolean instance
nboolean = NBooleanSubtraction(NBooleanSubtraction(NBooleanSubtraction(NBooleanSubtraction(Box{T}(2.035, 100.35000000000001, 0.5750000000000001),
Box{T}(0.3175, 100.35000000000001, 0.065),
Transformation3D{T}(1.7175000000000002, 0.0, 0.51)),
Box{T}(0.3175, 100.35000000000001, 0.065),
Transformation3D{T}(1.7175000000000002, 0.0, -0.51)),
Box{Float64}(0.3175, 100.35000000000001, 0.065),
Transformation3D{T}(-1.7175000000000002, 0.0, 0.51)),
Box{Float64}(0.3175, 100.35000000000001, 0.065),
Transformation3D{T}(-1.7175000000000002, 0.0, -0.51))
#---Old boolean instance
boolean = BooleanSubtraction(BooleanSubtraction(BooleanSubtraction(BooleanSubtraction(Box{T}(2.035, 100.35000000000001, 0.5750000000000001),
Box{T}(0.3175, 100.35000000000001, 0.065),
Transformation3D{T}(1.7175000000000002, 0.0, 0.51)),
Box{T}(0.3175, 100.35000000000001, 0.065),
Transformation3D{T}(1.7175000000000002, 0.0, -0.51)),
Box{Float64}(0.3175, 100.35000000000001, 0.065),
Transformation3D{T}(-1.7175000000000002, 0.0, 0.51)),
Box{Float64}(0.3175, 100.35000000000001, 0.065),
Transformation3D{T}(-1.7175000000000002, 0.0, -0.51))
point = Point3{T}(0,0,0)
# benchmaking
using BenchmarkTools
@btime inside(boolean, point)
@btime inside(nboolean, point)