Software Architecture

This page is intended to provide an overview of the design, architecture and structure of the Tinman 3D SDK and to familiarize developers with the functionality it provides.

By convention, interface names always start with the prefix character I, for example ISomeInterface or IOtherInterface. In the documentation, interface names are used as if the prefix character was not present:
"This is a sentence with a ISomeInterface object…​"
"This is a sentence with an IOtherInterface object…​"

Code Concepts

This section describes the concepts which can be found in the source code. These concepts represent the foundation of the Tinman 3D SDK.

DEBUG vs. RELEASE

The binaries of the Tinman 3D SDK have two variants:

  • DEBUG
    Code optimization was disabled for the build.
    Runtime checks are enabled.

  • RELEASE
    Code optimization was enabled for the build.
    Runtime checks are disabled.

During initialization, a log message is output which indicates whether DEBUG or RELEASE mode is used.

Log messages for DEBUG / RELEASE mode
2024-03-15 20:58:09 [D] Using DEBUG binaries.             [Tinman.Core.TinmanModule]
...
2024-03-15 20:58:19 [D] Using RELEASE binaries.           [Tinman.Core.TinmanModule]

The following basic logging configuration can be used to enable output of the log messages.

Enable logging of DEBUG / RELEASE mode
LoggingUtil.BasicConfiguration(LogVerbosity.Info, LogVerbosity.Debug); (1)
TinmanModule.Logger.ChangeVerbosity(LogVerbosity.Debug); (2)
1 Chooses Info as root log verbosity and Debug for the console appender.
2 Enables Debug verbosity for TinmanModule.Logger, which outputs the DEBUG / RELEASE messages.

Distribution licence keys should not be used in these cases:

  • One or more DEBUG binaries are used, see DebugFlags.DebugBinary:
    The distribution licence key will not unlock the Tinman 3D SDK.

  • A debugger is attached to the process, see DebugFlags.DebugAttached:
    To get support, an error report with DEBUG binaries might be required.

A Development licence key is required in such a non-RELEASE case.

Table 1. Debug scenario vs. licence key

Debug Scenarios

Development Key
LicenceFlags.Machine

Distribution Key
~LicenceFlags.Machine

DEBUG binaries?
DebugFlags.DebugBinary

yes

no [1]

Debugger active?
DebugFlags.DebugAttached

yes

yes [2]

RELEASE binaries?
DebugFlags.ReleaseBinary

yes

yes

Due to the variety of project environments in which the Tinman 3D SDK may be used, it does not provide a global switch for choosing between the DEBUG and RELEASE binaries. Instead, one of the following mechanisms may be used:

NuGet Packages

The NuGet packages[3] contain two sets of binaries: one for DEBUG and one for RELEASE. The custom MSBuild property $(TinmanBinariesNuget) may be used to choose between the binary sets.

MSBuild DEBUG / RELEASE choice
<Project>
  <PropertyGroup>
    <TinmanBinariesNuget>DEBUG</TinmanBinariesNuget> (1)
    <TinmanBinariesNuget>RELEASE</TinmanBinariesNuget> (2)
  </PropertyGroup>
</Project>
1 Force use of DEBUG binary set.
2 Force use of RELEASE binary set.

If the $(TinmanBinariesNuget) property has not been specified, the binary set is chosen automatically, based on the value of $(Configuration):

  • If it contains "DEBUG" (case-insensitive), the DEBUG binary set is chosen.

  • Otherwise, the RELEASE binary set is chosen.

sdk://src.cs/

The MSBuild files of the C# solutions projects in this directory use <ProjectReference/> to reference other solution projects and <Reference/> to reference the library binaries in lib/win.any/:

MSBuild references for C#
<Project>
  <ItemGroup>
    <Reference Include="CodeX.System"> (1)
      <HintPath>...$(Configuration)/CodeX.System.dll</HintPath>
    </Reference>
    <ProjectReference Include=".../Tinman.AddOns.csproj"/> (2)
  </ItemGroup>
</Project>
1 The configuration name is used to automatically choose the directory that contains the correct binaries.
2 Another solution project is referenced, which implicitly chooses the correct binaries.
sdk://ide.cpp/vs/

The MSBuild files of the C++ solution projects in this directory use <ProjectReference/> items to reference other solutions projects, which implicitly chooses the correct binaries.

The Tinman 3D SDK contains obfuscated C++ source code instead of pre-built static/dynamic link libraries (see Components), which allows to fine-tune the build settings and to generate tailor-made link libraries.

