3 http://www.cs.otago.ac.nz/graphics/Mirage/node59.html
4 http://en.wikipedia.org/wiki/Heightmap
5 http://en.wikipedia.org/wiki/Netpbm_format
9 from __future__ import absolute_import
11 from fabmetheus_utilities.geometry.creation import solid
12 from fabmetheus_utilities.geometry.geometry_utilities import evaluate
13 from fabmetheus_utilities.geometry.solids import triangle_mesh
14 from fabmetheus_utilities.vector3 import Vector3
15 from fabmetheus_utilities.vector3index import Vector3Index
16 from fabmetheus_utilities import archive
17 from fabmetheus_utilities import euclidean
20 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
21 __credits__ = 'Art of Illusion <http://www.artofillusion.org/>'
22 __date__ = '$Date: 2008/02/05 $'
23 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
26 def addHeightsByBitmap(heights, textLines):
27 'Add heights by bitmap.'
28 for line in textLines[3:]:
29 for integerWord in line.split():
30 heights.append(float(integerWord))
32 def addHeightsByGraymap(heights, textLines):
33 'Add heights by graymap.'
34 divisor = float(textLines[3])
35 for line in textLines[4:]:
36 for integerWord in line.split():
37 heights.append(float(integerWord) / divisor)
39 def getAddIndexedHeightGrid(heightGrid, minimumXY, step, top, vertexes):
40 'Get and add an indexed heightGrid.'
41 indexedHeightGrid = []
42 for rowIndex, row in enumerate(heightGrid):
44 indexedHeightGrid.append(indexedRow)
45 rowOffset = step.imag * float(rowIndex) + minimumXY.imag
46 for columnIndex, element in enumerate(row):
47 columnOffset = step.real * float(columnIndex) + minimumXY.real
48 vector3index = Vector3Index(len(vertexes), columnOffset, rowOffset, top * element)
49 indexedRow.append(vector3index)
50 vertexes.append(vector3index)
51 return indexedHeightGrid
53 def getAddIndexedSegmentedPerimeter(heightGrid, maximumXY, minimumXY, step, vertexes, z=0.0):
54 'Get and add an indexed segmented perimeter.'
55 indexedSegmentedPerimeter = []
56 firstRow = heightGrid[0]
57 columnOffset = minimumXY.real
58 numberOfRowsMinusTwo = len(heightGrid) - 2
59 for column in firstRow:
60 vector3index = Vector3Index(len(vertexes), columnOffset, minimumXY.imag, z)
61 vertexes.append(vector3index)
62 indexedSegmentedPerimeter.append(vector3index)
63 columnOffset += step.real
64 rowOffset = minimumXY.imag
65 for rowIndex in xrange(numberOfRowsMinusTwo):
66 rowOffset += step.imag
67 vector3index = Vector3Index(len(vertexes), maximumXY.real, rowOffset, z)
68 vertexes.append(vector3index)
69 indexedSegmentedPerimeter.append(vector3index)
70 columnOffset = maximumXY.real
71 for column in firstRow:
72 vector3index = Vector3Index(len(vertexes), columnOffset, maximumXY.imag, z)
73 vertexes.append(vector3index)
74 indexedSegmentedPerimeter.append(vector3index)
75 columnOffset -= step.real
76 rowOffset = maximumXY.imag
77 for rowIndex in xrange(numberOfRowsMinusTwo):
78 rowOffset -= step.imag
79 vector3index = Vector3Index(len(vertexes), minimumXY.real, rowOffset, z)
80 vertexes.append(vector3index)
81 indexedSegmentedPerimeter.append(vector3index)
82 return indexedSegmentedPerimeter
84 def getGeometryOutput(elementNode):
85 'Get vector3 vertexes from attribute dictionary.'
86 derivation = HeightmapDerivation(elementNode)
87 heightGrid = derivation.heightGrid
88 if derivation.fileName != '':
89 heightGrid = getHeightGrid(archive.getAbsoluteFolderPath(elementNode.getOwnerDocument().fileName, derivation.fileName))
90 return getGeometryOutputByHeightGrid(derivation, elementNode, heightGrid)
92 def getGeometryOutputByArguments(arguments, elementNode):
93 'Get vector3 vertexes from attribute dictionary by arguments.'
94 evaluate.setAttributesByArguments(['file', 'start'], arguments, elementNode)
95 return getGeometryOutput(elementNode)
97 def getGeometryOutputByHeightGrid(derivation, elementNode, heightGrid):
98 'Get vector3 vertexes from attribute dictionary.'
99 numberOfColumns = len(heightGrid)
100 if numberOfColumns < 2:
101 print('Warning, in getGeometryOutputByHeightGrid in heightmap there are fewer than two rows for:')
105 numberOfRows = len(heightGrid[0])
107 print('Warning, in getGeometryOutputByHeightGrid in heightmap there are fewer than two columns for:')
111 for row in heightGrid:
112 if len(row) != numberOfRows:
113 print('Warning, in getGeometryOutputByHeightGrid in heightmap the heightgrid is not rectangular for:')
117 inradiusComplex = derivation.inradius.dropAxis()
118 minimumXY = -inradiusComplex
119 step = complex(derivation.inradius.x / float(numberOfRows - 1), derivation.inradius.y / float(numberOfColumns - 1))
122 heightGrid = getRaisedHeightGrid(heightGrid, derivation.start)
123 top = derivation.inradius.z + derivation.inradius.z
125 indexedBottomLoop = getAddIndexedSegmentedPerimeter(heightGrid, inradiusComplex, minimumXY, step, vertexes)
126 indexedLoops = [indexedBottomLoop]
127 indexedGridTop = getAddIndexedHeightGrid(heightGrid, minimumXY, step, top, vertexes)
128 indexedLoops.append(triangle_mesh.getIndexedLoopFromIndexedGrid(indexedGridTop))
129 vertexes = triangle_mesh.getUniqueVertexes(indexedLoops + indexedGridTop)
130 triangle_mesh.addPillarFromConvexLoopsGridTop(faces, indexedGridTop, indexedLoops)
131 return triangle_mesh.getGeometryOutputByFacesVertexes(faces, vertexes)
133 def getHeightGrid(fileName):
134 'Get heightGrid by fileName.'
135 if 'models/' not in fileName:
136 print('Warning, models/ was not in the absolute file path, so for security nothing will be done for:')
138 print('The heightmap tool can only read a file which has models/ in the file path.')
139 print('To import the file, move the file into a folder called model/ or a subfolder which is inside the model folder tree.')
141 pgmText = archive.getFileText(fileName)
142 textLines = archive.getTextLines(pgmText)
143 format = textLines[0].lower()
144 sizeWords = textLines[2].split()
145 numberOfColumns = int(sizeWords[0])
146 numberOfRows = int(sizeWords[1])
149 addHeightsByBitmap(heights, textLines)
151 addHeightsByGraymap(heights, textLines)
153 print('Warning, the file format was not recognized for:')
155 print('Heightmap can only read the Netpbm Portable bitmap format and the Netpbm Portable graymap format.')
156 print('The Netpbm formats are described at:')
157 print('http://en.wikipedia.org/wiki/Netpbm_format')
161 for rowIndex in xrange(numberOfRows):
163 heightGrid.append(row)
164 for columnIndex in xrange(numberOfColumns):
165 row.append(heights[heightIndex])
169 def getNewDerivation(elementNode):
170 'Get new derivation.'
171 return HeightmapDerivation(elementNode)
173 def getRaisedHeightGrid(heightGrid, start):
174 'Get heightGrid raised above start.'
175 raisedHeightGrid = []
176 remainingHeight = 1.0 - start
177 for row in heightGrid:
179 raisedHeightGrid.append(raisedRow)
181 raisedElement = remainingHeight * element + start
182 raisedRow.append(raisedElement)
183 return raisedHeightGrid
185 def processElementNode(elementNode):
186 'Process the xml element.'
187 solid.processElementNodeByGeometry(elementNode, getGeometryOutput(elementNode))
190 class HeightmapDerivation(object):
191 'Class to hold heightmap variables.'
192 def __init__(self, elementNode):
194 self.fileName = evaluate.getEvaluatedString('', elementNode, 'file')
195 self.heightGrid = evaluate.getEvaluatedValue([], elementNode, 'heightGrid')
196 self.inradius = evaluate.getVector3ByPrefixes(elementNode, ['demisize', 'inradius'], Vector3(10.0, 10.0, 5.0))
197 self.inradius = evaluate.getVector3ByMultiplierPrefix(elementNode, 2.0, 'size', self.inradius)
198 self.start = evaluate.getEvaluatedFloat(0.0, elementNode, 'start')
201 'Get the string representation of this HeightmapDerivation.'
202 return euclidean.getDictionaryString(self.__dict__)