module GR3

OverView of GR.rb

+--------------------+  +--------------------+
| GR module          |  | GR3 module         |
| +----------------+ |  | +----------------+ |
| | GR::FFI        | |  | | GR3::FFI       | |
| | +   libGR.so   | |  | | +    libGR3.so | |
| +----------------+ |  | +----------------+ |
|   | define_method  |  |   | define_method  |
| +----------------+ |  | +----------------+ |
| | | GR::GRBase   | |  | | | GR3::GR3Base | |
| | v  (Pri^ate)   | |  | | v  (Pri^ate)   | |
| +++--------------+ |  | +++--------------+ |
|  | Extend          |  |  | Extend          |
|  v                 |  |  v       +-------+ |
|      +-----------+ |  |          | Check | |
|      | GR::Plot  | |  |       <--+ Error | |
|      +-----------+ |  |          +-------+ |
+--------------------+  +----------+---------+
          ^                        ^
          |  +------------------+  |
   Extend |  | GRCommons module |  | Extend
          |  | +--------------+ |  |
          |  | |    Fiddley   | |  |
          |  | +--------------+ |  |
          |  | +--------------+ |  |
          +----+ CommonUtils  +----+
          |  | +--------------+ |  |
          |  | +--------------+ |  |
          +----+    Version   +----+
          |  | +--------------+ |
          |  | +--------------+ |
          +----+JupyterSupport| |
             | +--------------+ |
             +------------------+

(You can edit the above AA diagram with asciiflow.com/)

Fiddley is Ruby-FFI compatible API layer for Fiddle.

@note

Why not GR::GR3?
* kojix2 did not want to force gr3 to be loaded when gr is loaded.
* kojix2 did not want to write `GR3 = GR::GR3` or something.
* This is a opinion of kojix2 and may be changed by future maintainers.

@note

GR3 uses Numo::Narrray.
* It is difficult to write GR3 modules with only Ruby arrays.
* Numo::Narray has better performance and is easier to read.
* Numo::Narray does not work with JRuby.
  * https://github.com/ruby-numo/numo-narray/issues/147

This is a procedural interface to the GR3 in GR plotting library, github.com/sciapp/gr

Constants

ATOM_COLORS
ATOM_NUMBERS
ATOM_RADII
ATOM_VALENCE_RADII
DRAWABLE_GKS
DRAWABLE_OPENGL

Drawable

ERROR_CAMERA_NOT_INITIALIZED
ERROR_CANNOT_OPEN_FILE
ERROR_EXPORT
ERROR_INIT_FAILED
ERROR_INVALID_ATTRIBUTE
ERROR_INVALID_VALUE
ERROR_NONE

Error

ERROR_NOT_INITIALIZED
ERROR_OPENGL_ERR
ERROR_OUT_OF_MEM
ERROR_UNKNOWN_FILE_EXTENSION
IA_END_OF_LIST

InitAttribute

IA_FRAMEBUFFER_HEIGHT
IA_FRAMEBUFFER_WIDTH
IA_NUM_THREADS
PROJECTION_ORTHOGRAPHIC
PROJECTION_PARALLEL
PROJECTION_PERSPECTIVE

Projection

QUALITY_OPENGL_16X_SSAA
QUALITY_OPENGL_2X_SSAA
QUALITY_OPENGL_4X_SSAA
QUALITY_OPENGL_8X_SSAA
QUALITY_OPENGL_NO_SSAA

Quality

QUALITY_POVRAY_16X_SSAA
QUALITY_POVRAY_2X_SSAA
QUALITY_POVRAY_4X_SSAA
QUALITY_POVRAY_8X_SSAA
QUALITY_POVRAY_NO_SSAA
SURFACE_DEFAULT

SurfaceOption

SURFACE_FLAT
SURFACE_GRCOLOR
SURFACE_GRTRANSFORM
SURFACE_GRZSHADED
SURFACE_NORMALS
VERSION