Mixing DEBUG and RELEASE binaries should be avoided. Doing so can cause compilation and/or runtime errors. Even if this is not the case, it will most likely lead to confusing or even deceptive behaviour at runtime, which complicates error analysis:

  • The developer assumes that runtime checks are in place, receives unexpected exceptions for RELEASE binaries and thus concludes that there is a difficult code bug somewhere, although the problem and solution would be apparent with the DEBUG binaries.

    Before filing a bug report, please reproduce the problem with DEBUG binaries, if applicable.
  • A single DEBUG binary is sufficient to trigger DebugFlags.DebugBinary, which in turn may trigger additional debugging helpers, for example runtime validations for GPU rendering. This can reduce performance significantly, see Performance Guide for details.

A log message is output during initialization if binaries are mixed.

Using mixed binaries
2024-03-15 21:00:04 [W] Using mixed DEBUG / RELEASE binaries:
                        - Tinman.Core v1.0 : DEBUG
                        - Tinman.Terrain v1.0 : RELEASE (1)
                        - Tinman.Engine v1.0 : DEBUG
                        - Tinman.AddOns v1.0 : DEBUG
                        - Tinman.AddOns.DirectX11 v1.0 : DEBUG
                        - Tinman.AddOns.DirectX12 v1.0 : DEBUG
                        - Tinman.AddOns.DirectX9 v1.0 : DEBUG
                        - Tinman.AddOns.Vulkan v1.0 : DEBUG
                        - Tinman.Demo v1.0 : DEBUG
                        - Tinman.Demo.StandAlone v1.0 : DEBUG
                                                          [Tinman.Core.TinmanModule]
1 A binary with mismatching mode

Initialization / Shutdown

The Tinman 3D SDK must be initialized before it can be used by an application. When it is longer being used, it may be shut down. It is possible to re-initialize the SDK after it has been shut down.

The SDK is composed of individual modules, which may have dependencies on each other. Their dependency graph is taken into account by the initialization and shut down process.

During initialization, the following steps are performed:

  1. Licence checking
    The SDK is unlocked using one of the specified licence keys.

  2. Global object initialization
    The computing environment will initialize objects in the global scope lazily upon first use. To ensure deterministic behaviour, lazy initialization is enforced with IPleaseIncludeInBinaryThanks.

  3. Module-specific initialization
    A module may need to perform custom initialization work, for example checking if a graphics API is available.

The code below will initialize the Tinman 3D SDK, using the top-level module TinmanEngineModule.

In most cases, a single top-level module is sufficient, because of the module’s dependencies. For example, TinmanAddOnsDirectX11Module would be sufficient for a Direct3D 11 application. The Demo Application uses its own module.
Initialization in C#
using Tinman.Core;
using Tinman.Core.Licensing;
using Tinman.Engine;
...
LicenceDomain.Tinman.LicenceKey("..."); (1)
TinmanEngineModule.Instance.PleaseIncludeInBinaryThanks(); (2)
TinmanModule.Initialize(); (3)
1 Set the licence key.
2 Force lazy initialization.
3 Initialize the SDK modules.

The code below will shut down the Tinman 3D SDK.

Shutdown in C#
using Tinman.Core;
...
TinmanModule.Shutdown(); (1)
1 Shutdown the Tinman 3D SDK.
Objects in the global scope that are affected by initialization and shut down are annotated with the ShutdownClearAttribute and ShutdownSurviveAttribute attributes, which depict how these objects are treated during shut down.

Disposal and Ownership

The life-time of all dynamically allocated objects is governed by the computing environment and cannot be controlled directly with the API, i.e. objects cannot be deleted explicitly.

For C#, the garbage collector of the .NET implementation takes care of object life-time, whereas for C++, this is achieved by using reference-counting smart pointers.

The Tinman 3D SDK assumes that these two approaches - regardless of their implementation - have the following properties:

  1. Garbage collectors delete whole graphs of unreferenced objects, i.e. cyclic references will not cause memory leaks.

  2. Garbage collectors delete unreferenced objects at their own discretion, i.e. at the time they choose, using the thread they choose.

  3. Smart pointers do not delete whole whole graphs of unreferenced objects, i.e. cyclic references can cause memory leaks.

  4. Smart pointers delete unreferenced objects immediately, i.e. both time and thread are well-defined.

To get the best of both worlds, the Tinman 3D SDK uses the well-known concept of disposable objects (see IDisposable), where the life-time of an object is still managed by the computing environment but a dedicated method exists to explicitly dispose the object (which solves 2.), making it release all resources and references it holds. This effectively breaks cyclic references (which solves 3.).

On top of this, the Tinman 3D SDK uses the concept of ownership: when a disposable object is created, the creating expression receives ownership of it. From there, ownership must be transferred or shared - following the rules defined by IDisposable - until all owners terminate their ownership. Then the object will have been disposed and the computing environment may perform object deletion at its own discretion.

The rules of ownership transfer may be verified using static code analysis. This verification is part of every build of the Tinman 3D SDK.

Exception Handling

The Tinman 3D SDK uses exception classes for handling exceptional error situations. For C# and C++, the standard mechanism via throw / catch is used.

