# How to broadcast a scalar multiplication through a Mesh?

**URL:** <https://discourse.julialang.org/t/how-to-broadcast-a-scalar-multiplication-through-a-mesh/93038>\
**Category:** General Usage\
**Tags:** broadcast, type, mutable-structure, staticarrays\
**Created:** [January 16, 2023, 6:46pm UTC](https://discourse.julialang.org/t/how-to-broadcast-a-scalar-multiplication-through-a-mesh/93038 "2023-01-16T18:46:07Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![g2g](https://avatars.discourse-cdn.com/v4/letter/g/41988e/32.png) [@g2g](https://discourse.julialang.org/u/g2g)\
**Post date:** [January 16, 2023, 6:46pm UTC](https://discourse.julialang.org/t/how-to-broadcast-a-scalar-multiplication-through-a-mesh/93038/1 "2023-01-16T18:46:07Z")

</div>

I’m trying to port a JavaScript 3D slicer example to Julia. The example loads a 3D model of a bunny (from [https://enkimute.github.io/ganja.js/examples/bunny.obj](https://enkimute.github.io/ganja.js/examples/bunny.obj) having 5002 triangle faces) and then calculates horizontal cross sections. The JavaScript version scales all the vertex coordinates in the mesh by a factor of 13 while parsing the individual text lines of the bunny object file. The Julia version can parse the whole bunny object file using Makie.FileIO’s load(). A scalar multiplication broadcast works at the level of triangle faces, but I don’t know how to save the multiplication results back into the structure.

```julia
julia> using Makie.FileIO

julia> faces = load("bunny.obj");

julia> nFace = length(faces)
5002

julia> typeof(faces)
GeometryBasics.Mesh{3, Float32, GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}, GeometryBasics.SimpleFaceView{3, Float32, 3, GeometryBasics.OffsetInteger{-1, UInt32}, Point{3, Float32}, GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}}}

julia> typeof(faces[1]) # 3 vertices of a triangle face
GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}

julia> typeof(faces[1][1]) # 3 coordinates of a vertex
Point{3, Float32}

julia> typeof(faces[1][1][1]) # a coordinate
Float32

julia> faces[1]
Triangle(Float32[-0.089491, 0.143926, 0.0124885], Float32[-0.0865619, 0.142492, 0.00843268], Float32[-0.0896984, 0.139713, 0.0137748])

julia> faces[1] .* 13
3-element Vector{Point{3, Float32}}:
 [-1.163383, 1.871038, 0.1623505]
 [-1.1253047, 1.852396, 0.10962484]
 [-1.1660792, 1.816269, 0.1790724]

julia> faces[1] = faces[1] .* 13
ERROR: CanonicalIndexError: setindex! not defined for GeometryBasics.Mesh{3, Float32, GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}, GeometryBasics.SimpleFaceView{3, Float32, 3, GeometryBasics.OffsetInteger{-1, UInt32}, Point{3, Float32}, GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}}}
Stacktrace:
 [1] error_if_canonical_setindex(#unused#::IndexCartesian, A::GeometryBasics.Mesh{3, Float32, GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}, GeometryBasics.SimpleFaceView{3, Float32, 3, GeometryBasics.OffsetInteger{-1, UInt32}, Point{3, Float32}, GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}}}, #unused#::Int64)
   @ Base .\abstractarray.jl:1354
 [2] setindex!(A::GeometryBasics.Mesh{3, Float32, GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}, GeometryBasics.SimpleFaceView{3, Float32, 3, GeometryBasics.OffsetInteger{-1, UInt32}, Point{3, Float32}, GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}}}, v::Vector{Point{3, Float32}}, I::Int64)
   @ Base .\abstractarray.jl:1343
 [3] top-level scope
   @ REPL[17]:1

julia>

```

I tried using StaticArrays and declaring faces::MArray = load(“bunny.obj”) but got the following error and hint, but I don’t know how to apply the hint to such a complex structure. Any suggestions?

```julia
ERROR: LoadError: The size of type `MArray` is not known.

If you were trying to construct (or `convert` to) a `StaticArray` you
may need to add the size explicitly as a type parameter so its size is
inferrable to the Julia compiler (or performance would be terrible). For
example, you might try

    m = zeros(3,3)
    SMatrix(m) # this error
    SMatrix{3,3}(m) # correct - size is inferrable
    SArray{Tuple{3,3}}(m) # correct, note Tuple{3,3}

Stacktrace:
 [1] error(s::String)
   @ Base .\error.jl:35
 [2] missing_size_error(#unused#::Type{MArray})
   @ StaticArraysCore C:\Users\gsgm2\.julia\packages\StaticArraysCore\U2Z1K\src\StaticArraysCore.jl:475
 [3] Size(#unused#::Type{MArray})
   @ StaticArraysCore C:\Users\gsgm2\.julia\packages\StaticArraysCore\U2Z1K\src\StaticArraysCore.jl:491
 [4] length(a::Type{MArray})
   @ StaticArrays C:\Users\gsgm2\.julia\packages\StaticArrays\jA1zK\src\abstractarray.jl:2
 [5] convert(#unused#::Type{MArray}, a::GeometryBasics.Mesh{3, Float32, GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}, GeometryBasics.SimpleFaceView{3, Float32, 3, GeometryBasics.OffsetInteger{-1, UInt32}, Point{3, Float32}, GeometryBasics.NgonFace{3, GeometryBasics.OffsetInteger{-1, UInt32}}}})
   @ StaticArrays C:\Users\gsgm2\.julia\packages\StaticArrays\jA1zK\src\convert.jl:195
 [6] top-level scope
   @ C:\dev\olarth\PGA\ripga2d3d\slicing.jl:9
 [7] include(fname::String)
   @ Base.MainInclude .\client.jl:476
 [8] top-level scope
   @ REPL[1]:1
in expression starting at C:\dev\olarth\PGA\ripga2d3d\slicing.jl:9

```

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [January 16, 2023, 6:54pm UTC](https://discourse.julialang.org/t/how-to-broadcast-a-scalar-multiplication-through-a-mesh/93038/2 "2023-01-16T18:54:27Z")

</div>

> [@g2g](#):
>
> faces::MArray = load(“bunny.obj”)

Don’t use StaticArrays for something like this. StaticArrays are when the length is a _small compile-time_ constant, and they will be terrible for a length-5002 array (especially whose length is known only at runtime, but even if this were a compile-time constant it would be too long for a StaticArray to be a good idea).

If you want to convert `faces= load(“bunny.obj”)` into an ordinary `Array`, you could do `Array(faces)`, since [`GeometryMesh` is a type of `AbstractVector`. Or, in one step:

```julia
faces= Array(load(“bunny.obj”))

```

> [@g2g](#):
>
> I don’t know how to save the multiplication results back into the structure

That’s because the `Geometry.Mesh` is a read-only structure (it implements `getindex` but not `setindex!`). That’s why you have to convert to a mutable type like `Array` to write into the `faces` array.

---

<div class="post-metadata">

**Author:** ![g2g](https://avatars.discourse-cdn.com/v4/letter/g/41988e/32.png) [@g2g](https://discourse.julialang.org/u/g2g)\
**Post date:** [January 17, 2023, 1:07am UTC](https://discourse.julialang.org/t/how-to-broadcast-a-scalar-multiplication-through-a-mesh/93038/3 "2023-01-17T01:07:18Z")

</div>

> [@stevengj](#):
>
> If you want to convert `faces= load(“bunny.obj”)` into an ordinary `Array`, you could do `Array(faces)`, since [`GeometryMesh` is a type of `AbstractVector`.

That removes some abstraction but apparently not enough to allow a scaled element to be saved.

```julia
julia> using Makie.FileIO, GeometryBasics

julia> faces = load("bunny.obj");

julia> length(faces)
5002

julia> Afaces = Array(faces);

julia> length(Afaces)
5002

julia> typeof(faces)
GeometryBasics.Mesh{3, Float32, GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}, SimpleFaceView{3, Float32, 3, OffsetInteger{-1, UInt32}, Point{3, Float32}, NgonFace{3, OffsetInteger{-1, UInt32}}}}

julia> typeof(Afaces)
Vector{Ngon{3, Float32, 3, Point{3, Float32}}} (alias for Array{GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}, 1})

julia> typeof(Afaces[1])
GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}

julia> Afaces[1]
Triangle(Float32[-0.089491, 0.143926, 0.0124885], Float32[-0.0865619, 0.142492, 0.00843268], Float32[-0.0896984, 0.139713, 0.0137748])

julia> Afaces[1] .* 13
3-element Vector{Point{3, Float32}}:
 [-1.163383, 1.871038, 0.1623505]
 [-1.1253047, 1.852396, 0.10962484]
 [-1.1660792, 1.816269, 0.1790724]

julia> Afaces[1] = Afaces[1] .* 13
ERROR: MethodError: Cannot `convert` an object of type Vector{Point{3, Float32}} to an object of type GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}
Closest candidates are:
  convert(::Type, ::GeoInterface.AbstractGeometryTrait, ::Any) at C:\Users\gsgm2\.julia\packages\GeoInterface\J298z\src\fallbacks.jl:112
  convert(::Type{T}, ::T) where T at Base.jl:61
  GeometryBasics.Ngon{Dim, T, N, Point}(::Any) where {Dim, T<:Real, N, Point<:AbstractPoint{Dim, T}} at C:\Users\gsgm2\.julia\packages\GeometryBasics\KE3OI\src\basic_types.jl:60
Stacktrace:
 [1] setindex!(A::Vector{GeometryBasics.Ngon{3, Float32, 3, Point{3, Float32}}}, x::Vector{Point{3, Float32}}, i1::Int64)
   @ Base .\array.jl:966
 [2] top-level scope
   @ REPL[155]:1

julia>

```

For a while I was hopeful about using GeometryBasics’ coordinates(), but that loses about half of the faces.

```julia
julia> Cfaces = coordinates(faces);

julia> length(Cfaces)
2503

julia> typeof(Cfaces)
Vector{Point{3, Float32}} (alias for Array{Point{3, Float32}, 1})

julia>

```

There might be some way for Array() or coordinates() or decompose() or convert() or collect() to remove enough abstraction while retaining all the triangle faces but I haven’t found it.