Attributes

ffi_lib[RW]

Public Class Methods

createindexedmesh(num_vertices, vertices, normals, colors, num_indices, indices) click to toggle source

This function creates an indexed mesh from vertex information (position, normal and color) and triangle information (indices). Returns a mesh.

@param num_vertices [Integer] the number of vertices in the mesh @param vertices [Array, NArray] the vertex positions @param normals [Array, NArray] the vertex normals @param colors [Array, NArray] the vertex colors, they should be

white (1,1,1) if you want to change
the color for each drawn mesh

@param num_indices [Integer] the number of indices in the mesh

(three times the number of triangles)

@param indices [Array, NArray] the index array (vertex indices for

each triangle)

@return [Integer]

Calls superclass method
# File lib/gr3.rb, line 278
def createindexedmesh(num_vertices, vertices, normals, colors, num_indices, indices)
  inquiry_int do |mesh|
    super(mesh, num_vertices, vertices, normals, colors, num_indices, indices)
  end
end
createindexedmesh_nocopy(num_vertices, vertices, normals, colors, num_indices, indices) click to toggle source

This function creates a mesh from vertex position, normal and color data.

@return [Integer]

Calls superclass method
# File lib/gr3.rb, line 255
def createindexedmesh_nocopy(num_vertices, vertices, normals, colors, num_indices, indices)
  inquiry_int do |mesh|
    super(mesh, num_vertices, vertices, normals, colors, num_indices, indices)
  end
end
createisosurfacemesh(grid, step, offset, isolevel) click to toggle source

This function creates an isosurface from voxel data using the marching cubes algorithm. Returns a mesh.

@param grid [NArray] 3D narray array containing the voxel data @param step [Array, NArray] voxel sizes in each direction @param offset [Array, NArray] coordinate origin in each direction @param isolevel [Integer] isovalue at which the surface will be created

@return [Integer]

Calls superclass method
# File lib/gr3.rb, line 417
def createisosurfacemesh(grid, step, offset, isolevel)
  args = _preprocess_createslicemesh(grid, step, offset)
  grid = args.shift
  inquiry_int do |mesh|
    super(mesh, uint16(grid), isolevel, *args)
  end
end
createmesh(n, vertices, normals, colors) click to toggle source

This function creates a int from vertex position, normal and color data. Returns a mesh.

@param n [Integer] the number of vertices in the mesh @param vertices [Array, NArray] the vertex positions @param normals [Array, NArray] the vertex normals @param colors [Array, NArray] the vertex colors, they should be

white (1,1,1) if you want to change the color for each drawn mesh

@return [Integer]

Calls superclass method
# File lib/gr3.rb, line 246
def createmesh(n, vertices, normals, colors)
  inquiry_int do |mesh|
    super(mesh, n, vertices, normals, colors)
  end
end
createmesh_nocopy(n, vertices, normals, colors) click to toggle source

This function creates a mesh from vertex position, normal and color data.

@return [Integer]

Calls superclass method
# File lib/gr3.rb, line 229
def createmesh_nocopy(n, vertices, normals, colors)
  inquiry_int do |mesh|
    super(mesh, n, vertices, normals, colors)
  end
end
createslicemeshes(grid, x = nil, y = nil, z = nil, step = nil, offset = nil) click to toggle source

Creates meshes for slices through the given data, using the current GR colormap. Use the parameters x, y or z to specify what slices should be drawn and at which positions they should go through the data. If neither x nor y nor z are set, 0.5 will be used for all three. Returns meshes for the yz-slice, the xz-slice and the xy-slice.

@param grid [NArray] 3D narray array containing the voxel data @param x [Numeric] the position of the slice through the xz-plane (0 to 1) @param y [Numeric] the position of the slice through the xz-plane (0 to 1) @param z [Numeric] the position of the slice through the xz-plane (0 to 1) @param step [Array, NArray] voxel sizes in each direction @param offset [Array, NArray] coordinate origin in each direction