All thrown exception objects are instances of TinmanException or one of its subclasses. Each exception object carries the following information:

  • Error Source Tag
    This is a short textual token that identifies a member of a type (for example: 'TinmanException.Source'), to ensure that at least a minimalistic stacktrace is available. The native stacktrace provided by the computing environment may be incomplete, inaccurate or simply not existent, for example due to aggressive optimization by the compiler.

  • Error Message
    This is the error message that has been constructed at the throw site. It should be human-readable. Together with the Error Source Tag, this will usually narrow down the set of possible throw sites and error causes to one or two. If possible, please make sure to include this information in your bug reports.

  • Error Details
    This is a TinmanError object that contains detailed information about the exception, including the Error Source Tag and the Error Message. It may contain zero or more aggregated TinmanError objects, which represent the causes that have led to the error. Additionally, a native stacktrace in an unspecified format may be included, to aid debugging.

In addition to a thrown exception, an error dump file may be generated, in order to provide detailed information on how to reproduce the problem. For details, please refer to TinmanErrorDump. Including error dump files in your bug reports is of great help.

The intention behind the exception handling rules of the Tinman 3D SDK is to avoid use of exceptions where possible in low-level code (to allow compilers to apply aggressive optimization techniques), but to use exceptions where applicable in high-level code (to keep the API as clean as possible).

There are three kinds of exceptional error situations, each having different handling rules.

Failed Assertions

Assertions are used to validate the internal state of objects and to check the arguments of a called method against the defined preconditions:

These exceptions are never documented as being thrown by a method. Instead, method preconditions are documented for each parameter and state requirements are documented in the description text or can be inferred from the semantic rules.

Assertions are only triggered in DEBUG mode. In RELEASE mode, failed assertions may lead to unpredictable behaviour.

A failed assertion is interpreted as a programmer’s mistake. As a developer who is using the Tinman 3D SDK, please check the error message and consult the documentation to find out what went wrong.

Please keep in mind that it is also possible that a programmer at Tinman 3D made a mistake. If you feel this could be the case, please file a bug report.

Runtime Errors

A runtime error is a recoverable situation that is not expected under normal operation, but could very well happen in practice (for example, a sharing violation when trying to write to a file).

All TinmanException classes that do not represent failed assertions describe a runtime error. An application may inspect the concrete exception type and analyze the contained error information, in order to perform error recovery at its own discretion.

The exceptions that may be thrown for runtime errors are always listed in the documentation (for example IOException for IFile.Read), including a short description of the error situation.

Critical errors

A critical error is an unrecoverable situation, which should never happen. Allowing code to throw an exception in these cases would reduce the amount of non-throwing code significantly (which would limit the use of the C++ noexcept operator, for example).

The Tinman 3D SDK uses dedicated panic methods to handle critical errors:

An application may define its own panic handler with TinmanException.PanicHandler.

If the panic handler returns, the behaviour of subsequently executed code may be undefined.

Semantic Rules

Attributes and interface are used to establish a number of standard semantic rules, which are used throughout the Tinman 3D SDK.

  • BetaAttribute
    The type or member is in beta state and should be considered experimental.

  • ConstantAttribute
    A property will return the same value throughout the life-time of the enclosing object.

  • DebugHelperAttribute
    A member provides ancillary features that are solely intended to be used for debugging and testing.

  • EmptyBodyAttribute
    A virtual member has a trivial body and does not need to be called from overriding members.

  • FlagsAttribute
    The value of an enumeration is a bit-field and may thus be composed of zero or more enumeration items.

  • PureAttribute
    The method does not have any side-effects.

  • SemanticEnumAttribute
    The ordinal values of the enumeration items are used in binary storage formats and must not change.

  • ShutdownAttribute
    There is only a single global shutdown method for the Tinman 3D SDK: TinmanModule.Shutdown.

  • StaticTemplateAttribute
    The interface is a static template for high-performance code.

  • ThreadSafeAttribute
    The member may be invoked concurrently by multiple threads.

The following attributes only need to be considered when using the Code-X workflow (that is, using the CxCodeUnit class to parse, validate and generate source code) to implement own libraries and may be ignored otherwise:

Since Code-X is based on a subset of C#/.NET, the following well-known system types are re-used, which get translated to their semantic counter-parts in other programming environments:

By using well-known system types, language specific code constructs may be used when consuming a Code-X library, for example the foreach and using statements of C#.

Begin/end blocks are used frequently in the Tinman 3D SDK.

Code block with begin/end semantics
obj.Begin();
try
{
  obj.DoSomething();
}
finally
{
  obj.End();
}

Classes that exhibit begin/end semantics implement the IBeginEnd interface. Additional semantic may be present, as indicated by IBeginEndWrite and IBeginEndSync.

