tyVolumeObjectExt Interface

The tyVolumeObjectExt interface can be used to query tyFlow volume data.

Developers testing their implementation of the interface can pass “debug” as the plugin name when calling “UpdateVolumes” on a tyFlow object. This will generate density/color values in the shape of a sphere, across a set of 64 individual non-AABB volumes. To preview the structure/shape of these volumes, enable “Display volume interface preview” in the “Interfaces” rollout of the tyFlow object. This will generate a point/line display of the data in the viewport, so developers can see whether their interpretation of the same data is valid. For proper release builds, ensure you do not pass “debug” as the plugin name, as doing so will prevent normal volume data from passing through the interface.

/*Copyright (c) 2025, Tyson Ibele Productions Inc.
All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this file ("tyVolumeObjectExt.h") and associated documentation files (the
"Software"), to deal in the Software without restriction, including without
limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#pragma once

// headers from Max SDK
#include "box3.h"
#include "matrix3.h"
#include "object.h"
#include "point2.h"
#include "point3.h"
#include "point4.h"

#define TYVOLUME_INTERFACE_V1 Interface_ID (0x1213b15, 0x1e23512)

/*

tyVolumeObjectExt (v1) CHANGELOG:

05/20/2024:

* initial creation of basic volume interface

*/

namespace tyFlow
{
	/*
	A wrapper class containing all relevant properties and
	accessor functions for a given volume.
	*/
	class tyVolume
	{
	public:
		enum ScalarType : int
		{
			density,
			fuel,
			temperature,
		};

		enum VectorType : int
		{
			color,
			velocity,
		};

		/*
		The world-space transform of the volume, relative
		to its zero-th voxel coordinate.
		*/
		Matrix3 transform;

		/*
		The number of voxels the volume contains
		along each X/Y/Z axis. The total number of
		voxels for a given volume is:
		dimensions.x * dimensions.y * dimensions.z
		*/
		IPoint3 dimensions;

		/*
		Accessor functions and enums which allow you to query
		per-voxel volume values, given a volume-local XYZ coordinate
		and a voxel data type.
		Fractional coordinates will be trilinear interpolated.
		*/
		virtual float GetScalar(const Point3 xyz, const ScalarType type) = 0;
		virtual Point3 GetVector(const Point3 xyz, const VectorType type) = 0;

		/*
		Accessor functions which allow you to query fire display
		values, that match tyFlow's own viewport display of
		volume fire. By passing a temperature value (obtained
		through the GetScalar function), fire opacity/color
		values will be returned, which correspond to the fire
		opacity/color gradients assigned in tyFlow's volume
		display UI.
		Color values are not clamped and may have xyz components
		greater than 1, depending on the fire color intensity
		value assigned in tyFlow's volume display UI.
		Fractional coordinates will be trilinear interpolated.
		*/
		virtual float GetFireOpacity(const float temperature) = 0;
		virtual Point3 GetFireColor(const float temperature) = 0;

		/*
		Accessor functions which allow you to query per-volume
		opacity multipliers, assigned in tyFlow's volume display UI.
		These multipliers should be applied uniformly to all
		voxels in the volume.
		*/
		virtual float GetDensityOpacityMultiplier() = 0;
		virtual float GetFuelOpacityMultiplier() = 0;
		virtual float GetOverallOpacityMultiplier() = 0;

		/*
		Helper function to return this volume's
		logical coordinates, relative to the local parent
		interface origin (the inverse transform of the first
		volume returned by the parent interface).

		The validity of logical coordinates is dependent
		on the following three volume interface axioms:

		(1) All volumes returned by a particular
		volume interface have the same scale and voxel dimensions.

		(2) No two volumes returned by a particular volume
		interface will share the same world-space transform.

		(3) All volume local-interface-coordinates
		are evenly-divisible by their dimensions.

		These axioms entail that all volumes returned
		by a particular interface will be the same size, and be
		non-overlapping.

		Given these axioms, it's easy to calculate logical
		coordinates for a volume, using the following
		formula:

		Point3 logicalCoordinate =
		(
				volume->transform *
				Inverse(interface->GetVolume(0)->transform)
		).GetTrans() / volume->dimensions;

		So, the logical coordinates of the first volume
		returned by a volume interface would be [0,0,0].
		A volume immediately adjacent to the right of
		the first volume would have logical coordinates
		of [1,0,0]. A volume immediately adjacent to the
		top of that volume would have logical coordinates
		of [1,0,1], and so on.

		Because the logical coordinates of volumes can be
		hashed in a fewer number of bits than regular world-space
		coorindates, they are optimal candidates for spatial
		hashing structures which may be used to accelerate
		world-space-to-volume lookups. You can also use
		logical coordinates to quickly query an interface for
		the neighbor volumes of a particular volume, when doing
		things like trilinear interpolation. Ex:

		//get neighbor to the right of this volume, if it exists
		tyVolume* neighborVolume =
		interface->GetVolume(volume->GetLogicalCoordinates() +
		IPoint3(1,0,0));
		*/
		virtual IPoint3 GetLogicalCoordinates() = 0;
	};