# File lib/gr3.rb, line 608
def createslicemeshes(grid, x = nil, y = nil, z = nil, step = nil, offset = nil)
  if [x, y, z].all?(&:nil?)
    x = 0.5
    y = 0.5
    z = 0.5
  end
  mesh_x = (createxslicemesh(grid, x, step, offset) if x)
  mesh_y = (createyslicemesh(grid, y, step, offset) if y)
  mesh_z = (createzslicemesh(grid, z, step, offset) if z)
  [mesh_x, mesh_y, mesh_z]
end
createsurfacemesh(nx, ny, x, y, z, option = 0) click to toggle source

Create a mesh of a surface plot similar to gr_surface. Uses the current colormap. To apply changes of the colormap a new mesh has to be created.

@param nx [Integer] number of points in x-direction @param ny [Integer] number of points in y-direction @param x [Array, NArray] an array containing the x-coordinates @param y [Array, NArray] an array containing the y-coordinates @param z [Array, NArray] an array of length nx * ny containing the z-coordinates @param option [Integer] option for the surface mesh; the GR3_SURFACE constants can be combined with bitwise or. See the table below.

* 0  : GR3_SURFACE_DEFAULT
  *    default behavior
* 1  : GR3_SURFACE_NORMALS
  *    interpolate the vertex normals from the gradient
* 2  : GR3_SURFACE_FLAT
  *    set all z-coordinates to zero
* 4  : GR3_SURFACE_GRTRANSFORM
  *    use gr_inqwindow, gr_inqspace and gr_inqscale to transform the data to NDC coordinates
* 8  : GR3_SURFACE_GRCOLOR
  *    color the surface according to the current gr colormap
* 16 : GR3_SURFACE_GRZSHADED
  *    like GR3_SURFACE_GRCOLOR, but use the z-value directly as color index

@return [Integer]

Calls superclass method
# File lib/gr3.rb, line 449
def createsurfacemesh(nx, ny, x, y, z, option = 0)
  inquiry_int do |mesh|
    super(mesh, nx, ny, x, y, z, option)
  end
end
createtubemesh(n, points, colors, radii, num_steps = 10, num_segments = 20) click to toggle source

Create a mesh object in the shape of a tube following a path given by a list of points. The colors and radii arrays specify the color and radius at each point.

@param n [Integer] the number of points given @param points [Array, NArray] the points the tube should go through @param colors [Array, NArray] the color at each point @param radii [Array, NArray] the desired tube radius at each point @param num_steps [Integer] the number of steps between each point,

allowing for a more smooth tube

@param num_segments [Integer] the number of segments each ring of the

tube consists of, e.g. 3 would yield a
triangular tube

@return [Integer]

Calls superclass method
# File lib/gr3.rb, line 527
def createtubemesh(n, points, colors, radii, num_steps = 10, num_segments = 20)
  inquiry_uint do |mesh| # mesh should be Int?
    super(mesh, n, points, colors, radii, num_steps, num_segments)
  end
end
createxslicemesh(grid, x = 0.5, step = nil, offset = nil) click to toggle source

Creates a meshes for a slices through the yz-plane of the given data, using the current GR colormap. Use the x parameter to set the position of the yz-slice. Returns a mesh for the yz-slice.

@param grid [NArray] 3D narray array containing the voxel data @param x [Numeric] the position of the slice through the xz-plane (0 to 1) @param step [Array, NArray] voxel sizes in each direction @param offset [Array, NArray] coordinate origin in each direction

Calls superclass method
# File lib/gr3.rb, line 629
def createxslicemesh(grid, x = 0.5, step = nil, offset = nil)
  args = _preprocess_createslicemesh(grid, step, offset)
  grid = args.shift
  x = (x.clamp(0, 1) * args[0]).floor
  inquiry_int do |mesh|
    super(mesh, uint16(grid), x, *args)
  end