Methods that require an active begin/end block (such as the DoSomething method above) are annotated with BeginEndAttribute or BeginWriteEndAttribute.

API Libraries

This section provides an overview of the types that are present in the libraries of the Tinman 3D SDK.

The libraries of the Tinman 3D SDK contain considerable amount of functionality that is also available in other standard libraries, such as .NET Standard, C++ Standard Template Library or Boost. At first glance, this may seem like "re-inventing the wheel", but actually makes sense when taking the following into account.

The Tinman 3D SDK is maintained in a subset of the C# programming language (in terms of syntax and semantic), which is named Code-X. Using a proprietary source code analysis and transformation engine, the Code-X sources are transformed to other programming languages (e.g. C++, Java, Rust) in a fully automated process.

For details on the syntax and semantic of Code-X, please refer to CxCodeUnit and Code-X Framework.

In this context, the benefit of having a concise and well-tested codebase in Code-X with uniform behaviour that can be transformed to other languages outweighs the cost of "re-inventing the wheel" for standard algorithms and data structures.

Core

The Core library contains general-purpose code that is not terrain-specific.

Tinman.Core

  • AlgorithmUtil
    General-purpose helpers for common algorithms

  • ArrayUtil
    General-purpose helpers for dealing with arrays

  • StringUtil
    General-purpose helpers for strings

  • SystemUtil
    General-purpose helpers for common programming tasks

  • TinmanModule
    Base class for code module descriptors

Tinman.Core.Caching

  • ICache
    Basic caching for mapping generic IDs to logical pages of information

  • DataCache
    Thread-safe caching with support for asynchronous reading and writing of cache page data

Tinman.Core.Codec

Tinman.Core.Collections

Tinman.Core.Config

Tinman.Core.Cx

  • CxCodeUnit
    The program structure information (PSI) model for Code-X

Tinman.Core.Database

  • BlockStorage
    Lightweight embedded block-based database engine

Tinman.Core.Document

  • TextDocument
    Object model for simple text-based documents.

Tinman.Core.Formatting

Tinman.Core.IO

Tinman.Core.Licensing

Tinman.Core.Logging

Tinman.Core.Math

Tinman.Core.Parsing

Tinman.Core.Privileges

  • Privilege
    Privilege-based access control for utilization of native APIs and system resources by the SDK

Tinman.Core.System

Tinman.Core.Threading

Tinman.Core.Util

Tinman.Core.Validating

Tinman.Core.Xml

  • XmlDocument
    A minimalistic XML parser and document model.

Terrain

The Terrain library contains all the terrain-specific features of the Tinman 3D SDK. Using this library is sufficient to integrate these feature into an application.

Tinman.Terrain.Buffers

Tinman.Terrain.Codec

  • IBufferCodec
    Codecs for lossless compression of sample grids

  • LodPartition
    Level-of-detail partitioning of sample grids

Tinman.Terrain.Datasets

Tinman.Terrain.Geometries

Tinman.Terrain.Georef

Tinman.Terrain.Heightmaps

Tinman.Terrain.Imaging

Tinman.Terrain.Kernel

Tinman.Terrain.Mapping

Tinman.Terrain.Meshing

Tinman.Terrain.Procedural

Tinman.Terrain.Pyramids

Tinman.Terrain.Rendering

Tinman.Terrain.Shapes

Tinman.Terrain.Tools

  • ICanvasFactory
    Pixel-perfect rendering of raster or pyramid data on a 2D canvas

  • GridLineBuilder
    Generation of graticule lines on projected maps

  • MapView
    Smooth rendering of raster or pyramid data on a 2D canvas with scrolling, zooming and projection

Tinman.Terrain.Util

Tinman.Terrain.Vertices

Tinman.Terrain.Visibility

Engine

The Engine library provides a lightweight engine for realtime applications. It may optionally be used, in addition to the Terrain library.

Tinman.Engine.API

  • GLBase
    Bindings for GL-style C-APIs, such OpenGL and WGL

Tinman.Engine.Application

Tinman.Engine.Drawing

Tinman.Engine.GUI

Tinman.Engine.Models

Tinman.Engine.Particles

Tinman.Engine.Profiling

  • IProfiler
    Object model for collecting performance profiling values at runtime

Tinman.Engine.Rendering

Tinman.Engine.Scenes

Tinman.Engine.Widgets

AddOns

The AddOns library contains several modules that can be integrated into an application, as required. These modules can be seen as plug-ins, mainly to interface with other 3rd-party libraries.

Tinman.AddOns.Application

Tinman.AddOns.Assimp

Tinman.AddOns.DirectX…​

Tinman.AddOns.Editors

Tinman.AddOns.Export

  • Example code for exporting terrain data.

Tinman.AddOns.GDAL

Tinman.AddOns.Heightmaps

  • Example implementations of the IHeightmap interface.

Tinman.AddOns.MFC

