/** * @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 . */ #include #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(_meshData.x()); _numSlices[COL_SLICE_TYPE] = static_cast(_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[INFO_BUFFER_SIZE] = 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 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 rowDistances(_meshData.x(), _meshData.y()); AllocatedMatrix 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& 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& 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& 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& 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& 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& rowDistances, const Matrix& colDistances) { // Grab the "original value" info if the equation uses it. AllocatedMatrix oldValues(_meshData.x(), _meshData.y()); AllocatedMatrix 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); } }