end
createyslicemesh(grid, y = 0.5, step = nil, offset = nil) click to toggle source

Creates a meshes for a slices through the xz-plane of the given data, using the current GR colormap. Use the y parameter to set the position of the xz-slice. Returns a mesh for the xz-slice.

@param grid [NArray] 3D narray array containing the voxel data @param y [Numeric] the position of the slice through the xz-plane (0 to 1) @param step [Array, NArray] voxel sizes in each direction @param offset [Array, NArray] coordinate origin in each direction

Calls superclass method
# File lib/gr3.rb, line 648
def createyslicemesh(grid, y = 0.5, step = nil, offset = nil)
  args = _preprocess_createslicemesh(grid, step, offset)
  grid = args.shift
  y = (y.clamp(0, 1) * args[1]).floor
  inquiry_int do |mesh|
    super(mesh, uint16(grid), y, *args)
  end
end
createzslicemesh(grid, z = 0.5, step = nil, offset = nil) click to toggle source

Creates a meshes for a slices through the xy-plane of the given data, using the current GR colormap. Use the z parameter to set the position of the xy-slice. Returns a mesh for the xy-slice.

@param grid [NArray] 3D narray array containing the voxel data @param z [Numeric] the position of the slice through the xz-plane (0 to 1) @param step [Array, NArray] voxel sizes in each direction @param offset [Array, NArray] coordinate origin in each direction

Calls superclass method
# File lib/gr3.rb, line 667
def createzslicemesh(grid, z = 0.5, step = nil, offset = nil)
  args = _preprocess_createslicemesh(grid, step, offset)
  grid = args.shift
  z = (z.clamp(0, 1) * args[2]).floor
  inquiry_int do |mesh|
    super(mesh, uint16(grid), z, *args)
  end
end
drawmolecule(positions, colors = nil, radii = nil, spins = nil, bond_radius = nil, bond_color = nil, bond_delta = nil, set_camera = true, rotation = 0, tilt = 0) click to toggle source

drawmolecule

Calls superclass method
# File lib/gr3.rb, line 543
def drawmolecule(positions, colors = nil, radii = nil, spins = nil,
                 bond_radius = nil, bond_color = nil, bond_delta = nil,
                 set_camera = true, rotation = 0, tilt = 0)
  # Should `drawmolecule` take keyword arguments?
  # Setting default values later for now.

  # Should it be RubyArray instead of Narray?
  # If NArray is required, add no NArray error.
  positions = Numo::SFloat.cast(positions)
  n = positions.shape[0]

  colors = if colors.nil?
             Numo::SFloat.ones(n, 3)
           else
             Numo::SFloat.cast(colors).reshape(n, 3)
           end

  radii = if radii.nil?
            Numo::SFloat.new(n).fill(0.3)
          else
            Numo::SFloat.cast(radii).reshape(n)
          end

  bond_color ||= [0.8, 0.8, 0.8]
  bond_color = Numo::SFloat.cast(bond_color).reshape(3)
  bond_delta ||= 1.0
  bond_radius ||= 0.1

  if set_camera
    deg2rad = ->(degree) { degree * Math::PI / 180 } # room for improvement
    cx, cy, cz = *positions.mean(axis: 0)
    dx, dy, dz = *positions.ptp(axis: 0)
    d = [dx, dy].max / 2 / 0.4142 + 3
    r = dz / 2 + d
    rx = r * Math.sin(deg2rad.call(tilt)) * Math.sin(deg2rad.call(rotation))
    ry = r * Math.sin(deg2rad.call(tilt)) * Math.cos(deg2rad.call(rotation))
    rz = r * Math.cos(deg2rad.call(tilt))
    ux = Math.sin(deg2rad.call(tilt + 90)) * Math.sin(deg2rad.call(rotation))
    uy = Math.sin(deg2rad.call(tilt + 90)) * Math.cos(deg2rad.call(rotation))
    uz = Math.cos(deg2rad.call(tilt + 90))
    cameralookat(cx + rx, cy + ry, cz + rz, cx, cy, cz, ux, uy, uz)
    setcameraprojectionparameters(45, d - radii.max - 3, d + dz + radii.max + 3)
  end

  super(n, positions, colors, radii, bond_radius, bond_color, bond_delta)

  if spins
    spins = Numo::SFloat.cast(spins).reshape(n, 3)
    drawspins(positions, spins, colors)
  end