Tinman.AddOns.OpenFlight

Tinman.AddOns.OpenGL…​

Tinman.AddOns.Pyramids

Tinman.AddOns.Scenes

  • Example implementations and components for the Scene API.

Tinman.AddOns.SQLite

Tinman.AddOns.Widgets

Tinman.AddOns.WinForms

Tinman.AddOns.WPF

Processor

The Processor library contains the source code of the Geodata Processor tool. It is contained in Source licence.

Tinman.Processor

Demo

The Demo library contains all examples and tutorials of the Demo Application.

Tinman.Demo.Examples

Tinman.Demo.Tutorials

Abstraction Layers

This section gives an overview of the abstraction layers in the Tinman 3D SDK, which provide platform-agnostic API entry points to platform-specific features.

In Code-X, platform-specific code is wrapped in so-called native regions, which must be implemented for each programming language and for each platform. During the source code transformation process, the contents of native regions is retained, by tracing it with the native region GUID.

With a source code licence, it is possible to modify or re-implement all native regions of the Tinman 3D SDK.
Example native region in C#
// NATIVE: Flush file buffers.
// THROWS: IOException

#region Native {49EF28C7-AC50-465E-85EC-AB0BAB73CF56} (1)

if (LowLevel.IsPosix) (2)
{
  if (SafeNativeMethods.fsync((int) hFile) != 0)
    throw IOException.FileError("LocalFile.Flush", pathInfo);
}
else if (LowLevel.IsWindows)
{
  if (!SafeNativeMethods.FlushFileBuffers(hFile))
    throw IOException.FileError("LocalFile.Flush", pathInfo);
}
else (3)
{
  try
  {
    lock (stream)
      stream.Flush(true);
  }
  catch (Exception e)
  {
    throw IOException.FileError("LocalFile.Flush", pathInfo, e);
  }
}

#endregion
1 Native region marker
2 Platform detection
3 Fallback to .NET Standard
Example native region for in C++
// NATIVE: Flush file buffers.
// THROWS: IOException

NATIVE_REGION_BEGIN ("49EF28C7-AC50-465E-85EC-AB0BAB73CF56") (1)

#if defined(CODEX_PLATFORM_POSIX) (2)

if (fsync(hFile) != 0)
  throw IOException::FileError("LocalFile.Flush"_CX, pathInfo);

#elif defined(CODEX_PLATFORM_WIN)

if (!FlushFileBuffers(hFile))
  throw IOException::FileError("LocalFile.Flush"_CX, pathInfo);

#else

#error CODEX_PLATFORM_UNSUPPORTED

#endif

NATIVE_REGION_END   ("49EF28C7-AC50-465E-85EC-AB0BAB73CF56")
1 Native region marker
2 Platform detection

CPU Threading

CPU Threading
Figure 1. Overview of the CPU Threading layer

Using CPU threads is simple: first, implement a IThreadMain class. This is the thread’s main function. Then, create a Thread object, which will wrap the thread main function and run it in the background.

To synchronize between threads, create and use Monitor objects, as required.

The C# SDK contains additional debug helpers, to detect performance issues caused by overuse of thread synchronization:
Monitor.DebugDumpMonitorUsage
Monitor.DebugDumpWaitTimes

The RingBuffer class may be used to control the data flow between a producer and a consumer.

Lengthy background operations are be represented with IOperation objects. The IOperationOps interface may be used to aggregate and combine existing operations in different ways. The RunningOperation class provide a simple way of running a background operation, while tracking its progress.

The ITask and ITaskVoid interfaces represent tasks which can be scheduled by the TaskPool for background execution. The TaskResult and TaskResultVoid classes represent the pending result of a background task. With ITask.Queue, a ITaskQueue object can be created, which provides control over how tasks are executed.

Filesystem

Filesystem
Figure 2. Overview of the Filesystem layer

A file system is represented with the IFileSystem interface, which provides access to the contained files, which in turn are represented as IFile objects. Files are referred to with Path values.

Creating a FileSystem object will add it to the global list of active file systems, which is used to find the file system that knows how to open a given Path value, see FileSystem.From. Disposing a FileSystem object will remove it from the global list.

The grammar of Path values is designed so that it captures all common file path flavours:
c:\Windows\File.txt
\\UNC\$c\Windows\File.txt
/unix/file
https://website.net/file.txt

These are the built-in file system implementations of the Tinman 3D SDK:

GPU Rendering

The access to GPU rendering features is provided through a number of interfaces. This API is divided between the Terrain and Engine libraries.

Terrain

GPU Rendering 1
Figure 3. Overview of the GPU Rendering layer - part 1

The IBufferFactory interface is used to create GPU buffers. The ITextureFactory interface is used to create texture resources. Buffers and textures are processed by the Low-level Terrain API:

