netradiant-custom/contrib/meshtex/MeshEntity.cpp
2022-10-27 09:10:31 +03:00

1821 lines
67 KiB
C++

/**
* @file MeshEntity.cpp
* Implements the MeshEntity class.
* @ingroup meshtex-core
*/
/*
* Copyright 2012 Joel Baxter
*
* This file is part of MeshTex.
*
* MeshTex is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* MeshTex is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MeshTex. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cmath>
#include "MeshEntity.h"
#include "MeshEntityMessages.h"
#include "ishaders.h"
#include "texturelib.h"
/**
* Size of buffer for composing messages to send to the info callback.
*/
#define INFO_BUFFER_SIZE 1024
/**
* Stop successive refinement of path length estimates when the change in values
* is equal to or less than this tolerance.
*/
#define UNITS_ERROR_BOUND 0.5
/**
* Macro to get ROW_SLICE_TYPE from COL_SLICE_TYPE and vice versa, used in
* code that can operate on either kind of slice. This does mean that the
* numerical values assigned to ROW_SLICE_TYPE and COL_SLICE_TYPE are
* meaningful.
*
* @param sliceType Kind of slice to find the other of, so to speak.
*/
#define OtherSliceType(sliceType) (1 - (sliceType))
/**
* Macro to find the number of control points in a slice, which is equal to
* the number of slices of the other kind.
*
* @param sliceType Kind of slice to measure.
*/
#define SliceSize(sliceType) (_numSlices[OtherSliceType(sliceType)])
/**
* Macro to get rid of negative-zero values.
*
* @param floatnum Number to be sanitized.
*/
#define SanitizeFloat(floatnum) ((floatnum) == -0.0f ? 0.0f : (floatnum))
/**
* For a given slice kind, which texture axis (S or T) normally changes
* along it.
*/
MeshEntity::TextureAxis MeshEntity::_naturalAxis[NUM_SLICE_TYPES] =
{ S_TEX_AXIS, T_TEX_AXIS };
/**
* For a given slice kind, whether Radiant's "natural" scale along a texture
* axis is backwards compared to the progression of the indices of the
* orthogonal slices.
*/
bool MeshEntity::_radiantScaleInverted[NUM_SLICE_TYPES] = { false, true };
/**
* For a given slice kind, whether Radiant's interpretation of tiling along a
* texture axis is backwards compared to the progression of the indices of
* the orthogonal slices.
*/
bool MeshEntity::_radiantTilesInverted[NUM_SLICE_TYPES] = { false, false };
/**
* Message format strings for describing texture mapping on a slice.
*/
const char *MeshEntity::_infoSliceFormatString[NUM_SLICE_TYPES] =
{ INFO_ROW_FORMAT, INFO_COL_FORMAT };
/**
* Message format strings for describing texture mapping on a slice in the
* unusual case where the scale value is infinite.
*/
const char *MeshEntity::_infoSliceInfscaleFormatString[NUM_SLICE_TYPES] =
{ INFO_ROW_INFSCALE_FORMAT, INFO_COL_INFSCALE_FORMAT };
/**
* Message format strings for warning that a scale value is infinite and
* cannot be transferred to the Set S/T Scale dialog.
*/
const char *MeshEntity::_warningSliceInfscaleFormatString[NUM_SLICE_TYPES] =
{ WARNING_ROW_INFSCALE, WARNING_COL_INFSCALE };
/**
* Message format strings for an illegal slice number error.
*/
const char *MeshEntity::_errorBadSliceString[NUM_SLICE_TYPES] =
{ ERROR_BAD_ROW, ERROR_BAD_COL };
/**
* Message format strings for a scale = 0 error.
*/
const char *MeshEntity::_errorSliceZeroscaleString[NUM_SLICE_TYPES] =
{ ERROR_ROW_ZEROSCALE, ERROR_COL_ZEROSCALE };
/**
* Message format strings for a tiles = 0 error.
*/
const char *MeshEntity::_errorSliceZerotilesString[NUM_SLICE_TYPES] =
{ ERROR_ROW_ZEROTILES, ERROR_COL_ZEROTILES };
/**
* Constructor. If the constructor is unable to process the input mesh, then
* the internal valid flag (queryable through IsValid) is set false, and the
* errorReportCallback is invoked.
*
* @param mesh The patch mesh to construct a wrapper for.
* @param infoReportCallback Callback for future informational messages.
* @param warningReportCallback Callback for future warning messages.
* @param errorReportCallback Callback for future error messages.
*/
MeshEntity::MeshEntity(scene::Node& mesh,
const MessageCallback& infoReportCallback,
const MessageCallback& warningReportCallback,
const MessageCallback& errorReportCallback) :
_mesh(mesh),
_infoReportCallback(infoReportCallback),
_warningReportCallback(warningReportCallback),
_errorReportCallback(errorReportCallback)
{
// Get a handle on the control points, for future manipulation.
_meshData = GlobalPatchCreator().Patch_getControlPoints(_mesh);
// Record some useful characteristics of the mesh.
_numSlices[ROW_SLICE_TYPE] = static_cast<int>(_meshData.x());
_numSlices[COL_SLICE_TYPE] = static_cast<int>(_meshData.y());
const char *shaderName = GlobalPatchCreator().Patch_getShader(_mesh);
IShader *shader = GlobalShaderSystem().getShaderForName(shaderName);
qtexture_t *texture = shader->getTexture();
if (texture != NULL)
{
_naturalTexUnits[S_TEX_AXIS] = texture->width / 2.0f;
_naturalTexUnits[T_TEX_AXIS] = texture->height / 2.0f;
}
// We don't need the shader for anything else now.
shader->DecRef();
// Check for valid mesh; bail if not.
if (_numSlices[ROW_SLICE_TYPE] < 3 ||
_numSlices[COL_SLICE_TYPE] < 3 ||
texture == NULL)
{
_valid = false;
_errorReportCallback(ERROR_BAD_MESH);
return;
}
_valid = true;
// Find the worldspace extents of the mesh now... they won't change during
// the lifetime of this object.
UpdatePosMinMax(X_POS_AXIS);
UpdatePosMinMax(Y_POS_AXIS);
UpdatePosMinMax(Z_POS_AXIS);
// We'll calculate the S/T extents lazily.
_texMinMaxDirty[S_TEX_AXIS] = true;
_texMinMaxDirty[T_TEX_AXIS] = true;
}
/**
* Destructor. Note that this only destroys the wrapper object, not the patch
* mesh itself.
*/
MeshEntity::~MeshEntity()
{
}
/**
* Query if the patch mesh is valid, in the characteristics that this wrapper
* class cares about. If not valid then the results of operations on this
* wrapper object are undefined.
*
* @return true if valid, false if not.
*/
bool
MeshEntity::IsValid() const
{
return _valid;
}
/**
* Get information about the patch mesh.
*
* A message string describing general mesh information (number of rows/cols,
* min/max texture coords, extent in worldspace) will be composed and sent to
* the infoReportCallback that was specified when this wrapper object was
* constructed.
*
* Optionally this method can do additional reporting on a specific
* "reference row" and "reference column". If a reference row and/or column
* is specified, then information about the reference slice(s) will be added
* to the information message. If a reference row/col is specified AND a
* corresponding row/col TexInfoCallback is specified, then the scale and
* tiling values for the reference slice will also be passed to the relevant
* callback.
*
* @param refRow Pointer to reference row number; NULL if none.
* @param refCol Pointer to reference column number; NULL if none.
* @param rowTexInfoCallback Pointer to callback for reference row info; NULL
* if none.
* @param colTexInfoCallback Pointer to callback for reference column info; NULL
* if none.
*/
void
MeshEntity::GetInfo(const int *refRow,
const int *refCol,
const TexInfoCallback *rowTexInfoCallback,
const TexInfoCallback *colTexInfoCallback)
{
// Prep a message buffer to compose the response.
char messageBuffer[INFO_BUFFER_SIZE + 1];
messageBuffer[0] = 0;
size_t bufferOffset = 0;
// Get reference row info if requested; this will be written into the message
// buffer as well as sent to the row callback (if any).
if (refRow != NULL)
{
ReportSliceTexInfo(ROW_SLICE_TYPE, *refRow, _naturalAxis[ROW_SLICE_TYPE],
messageBuffer + bufferOffset,
INFO_BUFFER_SIZE - bufferOffset,
rowTexInfoCallback);
// Move the message buffer pointer along.
bufferOffset = strlen(messageBuffer);
}
// Get reference column info if requested; this will be written into the
// message buffer as well as sent to the column callback (if any).
if (refCol != NULL)
{
ReportSliceTexInfo(COL_SLICE_TYPE, *refCol, _naturalAxis[COL_SLICE_TYPE],
messageBuffer + bufferOffset,
INFO_BUFFER_SIZE - bufferOffset,
colTexInfoCallback);
// Move the message buffer pointer along.
bufferOffset = strlen(messageBuffer);
}
// Make sure we have up-to-date S/T extents.
UpdateTexMinMax(S_TEX_AXIS);
UpdateTexMinMax(T_TEX_AXIS);
// Add general mesh info to the message.
snprintf(messageBuffer + bufferOffset, INFO_BUFFER_SIZE - bufferOffset,
INFO_MESH_FORMAT,
_numSlices[ROW_SLICE_TYPE],
SanitizeFloat(_texMin[S_TEX_AXIS]), SanitizeFloat(_texMax[S_TEX_AXIS]),
_numSlices[COL_SLICE_TYPE],
SanitizeFloat(_texMin[T_TEX_AXIS]), SanitizeFloat(_texMax[T_TEX_AXIS]),
SanitizeFloat(_posMin[X_POS_AXIS]), SanitizeFloat(_posMax[X_POS_AXIS]),
SanitizeFloat(_posMin[Y_POS_AXIS]), SanitizeFloat(_posMax[Y_POS_AXIS]),
SanitizeFloat(_posMin[Z_POS_AXIS]), SanitizeFloat(_posMax[Z_POS_AXIS]));
// Send the response.
_infoReportCallback(messageBuffer);
}
/**
* For each of the specified texture axes, shift the lowest-valued texture
* coordinates off of the mesh until an integral texture coordinate (texture
* boundary) is on the mesh edge.
*
* @param axes The texture axes to align.
*/
void
MeshEntity::MinAlign(TextureAxisSelection axes)
{
// Implement this by applying MinAlignInt to each specified axis.
ProcessForAxes(&MeshEntity::MinAlignInt, axes);
}
/**
* For each of the specified texture axes, shift the highest-valued texture
* coordinates off of the mesh until an integral texture coordinate (texture
* boundary) is on the mesh edge.
*
* @param axes The texture axes to align.
*/
void
MeshEntity::MaxAlign(TextureAxisSelection axes)
{
// Implement this by applying MaxAlignInt to each specified axis.
ProcessForAxes(&MeshEntity::MaxAlignInt, axes);
}
/**
* For each of the specified texture axes, perform either MinMaxAlignStretch
* or MinMaxAlignShrink; the chosen operation will be the one with the least
* absolute change in the value of the texture scale.
*
* @param axes The texture axes to align.
*/
void
MeshEntity::MinMaxAlignAutoScale(TextureAxisSelection axes)
{
// Implement this by applying MinMaxAlignAutoScaleInt to each specified axis.
ProcessForAxes(&MeshEntity::MinMaxAlignAutoScaleInt, axes);
}
/**
* For each of the specified texture axes, align a texture boundary to one
* edge of the mesh, then increase the texture scale to align a texture
* boundary to the other edge of the mesh as well.
*
* @param axes The texture axes to align.
*/
void
MeshEntity::MinMaxAlignStretch(TextureAxisSelection axes)
{
// Implement this by applying MinMaxAlignStretchInt to each specified axis.
ProcessForAxes(&MeshEntity::MinMaxAlignStretchInt, axes);
}
/**
* For each of the specified texture axes, align a texture boundary to one
* edge of the mesh, then decrease the texture scale to align a texture
* boundary to the other edge of the mesh as well.
*
* @param axes The texture axes to align.
*/
void
MeshEntity::MinMaxAlignShrink(TextureAxisSelection axes)
{
// Implement this by applying MinMaxAlignShrinkInt to each specified axis.
ProcessForAxes(&MeshEntity::MinMaxAlignShrinkInt, axes);
}
/**
* Set the texture scaling along the rows or columns of the mesh. This
* affects only the texture axis that is naturally associated with rows (S)
* or columns (T) according to the chosen sliceType.
*
* The scaling may be input either as a multiple of the natural scale that
* Radiant would choose for this texture, or as the number of tiles of the
* texture that should fit on the mesh's row/column.
*
* Among the slices perpendicular to the direction of scaling, an alignment
* slice is used to fix the position of the texture boundary.
*
* A reference slice may optionally be chosen among the slices parallel to
* the scaling direction. If a reference slice is not specified, then the
* texture coordinates are independently determined for each slice. If a
* reference slice is specified, its texture coordinates are calculated first
* and used to affect the other slices. The reference slice's amount of
* texture tiling will be re-used for all other slices; optionally, the
* texture coordinate at each control point within the reference slice can be
* copied to the corresponding control point in every other slice.
*
* @param sliceType Choose to scale along rows or columns.
* @param alignSlice Pointer to alignment slice description; if NULL,
* slice 0 is assumed.
* @param refSlice Pointer to reference slice description,
* including how to use the reference; NULL if no
* reference.
* @param naturalScale true if naturalScaleOrTiles is a factor of the
* Radiant natural scale; false if
* naturalScaleOrTiles is a number of tiles.
* @param naturalScaleOrTiles Scaling determinant, interpreted according to
* the naturalScale parameter.
*/
void
MeshEntity::SetScale(SliceType sliceType,
const SliceDesignation *alignSlice,
const RefSliceDescriptor *refSlice,
bool naturalScale,
float naturalScaleOrTiles)
{
// We're about to make changes!
CreateUndoPoint();
// Check for bad inputs. Also convert from natural scale to raw scale.
if (alignSlice != NULL && !alignSlice->maxSlice)
{
if (alignSlice->index < 0 ||
alignSlice->index >= (int)SliceSize(sliceType))
{
_errorReportCallback(_errorBadSliceString[OtherSliceType(sliceType)]);
return;
}
}
if (refSlice != NULL && !refSlice->designation.maxSlice)
{
if (refSlice->designation.index < 0 ||
refSlice->designation.index >= (int)_numSlices[sliceType])
{
_errorReportCallback(_errorBadSliceString[sliceType]);
return;
}
}
TextureAxis axis = _naturalAxis[sliceType];
float rawScaleOrTiles = naturalScaleOrTiles;
if (naturalScale)
{
// In this case, naturalScaleOrTiles (copied to rawScaleOrTiles) was a
// natural-scale factor.
if (rawScaleOrTiles == 0)
{
_errorReportCallback(_errorSliceZeroscaleString[sliceType]);
return;
}
// If Radiant's internal orientation is backwards, account for that.
if (_radiantScaleInverted[sliceType])
{
rawScaleOrTiles = -rawScaleOrTiles;
}
// Raw scale is the divisor necessary to get texture coordinate from
// worldspace distance, so we can derive that from the "natural" scale.
rawScaleOrTiles *= _naturalTexUnits[axis];
}
else
{
// In this case, naturalScaleOrTiles (copied to rawScaleOrTiles) was a
// tiling amount.
if (rawScaleOrTiles == 0)
{
// XXX We could try to make zero-tiles work ("infinite scale": all
// values for this axis are the same along the slice). Need to sort
// out divide-by-zero dangers down the road.
_errorReportCallback(_errorSliceZerotilesString[sliceType]);
return;
}
// If Radiant's internal orientation is backwards, account for that.
if (_radiantTilesInverted[sliceType])
{
rawScaleOrTiles = -rawScaleOrTiles;
}
}
// Make sure we have a definite number for the alignment slice, and for the
// reference slice if any.
int alignSliceInt =
InternalSliceDesignation(alignSlice, (SliceType)OtherSliceType(sliceType));
RefSliceDescriptorInt descriptor;
RefSliceDescriptorInt *refSliceInt = // will be NULL if no reference
InternalRefSliceDescriptor(refSlice, sliceType, descriptor);
// Generate the surface texture coordinates using the mesh control points
// and the designated scaling/tiling.
AllocatedMatrix<float> surfaceValues(_meshData.x(), _meshData.y());
GenScaledDistanceValues(sliceType, alignSliceInt, refSliceInt,
naturalScale, rawScaleOrTiles,
surfaceValues);
// Derive the control point values necessary to achieve those surface values.
GenControlTexFromSurface(axis, surfaceValues);
// Done!
CommitChanges();
}
/**
* Set the mesh's texture coordinates according to a linear combination of
* factors. This equation can be used to set the texture coordinates at the
* control points themselves, or to directly set the texture coordinates at
* the locations on the mesh surface that correspond to each half-patch
* interval.
*
* An alignment row is used as the zero-point for any calculations of row
* number or of distance along a column surface when processing the equation.
* An alignment column is similarly used. (Note that the number identifying
* the alignment row/column is the real number used to index into the mesh;
* it's not the modified number as affected by the alignment column/row when
* processing the equation. We don't want to be stuck in a chicken-and-egg
* situation.)
*
* Calculations of distance along row/col surface may optionally be affected
* by a designated reference row/col. The reference row/col can be used as a
* source of end-to-end distance only, in which case the proportional spacing
* of the control points within the affected row/col will determine the
* distance value to be used for each control point. Or, the distance value
* for every control point in the reference row/col can be copied to each
* corresponding control point in the other rows/cols.
*
* @param sFactors Factors to determine the S texture coords; NULL if S
* axis unaffected.
* @param tFactors Factors to determine the T texture coords; NULL if T
* axis unaffected.
* @param alignRow Pointer to zero-point row; if NULL, row 0 is assumed.
* @param alignCol Pointer to zero-point column; if NULL, column 0 is
* assumed.
* @param refRow Pointer to reference row description, including how
* to use the reference; NULL if no reference.
* @param refCol Pointer to reference column description, including
* how to use the reference; NULL if no reference.
* @param surfaceValues true if calculations are for S/T values on the mesh
* surface; false if calculations are for S/T values at
* the control points.
*/
void
MeshEntity::GeneralFunction(const GeneralFunctionFactors *sFactors,
const GeneralFunctionFactors *tFactors,
const SliceDesignation *alignRow,
const SliceDesignation *alignCol,
const RefSliceDescriptor *refRow,
const RefSliceDescriptor *refCol,
bool surfaceValues)
{
// We're about to make changes!
CreateUndoPoint();
// Make sure we have a definite number for the alignment slices, and for the
// reference slices if any.
int alignRowInt = InternalSliceDesignation(alignRow, ROW_SLICE_TYPE);
int alignColInt = InternalSliceDesignation(alignRow, COL_SLICE_TYPE);
RefSliceDescriptorInt rowDescriptor;
RefSliceDescriptorInt *refRowInt = // will be NULL if no row reference
InternalRefSliceDescriptor(refRow, ROW_SLICE_TYPE, rowDescriptor);
RefSliceDescriptorInt colDescriptor;
RefSliceDescriptorInt *refColInt = // will be NULL if no column reference
InternalRefSliceDescriptor(refCol, COL_SLICE_TYPE, colDescriptor);
// Get the surface row/col distance values at each half-patch interval, if
// the input factors care about distances.
AllocatedMatrix<float> rowDistances(_meshData.x(), _meshData.y());
AllocatedMatrix<float> colDistances(_meshData.x(), _meshData.y());
if ((sFactors != NULL && sFactors->rowDistance != 0.0f) ||
(tFactors != NULL && tFactors->rowDistance != 0.0f))
{
GenScaledDistanceValues(ROW_SLICE_TYPE,
alignColInt,
refRowInt,
true,
1.0f,
rowDistances);
}
if ((sFactors != NULL && sFactors->colDistance != 0.0f) ||
(tFactors != NULL && tFactors->colDistance != 0.0f))
{
GenScaledDistanceValues(COL_SLICE_TYPE,
alignRowInt,
refColInt,
true,
1.0f,
colDistances);
}
// Modify the S axis if requested.
if (sFactors != NULL)
{
GeneralFunctionInt(*sFactors, S_TEX_AXIS, alignRowInt, alignColInt, surfaceValues,
rowDistances, colDistances);
}
// Modify the T axis if requested.
if (tFactors != NULL)
{
GeneralFunctionInt(*tFactors, T_TEX_AXIS, alignRowInt, alignColInt, surfaceValues,
rowDistances, colDistances);
}
// Done!
CommitChanges();
}
/**
* Update the internally stored information for the min and max extent of the
* mesh on the specified worldspace axis.
*
* @param axis The worldspace axis.
*/
void
MeshEntity::UpdatePosMinMax(PositionAxis axis)
{
// Iterate over all control points to find the min and max values.
_posMin[axis] = _meshData(0, 0).m_vertex[axis];
_posMax[axis] = _posMin[axis];
for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex++)
{
for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex++)
{
float current = _meshData(rowIndex, colIndex).m_vertex[axis];
if (current < _posMin[axis])
{
_posMin[axis] = current;
}
if (current > _posMax[axis])
{
_posMax[axis] = current;
}
}
}
}
/**
* Update the internally stored information for the min and max extent of the
* mesh on the specified texture axis.
*
* @param axis The texture axis.
*/
void
MeshEntity::UpdateTexMinMax(TextureAxis axis)
{
// Bail out if no operations have possibly changed these values.
if (!_texMinMaxDirty[axis])
{
return;
}
// Iterate over all control points to find the min and max values.
_texMin[axis] = _meshData(0, 0).m_texcoord[axis];
_texMax[axis] = _texMin[axis];
for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex++)
{
for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex++)
{
float current = _meshData(rowIndex, colIndex).m_texcoord[axis];
if (current < _texMin[axis])
{
_texMin[axis] = current;
}
if (current > _texMax[axis])
{
_texMax[axis] = current;
}
}
}
// See if the min and max are on texture boundaries.
_texMinAligned[axis] = (floorf(_texMin[axis]) == _texMin[axis]);
_texMaxAligned[axis] = (floorf(_texMax[axis]) == _texMax[axis]);
// Values are good until next relevant operation.
_texMinMaxDirty[axis] = false;
}
/**
* Interface to the Radiant undo buffer; save the current state of the mesh
* to allow rollback to this point by an undo operation.
*/
void
MeshEntity::CreateUndoPoint()
{
GlobalPatchCreator().Patch_undoSave(_mesh);
}
/**
* Commit the changes to the mesh so that they will be reflected in Radiant.
*/
void
MeshEntity::CommitChanges()
{
GlobalPatchCreator().Patch_controlPointsChanged(_mesh);
// Radiant undo-buffer behavior requires this:
CreateUndoPoint();
}
/**
* Convert from SliceDesignation to a slice number. Interpret max slice if
* necessary, and fall back to slice 0 if unspecified.
*
* @param sliceDesignation Pointer to slice description; may be NULL.
* @param sliceType Slice kind (row or column).
*
* @return The slice number.
*/
int
MeshEntity::InternalSliceDesignation(const SliceDesignation *sliceDesignation,
SliceType sliceType)
{
if (sliceDesignation != NULL)
{
// Interpret "max slice" if necessary.
if (sliceDesignation->maxSlice)
{
return _numSlices[sliceType] - 1;
}
else
{
return sliceDesignation->index;
}
}
else
{
// 0 if unspecified.
return 0;
}
}
/**
* Convert from RefSliceDescriptor to RefSliceDescriptorInt. Interpret max
* slice if necessary. Populate specified RefSliceDescriptorInt if input is
* non-NULL and return pointer to it; otherwise return NULL.
*
* @param refSlice Pointer to reference slice description; may be
* NULL.
* @param sliceType Slice kind (row or column).
* @param [out] refSliceInt RefSliceDescriptorInt to populate.
*
* @return NULL if input RefSliceDescriptor is NULL; else, pointer to populated
* RefSliceDescriptorInt.
*/
MeshEntity::RefSliceDescriptorInt *
MeshEntity::InternalRefSliceDescriptor(const RefSliceDescriptor *refSlice,
SliceType sliceType,
RefSliceDescriptorInt& refSliceInt)
{
if (refSlice != NULL)
{
// Preserve totalLengthOnly.
refSliceInt.totalLengthOnly = refSlice->totalLengthOnly;
// Convert slice designator to a slice number.
refSliceInt.index = InternalSliceDesignation(&(refSlice->designation), sliceType);
return &refSliceInt;
}
else
{
// NULL if unspecified.
return NULL;
}
}
/**
* Given a slice and a number of times that its texture should tile along it,
* find the appropriate texture scale. This result is determined by the
* surface length along the slice.
*
* @param sliceType Slice kind (row or column).
* @param slice Slice number, among slices of that kind in mesh.
* @param axis The texture axis of interest.
* @param tiles Number of times the texture tiles.
*
* @return The texture scale corresponding to the tiling amount.
*/
float
MeshEntity::GetSliceTexScale(SliceType sliceType,
int slice,
TextureAxis axis,
float tiles)
{
// XXX Similar to GenScaledDistanceValues; refactor for shared code?
// We're going to be walking patches along the mesh, choosing the patches
// that surround/affect the slice we are interested in. We'll calculate
// the length of the slice's surface across each patch & add those up.
// A SlicePatchContext will contain all the necessary information to
// evaluate our slice's surface length within each patch. Some aspects of
// the SlicePatchContext will vary as we move from patch to patch, but we
// can go ahead and calculate now any stuff that is invariant along the
// slice's direction.
SlicePatchContext context;
context.sliceType = sliceType;
if (slice != 0)
{
// This is the position of the slice within each patch, in the direction
// orthogonal to the slice. Even-numbered slices are at the edge of the
// patch (position 1.0), while odd-numbered slices are in the middle
// (position 0.5).
context.position = 1.0f - ((float)(slice & 0x1) / 2.0f);
}
else
{
// For the first slice, we can't give it the usual treatment for even-
// numbered slices (since there is no patch "before" it), so it gets
// position 0.0 instead.
context.position = 0.0f;
}
// This is the slice of the same kind that defines the 0.0 edge of the
// patch. It will be the next lowest even-numbered slice. (Note the
// integer division here.)
context.edgeSlice[sliceType] = 2 * ((slice - 1) / 2);
// Now it's time to walk the patches.
// We start off with no cumulative distance yet.
float cumulativeDistance = 0.0f;
// By iterating over the number of control points in this slice by
// increments of 2, we'll be walking the slice in patch-sized steps. Since
// we are only interested in the total length, we don't need to check in at
// finer granularities.
for (unsigned halfPatch = 2; halfPatch < SliceSize(sliceType); halfPatch += 2)
{
// Find the slice-of-other-kind that defines the patch edge orthogonal
// to our slice.
context.edgeSlice[OtherSliceType(sliceType)] = 2 * ((halfPatch - 1) / 2);
// Estimate the slice length along the surface of the patch.
float segmentLengthEstimate = EstimateSegmentLength(0.0f, 1.0f, context);
// Recursively refine that estimate until it is good enough, then add it
// to our cumulative distance.
cumulativeDistance += RefineSegmentLength(0.0f, 1.0f, context,
segmentLengthEstimate,
UNITS_ERROR_BOUND);
}
// The scale along this slice is defined as the surface length divided by
// the "natural" number of texture units along that length.
return cumulativeDistance / (tiles * _naturalTexUnits[axis]);
}
/**
* Populate the SliceTexInfo for the indicated slice and texture axis.
*
* @param sliceType Slice kind (row or column).
* @param slice Slice number, among slices of that kind in mesh.
* @param axis The texture axis of interest.
* @param [out] info Information on scale, tiles, and min/max for the
* specified texture axis.
*
* @return true on success, false if slice cannot be processed.
*/
bool
MeshEntity::GetSliceTexInfo(SliceType sliceType,
int slice,
TextureAxis axis,
SliceTexInfo& info)
{
// Bail out now if slice # is bad.
if (slice < 0 ||
slice >= (int)_numSlices[sliceType])
{
_errorReportCallback(_errorBadSliceString[sliceType]);
return false;
}
// Calculate the # of times the texture is tiled along the specified axis
// on this slice, and find the min and max values for that axis.
float texBegin =
MatrixElement(_meshData, sliceType, slice, 0).m_texcoord[axis];
float texEnd =
MatrixElement(_meshData, sliceType, slice, SliceSize(sliceType) - 1).m_texcoord[axis];
info.tiles = texEnd - texBegin;
if (texBegin < texEnd)
{
info.min = texBegin;
info.max = texEnd;
}
else
{
info.min = texEnd;
info.max = texBegin;
}
// Calculate the texture scale along this slice, using the tiling info
// along with the texture size and the length of the slice's surface.
info.scale = GetSliceTexScale(sliceType, slice, axis, info.tiles);
return true;
}
/**
* Take the information from GetSliceTexInfo and sanitize it for reporting.
* Optionally print to a provided message buffer and/or supply data to a
* provided TexInfoCallback.
*
* @param sliceType Slice kind (row or column).
* @param slice Slice number, among slices of that kind in mesh.
* @param axis The texture axis of interest.
* @param messageBuffer Buffer for message data; NULL if none.
* @param messageBufferSize Size of the message buffer.
* @param texInfoCallback Callback for passing texture scale/tiles info;
* NULL if none.
*/
void
MeshEntity::ReportSliceTexInfo(SliceType sliceType,
int slice,
TextureAxis axis,
char *messageBuffer,
unsigned messageBufferSize,
const TexInfoCallback *texInfoCallback)
{
// Fetch the raw info.
SliceTexInfo info;
if (!GetSliceTexInfo(sliceType, slice, axis, info))
{
return;
}
// Account for Radiant-inverted.
if (_radiantScaleInverted[sliceType])
{
info.scale = -info.scale;
}
if (_radiantTilesInverted[sliceType])
{
info.tiles = -info.tiles;
}
// Send texture info to callback if one is provided.
bool infscale = (info.scale > FLT_MAX || info.scale < -FLT_MAX);
if (texInfoCallback != NULL)
{
if (!infscale)
{
(*texInfoCallback)(SanitizeFloat(info.scale), SanitizeFloat(info.tiles));
}
else
{
// "Infinite scale" prevents us from invoking the callback, so
// raise a warning about that.
_warningReportCallback(_warningSliceInfscaleFormatString[sliceType]);
}
}
// Write texture info to buffer if one is provided.
if (messageBuffer != NULL)
{
if (!infscale)
{
snprintf(messageBuffer, messageBufferSize,
_infoSliceFormatString[sliceType], slice,
SanitizeFloat(info.scale), SanitizeFloat(info.tiles),
SanitizeFloat(info.min), SanitizeFloat(info.max));
}
else
{
// Special handling for "infinite scale".
snprintf(messageBuffer, messageBufferSize,
_infoSliceInfscaleFormatString[sliceType], slice,
SanitizeFloat(info.tiles),
SanitizeFloat(info.min), SanitizeFloat(info.max));
}
}
}
/**
* Apply some function with the InternalImpl signature to each of the
* designated texture axes. The undo point and state commit operations
* are handled here for such functions.
*
* @param internalImpl The function to apply.
* @param axes The texture axes to affect.
*/
void
MeshEntity::ProcessForAxes(InternalImpl internalImpl,
TextureAxisSelection axes)
{
// We're about to make changes!
CreateUndoPoint();
// Apply the function to the requested axes.
bool sChanged = false;
bool tChanged = false;
if (axes != T_TEX_AXIS_ONLY)
{
sChanged = (*this.*internalImpl)(S_TEX_AXIS);
}
if (axes != S_TEX_AXIS_ONLY)
{
tChanged = (*this.*internalImpl)(T_TEX_AXIS);
}
// Done! Commit changes if necessary.
if (sChanged || tChanged)
{
CommitChanges();
}
}
/**
* Add an offset to all control point values for the given texture axis.
*
* @param axis The texture axis to affect.
* @param shift The offset to add.
*/
void
MeshEntity::Shift(TextureAxis axis,
float shift)
{
// Iterate over all control points and add the offset.
for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex++)
{
for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex++)
{
_meshData(rowIndex, colIndex).m_texcoord[axis] += shift;
}
}
// This operation might have changed texture min/max.
_texMinMaxDirty[axis] = true;
}
/**
* On the given texture axis, find the distance of all control point values
* from the current minimum value and multiply that distance by the given
* scale factor.
*
* @param axis The texture axis to affect.
* @param scale The scale factor.
*/
void
MeshEntity::Scale(TextureAxis axis,
float scale)
{
// Make sure the min value is updated; we'll need it below.
UpdateTexMinMax(axis);
// Iterate over all control points and apply the scale factor.
for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex++)
{
for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex++)
{
// Leave the current minimum edge in place and scale out from that.
_meshData(rowIndex, colIndex).m_texcoord[axis] =
((_meshData(rowIndex, colIndex).m_texcoord[axis] - _texMin[axis]) * scale) +
_texMin[axis];
}
}
// This operation might have changed texture min/max.
_texMinMaxDirty[axis] = true;
}
/**
* Implementation of MinAlign for a single texture axis.
*
* @param axis The texture axis to affect.
*
* @return true if the mesh was changed, false if not.
*/
bool
MeshEntity::MinAlignInt(TextureAxis axis)
{
// Make sure the min-aligned value is updated.
UpdateTexMinMax(axis);
// If already aligned, we're done.
if (_texMinAligned[axis])
{
// Didn't make changes.
return false;
}
// Otherwise shift by the necessary amount to align.
Shift(axis, ceilf(_texMin[axis]) - _texMin[axis]);
// Made changes.
return true;
}
/**
* Implementation of MaxAlign for a single texture axis.
*
* @param axis The texture axis to affect.
*
* @return true if the mesh was changed, false if not.
*/
bool
MeshEntity::MaxAlignInt(TextureAxis axis)
{
// Make sure the max-aligned value is updated.
UpdateTexMinMax(axis);
// If already aligned, we're done.
if (_texMaxAligned[axis])
{
// Didn't make changes.
return false;
}
// Otherwise shift by the necessary amount to align.
Shift(axis, ceilf(_texMax[axis]) - _texMax[axis]);
// Made changes.
return true;
}
/**
* Implementation of MinMaxAlignAutoScale for a single texture axis.
*
* @param axis The texture axis to affect.
*
* @return true if the mesh was changed, false if not.
*/
bool
MeshEntity::MinMaxAlignAutoScaleInt(TextureAxis axis)
{
// Make sure the max value is updated.
UpdateTexMinMax(axis);
// Choose to stretch or shrink, based on which will cause less change.
if ((_texMax[axis] - floorf(_texMax[axis])) < 0.5)
{
return MinMaxAlignStretchInt(axis);
}
else
{
return MinMaxAlignShrinkInt(axis);
}
}
/**
* The meat of MinMaxAlignStretchInt and MinMaxAlignShrinkInt.
*
* @param axis The texture axis to affect.
* @param op Whether to stretch or shrink.
*
* @return true if the mesh was changed, false if not.
*/
bool
MeshEntity::MinMaxAlignScale(TextureAxis axis,
ScaleOperation op)
{
// First make sure we are min-aligned.
bool changed = MinAlignInt(axis);
// Make sure the min/max values are updated.
UpdateTexMinMax(axis);
// More work to do if not max-aligned.
if (!_texMaxAligned[axis])
{
// Find the current tiling.
float oldRepeats = _texMax[axis] - _texMin[axis];
// Find the desired tiling, depending on whether we are stretching or
// shrinking.
float newRepeats;
if (op == STRETCH_SCALE_OP)
{
newRepeats = floorf(_texMax[axis]) - _texMin[axis];
}
else
{
newRepeats = ceilf(_texMax[axis]) - _texMin[axis];
}
// Apply the necessary scaling to get the desired tiling.
Scale(axis, newRepeats / oldRepeats);
// Made changes.
changed = true;
}
return changed;
}
/**
* Implementation of MinMaxAlignStretch for a single texture axis.
*
* @param axis The texture axis to affect.
*
* @return true if the mesh was changed, false if not.
*/
bool
MeshEntity::MinMaxAlignStretchInt(TextureAxis axis)
{
// Hand off to MinMaxAlignScale.
return MinMaxAlignScale(axis, STRETCH_SCALE_OP);
}
/**
* Implementation of MinMaxAlignShrink for a single texture axis.
*
* @param axis The texture axis to affect.
*
* @return true if the mesh was changed, false if not.
*/
bool
MeshEntity::MinMaxAlignShrinkInt(TextureAxis axis)
{
// Hand off to MinMaxAlignScale.
return MinMaxAlignScale(axis, SHRINK_SCALE_OP);
}
/**
* Calculate the d(x, y, or z)/dt of a patch slice, evaluated at a given t
* (parameter for the Bezier function, between 0 and 1).
*
* @param axis The worldspace axis of interest.
* @param t Bezier parameter.
* @param context The slice and patch.
*
* @return d(x, y, or z)/dt at the given t.
*/
float
MeshEntity::SliceParametricSpeedComponent(PositionAxis axis,
float t,
const SlicePatchContext& context)
{
float a = 1.0f - context.position;
float b = 2.0f * context.position * a;
a *= a;
float c = context.position * context.position;
float d = 2.0f * t - 2.0f;
float e = 2.0f - 4.0f * t;
float f = 2.0f * t;
int patchStartCol = context.edgeSlice[COL_SLICE_TYPE];
int patchStartRow = context.edgeSlice[ROW_SLICE_TYPE];
if (context.sliceType == ROW_SLICE_TYPE)
{
return
_meshData(patchStartRow+0, patchStartCol+0).m_vertex[axis] * a * d +
_meshData(patchStartRow+0, patchStartCol+1).m_vertex[axis] * a * e +
_meshData(patchStartRow+0, patchStartCol+2).m_vertex[axis] * a * f +
_meshData(patchStartRow+1, patchStartCol+0).m_vertex[axis] * b * d +
_meshData(patchStartRow+1, patchStartCol+1).m_vertex[axis] * b * e +
_meshData(patchStartRow+1, patchStartCol+2).m_vertex[axis] * b * f +
_meshData(patchStartRow+2, patchStartCol+0).m_vertex[axis] * c * d +
_meshData(patchStartRow+2, patchStartCol+1).m_vertex[axis] * c * e +
_meshData(patchStartRow+2, patchStartCol+2).m_vertex[axis] * c * f;
}
else
{
return
_meshData(patchStartRow+0, patchStartCol+0).m_vertex[axis] * a * d +
_meshData(patchStartRow+1, patchStartCol+0).m_vertex[axis] * a * e +
_meshData(patchStartRow+2, patchStartCol+0).m_vertex[axis] * a * f +
_meshData(patchStartRow+0, patchStartCol+1).m_vertex[axis] * b * d +
_meshData(patchStartRow+1, patchStartCol+1).m_vertex[axis] * b * e +
_meshData(patchStartRow+2, patchStartCol+1).m_vertex[axis] * b * f +
_meshData(patchStartRow+0, patchStartCol+2).m_vertex[axis] * c * d +
_meshData(patchStartRow+1, patchStartCol+2).m_vertex[axis] * c * e +
_meshData(patchStartRow+2, patchStartCol+2).m_vertex[axis] * c * f;
}
}
/**
* Calculates the rate of change in worldspace units of a patch slice,
* evaluated at a given t (parameter for the Bezier function, between 0 and
* 1).
*
* @param t Bezier parameter.
* @param context The slice and patch.
*
* @return Path length.
*/
float
MeshEntity::SliceParametricSpeed(float t,
const SlicePatchContext& context)
{
float xDotEval = SliceParametricSpeedComponent(X_POS_AXIS, t, context);
float yDotEval = SliceParametricSpeedComponent(Y_POS_AXIS, t, context);
float zDotEval = SliceParametricSpeedComponent(Z_POS_AXIS, t, context);
return sqrtf(xDotEval*xDotEval + yDotEval*yDotEval + zDotEval*zDotEval);
}
/**
* Estimate the surface length of a slice segment, using ten point
* Gauss-Legendre integration of the parametric speed function. The value
* returned will always be positive (absolute value).
*
* @param startPosition Bezier parameter value for the start point of the
* slice segment.
* @param endPosition Bezier parameter value for the end point of the slice
* segment.
* @param context The slice and patch.
*
* @return Estimate of segment length.
*/
float
MeshEntity::EstimateSegmentLength(float startPosition,
float endPosition,
const SlicePatchContext& context)
{
// Gauss-Legendre implementation taken from "Numerical Recipes in C".
static float x[] = {0.0f, 0.1488743389f, 0.4333953941f, 0.6794095682f, 0.8650633666f, 0.9739065285f};
static float w[] = {0.0f, 0.2955242247f, 0.2692667193f, 0.2190863625f, 0.1494513491f, 0.0666713443f};
float xm = 0.5f * (endPosition + startPosition);
float xr = 0.5f * (endPosition - startPosition);
float s = 0.0f;
for (unsigned j = 1; j <= 5; j++) {
float dx = xr * x[j];
s += w[j] * (SliceParametricSpeed(xm + dx, context) +
SliceParametricSpeed(xm - dx, context));
}
return fabsf(s * xr);
}
/**
* Recursively improve the estimate of the surface length of a slice segment,
* by estimating the length of its halves, until the change between estimates
* is equal to or less than an acceptable error threshold.
*
* @param startPosition Bezier parameter value for the start point of
* the slice segment.
* @param endPosition Bezier parameter value for the end point of the
* slice segment.
* @param context The slice and patch.
* @param segmentLengthEstimate Starting estimate for segment legnth.
* @param maxError Max acceptable variance between estimates.
*
* @return Improved estimate of segment length.
*/
float
MeshEntity::RefineSegmentLength(float startPosition,
float endPosition,
const SlicePatchContext& context,
float segmentLengthEstimate,
float maxError)
{
// Estimate the lengths of the two halves of this segment.
float midPosition = (startPosition + endPosition) / 2.0f;
float leftLength = EstimateSegmentLength(startPosition, midPosition, context);
float rightLength = EstimateSegmentLength(midPosition, endPosition, context);
// If the sum of the half-segment estimates is too far off from the
// whole-segment estimate, then we're in a regime with too much error in
// the estimates. Recurse to refine the half-segment estimates.
if (fabsf(segmentLengthEstimate - (leftLength + rightLength)) > maxError)
{
leftLength = RefineSegmentLength(startPosition, midPosition,
context,
leftLength,
maxError / 2.0f);
rightLength = RefineSegmentLength(midPosition, endPosition,
context,
rightLength,
maxError / 2.0f);
}
// Return the sum of the (refined) half-segment estimates.
return (leftLength + rightLength);
}
/**
* Derive control point texture coordinates (on a given texture axis) from a
* set of mesh surface texture coordinates.
*
* @param axis The texture axis of interest.
* @param surfaceValues The surface texture coordinates.
*/
void
MeshEntity::GenControlTexFromSurface(TextureAxis axis,
const Matrix<float>& surfaceValues)
{
// The control points on even rows & even columns (i.e. patch corners)
// have texture coordinates that match the surface values.
for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
{
for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
{
_meshData(rowIndex, colIndex).m_texcoord[axis] =
surfaceValues(rowIndex, colIndex);
}
}
// Set the control points on odd rows & even columns (i.e. the centers of
// columns that are patch edges).
for (unsigned rowIndex = 1; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
{
for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
{
_meshData(rowIndex, colIndex).m_texcoord[axis] =
2.0f * surfaceValues(rowIndex, colIndex) -
(surfaceValues(rowIndex - 1, colIndex) +
surfaceValues(rowIndex + 1, colIndex)) / 2.0f;
}
}
// Set the control points on even rows & odd columns (i.e. the centers of
// rows that are patch edges).
for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
{
for (unsigned colIndex = 1; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
{
_meshData(rowIndex, colIndex).m_texcoord[axis] =
2.0f * surfaceValues(rowIndex, colIndex) -
(surfaceValues(rowIndex, colIndex - 1) +
surfaceValues(rowIndex, colIndex + 1)) / 2.0f;
}
}
// And finally on odd rows & odd columns (i.e. patch centers).
for (unsigned rowIndex = 1; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
{
for (unsigned colIndex = 1; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
{
_meshData(rowIndex, colIndex).m_texcoord[axis] =
4.0f * surfaceValues(rowIndex, colIndex) -
(surfaceValues(rowIndex, colIndex - 1) +
surfaceValues(rowIndex, colIndex + 1) +
surfaceValues(rowIndex - 1, colIndex) +
surfaceValues(rowIndex + 1, colIndex)) / 2.0f -
(surfaceValues(rowIndex - 1, colIndex - 1) +
surfaceValues(rowIndex + 1, colIndex + 1) +
surfaceValues(rowIndex - 1, colIndex + 1) +
surfaceValues(rowIndex + 1, colIndex - 1)) / 4.0f;
}
}
// This operation might have changed texture min/max.
_texMinMaxDirty[axis] = true;
}
/**
* Overwrite control point texture coordinates (on a given texture axis) with
* the input texture coordinates.
*
* @param axis The texture axis of interest.
* @param values The input texture coordinates.
*/
void
MeshEntity::CopyControlTexFromValues(TextureAxis axis,
const Matrix<float>& values)
{
// Iterate over all control points and just do a straight copy.
for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex++)
{
for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex++)
{
_meshData(rowIndex, colIndex).m_texcoord[axis] =
values(rowIndex, colIndex);
}
}
// This operation might have changed texture min/max.
_texMinMaxDirty[axis] = true;
}
/**
* Derive a set of surface texture coordinates (on a given texture axis) from
* the control point texture coordinates.
*
* @param axis The texture axis of interest.
* @param [out] surfaceValues The surface texture coordinates.
*/
void
MeshEntity::GenSurfaceFromControlTex(TextureAxis axis,
Matrix<float>& surfaceValues)
{
// The surface values on even rows & even columns (i.e. patch corners)
// have texture coordinates that match the control points.
for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
{
for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
{
surfaceValues(rowIndex, colIndex) =
_meshData(rowIndex, colIndex).m_texcoord[axis];
}
}
// Set the surface values on odd rows & odd columns (i.e. patch centers).
for (unsigned rowIndex = 1; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
{
for (unsigned colIndex = 1; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
{
surfaceValues(rowIndex, colIndex) =
_meshData(rowIndex, colIndex).m_texcoord[axis] / 4.0f +
(_meshData(rowIndex, colIndex - 1).m_texcoord[axis] +
_meshData(rowIndex, colIndex + 1).m_texcoord[axis] +
_meshData(rowIndex - 1, colIndex).m_texcoord[axis] +
_meshData(rowIndex + 1, colIndex).m_texcoord[axis]) / 8.0f +
(_meshData(rowIndex - 1, colIndex - 1).m_texcoord[axis] +
_meshData(rowIndex + 1, colIndex + 1).m_texcoord[axis] +
_meshData(rowIndex - 1, colIndex + 1).m_texcoord[axis] +
_meshData(rowIndex + 1, colIndex - 1).m_texcoord[axis]) / 16.0f;
}
}
// Set the surface values on even rows & odd columns (i.e. the centers of
// rows that are patch edges).
for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
{
for (unsigned colIndex = 1; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
{
surfaceValues(rowIndex, colIndex) =
_meshData(rowIndex, colIndex).m_texcoord[axis] / 2.0f +
(_meshData(rowIndex, colIndex - 1).m_texcoord[axis] +
_meshData(rowIndex, colIndex + 1).m_texcoord[axis]) / 4.0f;
}
}
// And finally on odd rows & even columns (i.e. the centers of columns that
// are patch edges).
for (unsigned rowIndex = 1; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex += 2)
{
for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex += 2)
{
surfaceValues(rowIndex, colIndex) =
_meshData(rowIndex, colIndex).m_texcoord[axis] / 2.0f +
(_meshData(rowIndex - 1, colIndex).m_texcoord[axis] +
_meshData(rowIndex + 1, colIndex).m_texcoord[axis]) / 4.0f;
}
}
}
/**
* Copy the control point texture coordinates (on a given texture axis) to
* the output texture coordinates parameter.
*
* @param axis The texture axis of interest.
* @param [out] values The output texture coordinates.
*/
void
MeshEntity::CopyValuesFromControlTex(TextureAxis axis,
Matrix<float>& values)
{
// Iterate over all control points and just do a straight copy.
for (unsigned rowIndex = 0; rowIndex < _numSlices[ROW_SLICE_TYPE]; rowIndex++)
{
for (unsigned colIndex = 0; colIndex < _numSlices[COL_SLICE_TYPE]; colIndex++)
{
values(rowIndex, colIndex) =
_meshData(rowIndex, colIndex).m_texcoord[axis];
}
}
}
/**
* Generate a set of values based on surface slice lengths and some amount of
* desired scaling or tiling.
*
* This method does a great deal of the work for the SetScale public method;
* refer to that method's comment header for more details about the alignment
* slice and reference slice inputs. The main difference from the SetScale
* input parameters is that the scale/tiles factor has been processed some.
* It has been flipped if necessary to account for Radiant's internal
* scale/tiles orientation differing from the sensible external orientation.
* And natural scaling has been converted to raw scaling, which is the actual
* desired divisor to get texture coordinates from worldspace lengths.
*
* @param sliceType Process rows or colums.
* @param alignSlice Pointer to alignment slice description; if NULL,
* slice 0 is assumed.
* @param refSlice Pointer to reference slice description, including how
* to use the reference; NULL if no reference.
* @param rawScale true if rawScaleOrTiles is a scale factor; false if
* rawScaleOrTiles is a number of tiles.
* @param rawScaleOrTiles Scaling determinant, interpreted according to the
* rawScale parameter.
* @param [out] values The generated values.
*/
void
MeshEntity::GenScaledDistanceValues(SliceType sliceType,
int alignSlice,
const RefSliceDescriptorInt *refSlice,
bool rawScale,
float rawScaleOrTiles,
Matrix<float>& values)
{
// For every half-patch interval along the surface, we want to generate a
// value based on the surface distance along the row (or column) to that
// spot. So, first we need to determine those distances.
// XXX Similar to GetSliceTexScale; refactor for shared code?
// We're going to be walking patches along the mesh, choosing the patches
// that surround/affect the slice we are interested in.
// A SlicePatchContext will contain all the necessary information to
// evaluate our slice's surface length within each patch.
SlicePatchContext context;
context.sliceType = sliceType;
// Pick the slices that we will measure.
int firstSlice, lastSlice;
if (refSlice != NULL && !refSlice->totalLengthOnly)
{
// If a reference slice is provided, and totalLengthOnly is false, then we
// will only need to measure the reference slice. The values generated for
// it will later be copied to other slices.
firstSlice = lastSlice = refSlice->index;
}
else
{
// Otherwise we'll measure all of the slices.
firstSlice = 0;
lastSlice = _numSlices[sliceType] - 1;
}
// Iterate over the slices that need to be measured.
for (int slice = firstSlice; slice <= lastSlice; slice++)
{
// Some aspects of the SlicePatchContext will vary as we move from patch
// to patch, but we can go ahead and calculate now any stuff that is
// invariant along the slice's direction.
if (slice != 0)
{
// This is the position of the slice within each patch, in the
// direction orthogonal to the slice. Even-numbered slices are at the
// edge of the patch (position 1.0), while odd-numbered slices are in
// the middle (position 0.5).
context.position = 1.0f - ((float)(slice & 0x1) / 2.0f);
}
else
{
// For the first slice, we can't give it the usual treatment for even-
// numbered slices (since there is no patch "before" it), so it gets
// position 0.0 instead.
context.position = 0.0f;
}
// This is the slice of the same kind that defines the 0.0 edge of the
// patch. It will be the next lowest even-numbered slice. (Note the
// integer division here.)
context.edgeSlice[sliceType] = 2 * ((slice - 1) / 2);
// The alignment slice marks the zero-point from which we will be
// calculating the distances. So the cumulative distance there is zero.
MatrixElement(values, sliceType, slice, alignSlice) = 0.0f;
// Now we're going to calculate distances for control points "greater
// than" the one marked by the alignment slice.
// Start with zero cumulative distance.
float cumulativeDistance = 0.0f;
// Each pair of control points delineates a "half patch" (the middle
// control point corresponds to surface coords generated from t=0.5).
// Since distance measurements are done within each patch, and we want to
// measure the distance at half-patch increments, we need to alternate
// doing the segment measurements from 0 to 0.5 and from 0.5 to 1.0 (for
// values of t). We start with a t target of 0.5 or 1.0 depending on the
// even/odd nature of the alignment slice.
float slicewiseFraction = (float)((alignSlice & 0x1) + 1) / 2.0f;
// Iterate over the control points greater than the alignment point.
for (int halfPatch = alignSlice + 1; halfPatch < (int)SliceSize(sliceType); halfPatch++)
{
// Find the slice-of-other-kind that defines the patch edge orthogonal
// to our slice.
context.edgeSlice[OtherSliceType(sliceType)] = 2 * ((halfPatch - 1) / 2);
// Estimate the slice length along the surface of the half patch.
float segmentLengthEstimate =
EstimateSegmentLength(slicewiseFraction - 0.5f, slicewiseFraction, context);
// Recursively refine that estimate until it is good enough, then add it
// to our cumulative distance.
cumulativeDistance +=
RefineSegmentLength(slicewiseFraction - 0.5f, slicewiseFraction, context,
segmentLengthEstimate, UNITS_ERROR_BOUND);
// Store that cumulative distance in the output array.
MatrixElement(values, sliceType, slice, halfPatch) = cumulativeDistance;
// Flip to measure the other half patch.
slicewiseFraction = 1.5f - slicewiseFraction;
}
// Now we're going to calculate distances for control points "less
// than" the one marked by the alignment slice.
// Start with zero cumulative distance.
cumulativeDistance = 0.0f;
// We need to alternate doing the segment measurements from 1.0 to 0.5 and
// from 0.5 to 0 (for values of t). We start with a t target of 0.5 or 0
// depending on the even/odd nature of the alignment slice.
slicewiseFraction = (float)((alignSlice - 1) & 0x1) / 2.0f;
// Iterate over the control points less than the alignment point.
for (int halfPatch = alignSlice - 1; halfPatch >= 0; halfPatch--)
{
// Find the slice-of-other-kind that defines the patch edge orthogonal
// to our slice.
context.edgeSlice[OtherSliceType(sliceType)] = 2 * ((halfPatch - 1) / 2);
// Estimate the slice length along the surface of the half patch.
float segmentLengthEstimate =
EstimateSegmentLength(slicewiseFraction + 0.5f, slicewiseFraction, context);
// Recursively refine that estimate until it is good enough, then add it
// to our cumulative distance. (Which is negative on this side!)
cumulativeDistance -=
RefineSegmentLength(slicewiseFraction + 0.5f, slicewiseFraction, context,
segmentLengthEstimate, UNITS_ERROR_BOUND);
// Store that cumulative distance in the output array.
MatrixElement(values, sliceType, slice, halfPatch) = cumulativeDistance;
// Flip to measure the other half patch.
slicewiseFraction = 0.5f - slicewiseFraction;
}
}
// Now we may adjust the distance values based on scaling/tiling input.
// If there's a reference slice, we're going to need to know the total slice
// length, so save that away.
float refTotalLength;
if (refSlice != NULL)
{
refTotalLength =
MatrixElement(values, sliceType, refSlice->index, SliceSize(sliceType) - 1) -
MatrixElement(values, sliceType, refSlice->index, 0);
}
else
{
refTotalLength = 1.0f; // unused, but avoid uninitialized-var warning
}
#if defined(_DEBUG)
ASSERT_MESSAGE(refTotalLength != 0.0f, "calculated length of reference slice is zero");
ASSERT_MESSAGE(rawScaleOrTiles != 0.0f, "internal scale or tiles value is zero");
#endif
// Iterate over the slices we're processing and adjust the distance values
// (remember that this may just be the reference slice).
for (int slice = firstSlice; slice <= lastSlice; slice++)
{
// Figure out what we're going to divide the distances by.
float scaleFactor;
if (rawScale)
{
// In this case we've just been passed in the value to divide by.
scaleFactor = rawScaleOrTiles;
if (refSlice != NULL)
{
// However if there's a reference slice, adjust the divisor by the
// ratio of this slice's length to the reference slice's length.
// (Which is a NOP if this slice actually is the reference slice.)
// Example: if the ref slice is length 2, this slice is length 3,
// and the raw scale factor is 4... then all distances on the ref
// slice would be divided by 4 * 2 / 2 = 4, and distances on this
// slice would be divided by 4 * 3 / 2 = 6.
scaleFactor *=
((MatrixElement(values, sliceType, slice, SliceSize(sliceType) - 1) -
MatrixElement(values, sliceType, slice, 0)) /
refTotalLength);
}
}
else
{
// In this case we've been passed in a desired tiling value. We're
// going to want to eventually divide the distances by length / tiles.
// Example: if this slice is length 6 and the desired tiling is 3, we
// will divide the distances on this slice by 6 / 3 = 2.
scaleFactor =
(MatrixElement(values, sliceType, slice, SliceSize(sliceType) - 1) -
MatrixElement(values, sliceType, slice, 0)) /
rawScaleOrTiles;
}
// Adjust the distances for this slice by the divisor we calculated.
for (unsigned halfPatch = 0; halfPatch < SliceSize(sliceType); halfPatch++)
{
MatrixElement(values, sliceType, slice, halfPatch) /= scaleFactor;
}
}
// One final step if we have a reference slice and totalLengthOnly is false.
// In that case, up until this point we have only been processing the
// reference slice. Now we have to copy the reference slice's values to all
// other slices.
// (These loops also copy the reference slice to itself, which is fine.)
if (refSlice != NULL && !refSlice->totalLengthOnly)
{
for (unsigned slice = 0; slice < _numSlices[sliceType]; slice++)
{
for (unsigned halfPatch = 0; halfPatch < SliceSize(sliceType); halfPatch++)
{
MatrixElement(values, sliceType, slice, halfPatch) =
MatrixElement(values, sliceType, refSlice->index, halfPatch);
}
}
}
}
/**
* Generate coordinates for a specified texture axis based on a linear
* combination of factors. This method does the final work for the
* GeneralFunction public method.
*
* @param factors Factors to determine the texture coords.
* @param axis The texture axis to process.
* @param alignRow Zero-point row.
* @param alignCol Zero-point column.
* @param surfaceValues true if calculations are for S/T values on the mesh
* surface; false if calculations are for S/T values at
* the control points.
* @param rowDistances Surface distance-along-row values (measured from
* alignment column) for spots corresponding to each
* control point.
* @param colDistances Surface distance-along-column values (measured from
* alignment row) for spots corresponding to each
* control point.
*/
void
MeshEntity::GeneralFunctionInt(const GeneralFunctionFactors& factors,
TextureAxis axis,
int alignRow,
int alignCol,
bool surfaceValues,
const Matrix<float>& rowDistances,
const Matrix<float>& colDistances)
{
// Grab the "original value" info if the equation uses it.
AllocatedMatrix<float> oldValues(_meshData.x(), _meshData.y());
AllocatedMatrix<float> newValues(_meshData.x(), _meshData.y());
if (factors.oldValue != 0.0f)
{
if (surfaceValues)
{
// Will be manipulating surface values.
GenSurfaceFromControlTex(axis, oldValues);
}
else
{
// Will be manipulating control point values.
CopyValuesFromControlTex(axis, oldValues);
}
}
// Iterate over all values and apply the equation.
for (int rowIndex = 0; rowIndex < (int)_numSlices[ROW_SLICE_TYPE]; rowIndex++)
{
for (int colIndex = 0; colIndex < (int)_numSlices[COL_SLICE_TYPE]; colIndex++)
{
newValues(rowIndex, colIndex) =
factors.oldValue * oldValues(rowIndex, colIndex) +
factors.rowDistance * rowDistances(rowIndex, colIndex) +
factors.colDistance * colDistances(rowIndex, colIndex) +
factors.rowNumber * (rowIndex - alignRow) +
factors.colNumber * (colIndex - alignCol) +
factors.constant;
}
}
// Store the generated values.
if (surfaceValues)
{
// If we're manipulating surface values, figure the necessary control
// point values to make those.
GenControlTexFromSurface(axis, newValues);
}
else
{
// If we're manipulating control point values, store the new values.
CopyControlTexFromValues(axis, newValues);
}
}