end
drawslicemeshes(grid, x = nil, y = nil, z = nil, step = nil, offset = nil, position = [0, 0, 0], direction = [0, 0, 1], up = [0, 1, 0], color = [1, 1, 1], scale = [1, 1, 1]) click to toggle source

raw slices through the given data, using the current GR colormap. Use the parameters x, y or z to specify what slices should be drawn and at which positions they should go through the data. If neither x nor y nor z are set, 0.5 will be used for all three.

@param grid [NArray] 3D narray array containing the voxel data @param x [Numeric] the position of the slice through the yz-plane (0 to 1) @param y [Numeric] the position of the slice through the xz-plane (0 to 1) @param z [Numeric] the position of the slice through the xy-plane (0 to 1) @param step [Array, NArray] voxel sizes in each direction @param offset [Array, NArray] coordinate origin in each direction @param position [Array, NArray] the positions where the meshes should be drawn @param direction [Array, NArray] the forward directions the meshes should be facing at @param up [Array, NArray] the up directions @param color [Array, NArray] the colors the meshes should be drawn in,

it will be multiplied with each vertex color

@param scale [Array, NArray] the scaling factors

# File lib/gr3.rb, line 757
def drawslicemeshes(grid, x = nil, y = nil, z = nil, step = nil,
                    offset = nil, position = [0, 0, 0], direction = [0, 0, 1], up = [0, 1, 0],
                    color = [1, 1, 1], scale = [1, 1, 1])
  meshes = createslicemeshes(grid, x, y, z, step, offset)
  meshes.each do |mesh|
    if mesh
      drawmesh(mesh, 1, position, direction, up, color, scale)
      deletemesh(mesh)
    end
  end
end
drawspins(positions, directions, colors = nil, cone_radius = 0.4, cylinder_radius = 0.2, cone_height = 1.0, cylinder_height = 1.0) click to toggle source

drawspins

Calls superclass method
# File lib/gr3.rb, line 534
def drawspins(positions, directions, colors = nil,
              cone_radius = 0.4, cylinder_radius = 0.2,
              cone_height = 1.0, cylinder_height = 1.0)
  n = positions.length
  colors = [1] * n * 3 if colors.nil?
  super(n, positions, directions, colors, cone_radius, cylinder_radius, cone_height, cylinder_height)
end
drawtubemesh(n, points, colors, radii, num_steps = 10, num_segments = 20) click to toggle source

Draw a tube following a path given by a list of points. The colors and radii arrays specify the color and radius at each point.

@param n [Integer] the number of points given @param points [Array, NArray] the points the tube should go through @param colors [Array, NArray] the color at each point @param radii [Array, NArray] the desired tube radius at each point @param num_steps [Integer] the number of steps between each point,

allowing for a more smooth tube

@param num_segments [Integer] the number of segments each ring of the tube

consists of, e.g. 3 would yield a triangular tube

@return [Integer]

Calls superclass method
# File lib/gr3.rb, line 508
def drawtubemesh(n, points, colors, radii, num_steps = 10, num_segments = 20)
  super(n, points, colors, radii, num_steps, num_segments)
end
drawxslicemesh(grid, x = 0.5, step = nil, offset = nil, position = [0, 0, 0], direction = [0, 0, 1], up = [0, 1, 0], color = [1, 1, 1], scale = [1, 1, 1]) click to toggle source