GPU Rendering 2
Figure 4. Overview of the GPU Rendering layer - part 2

An application may then use suitable implementations of the following interfaces to perform GPU rendering with its own graphics engine:

Engine

GPU Rendering 3
Figure 5. Overview of the GPU Rendering layer - part 3

The High-level Terrain API is built around the IGraphicsContext interface, which represents the basis of the graphics engine of the Tinman 3D SDK.

To obtain a graphics context, an instance of IGraphicsContextFactory must be created and configured. There is a dedicated implementation class for each supported render API.

GPU Rendering 4
Figure 6. Overview of the GPU Rendering layer - part 4

GPU programs are represented with IRenderEffect objects. A render effect exposes its parameters in a way that is independent of the underlying render API, by implementing a subclass of RenderEffectParameters.

A render effect must have a separate implementation for each render API. An instance of IRenderEffectFactory is used to choose the render effect implementation, based on a given graphics context and the given render effect parameters.

Native Libraries

The NativeLibrary class is used to interface with native libraries, which involves the following steps:

  1. Find a native library binary and load it.

  2. Query native function pointers.

  3. Wrap native functions with platform-agnostic API entry points.

Step 1. is controlled by implementing LibraryNames and may further be hinted by providing additional library search paths via AddSearchPath.

Step 2. is performed by subclasses of NativeLibrary, which use GetFunctionPointer to discover native functions.

Step 3. is implemented by the source code parts of the NativeLibrary subclasses that are covered by Code-X translation, as described in API Libraries.

Subclasses of NativeLibrary are usually an implementation detail and are not part of the public API, for example AssimpModelFormat or GdalImageFormat.
Classes that deal with native functionality usually implement the INativeHandle interface, which allows client code to interface with the underlying objects directly. For details on how to interpret the native handle value, please refer to the documentation of the respective class.

Networking

Networking
Figure 7. Overview of the Networking layer

The Tinman 3D SDK provides networking capabilities for streaming geodata at runtime. Streaming may be performed over HTTP / HTTPS or plain TCP/IP.

Web Requests

Web resources may be fetched with HTTP GET requests by using the ISimpleHttp interface, usually through an instance of the standard implementation SimpleHttp.

The SimpleHttp class uses the HTTP / HTTPS features of the computing environment. It does not re-implement the network stack for web requests.

For full control over web requests, client code may choose to use its own implementation of ISimpleHttp. Alternatively, client code may use custom implementations of ISimpleHttpImage and ISimpleHttpText to provide image and text decoding capabilities that are not part of the Tinman 3D SDK.

Client/Server

Synchronous stream-based client-server communication is provided by the IEndPoint interface, which acts as a factory for IServer and IConnection objects.

The standard implementation SocketEndPoint uses the TCP/IP stack of the computing environment.

Window System

The Engine library allows to create a separate top-level application window via ApplicationWindow.Create, for running IApplication objects directly on the desktop of the computing environment.

For integration with a specific GUI framework, use one of the IApplicationControl implementations, which use a custom implementation of the IApplicationWindow interface that is not exposed to client code.

By writing custom implementations of IApplicationWindow, client code may provide additional GUI framework integrations, which are not part of the Tinman 3D SDK.

Domain Models

Several non terrain-specific domain models are part of the Tinman 3D SDK. These are used to access and import data. An overview of the domain models is presented here. For further details on how to import data, please refer to Geodata Processing.

Pixel Images

A common way to provide geodata is in form of regular sample grids, which are typically stored a as image files (for example: GeoTIFF, PNG, JPEG). A geo-reference may be present, which maps the center of image pixels to map coordinates.

Image data is processed incrementally, one pixel row after another, using IImageReader objects. During processing, various operations and conversions may be applied, as defined by IImageOps.

Image readers are obtained from image descriptors, which are IImage objects. In most cases, an image descriptor is created for a file with Image.File and then modified, for example by Scaling. Image descriptors may also be defined via scripting by using the Image type.

In theory, image files include all the required metadata to import the image data properly. In practice, it is often the case that some metadata is missing and other metadata is wrong. In these cases, image descriptors need to be adjusted manually. These are the most common pitfalls:

  • sRGB / Gamma
    If gamma correction is not applied correctly, surface textures will look wrong (too dark, to bright), whereas digital elevation models will have incorrect height values.

  • Value range
    The range of pixel values (for example 0 to 255 for 8-bit grayscale images) must be mapped to elevation and displacement ranges (for example -10,000m to +10,000m) at some point. It might be necessary to adjust the pixel range of the source image, in order to achieve optimal quality and compression.

  • Pixel coverage
    The pixel coverage defines how to interpret pixel coordinates with respect to geo-referencing. If the pixel coverage is wrong, inaccuracies will be introduced, having the same magnitude as the ground sample distance.

  • No-data value
    Geodata often defines a special pixel value as void, to identify image regions for which no data is present. Those regions must be imported and processed in a different way than transparent regions, which are defined by pixel alpha values.

  • Data layer
    Image data may be interpreted in different ways, for example as elevation models or surface imagery (see HeightmapLayer). If the layer is not chosen correctly, geodata will not show up as expected.