	/*
	The tyVolumeObjectExt interface class can be used to access
	a tyFlow object's volume data.
	*/
	class tyVolumeObjectExt_v1
	{
	  public:
		/*
		This function prepares tyFlow volumes for API access and
		should be called on a per-frame basis prior to calling any
		other volume API functions.

		The plugin argument of this function takes the name of the
		plugin querying this interface, in lowercase letters.
		Ex: _T("arnold"), _T("redshift"), _T("vray"), etc. This is
		a somewhat arbitrary value, but by having plugins identify
		themselves during a query, tyFlow can internally determine
		if any plugin-specific edge-cases need to be processed.

		If you pass "debug" as the plugin name, a set of debug
		volumes will be generated, whose combined densities form
		a sphere at the world origin, with interpolated RGB coloring.
		This mode can be  useful for sanity-checking code that
		references this interface. A point-based preview of this
		data can be displayed in the viewport by enabling "Display
		volume debug data" in the "Debugging" rollout of any
		tyFlow object.
		*/
		virtual void UpdateVolumes(const TimeValue t, const TSTR plugin) = 0;

		/*
		Developers should call this once they are done processing
		tyFlow volume data, to clear any internal allocations that
		may have been made during the prior call to UpdateVolumes.
		*/
		virtual void ReleaseVolumes() = 0;

		/*
		Returns the total number of volumes available. Depending on
		the complexity of sparse simulation data exposed by this
		interface, the number of volumes returned may be in the
		tens of thousands. All tyVolume accessor functions are
		thread-safe, so multi-threaded processing of these volumes
		is recommended.
		*/
		virtual int NumVolumes() = 0;

		/*
		Returns a tyVolume by index (range: 0 - [NumVolumes()-1]).
		The interface retains ownership of these pointers and
		cleans them up in the call to ReleaseVolumes().
		*/
		virtual tyVolume* GetVolume(const int index) = 0;

		/*
		Returns the tyVolume (if it exists, otherwise NULL) located
		at the specified logical coordinate point, as well as
		its interface index.
		The interface retains ownership of these pointers and
		cleans them up in the call to ReleaseVolumes().
		*/
		virtual tyVolume* GetVolume(
			const IPoint3 logicalCoordinates, 
			int &index) = 0;

		/*
		Converts a world-space position coordinate into a composite
		volume coordinate. The index component of the coordinate is
		the volume index (or -1 if the position coordinate is outside
		of all volumes), and the xyz component of the coordinate is
		the volume-local voxel XYZ coordinate that can be used to
		query per-voxel values of the indexed volume using the volume's
		scalar/vector accessor functions.
		*/
		struct VolumeCoordinate
		{
			int index = -1;
			Point3 xyz;
		};

		virtual VolumeCoordinate GetVolumeCoordinate(const Point3 pos) = 0;

		/*
		Helper function to convert temperature values from internal,
		normalized units to the specified unit type.
		*/
		enum TemperatureUnitType : int
		{
			normalized,
			celcius,
			fahrenheit,
			kelvin,
		};

		virtual float ConvertTemperature (
			const float temperature,
			const TemperatureUnitType units) = 0;
	};

	typedef tyVolumeObjectExt_v1 tyVolumeInterface;

	/*
	Helper function to return the tyVolumeInterface from any given
	base object, if it's implemented. Developers should not
	assume that only tyFlow objects will return the volume interface,
	as other tyFlow-adjacent object classes may be extended to return
	this interface in the future (ex: tyCache). In other words, check
	all base objects for this interface, not just objects with a
	matching tyFlow ClassID.
	*/
	inline tyVolumeInterface *
	GetTyVolumeInterface (BaseObject *obj)
	{
		return (tyVolumeInterface *)obj->GetInterface (TYVOLUME_INTERFACE_V1);
	}
};