Draw a yz-slice through the given data, using the current GR colormap.

@param grid [NArray] 3D narray array containing the voxel data @param x [Numeric] the position of the slice through the yz-plane (0 to 1) @param step [Array, NArray] voxel sizes in each direction @param offset [Array, NArray] coordinate origin in each direction @param position [Array, NArray] the positions where the meshes should be drawn @param direction [Array, NArray] the forward directions the meshes should be facing at @param up [Array, NArray] the up directions @param color [Array, NArray] the colors the meshes should be drawn in,

it will be multiplied with each vertex color

@param scale [Array, NArray] the scaling factors

# File lib/gr3.rb, line 689
def drawxslicemesh(grid, x = 0.5, step = nil, offset = nil,
                   position = [0, 0, 0], direction = [0, 0, 1], up = [0, 1, 0],
                   color = [1, 1, 1], scale = [1, 1, 1])
  mesh = createxslicemesh(grid, x, step, offset)
  drawmesh(mesh, 1, position, direction, up, color, scale)
  deletemesh(mesh)
end
drawyslicemesh(grid, y = 0.5, step = nil, offset = nil, position = [0, 0, 0], direction = [0, 0, 1], up = [0, 1, 0], color = [1, 1, 1], scale = [1, 1, 1]) click to toggle source

Draw a xz-slice through the given data, using the current GR colormap.

@param grid [NArray] 3D narray array containing the voxel data @param y [Numeric] the position of the slice through the xz-plane (0 to 1) @param step [Array, NArray] voxel sizes in each direction @param offset [Array, NArray] coordinate origin in each direction @param position [Array, NArray] the positions where the meshes should be drawn @param direction [Array, NArray] the forward directions the meshes should be facing at @param up [Array, NArray] the up directions @param color [Array, NArray] the colors the meshes should be drawn in,

it will be multiplied with each vertex color

@param scale [Array, NArray] the scaling factors

# File lib/gr3.rb, line 710
def drawyslicemesh(grid, y = 0.5, step = nil, offset = nil,
                   position = [0, 0, 0], direction = [0, 0, 1], up = [0, 1, 0],
                   color = [1, 1, 1], scale = [1, 1, 1])
  mesh = createyslicemesh(grid, y, step, offset)
  drawmesh(mesh, 1, position, direction, up, color, scale)
  deletemesh(mesh)
end
drawzslicemesh(grid, z = 0.5, step = nil, offset = nil, position = [0, 0, 0], direction = [0, 0, 1], up = [0, 1, 0], color = [1, 1, 1], scale = [1, 1, 1]) click to toggle source

Draw a xy-slice through the given data, using the current GR colormap.

@param grid [NArray] 3D narray array containing the voxel data @param z [Numeric] the position of the slice through the xy-plane (0 to 1) @param step [Array, NArray] voxel sizes in each direction @param offset [Array, NArray] coordinate origin in each direction @param position [Array, NArray] the positions where the meshes should be drawn @param direction [Array, NArray] the forward directions the meshes should be facing at @param up [Array, NArray] the up directions @param color [Array, NArray] the colors the meshes should be drawn in,

it will be multiplied with each vertex color

@param scale [Array, NArray] the scaling factors

# File lib/gr3.rb, line 731
def drawzslicemesh(grid, z = 0.5, step = nil, offset = nil,
                   position = [0, 0, 0], direction = [0, 0, 1], up = [0, 1, 0],
                   color = [1, 1, 1], scale = [1, 1, 1])
  mesh = createzslicemesh(grid, z, step, offset)
  drawmesh(mesh, 1, position, direction, up, color, scale)
  deletemesh(mesh)
end
geterrorstring(*) click to toggle source

This function returns a string representation of a given error code.

@return [String]

Calls superclass method
# File lib/gr3.rb, line 180
def geterrorstring(*)
  super.to_s
end
getimage(width, height, use_alpha = true) click to toggle source

