MrDraw/SkeinPyPy/fabmetheus_utilities/geometry/creation/heightmap.py

208 lines
8.1 KiB
Python

"""
Heightmap.
http://www.cs.otago.ac.nz/graphics/Mirage/node59.html
http://en.wikipedia.org/wiki/Heightmap
http://en.wikipedia.org/wiki/Netpbm_format
"""
from __future__ import absolute_import
#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.
import __init__
from fabmetheus_utilities.geometry.creation import lineation
from fabmetheus_utilities.geometry.creation import solid
from fabmetheus_utilities.geometry.geometry_tools import path
from fabmetheus_utilities.geometry.geometry_utilities import evaluate
from fabmetheus_utilities.geometry.solids import triangle_mesh
from fabmetheus_utilities.vector3 import Vector3
from fabmetheus_utilities.vector3index import Vector3Index
from fabmetheus_utilities import archive
from fabmetheus_utilities import euclidean
import math
import random
__author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
__credits__ = 'Art of Illusion <http://www.artofillusion.org/>'
__date__ = '$Date: 2008/02/05 $'
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
def addHeightsByBitmap(heights, textLines):
'Add heights by bitmap.'
for line in textLines[3:]:
for integerWord in line.split():
heights.append(float(integerWord))
def addHeightsByGraymap(heights, textLines):
'Add heights by graymap.'
divisor = float(textLines[3])
for line in textLines[4:]:
for integerWord in line.split():
heights.append(float(integerWord) / divisor)
def getAddIndexedHeightGrid(heightGrid, minimumXY, step, top, vertexes):
'Get and add an indexed heightGrid.'
indexedHeightGrid = []
for rowIndex, row in enumerate(heightGrid):
indexedRow = []
indexedHeightGrid.append(indexedRow)
rowOffset = step.imag * float(rowIndex) + minimumXY.imag
for columnIndex, element in enumerate(row):
columnOffset = step.real * float(columnIndex) + minimumXY.real
vector3index = Vector3Index(len(vertexes), columnOffset, rowOffset, top * element)
indexedRow.append(vector3index)
vertexes.append(vector3index)
return indexedHeightGrid
def getAddIndexedSegmentedPerimeter(heightGrid, maximumXY, minimumXY, step, vertexes, z=0.0):
'Get and add an indexed segmented perimeter.'
indexedSegmentedPerimeter = []
firstRow = heightGrid[0]
columnOffset = minimumXY.real
numberOfRowsMinusTwo = len(heightGrid) - 2
for column in firstRow:
vector3index = Vector3Index(len(vertexes), columnOffset, minimumXY.imag, z)
vertexes.append(vector3index)
indexedSegmentedPerimeter.append(vector3index)
columnOffset += step.real
rowOffset = minimumXY.imag
for rowIndex in xrange(numberOfRowsMinusTwo):
rowOffset += step.imag
vector3index = Vector3Index(len(vertexes), maximumXY.real, rowOffset, z)
vertexes.append(vector3index)
indexedSegmentedPerimeter.append(vector3index)
columnOffset = maximumXY.real
for column in firstRow:
vector3index = Vector3Index(len(vertexes), columnOffset, maximumXY.imag, z)
vertexes.append(vector3index)
indexedSegmentedPerimeter.append(vector3index)
columnOffset -= step.real
rowOffset = maximumXY.imag
for rowIndex in xrange(numberOfRowsMinusTwo):
rowOffset -= step.imag
vector3index = Vector3Index(len(vertexes), minimumXY.real, rowOffset, z)
vertexes.append(vector3index)
indexedSegmentedPerimeter.append(vector3index)
return indexedSegmentedPerimeter
def getGeometryOutput(elementNode):
'Get vector3 vertexes from attribute dictionary.'
derivation = HeightmapDerivation(elementNode)
heightGrid = derivation.heightGrid
if derivation.fileName != '':
heightGrid = getHeightGrid(archive.getAbsoluteFolderPath(elementNode.getOwnerDocument().fileName, derivation.fileName))
return getGeometryOutputByHeightGrid(derivation, elementNode, heightGrid)
def getGeometryOutputByArguments(arguments, elementNode):
'Get vector3 vertexes from attribute dictionary by arguments.'
evaluate.setAttributesByArguments(['file', 'start'], arguments, elementNode)
return getGeometryOutput(elementNode)
def getGeometryOutputByHeightGrid(derivation, elementNode, heightGrid):
'Get vector3 vertexes from attribute dictionary.'
numberOfColumns = len(heightGrid)
if numberOfColumns < 2:
print('Warning, in getGeometryOutputByHeightGrid in heightmap there are fewer than two rows for:')
print(heightGrid)
print(elementNode)
return None
numberOfRows = len(heightGrid[0])
if numberOfRows < 2:
print('Warning, in getGeometryOutputByHeightGrid in heightmap there are fewer than two columns for:')
print(heightGrid)
print(elementNode)
return None
for row in heightGrid:
if len(row) != numberOfRows:
print('Warning, in getGeometryOutputByHeightGrid in heightmap the heightgrid is not rectangular for:')
print(heightGrid)
print(elementNode)
return None
inradiusComplex = derivation.inradius.dropAxis()
minimumXY = -inradiusComplex
step = complex(derivation.inradius.x / float(numberOfRows - 1), derivation.inradius.y / float(numberOfColumns - 1))
step += step
faces = []
heightGrid = getRaisedHeightGrid(heightGrid, derivation.start)
top = derivation.inradius.z + derivation.inradius.z
vertexes = []
indexedBottomLoop = getAddIndexedSegmentedPerimeter(heightGrid, inradiusComplex, minimumXY, step, vertexes)
indexedLoops = [indexedBottomLoop]
indexedGridTop = getAddIndexedHeightGrid(heightGrid, minimumXY, step, top, vertexes)
indexedLoops.append(triangle_mesh.getIndexedLoopFromIndexedGrid(indexedGridTop))
vertexes = triangle_mesh.getUniqueVertexes(indexedLoops + indexedGridTop)
triangle_mesh.addPillarFromConvexLoopsGridTop(faces, indexedGridTop, indexedLoops)
return triangle_mesh.getGeometryOutputByFacesVertexes(faces, vertexes)
def getHeightGrid(fileName):
'Get heightGrid by fileName.'
if 'models/' not in fileName:
print('Warning, models/ was not in the absolute file path, so for security nothing will be done for:')
print(fileName)
print('The heightmap tool can only read a file which has models/ in the file path.')
print('To import the file, move the file into a folder called model/ or a subfolder which is inside the model folder tree.')
return
pgmText = archive.getFileText(fileName)
textLines = archive.getTextLines(pgmText)
format = textLines[0].lower()
sizeWords = textLines[2].split()
numberOfColumns = int(sizeWords[0])
numberOfRows = int(sizeWords[1])
heights = []
if format == 'p1':
addHeightsByBitmap(heights, textLines)
elif format == 'p2':
addHeightsByGraymap(heights, textLines)
else:
print('Warning, the file format was not recognized for:')
print(fileName)
print('Heightmap can only read the Netpbm Portable bitmap format and the Netpbm Portable graymap format.')
print('The Netpbm formats are described at:')
print('http://en.wikipedia.org/wiki/Netpbm_format')
return []
heightGrid = []
heightIndex = 0
for rowIndex in xrange(numberOfRows):
row = []
heightGrid.append(row)
for columnIndex in xrange(numberOfColumns):
row.append(heights[heightIndex])
heightIndex += 1
return heightGrid
def getNewDerivation(elementNode):
'Get new derivation.'
return HeightmapDerivation(elementNode)
def getRaisedHeightGrid(heightGrid, start):
'Get heightGrid raised above start.'
raisedHeightGrid = []
remainingHeight = 1.0 - start
for row in heightGrid:
raisedRow = []
raisedHeightGrid.append(raisedRow)
for element in row:
raisedElement = remainingHeight * element + start
raisedRow.append(raisedElement)
return raisedHeightGrid
def processElementNode(elementNode):
'Process the xml element.'
solid.processElementNodeByGeometry(elementNode, getGeometryOutput(elementNode))
class HeightmapDerivation:
'Class to hold heightmap variables.'
def __init__(self, elementNode):
'Set defaults.'
self.fileName = evaluate.getEvaluatedString('', elementNode, 'file')
self.heightGrid = evaluate.getEvaluatedValue([], elementNode, 'heightGrid')
self.inradius = evaluate.getVector3ByPrefixes(elementNode, ['demisize', 'inradius'], Vector3(10.0, 10.0, 5.0))
self.inradius = evaluate.getVector3ByMultiplierPrefix(elementNode, 2.0, 'size', self.inradius)
self.start = evaluate.getEvaluatedFloat(0.0, elementNode, 'start')
def __repr__(self):
'Get the string representation of this HeightmapDerivation.'
return euclidean.getDictionaryString(self.__dict__)