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
10 #Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module.
13 from fabmetheus_utilities.geometry.creation import lineation
14 from fabmetheus_utilities.geometry.creation import solid
15 from fabmetheus_utilities.geometry.geometry_tools import path
16 from fabmetheus_utilities.geometry.geometry_utilities import evaluate
17 from fabmetheus_utilities.geometry.solids import triangle_mesh
18 from fabmetheus_utilities.vector3 import Vector3
19 from fabmetheus_utilities.vector3index import Vector3Index
20 from fabmetheus_utilities import archive
21 from fabmetheus_utilities import euclidean
26 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
27 __credits__ = 'Art of Illusion <http://www.artofillusion.org/>'
28 __date__ = '$Date: 2008/02/05 $'
29 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
32 def addHeightsByBitmap(heights, textLines):
33 'Add heights by bitmap.'
34 for line in textLines[3:]:
35 for integerWord in line.split():
36 heights.append(float(integerWord))
38 def addHeightsByGraymap(heights, textLines):
39 'Add heights by graymap.'
40 divisor = float(textLines[3])
41 for line in textLines[4:]:
42 for integerWord in line.split():
43 heights.append(float(integerWord) / divisor)
45 def getAddIndexedHeightGrid(heightGrid, minimumXY, step, top, vertexes):
46 'Get and add an indexed heightGrid.'
47 indexedHeightGrid = []
48 for rowIndex, row in enumerate(heightGrid):
50 indexedHeightGrid.append(indexedRow)
51 rowOffset = step.imag * float(rowIndex) + minimumXY.imag
52 for columnIndex, element in enumerate(row):
53 columnOffset = step.real * float(columnIndex) + minimumXY.real
54 vector3index = Vector3Index(len(vertexes), columnOffset, rowOffset, top * element)
55 indexedRow.append(vector3index)
56 vertexes.append(vector3index)
57 return indexedHeightGrid
59 def getAddIndexedSegmentedPerimeter(heightGrid, maximumXY, minimumXY, step, vertexes, z=0.0):
60 'Get and add an indexed segmented perimeter.'
61 indexedSegmentedPerimeter = []
62 firstRow = heightGrid[0]
63 columnOffset = minimumXY.real
64 numberOfRowsMinusTwo = len(heightGrid) - 2
65 for column in firstRow:
66 vector3index = Vector3Index(len(vertexes), columnOffset, minimumXY.imag, z)
67 vertexes.append(vector3index)
68 indexedSegmentedPerimeter.append(vector3index)
69 columnOffset += step.real
70 rowOffset = minimumXY.imag
71 for rowIndex in xrange(numberOfRowsMinusTwo):
72 rowOffset += step.imag
73 vector3index = Vector3Index(len(vertexes), maximumXY.real, rowOffset, z)
74 vertexes.append(vector3index)
75 indexedSegmentedPerimeter.append(vector3index)
76 columnOffset = maximumXY.real
77 for column in firstRow:
78 vector3index = Vector3Index(len(vertexes), columnOffset, maximumXY.imag, z)
79 vertexes.append(vector3index)
80 indexedSegmentedPerimeter.append(vector3index)
81 columnOffset -= step.real
82 rowOffset = maximumXY.imag
83 for rowIndex in xrange(numberOfRowsMinusTwo):
84 rowOffset -= step.imag
85 vector3index = Vector3Index(len(vertexes), minimumXY.real, rowOffset, z)
86 vertexes.append(vector3index)
87 indexedSegmentedPerimeter.append(vector3index)
88 return indexedSegmentedPerimeter
90 def getGeometryOutput(elementNode):
91 'Get vector3 vertexes from attribute dictionary.'
92 derivation = HeightmapDerivation(elementNode)
93 heightGrid = derivation.heightGrid
94 if derivation.fileName != '':
95 heightGrid = getHeightGrid(archive.getAbsoluteFolderPath(elementNode.getOwnerDocument().fileName, derivation.fileName))
96 return getGeometryOutputByHeightGrid(derivation, elementNode, heightGrid)
98 def getGeometryOutputByArguments(arguments, elementNode):
99 'Get vector3 vertexes from attribute dictionary by arguments.'
100 evaluate.setAttributesByArguments(['file', 'start'], arguments, elementNode)
101 return getGeometryOutput(elementNode)
103 def getGeometryOutputByHeightGrid(derivation, elementNode, heightGrid):
104 'Get vector3 vertexes from attribute dictionary.'
105 numberOfColumns = len(heightGrid)
106 if numberOfColumns < 2:
107 print('Warning, in getGeometryOutputByHeightGrid in heightmap there are fewer than two rows for:')
111 numberOfRows = len(heightGrid[0])
113 print('Warning, in getGeometryOutputByHeightGrid in heightmap there are fewer than two columns for:')
117 for row in heightGrid:
118 if len(row) != numberOfRows:
119 print('Warning, in getGeometryOutputByHeightGrid in heightmap the heightgrid is not rectangular for:')
123 inradiusComplex = derivation.inradius.dropAxis()
124 minimumXY = -inradiusComplex
125 step = complex(derivation.inradius.x / float(numberOfRows - 1), derivation.inradius.y / float(numberOfColumns - 1))
128 heightGrid = getRaisedHeightGrid(heightGrid, derivation.start)
129 top = derivation.inradius.z + derivation.inradius.z
131 indexedBottomLoop = getAddIndexedSegmentedPerimeter(heightGrid, inradiusComplex, minimumXY, step, vertexes)
132 indexedLoops = [indexedBottomLoop]
133 indexedGridTop = getAddIndexedHeightGrid(heightGrid, minimumXY, step, top, vertexes)
134 indexedLoops.append(triangle_mesh.getIndexedLoopFromIndexedGrid(indexedGridTop))
135 vertexes = triangle_mesh.getUniqueVertexes(indexedLoops + indexedGridTop)
136 triangle_mesh.addPillarFromConvexLoopsGridTop(faces, indexedGridTop, indexedLoops)
137 return triangle_mesh.getGeometryOutputByFacesVertexes(faces, vertexes)
139 def getHeightGrid(fileName):
140 'Get heightGrid by fileName.'
141 if 'models/' not in fileName:
142 print('Warning, models/ was not in the absolute file path, so for security nothing will be done for:')
144 print('The heightmap tool can only read a file which has models/ in the file path.')
145 print('To import the file, move the file into a folder called model/ or a subfolder which is inside the model folder tree.')
147 pgmText = archive.getFileText(fileName)
148 textLines = archive.getTextLines(pgmText)
149 format = textLines[0].lower()
150 sizeWords = textLines[2].split()
151 numberOfColumns = int(sizeWords[0])
152 numberOfRows = int(sizeWords[1])
155 addHeightsByBitmap(heights, textLines)
157 addHeightsByGraymap(heights, textLines)
159 print('Warning, the file format was not recognized for:')
161 print('Heightmap can only read the Netpbm Portable bitmap format and the Netpbm Portable graymap format.')
162 print('The Netpbm formats are described at:')
163 print('http://en.wikipedia.org/wiki/Netpbm_format')
167 for rowIndex in xrange(numberOfRows):
169 heightGrid.append(row)
170 for columnIndex in xrange(numberOfColumns):
171 row.append(heights[heightIndex])
175 def getNewDerivation(elementNode):
176 'Get new derivation.'
177 return HeightmapDerivation(elementNode)
179 def getRaisedHeightGrid(heightGrid, start):
180 'Get heightGrid raised above start.'
181 raisedHeightGrid = []
182 remainingHeight = 1.0 - start
183 for row in heightGrid:
185 raisedHeightGrid.append(raisedRow)
187 raisedElement = remainingHeight * element + start
188 raisedRow.append(raisedElement)
189 return raisedHeightGrid
191 def processElementNode(elementNode):
192 'Process the xml element.'
193 solid.processElementNodeByGeometry(elementNode, getGeometryOutput(elementNode))
196 class HeightmapDerivation:
197 'Class to hold heightmap variables.'
198 def __init__(self, elementNode):
200 self.fileName = evaluate.getEvaluatedString('', elementNode, 'file')
201 self.heightGrid = evaluate.getEvaluatedValue([], elementNode, 'heightGrid')
202 self.inradius = evaluate.getVector3ByPrefixes(elementNode, ['demisize', 'inradius'], Vector3(10.0, 10.0, 5.0))
203 self.inradius = evaluate.getVector3ByMultiplierPrefix(elementNode, 2.0, 'size', self.inradius)
204 self.start = evaluate.getEvaluatedFloat(0.0, elementNode, 'start')
207 'Get the string representation of this HeightmapDerivation.'
208 return euclidean.getDictionaryString(self.__dict__)