@return [Integer]

Calls superclass method
# File lib/gr3.rb, line 213
def getimage(width, height, use_alpha = true)
  bpp = use_alpha ? 4 : 3
  inquiry(uint8: width * height * bpp) do |bitmap|
    super(width, height, (use_alpha ? 1 : 0), bitmap)
  end
end
getrenderpathstring(*) click to toggle source

This function allows the user to find out how his commands are rendered.

If gr3 is initialized, a string in the format: `“gr3 - ” + window toolkit + “ - ” + framebuffer extension + “ - ” + OpenGL version + “ - ” + OpenGL renderer string`. For example `“gr3 - GLX - GL_ARB_framebuffer_object - 2.1 Mesa 7.10.2 - Software Rasterizer”` might be returned on a Linux system (using GLX) with an available GL_ARB_framebuffer_object implementation. If gr3 is not initialized `“Not initialized”` is returned.

@return [String]

Calls superclass method
# File lib/gr3.rb, line 172
def getrenderpathstring(*)
  super.to_s
end
surface(x, y, z, option) click to toggle source

Create a surface plot with gr3 and draw it with gks as cellarray.

@param x [Array, NArray] an array containing the x-coordinates @param y [Array, NArray] an array containing the y-coordinates @param z [Array, NArray] an array of length nx * ny containing the z-coordinates @param option [Integer] see the option parameter of gr_surface.

OPTION_COLORED_MESH and OPTION_Z_SHADED_MESH are supported.
Calls superclass method
# File lib/gr3.rb, line 487
def surface(x, y, z, option)
  nx = x.length
  ny = y.length
  # TODO: Check out_of_bounds
  super(nx, ny, x, y, z, option)
end
volume(data, algorithm) click to toggle source
Calls superclass method
# File lib/gr3.rb, line 769
def volume(data, algorithm)
  data = Numo::DFloat.cast(data) if data.is_a? Array
  inquiry %i[double double] do |dmin, dmax|
    dmin.write_double(-1)
    dmax.write_double(-1)
    nx, ny, nz = data.shape
    super(nx, ny, nz, data, algorithm, dmin, dmax)
  end
end

Private Class Methods

_preprocess_createslicemesh(grid, step, offset) click to toggle source
# File lib/gr3.rb, line 781
def _preprocess_createslicemesh(grid, step, offset)
  # TODO: raise error when grid is not narray
  # grid
  case grid.class::MAX
  when Integer
    input_max = grid.class::MAX
  when Float
    # floating point values are expected to be in range [0, 1]
    # Complex narrays are not taken into account
    input_max = 1
    grid[grid > 1] = 1
  else
    raise ArgumentError, 'grid must be three dimensional array of Real numbers'
  end
  scaling_factor = Numo::UInt16::MAX / input_max.to_f
  grid = (grid.cast_to(Numo::UInt64) * scaling_factor).cast_to(Numo::UInt16) # room for improvement

  # step & offset
  nx, ny, nz = grid.shape
  if step.nil? && offset.nil?
    step = [2.0 / (nx - 1), 2.0 / (ny - 1), 2.0 / (nz - 1)]
    offset = [-1.0, -1.0, -1.0]
  elsif offset.nil?
    offset = [-step[0] * (nx - 1) / 2.0,
              -step[1] * (ny - 1) / 2.0,
              -step[2] * (nz - 1) / 2.0]
  elsif step.nil?
    step = [-offset[0] * 2.0 / (nx - 1),
            -offset[1] * 2.0 / (ny - 1),
            -offset[2] * 2.0 / (nz - 1)]
  end

  step_x, step_y, step_z = step
  offset_x, offset_y, offset_z = offset

  # strides
  stride_x = ny * nz
  stride_y = nz
  stride_z = 1

  [grid, nx, ny, nz, stride_x, stride_y, stride_z, step_x, step_y, step_z, offset_x, offset_y, offset_z]
end