To write an image file in a supported image format, use ImageFormat.WriteImage to obtain an IImageWriter object. The writer will consume the data of a given IImageReader object and write the resulting image file.

For additional information, please refer to Geodata Processing / Pixel Data.

2D Shapes

In the Tinman 3D SDK, shapes are represented with IShape objects, which define a signed distance field in a two-dimensional coordinate system. In addition to the distance function, a shape may provide information about its geometric structure, by exposing its vertices, edges, polygon contours and triangles.

To obtain a IShape object, use one of the constructors of the Shape class. This includes loading a shape from a supported shape file format. Shapes may also be constructed via scripting by using the Shape class.

If a shape file contains more than one shape definition, Shape.File will use a Shape.Group to represent them. To read the shape definitions sequentially, use ShapeFormat.ReadShapes and then consume the returned IShapeReader.

To build a geometric shape from scratch, use a ShapeBuilder object, which may be obtained from Shape.Geometry.

For additional information, please refer to Geodata Processing / Shape Data.

Rasterization

Shapes can be rasterized in order to generate terrain data dynamically at runtime:

Since the generated terrain data itself is represented as IHeightmap and IPixelPyramid objects, it may be combined freely with other terrain data objects.

A common use for vector shapes is to crop terrain data to specific regions of interest (see TextureLayer.Region).

Geometric Subdivision

When transforming geometric shapes, subdivision is performed in order to maintain a given accuracy. This creates additional vertices, edges and triangles.

Subdivision is required because a conversion between two coordinate systems may not be representable with an affine transformation matrix:

  1. IShapeOps.TransformGeometry
    An immediate transformation to another coordinate system, where subdivision may either be applied in the target 2D coordinate system or in the corresponding 3D geocentric coordinate system

    Subdivision in the 3D geocentric coordinate system is usually required when extruding shapes to 3D models via IShapeExtruder.
  2. IShapeOps.TransformCoordinateSystem
    A deferred transformation to another coordinate system that delegates to 1.

  3. IShapeOps.TransformCubemap
    A deferred transformation to the faces of a geographic cubemap coordinate system that delegates to 1.

3D Models

In the Low-level Terrain API, there is no use for 3D models.

The High-level Terrain API allows to place models on a TerrainMesh, by using TerrainModel objects. Terrain data can be produced by ray-casting 3D models, yielding elevation and displacement data, which may be used as a brush for terrain painting, for example.

The Scene API builds on that and adds features for populating interactive 3D scenes with 3D model data which are not strictly terrain-specific.

A 3D model is represented as a hierarchy of IModel objects, where a parent model may have zero or more child models, associating an affine transformation with each one.

This object model is a subset of glTF 2.0, with only minor semantic and structural differences.

Actual 3D geometry is represented with IModelPart objects, where a model may have zero or more model parts. A model part contains this information:

  • A reference to a shared model geometry buffer

  • A batch that represents graphics primitives in the referenced buffer, such as points, lines and triangles

  • A reference to a shared material definition, which uses textures for physically-based rendering (PBR)

The interfaces of this object model provide read-only access. For read/write access, use the default implementation class.

Table 2. Interfaces and default implementations
Read-only Read/Write

IMaterial

Material

IMaterialPart

MaterialPart

IModel

Model

IModelGeometry

ModelGeometry [4]

IModelPart

ModelPart

IModelTexture

ModelTexture [4]

To modify a 3D model through the read-only interfaces without changing its actual definition, use a ModelPose object and change the transformation or material of selected IModel objects.

To actually modify the definition of a 3D model, use IModel.Mutable, IModelPart.Mutable and IMaterial.Mutable to obtain a read/write object reference.

For spatial queries such as picking or distance computation, use the IModelCollider object that may be obtained via IModel.Collider. Use IModelCollider.ColliderWithPose to obtain a model collider that takes the current state of the given ModelPose into account.

For additional information, please refer to Geodata Processing / Model Data.

Geo References

The Raster class provides a complete definition of a geo-reference for a sample raster. The corresponding scripting type is Tinman.Georef.

The CoordinateSystem class provides a complete definition of a geo-reference for a rectangular map. The corresponding scripting type is CoordSys.

The object model for geo-references is based on the IGeoObject interface. Prebuilt object model graphs are provided via GeoRegistry objects.

The Tinman 3D SDK contains a registry for the EPSG Geodetic Parameter Dataset. After loading the registry file epsg-*.dat, it can be accessed from scripting with EPSG.

Frameworks

The following sections describe the frameworks that are contained in the Tinman 3D SDK.

