mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-03 02:15:43 -04:00
handy tool
This commit is contained in:
parent
ec408822c5
commit
8e6eb98afe
704
direct/src/showutil/TexMemWatcher.py
Normal file
704
direct/src/showutil/TexMemWatcher.py
Normal file
@ -0,0 +1,704 @@
|
|||||||
|
from pandac.PandaModules import *
|
||||||
|
from direct.showbase.DirectObject import DirectObject
|
||||||
|
import math
|
||||||
|
import copy
|
||||||
|
|
||||||
|
class TexMemWatcher(DirectObject):
|
||||||
|
"""
|
||||||
|
This class creates a separate graphics window that displays an
|
||||||
|
approximation of the current texture memory, showing the textures
|
||||||
|
that are resident and/or active, and an approximation of the
|
||||||
|
amount of texture memory consumed by each one. It's intended as a
|
||||||
|
useful tool to help determine where texture memory is being spent.
|
||||||
|
|
||||||
|
Although it represents the textures visually in a 2-d space, it
|
||||||
|
doesn't actually have any idea how textures are physically laid
|
||||||
|
out in memory--but it has to lay them out somehow, so it makes
|
||||||
|
something up. It occasionally rearranges the texture display when
|
||||||
|
it feels it needs to, without regard to what the graphics card is
|
||||||
|
actually doing. This tool can't be used to research texture
|
||||||
|
memory fragmentation issues.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, gsg = None, limit = None):
|
||||||
|
DirectObject.__init__(self)
|
||||||
|
|
||||||
|
# If no GSG is specified, use the main GSG.
|
||||||
|
if gsg is None:
|
||||||
|
gsg = base.win.getGsg()
|
||||||
|
elif isinstance(gsg, GraphicsOutput):
|
||||||
|
# If we were passed a window, use that window's GSG.
|
||||||
|
gsg = gsg.getGsg()
|
||||||
|
|
||||||
|
self.gsg = gsg
|
||||||
|
|
||||||
|
# Now open a new window just to render the output.
|
||||||
|
self.winSize = (300, 300)
|
||||||
|
name = 'Texture Memory'
|
||||||
|
props = WindowProperties()
|
||||||
|
props.setSize(*self.winSize)
|
||||||
|
props.setTitle(name)
|
||||||
|
props.setFullscreen(False)
|
||||||
|
|
||||||
|
fbprops = FrameBufferProperties.getDefault()
|
||||||
|
flags = GraphicsPipe.BFFbPropsOptional | GraphicsPipe.BFRequireWindow
|
||||||
|
|
||||||
|
self.win = base.graphicsEngine.makeOutput(base.pipe, name, 0, fbprops,
|
||||||
|
props, flags)
|
||||||
|
assert self.win
|
||||||
|
|
||||||
|
# We don't need to clear the color buffer, since we'll be
|
||||||
|
# filling it with a texture. But we can clear the depth
|
||||||
|
# buffer; we use the depth buffer to cut a hole in the matte.
|
||||||
|
self.win.setClearColor(False)
|
||||||
|
self.win.setClearDepth(True)
|
||||||
|
|
||||||
|
self.win.setWindowEvent('tex-mem-window')
|
||||||
|
self.accept('tex-mem-window', self.windowEvent)
|
||||||
|
|
||||||
|
# Make a render2d in this new window.
|
||||||
|
self.render2d = NodePath('render2d')
|
||||||
|
self.render2d.setDepthTest(False)
|
||||||
|
self.render2d.setDepthWrite(False)
|
||||||
|
self.render2d.setTwoSided(True)
|
||||||
|
|
||||||
|
# And a camera to view it.
|
||||||
|
self.dr = self.win.makeDisplayRegion()
|
||||||
|
cam = Camera('cam2d')
|
||||||
|
self.lens = OrthographicLens()
|
||||||
|
self.lens.setNearFar(-1000, 1000)
|
||||||
|
cam.setLens(self.lens)
|
||||||
|
|
||||||
|
self.cam = self.render2d.attachNewNode(cam)
|
||||||
|
self.dr.setCamera(self.cam)
|
||||||
|
|
||||||
|
self.canvas = self.render2d.attachNewNode('canvas')
|
||||||
|
self.background = None
|
||||||
|
self.overflowing = False
|
||||||
|
|
||||||
|
self.task = taskMgr.doMethodLater(0.5, self.updateTextures, 'TexMemWatcher')
|
||||||
|
|
||||||
|
self.setLimit(limit)
|
||||||
|
|
||||||
|
def setLimit(self, limit):
|
||||||
|
self.limit = limit
|
||||||
|
self.dynamicLimit = False
|
||||||
|
|
||||||
|
if limit is None:
|
||||||
|
# If no limit was specified, use the specified graphics
|
||||||
|
# memory limit, if any.
|
||||||
|
lruLimit = self.gsg.getPreparedObjects().getGraphicsMemoryLimit()
|
||||||
|
if lruLimit < 2**32 - 1:
|
||||||
|
# Got a real lruLimit. Use it.
|
||||||
|
self.limit = lruLimit
|
||||||
|
|
||||||
|
else:
|
||||||
|
# No LRU limit either, so there won't be a practical
|
||||||
|
# limit to the TexMemWatcher. We'll determine our
|
||||||
|
# limit on-the-fly instead.
|
||||||
|
|
||||||
|
self.dynamicLimit = True
|
||||||
|
|
||||||
|
# The actual height of the canvas, including the overflow
|
||||||
|
# area. The texture memory itself is restricted to (0..1)
|
||||||
|
# vertically; anything higher than 1 is overflow.
|
||||||
|
self.top = 1.25
|
||||||
|
if self.dynamicLimit:
|
||||||
|
# Actually, we'll never exceed texture memory, so never mind.
|
||||||
|
self.top = 1
|
||||||
|
|
||||||
|
self.lens.setFilmSize(1, self.top)
|
||||||
|
self.lens.setFilmOffset(0.5, self.top / 2.0) # lens covers 0..1 in x and y
|
||||||
|
|
||||||
|
self.makeWindowBackground()
|
||||||
|
self.reconfigureWindow()
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
# Remove the window.
|
||||||
|
if self.win:
|
||||||
|
base.graphicsEngine.removeWindow(self.win)
|
||||||
|
self.win = None
|
||||||
|
|
||||||
|
if self.task:
|
||||||
|
taskMgr.remove(self.task)
|
||||||
|
self.task = None
|
||||||
|
|
||||||
|
self.ignoreAll()
|
||||||
|
|
||||||
|
self.canvas.getChildren().detach()
|
||||||
|
self.texRecords = {}
|
||||||
|
self.texPlacements = {}
|
||||||
|
|
||||||
|
|
||||||
|
def windowEvent(self, win):
|
||||||
|
if win == self.win:
|
||||||
|
props = win.getProperties()
|
||||||
|
if not props.getOpen():
|
||||||
|
# User closed window.
|
||||||
|
self.cleanup()
|
||||||
|
return
|
||||||
|
|
||||||
|
size = (props.getXSize(), props.getYSize())
|
||||||
|
if size != self.winSize:
|
||||||
|
self.winSize = size
|
||||||
|
self.reconfigureWindow()
|
||||||
|
|
||||||
|
def reconfigureWindow(self):
|
||||||
|
""" Resets everything for a new window size. """
|
||||||
|
|
||||||
|
self.background.setTexScale(TextureStage.getDefault(),
|
||||||
|
self.winSize[0] / 20.0, self.winSize[1] / (20.0 * self.top))
|
||||||
|
self.repack()
|
||||||
|
|
||||||
|
def makeWindowBackground(self):
|
||||||
|
""" Creates a tile to use for coloring the background of the
|
||||||
|
window, so we can tell what empty space looks like. """
|
||||||
|
|
||||||
|
if self.background:
|
||||||
|
self.background.detachNode()
|
||||||
|
self.background = None
|
||||||
|
|
||||||
|
# We start with a simple checkerboard texture image.
|
||||||
|
p = PNMImage(2, 2, 1)
|
||||||
|
p.setGray(0, 0, 0.40)
|
||||||
|
p.setGray(1, 1, 0.40)
|
||||||
|
p.setGray(0, 1, 0.80)
|
||||||
|
p.setGray(1, 0, 0.80)
|
||||||
|
|
||||||
|
tex = Texture('check')
|
||||||
|
tex.load(p)
|
||||||
|
tex.setMagfilter(tex.FTNearest)
|
||||||
|
|
||||||
|
self.background = self.render2d.attachNewNode('background')
|
||||||
|
|
||||||
|
cm = CardMaker('background')
|
||||||
|
cm.setFrame(0, 1, 0, 1)
|
||||||
|
cm.setUvRange((0, 0), (1, 1))
|
||||||
|
self.background.attachNewNode(cm.generate())
|
||||||
|
|
||||||
|
cm.setFrame(0, 1, 1, self.top)
|
||||||
|
cm.setUvRange((0, 1), (1, self.top))
|
||||||
|
bad = self.background.attachNewNode(cm.generate())
|
||||||
|
bad.setColor((0.8, 0.2, 0.2, 1))
|
||||||
|
|
||||||
|
self.background.setBin('fixed', -100)
|
||||||
|
self.background.setTexture(tex)
|
||||||
|
|
||||||
|
|
||||||
|
def updateTextures(self, task):
|
||||||
|
""" Gets the current list of resident textures and adds new
|
||||||
|
textures or removes old ones from the onscreen display, as
|
||||||
|
necessary. """
|
||||||
|
|
||||||
|
pgo = self.gsg.getPreparedObjects()
|
||||||
|
totalSize = 0
|
||||||
|
|
||||||
|
texRecords = []
|
||||||
|
neverVisited = copy.copy(self.texRecords)
|
||||||
|
for tex in self.gsg.getPreparedTextures():
|
||||||
|
# We have visited this texture; remove it from the
|
||||||
|
# neverVisited list.
|
||||||
|
if tex in neverVisited:
|
||||||
|
del neverVisited[tex]
|
||||||
|
|
||||||
|
size = 0
|
||||||
|
if tex.getResident(pgo):
|
||||||
|
size = tex.getDataSizeBytes(pgo)
|
||||||
|
|
||||||
|
tr = self.texRecords.get(tex, None)
|
||||||
|
|
||||||
|
if size:
|
||||||
|
totalSize += size
|
||||||
|
active = tex.getActive(pgo)
|
||||||
|
if not tr:
|
||||||
|
# This is a new texture; need to record it.
|
||||||
|
tr = TexRecord(tex, size, active)
|
||||||
|
texRecords.append(tr)
|
||||||
|
else:
|
||||||
|
tr.setActive(active)
|
||||||
|
if tr.size != size:
|
||||||
|
# The size has changed; reapply it.
|
||||||
|
tr.setSize(size)
|
||||||
|
self.unplaceTexture(tr)
|
||||||
|
texRecords.append(tr)
|
||||||
|
else:
|
||||||
|
if tr:
|
||||||
|
# This texture is no longer resident; need to remove it.
|
||||||
|
self.unplaceTexture(tr)
|
||||||
|
|
||||||
|
# Now go through and make sure we unplace any textures that we
|
||||||
|
# didn't visit at all this pass.
|
||||||
|
for tr in neverVisited.values():
|
||||||
|
self.unplaceTexture(tr)
|
||||||
|
|
||||||
|
self.totalSize = totalSize
|
||||||
|
if totalSize > self.limit and self.dynamicLimit:
|
||||||
|
# Actually, never mind on the update: we have exceeded the
|
||||||
|
# dynamic limit computed before, and therefore we need to
|
||||||
|
# repack.
|
||||||
|
self.repack()
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Pack in just the newly-loaded textures.
|
||||||
|
|
||||||
|
# Sort the regions from largest to smallest to maximize
|
||||||
|
# packing effectiveness.
|
||||||
|
texRecords.sort(key = lambda tr: (-tr.w, -tr.h))
|
||||||
|
|
||||||
|
self.overflowing = False
|
||||||
|
for tr in texRecords:
|
||||||
|
self.placeTexture(tr)
|
||||||
|
self.texRecords[tr.tex] = tr
|
||||||
|
|
||||||
|
return task.again
|
||||||
|
|
||||||
|
|
||||||
|
def repack(self):
|
||||||
|
""" Repacks all of the current textures. """
|
||||||
|
|
||||||
|
self.canvas.getChildren().detach()
|
||||||
|
self.texRecords = {}
|
||||||
|
self.texPlacements = {}
|
||||||
|
self.w = 1
|
||||||
|
self.h = 1
|
||||||
|
|
||||||
|
pgo = self.gsg.getPreparedObjects()
|
||||||
|
totalSize = 0
|
||||||
|
|
||||||
|
for tex in self.gsg.getPreparedTextures():
|
||||||
|
if tex.getResident(pgo):
|
||||||
|
size = tex.getDataSizeBytes(pgo)
|
||||||
|
if size:
|
||||||
|
active = tex.getActive(pgo)
|
||||||
|
tr = TexRecord(tex, size, active)
|
||||||
|
self.texRecords[tex] = tr
|
||||||
|
totalSize += size
|
||||||
|
|
||||||
|
self.totalSize = totalSize
|
||||||
|
if not self.totalSize:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.dynamicLimit:
|
||||||
|
# Choose a suitable limit by rounding to the next power of two.
|
||||||
|
self.limit = Texture.upToPower2(self.totalSize)
|
||||||
|
|
||||||
|
# Now make that into a 2-D rectangle of the appropriate shape,
|
||||||
|
# such that w * h == limit.
|
||||||
|
|
||||||
|
# Window size
|
||||||
|
x, y = self.winSize
|
||||||
|
|
||||||
|
# There should be a little buffer on the top so we can see if
|
||||||
|
# we overflow.
|
||||||
|
y /= self.top
|
||||||
|
|
||||||
|
r = float(y) / float(x)
|
||||||
|
|
||||||
|
# Region size
|
||||||
|
w = math.sqrt(self.limit) / math.sqrt(r)
|
||||||
|
h = w * r
|
||||||
|
self.w = w
|
||||||
|
self.h = h
|
||||||
|
|
||||||
|
self.canvas.setScale(1.0 / w, 1.0, 1.0 / h)
|
||||||
|
|
||||||
|
# Sort the regions from largest to smallest to maximize
|
||||||
|
# packing effectiveness.
|
||||||
|
texRecords = self.texRecords.values()
|
||||||
|
texRecords.sort(key = lambda tr: (-tr.w, -tr.h))
|
||||||
|
|
||||||
|
self.overflowing = False
|
||||||
|
for tr in texRecords:
|
||||||
|
self.placeTexture(tr)
|
||||||
|
|
||||||
|
def unplaceTexture(self, tr):
|
||||||
|
""" Removes the texture from its place on the canvas. """
|
||||||
|
for tp in tr.placements:
|
||||||
|
del self.texPlacements[tp]
|
||||||
|
tr.placements = []
|
||||||
|
|
||||||
|
if tr.root:
|
||||||
|
tr.root.detachNode()
|
||||||
|
tr.root = None
|
||||||
|
|
||||||
|
def placeTexture(self, tr):
|
||||||
|
""" Places the texture somewhere on the canvas where it will
|
||||||
|
fit. """
|
||||||
|
|
||||||
|
if not self.overflowing:
|
||||||
|
tp = self.findHole(tr.w, tr.h)
|
||||||
|
if tp:
|
||||||
|
tr.placements = [tp]
|
||||||
|
tr.makeCard(self)
|
||||||
|
self.texPlacements[tp] = tr
|
||||||
|
return
|
||||||
|
|
||||||
|
# Couldn't find a hole; can we fit it if we rotate?
|
||||||
|
tp = self.findHole(tr.h, tr.w)
|
||||||
|
if tp:
|
||||||
|
tp.rotated = True
|
||||||
|
tr.placements = [tp]
|
||||||
|
tr.makeCard(self)
|
||||||
|
self.texPlacements[tp] = tr
|
||||||
|
return
|
||||||
|
|
||||||
|
# Couldn't find a hole of the right shape; can we find a
|
||||||
|
# single rectangular hole of the right area, but of any shape?
|
||||||
|
tp = self.findArea(tr.h * tr.w)
|
||||||
|
if tp:
|
||||||
|
texCmp = cmp(tr.w, tr.h)
|
||||||
|
holeCmp = cmp(tp.p[1] - tp.p[0], tp.p[3] - tp.p[2])
|
||||||
|
if texCmp != 0 and holeCmp != 0 and texCmp != holeCmp:
|
||||||
|
tp.rotated = True
|
||||||
|
tr.placements = [tp]
|
||||||
|
tr.makeCard(self)
|
||||||
|
self.texPlacements[tp] = tr
|
||||||
|
return
|
||||||
|
|
||||||
|
# Couldn't find a single rectangular hole. We'll have to
|
||||||
|
# divide the texture up into several smaller pieces to cram it
|
||||||
|
# in.
|
||||||
|
tpList = self.findHolePieces(tr.h * tr.w)
|
||||||
|
if tpList:
|
||||||
|
tr.placements = tpList
|
||||||
|
tr.makeCard(self)
|
||||||
|
for tp in tpList:
|
||||||
|
self.texPlacements[tp] = tr
|
||||||
|
return
|
||||||
|
|
||||||
|
# Just let it overflow.
|
||||||
|
self.overflowing = True
|
||||||
|
tp = self.findHole(tr.w, tr.h, allowOverflow = True)
|
||||||
|
if tp:
|
||||||
|
tr.placements = [tp]
|
||||||
|
tr.makeCard(self)
|
||||||
|
self.texPlacements[tp] = tr
|
||||||
|
return
|
||||||
|
|
||||||
|
# Something went wrong.
|
||||||
|
assert False
|
||||||
|
|
||||||
|
def findHole(self, w, h, allowOverflow = False):
|
||||||
|
""" Searches for a hole large enough for (w, h). If one is
|
||||||
|
found, returns an appropriate TexPlacement; otherwise, returns
|
||||||
|
None. """
|
||||||
|
|
||||||
|
if w > self.w:
|
||||||
|
# It won't fit within the row at all.
|
||||||
|
if not allowOverflow:
|
||||||
|
return None
|
||||||
|
# Just stack it on the top.
|
||||||
|
y = 0
|
||||||
|
if self.texPlacements:
|
||||||
|
y = max(map(lambda tp: tp.p[3], self.texPlacements.keys()))
|
||||||
|
tp = TexPlacement(0, w, y, y + h)
|
||||||
|
return tp
|
||||||
|
|
||||||
|
y = 0
|
||||||
|
while y + h <= self.h or allowOverflow:
|
||||||
|
nextY = None
|
||||||
|
|
||||||
|
# Scan along the row at 'y'.
|
||||||
|
x = 0
|
||||||
|
while x + w <= self.w:
|
||||||
|
# Consider the spot at x, y.
|
||||||
|
tp = TexPlacement(x, x + w, y, y + h)
|
||||||
|
overlap = self.findOverlap(tp)
|
||||||
|
if not overlap:
|
||||||
|
# Hooray!
|
||||||
|
return tp
|
||||||
|
|
||||||
|
nextX = overlap.p[1]
|
||||||
|
if nextY is None:
|
||||||
|
nextY = overlap.p[3]
|
||||||
|
else:
|
||||||
|
nextY = min(nextY, overlap.p[3])
|
||||||
|
|
||||||
|
assert nextX > x
|
||||||
|
x = nextX
|
||||||
|
|
||||||
|
assert nextY > y
|
||||||
|
y = nextY
|
||||||
|
|
||||||
|
# Nope, wouldn't fit anywhere.
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def findArea(self, area):
|
||||||
|
""" Searches for a rectangular hole that is at least area
|
||||||
|
square units big, regardless of its shape. If one is found,
|
||||||
|
returns an appropriate TexPlacement; otherwise, returns
|
||||||
|
None. """
|
||||||
|
|
||||||
|
y = 0
|
||||||
|
while y < self.h:
|
||||||
|
nextY = self.h
|
||||||
|
|
||||||
|
# Scan along the row at 'y'.
|
||||||
|
x = 0
|
||||||
|
while x < self.w:
|
||||||
|
nextX = self.w
|
||||||
|
|
||||||
|
# Consider the spot at x, y.
|
||||||
|
|
||||||
|
# How wide can we go? Start by trying to go all the
|
||||||
|
# way to the edge of the region.
|
||||||
|
tpw = self.w - x
|
||||||
|
|
||||||
|
# Now, given this particular width, how tall do we
|
||||||
|
# need to go?
|
||||||
|
tph = area / tpw
|
||||||
|
|
||||||
|
while y + tph < self.h:
|
||||||
|
tp = TexPlacement(x, x + tpw, y, y + tph)
|
||||||
|
overlap = self.findOverlap(tp)
|
||||||
|
if not overlap:
|
||||||
|
# Hooray!
|
||||||
|
return tp
|
||||||
|
|
||||||
|
nextX = min(nextX, overlap.p[1])
|
||||||
|
nextY = min(nextY, overlap.p[3])
|
||||||
|
|
||||||
|
# Shorten the available region.
|
||||||
|
tpw = overlap.p[0] - x
|
||||||
|
if tpw <= 0.0:
|
||||||
|
break
|
||||||
|
tph = area / tpw
|
||||||
|
|
||||||
|
assert nextX > x
|
||||||
|
x = nextX
|
||||||
|
|
||||||
|
assert nextY > y
|
||||||
|
y = nextY
|
||||||
|
|
||||||
|
# Nope, wouldn't fit anywhere.
|
||||||
|
return None
|
||||||
|
|
||||||
|
def findHolePieces(self, area):
|
||||||
|
""" Returns a list of holes whose net area sums to the given
|
||||||
|
area, or None if there are not enough holes. """
|
||||||
|
|
||||||
|
# First, save the original value of self.texPlacements, since
|
||||||
|
# we will be modifying that during this search.
|
||||||
|
savedTexPlacements = copy.copy(self.texPlacements)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
while area > 0:
|
||||||
|
tp = self.findLargestHole()
|
||||||
|
if not tp:
|
||||||
|
break
|
||||||
|
|
||||||
|
l, r, b, t = tp.p
|
||||||
|
tpArea = (r - l) * (t - b)
|
||||||
|
if tpArea >= area:
|
||||||
|
# we're done.
|
||||||
|
shorten = (tpArea - area) / (r - l)
|
||||||
|
tp.p = (l, r, b, t - shorten)
|
||||||
|
result.append(tp)
|
||||||
|
self.texPlacements = savedTexPlacements
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Keep going.
|
||||||
|
area -= tpArea
|
||||||
|
result.append(tp)
|
||||||
|
self.texPlacements[tp] = None
|
||||||
|
|
||||||
|
# Huh, not enough room, or no more holes.
|
||||||
|
self.texPlacements = savedTexPlacements
|
||||||
|
return None
|
||||||
|
|
||||||
|
def findLargestHole(self):
|
||||||
|
""" Searches for the largest available hole. """
|
||||||
|
|
||||||
|
holes = []
|
||||||
|
|
||||||
|
y = 0
|
||||||
|
while y < self.h:
|
||||||
|
nextY = self.h
|
||||||
|
|
||||||
|
# Scan along the row at 'y'.
|
||||||
|
x = 0
|
||||||
|
while x < self.w:
|
||||||
|
nextX = self.w
|
||||||
|
|
||||||
|
# Consider the spot at x, y.
|
||||||
|
|
||||||
|
# How wide can we go? Start by trying to go all the
|
||||||
|
# way to the edge of the region.
|
||||||
|
tpw = self.w - x
|
||||||
|
|
||||||
|
# And how tall can we go? Start by trying to go to
|
||||||
|
# the top of the region.
|
||||||
|
tph = self.h - y
|
||||||
|
|
||||||
|
while tpw > 0.0 and tph > 0.0:
|
||||||
|
tp = TexPlacement(x, x + tpw, y, y + tph)
|
||||||
|
overlap = self.findOverlap(tp)
|
||||||
|
if not overlap:
|
||||||
|
# Here's a hole.
|
||||||
|
holes.append((tpw * tph, tp))
|
||||||
|
break
|
||||||
|
|
||||||
|
nextX = min(nextX, overlap.p[1])
|
||||||
|
nextY = min(nextY, overlap.p[3])
|
||||||
|
|
||||||
|
# We've been intersected either on the top or the
|
||||||
|
# right. We need to shorten either width or
|
||||||
|
# height. Which way results in the largest
|
||||||
|
# remaining area?
|
||||||
|
|
||||||
|
tpw0 = overlap.p[0] - x
|
||||||
|
tph0 = overlap.p[2] - y
|
||||||
|
|
||||||
|
if tpw0 * tph > tpw * tph0:
|
||||||
|
# Shortening width results in larger.
|
||||||
|
tpw = tpw0
|
||||||
|
else:
|
||||||
|
# Shortening height results in larger.
|
||||||
|
tph = tph0
|
||||||
|
|
||||||
|
assert nextX > x
|
||||||
|
x = nextX
|
||||||
|
|
||||||
|
assert nextY > y
|
||||||
|
y = nextY
|
||||||
|
|
||||||
|
if not holes:
|
||||||
|
# No holes to be found.
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Return the biggest hole
|
||||||
|
return max(holes)[1]
|
||||||
|
|
||||||
|
def findOverlap(self, tp):
|
||||||
|
""" If there is another placement that overlaps the indicated
|
||||||
|
TexPlacement, returns it. Otherwise, returns None. """
|
||||||
|
|
||||||
|
for other in self.texPlacements.keys():
|
||||||
|
if other.intersects(tp):
|
||||||
|
return other
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TexRecord:
|
||||||
|
def __init__(self, tex, size, active):
|
||||||
|
self.tex = tex
|
||||||
|
self.active = active
|
||||||
|
self.root = None
|
||||||
|
self.placements = []
|
||||||
|
|
||||||
|
self.setSize(size)
|
||||||
|
|
||||||
|
def setSize(self, size):
|
||||||
|
self.size = size
|
||||||
|
x = self.tex.getXSize()
|
||||||
|
y = self.tex.getYSize()
|
||||||
|
r = float(y) / float(x)
|
||||||
|
|
||||||
|
# Card size
|
||||||
|
w = math.sqrt(self.size) / math.sqrt(r)
|
||||||
|
h = w * r
|
||||||
|
|
||||||
|
self.w = w
|
||||||
|
self.h = h
|
||||||
|
|
||||||
|
|
||||||
|
def setActive(self, flag):
|
||||||
|
self.active = flag
|
||||||
|
if self.active:
|
||||||
|
self.matte.clearColor()
|
||||||
|
else:
|
||||||
|
self.matte.setColor((0.4, 0.4, 0.4, 1))
|
||||||
|
|
||||||
|
def makeCard(self, tmw):
|
||||||
|
if self.root:
|
||||||
|
self.root.detachNode()
|
||||||
|
|
||||||
|
root = NodePath('root')
|
||||||
|
|
||||||
|
# A card to display the texture.
|
||||||
|
card = root.attachNewNode('card')
|
||||||
|
|
||||||
|
# A matte to frame the texture and indicate its status.
|
||||||
|
matte = root.attachNewNode('matte')
|
||||||
|
|
||||||
|
# A wire frame to ring the matte and separate the card from
|
||||||
|
# its neighbors.
|
||||||
|
frame = root.attachNewNode('frame')
|
||||||
|
|
||||||
|
|
||||||
|
for p in self.placements:
|
||||||
|
l, r, b, t = p.p
|
||||||
|
cx = (l + r) * 0.5
|
||||||
|
cy = (b + t) * 0.5
|
||||||
|
shrinkMat = Mat4.translateMat(-cx, 0, -cy) * Mat4.scaleMat(0.9) * Mat4.translateMat(cx, 0, cy)
|
||||||
|
|
||||||
|
cm = CardMaker('card')
|
||||||
|
cm.setFrame(l, r, b, t)
|
||||||
|
if p.rotated:
|
||||||
|
cm.setUvRange((0, 1), (0, 0), (1, 0), (1, 1))
|
||||||
|
c = card.attachNewNode(cm.generate())
|
||||||
|
c.setMat(shrinkMat)
|
||||||
|
|
||||||
|
cm = CardMaker('matte')
|
||||||
|
cm.setFrame(l, r, b, t)
|
||||||
|
matte.attachNewNode(cm.generate())
|
||||||
|
|
||||||
|
ls = LineSegs('frame')
|
||||||
|
ls.setColor(0, 0, 0, 1)
|
||||||
|
ls.moveTo(l, 0, b)
|
||||||
|
ls.drawTo(r, 0, b)
|
||||||
|
ls.drawTo(r, 0, t)
|
||||||
|
ls.drawTo(l, 0, t)
|
||||||
|
ls.drawTo(l, 0, b)
|
||||||
|
f1 = frame.attachNewNode(ls.create())
|
||||||
|
f2 = f1.copyTo(frame)
|
||||||
|
f2.setMat(shrinkMat)
|
||||||
|
|
||||||
|
# Instead of enabling transparency, we set a color blend
|
||||||
|
# attrib. We do this because plain transparency would also
|
||||||
|
# enable an alpha test, which we don't want; we want to draw
|
||||||
|
# every pixel.
|
||||||
|
card.setAttrib(ColorBlendAttrib.make(
|
||||||
|
ColorBlendAttrib.MAdd,
|
||||||
|
ColorBlendAttrib.OIncomingAlpha,
|
||||||
|
ColorBlendAttrib.OOneMinusIncomingAlpha))
|
||||||
|
card.setBin('fixed', 0)
|
||||||
|
card.setTexture(self.tex)
|
||||||
|
card.setY(-1) # the card gets pulled back, so the matte will z-test it out.
|
||||||
|
card.setDepthWrite(True)
|
||||||
|
card.setDepthTest(True)
|
||||||
|
#card.flattenStrong()
|
||||||
|
self.card = card
|
||||||
|
|
||||||
|
matte.setBin('fixed', 10)
|
||||||
|
matte.setDepthTest(True)
|
||||||
|
#matte.flattenStrong()
|
||||||
|
self.matte = matte
|
||||||
|
|
||||||
|
frame.setBin('fixed', 20)
|
||||||
|
#frame.flattenStrong()
|
||||||
|
self.frame = frame
|
||||||
|
|
||||||
|
root.reparentTo(tmw.canvas)
|
||||||
|
|
||||||
|
self.root = root
|
||||||
|
|
||||||
|
class TexPlacement:
|
||||||
|
def __init__(self, l, r, b, t):
|
||||||
|
self.p = (l, r, b, t)
|
||||||
|
self.rotated = False
|
||||||
|
|
||||||
|
def intersects(self, other):
|
||||||
|
""" Returns True if the placements intersect, False
|
||||||
|
otherwise. """
|
||||||
|
|
||||||
|
ml, mr, mb, mt = self.p
|
||||||
|
tl, tr, tb, tt = other.p
|
||||||
|
|
||||||
|
return (tl < mr and tr > ml and
|
||||||
|
tb < mt and tt > mb)
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user