X3D CAD/TransposeNurbsAxes
The web page NURBS demo files demonstrates a disagreement among current X3D browsers that implement the NURBS Component as to how they interpret the standard to place the parametric axes (generally labelled the u-v axes) on rendered 2D surface. This discrepancy affects the way the browser handles:
- the 'solid' attribute, which when set true will make the patch invisible from the 'inside'. The outward direction of the surface is determined by the 3-D orientation of the uv axes
- the mapping of texture coordinates on the surface
- the trimming of the surface by contours defined in uv-coordinates, for the NurbsTrimmedSurface node
This discrepancy amounts to interchanging the labelling of the parametic axes on the surface. It is possible to similarly interchange the parametric axes in an X3D model by
- interchanging the parameters uOrder with vOrder, uClosed with vClosed, etc.
- Transposing (rows with columns) the array of control points and weights before flattening to a list to encode in the Coordinate.point field.
The XSLT script below will perform this transformation on an XML-encoded X3D model containing NurbsTrimmedSurface or NurbsPatchSurface nodes.
An invocation of this script using the xsltproc XSLT engine from the command line would look like:
xsltproc transpose_nurbs_surface.xsl model.x3d > transposed_model.x3d
XSLT script: transpose_nurbs_surface.xsl
<?xml version="1.0" encoding="ISO-8859-1"?> <!-- Transposes the u-v parametric coordinate axes on a NurbsPatchSurface or NurbsTrimmedSurface For NurbsPatchSurface nodes and TrimmedNurbsSurface node the parameters which come in u-v pairs (ex uKnot, vKnot) have their values swapped The attributes NurbsPatchSurface/@weight and NurbsPatchSurface/Coordinate/@point are, in the input x3d data, lists of numbers which are the weight,control_points arrays listed in column-major order. In the transformed data these lists are the same arrays flattened in row-major order. Copyright 2012 Vincent Marchetti This program 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. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:str="http://exslt.org/strings" extension-element-prefixes="exsl" xmlns:ksh="http://kshell.com/ns/x3d"> <xsl:output method="xml" encoding="utf-8"/> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> <xsl:template match="NurbsPatchSurface/@uKnot|NurbsTrimmedSurface/@uKnot"> <xsl:if test="../@vKnot"> <xsl:attribute name="{name()}"> <xsl:value-of select="../@vKnot"/> </xsl:attribute> </xsl:if> </xsl:template> <xsl:template match="NurbsPatchSurface/@vKnot|NurbsTrimmedSurface/@vKnot"> <xsl:if test="../@uKnot"> <xsl:attribute name="{name()}"> <xsl:value-of select="../@uKnot"/> </xsl:attribute> </xsl:if> </xsl:template> <xsl:template match="NurbsPatchSurface/@uDimension|NurbsTrimmedSurface/@uDimension"> <xsl:if test="../@vDimension"> <xsl:attribute name="{name()}"> <xsl:value-of select="../@vDimension"/> </xsl:attribute> </xsl:if> </xsl:template> <xsl:template match="NurbsPatchSurface/@vDimension|NurbsTrimmedSurface/@vDimension"> <xsl:if test="../@uDimension"> <xsl:attribute name="{name()}"> <xsl:value-of select="../@uDimension"/> </xsl:attribute> </xsl:if> </xsl:template> <xsl:template match="NurbsPatchSurface/@uTessellation|NurbsTrimmedSurface/@uTessellation"> <xsl:if test="../@vTessellation"> <xsl:attribute name="{name()}"> <xsl:value-of select="../@vTessellation"/> </xsl:attribute> </xsl:if> </xsl:template> <xsl:template match="NurbsPatchSurface/@vTessellation|NurbsTrimmedSurface/@vTessellation"> <xsl:if test="../@uTessellation"> <xsl:attribute name="{name()}"> <xsl:value-of select="../@uTessellation"/> </xsl:attribute> </xsl:if> </xsl:template> <xsl:template match="NurbsPatchSurface/@uClosed|NurbsTrimmedSurface/@uClosed"> <xsl:if test="../@vClosed"> <xsl:attribute name="{name()}"> <xsl:value-of select="../@vClosed"/> </xsl:attribute> </xsl:if> </xsl:template> <xsl:template match="NurbsPatchSurface/@vClosed|NurbsTrimmedSurface/@vClosed"> <xsl:if test="../@uClosed"> <xsl:attribute name="{name()}"> <xsl:value-of select="../@uClosed"/> </xsl:attribute> </xsl:if> </xsl:template> <xsl:template match="NurbsPatchSurface/@uOrder|NurbsTrimmedSurface/@uOrder"> <xsl:if test="../@vOrder"> <xsl:attribute name="{name()}"> <xsl:value-of select="../@vOrder"/> </xsl:attribute> </xsl:if> </xsl:template> <xsl:template match="NurbsPatchSurface/@vOrder|NurbsTrimmedSurface/@vOrder"> <xsl:if test="../@uOrder"> <xsl:attribute name="{name()}"> <xsl:value-of select="../@uOrder"/> </xsl:attribute> </xsl:if> </xsl:template> <xsl:template match="NurbsPatchSurface/@weight|NurbsTrimmedSurface/@weight"> <xsl:attribute name="{name()}"> <xsl:variable name="transposed_weights"> <xsl:call-template name="ksh:transpose"> <xsl:with-param name="NC" select="number(../@vDimension)"/> <xsl:with-param name="NR" select="number(../@uDimension)"/> <xsl:with-param name="elements" select="str:split(normalize-space(.))"/> </xsl:call-template> </xsl:variable> <xsl:for-each select="exsl:node-set($transposed_weights)/token"> <xsl:value-of select="."/> <xsl:if test="last()>position()"><xsl:text> </xsl:text></xsl:if> </xsl:for-each> </xsl:attribute> </xsl:template> <xsl:template match="NurbsPatchSurface/Coordinate/@point|NurbsTrimmedSurface/Coordinate/@point"> <xsl:attribute name="{name()}"> <xsl:variable name="transposed_point"> <xsl:call-template name="ksh:transpose"> <xsl:with-param name="NC" select="number(../../@vDimension)"/> <xsl:with-param name="NR" select="number(../../@uDimension)"/> <xsl:with-param name="elements" select="str:split(.,',')"/> </xsl:call-template> </xsl:variable> <xsl:for-each select="exsl:node-set($transposed_point)/token"> <xsl:value-of select="."/> <xsl:if test="last() > position()"><xsl:text>, </xsl:text></xsl:if> </xsl:for-each> </xsl:attribute> </xsl:template> <!-- Templates for taking an array of nodes , NR rows and NC columns, output in column-major order, and preparing a node set of those nodes listed in row-major order --> <xsl:template name="ksh:transpose"> <xsl:param name="IR" select="1"/> <xsl:param name="NR"/> <xsl:param name="NC"/> <xsl:param name="elements"/> <!-- call the template which will list (in row major order the IR'th row --> <xsl:call-template name="ksh:transpose-row-out"> <xsl:with-param name="IR" select="$IR"/> <xsl:with-param name="NC" select="$NC"/> <xsl:with-param name="NR" select="$NR"/> <xsl:with-param name="elements" select="$elements"/> </xsl:call-template> <!-- the recursion: call this template starting at the next row --> <xsl:if test="$NR > $IR"> <xsl:call-template name="ksh:transpose"> <xsl:with-param name="IR" select="$IR + 1"/> <xsl:with-param name="NC" select="$NC"/> <xsl:with-param name="NR" select="$NR"/> <xsl:with-param name="elements" select="$elements"/> </xsl:call-template> </xsl:if> </xsl:template> <xsl:template name="ksh:transpose-row-out"> <xsl:param name="IC" select="1"/> <xsl:param name="IR"/> <xsl:param name="NR"/> <xsl:param name="NC"/> <xsl:param name="elements"/> <!-- send out the IC'th column of the IR'th row, using the indexing appropriate for column major indexing of the underlying list --> <xsl:param name="index" select="($IC -1)*$NR + $IR"/> <xsl:copy-of select="$elements[$index]"/> <!-- and the recursion, send out the rest of the columns of this row, starting at IC+1 --> <xsl:if test="$NC > $IC"> <xsl:call-template name="ksh:transpose-row-out"> <xsl:with-param name="IC" select="$IC + 1"/> <xsl:with-param name="IR" select="$IR"/> <xsl:with-param name="NC" select="$NC"/> <xsl:with-param name="NR" select="$NR"/> <xsl:with-param name="elements" select="$elements"/> </xsl:call-template> </xsl:if> </xsl:template> </xsl:stylesheet>