2D / 3D Graphics

Based on the GPU Rendering abstraction layer, the following render utilities exist in the Engine library.

Application

The Application framework of the Engine library provides the IApplication interface, which can be implemented in order to create a realtime application. Based on the 2D / 3D Graphics and Window System abstraction layers, it provides standard means to run an application in different environments.

The Hello World example shows how to create and run an application with minimal code.

To run an application, use the IApplication.Run method. To host an application in a native GUI framework, use one of the IApplicationControl implementations.

Extend the WidgetApplication class if you want your application to run one or more IWidget objects.

The application cycle is defined and performed by the ApplicationLoop class. If required, the application loop may be invoked by client code (see ApplicationLoop.Main), but usually this is done automatically by a helper in the SDK.

An application may optionally interact with its execution context through the IApplicationRunner interface, for example to toggle full-screen mode.

Collections

A simple collection framework is included in the Core library. Where applicable, integration with the native APIs is provided (for example, to be able to use foreach alike statements).

Collection classes always implement the IEnumerable interface, where the contained elements are returned by an IEnumerator object.

Collection classes may implement the ICollector interface, which provides a standard way to populate a collection with elements.

Definitions for element equality, sort order and hash values are provided by EqualsDelegate, CompareDelegate and HashCodeDelegate. If a definition is omitted, the default definition is used (see EqualsDefault and HashCodeDefault).

By convention, a collection object should be accessed through one of the collection interfaces. Separate interfaces exist for immutable access (having a Const name suffix) and mutable access (no name suffix).
Table 3. Collection interfaces and default implementations
Semantic Immutable Mutable Default Implementation

bag

IBagConst

IBag

Set, EmptySet, OrderedSet

sorted set

ISortedSetConst

ISortedSet

TreeSet

list

IVectorConst

IVector

ArrayVector, EmptyVector, ListeningVector

deque

-

IDeque

ArrayDeque

map

IMapConst

IMap

Map, IdentityMap

sorted map

ISortedMapConst

ISortedMap

TreeMap

bijective map

IBijectiveMapConst

IBijectiveMap

BijectiveMap

Logging

The Logging framework in the Core library is used to output log messages at runtime.

The purpose of this framework is not to re-implement yet another logging framework, but rather to provide a common API for logging purposes, especially for Code-X compliant code.

Log messages are generated via ILogger objects, which must be obtained with LoggingUtil.GetLogger. Each logger object belongs to a ILoggerCategory, which may also have a parent category.

Generated log messages are processed by zero or more ILogMessageHandler objects, for example by outputting a message on the process console or writing an entry into a logfile. Log message handlers may be attached to loggers and categories.

Each log message has a verbosity level, which is compared against the verbosity level of the log message source and the log message handlers, to decide whether to process the log message or not.

To quickly setup a standard configuration for logging, use LoggingUtil.BasicConfiguration.

Parsing

The Parsing framework in the Core library can be used to specify context-free grammars, using a proprietary specification language similar to the Extended Backus-Naur form (EBNF).

After constructing a Grammar object, it can be used to parse textual input, perform syntax analysis and output the resulting abstract syntax tree (AST).

Passing RuleToSourceFlags.GrammarAst to Grammar.ToSource produces the tree grammar, which is helpful for implementing code that consumes an AST.

The Grammar class does not use a separate lexer step. Instead, the stream of input characters is consumed directly by the grammar rules.

Scene

The Scene framework in the Engine Engine library implements the Scene API, which can be used to create interactive 3D scenes, based on a realtime 3D terrain.

This framework provides ready-to-use solutions for the features of the Tinman 3D SDK. It is the highest level API and can be used by developers to create 3D applications.

With a source code licence, it can serve as reference and example on how to add terrain features to an existing 3D application engine.

For details on the Scene framework, please refer to Scene Overview.

Scripting

The Scripting framework in the Core library defines a simple script language for describing the construction of native API objects, via IConfigurator objects.

By implementing the IConfigurable interface, native API objects become convertible to script code, which can be used to re-construct the object in its current state.

For details on the Scripting framework, please refer to Scripting Overview.

The Workshop Application can be used to edit scripts, construct native API objects and inspect them.

User Interface

The User Interface framework in the Engine library can be used to add a graphical user interface (GUI) to an IApplication or IWidget.

Its main purpose is to allow a developer to setup GUI controls quickly from code, to do rapid prototyping, testing and development.

For details on the User Interface framework, please refer to User Interface.


1. With a Source code licence, the restriction may be removed by applying a small code patch. Please contact support for details.
2. To obtain help from support, it might be necessary to run in DEBUG mode. Reporting in RELEASE mode might be insufficient.
3. Since version V1.0 RC4.20
4. This class acts as a factory for built-in model geometry resp. texture objects, which are (mostly) read-only.