Begin testing multiple implementations of relight algorithm.
Change relight API to have updateLightsByCoord, updateLightsInSelection, updateLightsInSection (incomplete) Move time_xxx.py files from test/ into bench/ Split time_relight into manmade and natural. Get time_relight_xxx working again. Add get/set BlockLight/SkyLight to WorldEditor for pure python relight. Implement pure python relight. Seems to work right.
This commit is contained in:
parent
987aa3e7c5
commit
9da7b289b2
52
src/mceditlib/bench/time_relight_manmade.py
Normal file
52
src/mceditlib/bench/time_relight_manmade.py
Normal file
@ -0,0 +1,52 @@
|
||||
from timeit import timeit
|
||||
|
||||
import numpy
|
||||
from mceditlib.selection import BoundingBox
|
||||
|
||||
from mceditlib.worldeditor import WorldEditor
|
||||
from mceditlib.test import templevel
|
||||
from mceditlib import relight
|
||||
|
||||
|
||||
def manmade_relight():
|
||||
world = templevel.TempLevel("AnvilWorld")
|
||||
dim = world.getDimension()
|
||||
stationEditor = WorldEditor("test_files/station.schematic")
|
||||
station = stationEditor.getDimension()
|
||||
|
||||
times = 1
|
||||
boxes = []
|
||||
|
||||
for x in range(times):
|
||||
for z in range(times):
|
||||
origin = (x * station.bounds.width, 54, z * station.bounds.length)
|
||||
boxes.append(BoundingBox(origin, station.bounds.size))
|
||||
dim.copyBlocks(station, station.bounds, origin, create=True)
|
||||
|
||||
box = reduce(lambda a, b: a.union(b), boxes)
|
||||
|
||||
positions = []
|
||||
for cx, cz in box.chunkPositions():
|
||||
for cy in box.sectionPositions(cx, cz):
|
||||
positions.append((cx, cy, cz))
|
||||
|
||||
poses = iter(positions)
|
||||
|
||||
def do_relight():
|
||||
cx, cy, cz = poses.next()
|
||||
print "Relighting section %s..." % ((cx, cy, cz),)
|
||||
indices = numpy.indices((16, 16, 16), numpy.uint32)
|
||||
indices.shape = 3, 16*16*16
|
||||
indices += ([cx], [cy], [cz])
|
||||
x, y, z = indices
|
||||
relight.updateLightsByCoord(dim, x, y, z)
|
||||
|
||||
sectionCount = 5
|
||||
t = timeit(do_relight, number=sectionCount)
|
||||
print "Relight manmade building: %d chunk-sections in %.02f seconds (%dms per section)" % (sectionCount, t, 1000 * t / sectionCount)
|
||||
|
||||
if __name__ == '__main__':
|
||||
manmade_relight()
|
||||
|
||||
|
||||
|
38
src/mceditlib/bench/time_relight_natural.py
Normal file
38
src/mceditlib/bench/time_relight_natural.py
Normal file
@ -0,0 +1,38 @@
|
||||
import numpy
|
||||
from mceditlib.worldeditor import WorldEditor
|
||||
from timeit import timeit
|
||||
|
||||
from mceditlib.test import templevel
|
||||
from mceditlib import relight
|
||||
|
||||
# run me with the source checkout as the working dir so I can find the test_files folder.
|
||||
|
||||
def natural_relight():
|
||||
world = templevel.TempLevel("AnvilWorld")
|
||||
dim = world.getDimension()
|
||||
positions = []
|
||||
for cx, cz in dim.chunkPositions():
|
||||
chunk = dim.getChunk(cx, cz)
|
||||
for cy in chunk.sectionPositions():
|
||||
positions.append((cx, cy, cz))
|
||||
|
||||
poses = iter(positions)
|
||||
|
||||
def do_relight():
|
||||
cx, cy, cz = poses.next()
|
||||
print "Relighting section %s..." % ((cx, cy, cz),)
|
||||
indices = numpy.indices((16, 16, 16), numpy.uint32)
|
||||
indices.shape = 3, 16*16*16
|
||||
indices += ([cx], [cy], [cz])
|
||||
x, y, z = indices
|
||||
relight.updateLightsByCoord(dim, x, y, z)
|
||||
|
||||
sectionCount = 5
|
||||
t = timeit(do_relight, number=sectionCount)
|
||||
print "Relight natural terrain: %d chunk-sections in %.02f seconds (%dms per section)" % (sectionCount, t, 1000 * t / sectionCount)
|
||||
|
||||
if __name__ == '__main__':
|
||||
natural_relight()
|
||||
|
||||
|
||||
|
@ -59,7 +59,7 @@ class FakeChunkData(object):
|
||||
arrays or its rootTag. Alternately, just set `chunk.dirty = True`
|
||||
|
||||
needsLighting is deprecated; Use the updateLights= argument
|
||||
of setBlocks and other editing functions, or call updateLights(level, x, y, z) to
|
||||
of setBlocks and other editing functions, or call updateLightsByCoord(level, x, y, z) to
|
||||
explicitly update lights yourself.
|
||||
|
||||
"""
|
||||
|
@ -356,7 +356,7 @@ def setBlocks(dimension, x, y, z,
|
||||
chunk.dirty = True
|
||||
|
||||
if updateLights:
|
||||
relight.updateLights(dimension, x, y, z)
|
||||
relight.updateLightsByCoord(dimension, x, y, z)
|
||||
|
||||
|
||||
def setChunkBlocks(chunk, x, y, z,
|
||||
|
@ -140,7 +140,7 @@ class FillBlocksOperation(Operation):
|
||||
z = coords[1] + (cz << 4)
|
||||
x = coords[2] + (cx << 4)
|
||||
|
||||
mceditlib.relight.updateLights(self.dimension, x, y, z)
|
||||
mceditlib.relight.updateLightsByCoord(self.dimension, x, y, z)
|
||||
|
||||
def include(ref):
|
||||
return ref.Position not in self.selection
|
||||
|
28
src/mceditlib/relight/__init__.py
Normal file
28
src/mceditlib/relight/__init__.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""
|
||||
__init__
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def setMethod(name):
|
||||
if name == "pure":
|
||||
from mceditlib.relight import pure_python
|
||||
setModule(pure_python)
|
||||
if name == "cython":
|
||||
from mceditlib.relight import with_cython
|
||||
setModule(with_cython)
|
||||
if name == "setBlocks":
|
||||
from mceditlib.relight import with_setblocks
|
||||
setModule(with_setblocks)
|
||||
if name == "sections":
|
||||
from mceditlib.relight import with_sections
|
||||
setModule(with_sections)
|
||||
|
||||
def setModule(mod):
|
||||
global updateLightsByCoord, updateLightsInSelection
|
||||
updateLightsByCoord = mod.updateLightsByCoord
|
||||
updateLightsInSelection = mod.updateLightsInSelection
|
||||
|
||||
setMethod("pure")
|
98
src/mceditlib/relight/pure_python.py
Normal file
98
src/mceditlib/relight/pure_python.py
Normal file
@ -0,0 +1,98 @@
|
||||
"""
|
||||
pure_python
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def updateLightsByCoord(dim, x, y, z):
|
||||
for i in range(len(x)):
|
||||
updateLights(dim, x[i], y[i], z[i])
|
||||
|
||||
def neighbors(x, y, z):
|
||||
yield x-1, y, z
|
||||
yield x+1, y, z
|
||||
yield x, y-1, z
|
||||
yield x, y+1, z
|
||||
yield x, y, z-1
|
||||
yield x, y, z+1
|
||||
|
||||
|
||||
def updateLights(dim, x, y, z):
|
||||
# import pdb; pdb.set_trace()
|
||||
previousLight = dim.getBlockLight(x, y, z)
|
||||
light = dim.getBlock(x, y, z).brightness
|
||||
dim.setBlockLight(x, y, z, light)
|
||||
|
||||
drawLight(dim, x, y, z)
|
||||
|
||||
if previousLight < light:
|
||||
spreadLight(dim, x, y, z)
|
||||
|
||||
if previousLight > light:
|
||||
fadeLight(dim, x, y, z, previousLight)
|
||||
|
||||
def getOpacity(dim, x, y, z):
|
||||
return max(1, dim.getBlock(x, y, z).opacity)
|
||||
|
||||
def drawLight(dim, x, y, z):
|
||||
opacity = getOpacity(dim, x, y, z)
|
||||
|
||||
for nx, ny, nz in neighbors(x, y, z):
|
||||
adjacentLight = dim.getBlockLight(nx, ny, nz)
|
||||
if adjacentLight - opacity > dim.getBlockLight(x, y, z):
|
||||
dim.setBlockLight(x, y, z, adjacentLight - opacity)
|
||||
|
||||
def spreadLight(dim, x, y, z):
|
||||
light = dim.getBlockLight(x, y, z)
|
||||
if light <= 0:
|
||||
return
|
||||
|
||||
for nx, ny, nz in neighbors(x, y, z):
|
||||
|
||||
# xxx cast to int because one of these is a numpy.uint8 and
|
||||
# light - opacity rolls over to a large number.
|
||||
adjacentLight = int(dim.getBlockLight(nx, ny, nz))
|
||||
adjacentOpacity = getOpacity(dim, nx, ny, nz)
|
||||
newLight = light - adjacentOpacity
|
||||
# If the adjacent cell already has the "correct" light value, stop.
|
||||
if newLight > adjacentLight:
|
||||
dim.setBlockLight(nx, ny, nz, newLight)
|
||||
spreadLight(dim, nx, ny, nz)
|
||||
|
||||
|
||||
def fadeLight(dim, x, y, z, previousLight):
|
||||
fadedCells = findFadedCells(dim, x, y, z, previousLight)
|
||||
for x, y, z in fadedCells:
|
||||
dim.setBlockLight(x, y, z, dim.getBlock(x, y, z).brightness)
|
||||
# dim.setBlock(x, y, z, "glass")
|
||||
for x, y, z in fadedCells:
|
||||
drawLight(dim, x, y, z)
|
||||
for x, y, z in fadedCells:
|
||||
spreadLight(dim, x, y, z)
|
||||
|
||||
|
||||
def relCoords(ox, oy, oz, coords):
|
||||
for x, y, z, l in coords:
|
||||
yield x - ox, y - oy, z - oz
|
||||
|
||||
|
||||
def findFadedCells(dim, ox, oy, oz, oPreviousLight):
|
||||
foundCells = set()
|
||||
toScan = [(ox, oy, oz, oPreviousLight)]
|
||||
|
||||
while len(toScan):
|
||||
x, y, z, previousLight = toScan.pop(0)
|
||||
for nx, ny, nz in neighbors(x, y, z):
|
||||
|
||||
adjacentLight = int(dim.getBlockLight(nx, ny, nz))
|
||||
adjacentOpacity = getOpacity(dim, nx, ny, nz)
|
||||
if previousLight - adjacentOpacity <= 0:
|
||||
continue
|
||||
if previousLight - adjacentOpacity == adjacentLight:
|
||||
if (nx, ny, nz) not in foundCells:
|
||||
toScan.append((nx, ny, nz, adjacentLight))
|
||||
foundCells.add((nx, ny, nz))
|
||||
|
||||
return foundCells
|
0
src/mceditlib/relight/with_cython.pyx
Normal file
0
src/mceditlib/relight/with_cython.pyx
Normal file
88
src/mceditlib/relight/with_sections.py
Normal file
88
src/mceditlib/relight/with_sections.py
Normal file
@ -0,0 +1,88 @@
|
||||
"""
|
||||
with_sections
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
import logging
|
||||
import numpy
|
||||
from mceditlib.exceptions import ChunkNotPresent
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def updateLightsInSelection(dim, selection):
|
||||
for cx, cz in selection.chunkPositions():
|
||||
for cy in selection.sectionPositions(cx, cz):
|
||||
updateLightsInSection(dim, cx, cy, cz)
|
||||
|
||||
def updateLightsByCoord(dim, x, y, z):
|
||||
"""
|
||||
|
||||
:param dim:
|
||||
:param x:
|
||||
:param y:
|
||||
:param z:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# gross.
|
||||
cx = numpy.asanyarray(x) >> 4
|
||||
cy = numpy.asanyarray(y) >> 4
|
||||
cz = numpy.asanyarray(z) >> 4
|
||||
|
||||
cx = numpy.unique(cx)
|
||||
cy = numpy.unique(cy)
|
||||
cz = numpy.unique(cz)
|
||||
|
||||
for i in range(len(cx)):
|
||||
updateLightsInSection(dim, cx[i], cy[i], cz[i])
|
||||
|
||||
|
||||
def updateLightsInSection(dim, cx, cy, cz):
|
||||
try:
|
||||
chunk = dim.getChunk(cx, cz)
|
||||
except ChunkNotPresent:
|
||||
return
|
||||
|
||||
section = chunk.getSection(cy)
|
||||
if section is None:
|
||||
return
|
||||
|
||||
light = section.BlockLight
|
||||
blocks = section.Blocks
|
||||
|
||||
# Reset all lights to block brightness values
|
||||
# xxx if BlockLight
|
||||
light[:] = dim.blocktypes.brightness[blocks]
|
||||
|
||||
# Get all block opacities only once
|
||||
opacity = dim.blocktypes.opacity[blocks]
|
||||
|
||||
directions = ((0, -1),
|
||||
(0, 1),
|
||||
(1, -1),
|
||||
(1, 1),
|
||||
(2, -1),
|
||||
(2, 1))
|
||||
|
||||
for axis, direction in directions:
|
||||
leftSlices = [None, None, None]
|
||||
rightSlices = [None, None, None]
|
||||
if direction == 1:
|
||||
leftSlices[axis] = slice(None, -1)
|
||||
rightSlices[axis] = slice(1, None)
|
||||
else:
|
||||
leftSlices[axis] = slice(1, None)
|
||||
rightSlices[axis] = slice(None, -1)
|
||||
|
||||
leftLight = light[leftSlices]
|
||||
rightOpacity = opacity[rightSlices]
|
||||
|
||||
newRightLight = leftLight - rightOpacity
|
||||
# BlockLight is unsigned. Lights that were zero or less are now huge, so clip light values
|
||||
newRightLight.view('int8').clip(0, 15, newRightLight)
|
||||
|
||||
changed = newRightLight != leftLight
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -140,7 +140,12 @@ def updateHeightmap(dimension, x, y, z):
|
||||
|
||||
ENABLE_LIGHTING = True
|
||||
|
||||
def updateLights(dimension, x, y, z):
|
||||
def updateLightsInSelection(dimension, selection):
|
||||
# xxx slow
|
||||
x, y, z = numpy.array(selection.positions).T
|
||||
return updateLightsByCoord(dimension, x, y, z)
|
||||
|
||||
def updateLightsByCoord(dimension, x, y, z):
|
||||
"""
|
||||
|
||||
:param dimension:
|
@ -1,37 +0,0 @@
|
||||
from mceditlib.worldeditor import WorldEditor
|
||||
from timeit import timeit
|
||||
|
||||
import templevel
|
||||
|
||||
#import logging
|
||||
#logging.basicConfig(level=logging.INFO)
|
||||
|
||||
def natural_relight():
|
||||
world = templevel.TempLevel("AnvilWorld")
|
||||
dim = world.getDimension()
|
||||
t = timeit(lambda: dim.generateLights(dim.chunkPositions()), number=1)
|
||||
print "Relight natural terrain: %d chunks in %.02f seconds (%.02fms per chunk)" % (dim.chunkCount, t,
|
||||
t / world.chunkCount * 1000)
|
||||
|
||||
|
||||
def manmade_relight():
|
||||
t = templevel.TempLevel("TimeRelight", createFunc=lambda f:WorldEditor(f, create=True))
|
||||
|
||||
world = t
|
||||
station = WorldEditor("test_files/station.schematic")
|
||||
|
||||
times = 2
|
||||
|
||||
for x in range(times):
|
||||
for z in range(times):
|
||||
world.copyBlocksFrom(station, station.bounds, (x * station.Width, 63, z * station.Length), create=True)
|
||||
|
||||
t = timeit(lambda: world.generateLights(world.chunkPositions), number=1)
|
||||
print "Relight manmade building: %d chunks in %.02f seconds (%.02fms per chunk)" % (world.chunkCount, t, t / world.chunkCount * 1000)
|
||||
|
||||
if __name__ == '__main__':
|
||||
natural_relight()
|
||||
manmade_relight()
|
||||
|
||||
|
||||
|
@ -849,6 +849,45 @@ class WorldEditorDimension(object):
|
||||
array[y & 0xf, z & 0xf, x & 0xf] = value
|
||||
chunk.dirty = True
|
||||
|
||||
def getLight(self, arrayName, x, y, z, default=0):
|
||||
cx = x >> 4
|
||||
cy = y >> 4
|
||||
cz = z >> 4
|
||||
if self.containsChunk(cx, cz):
|
||||
chunk = self.getChunk(cx, cz)
|
||||
sec = chunk.getSection(cy)
|
||||
if sec:
|
||||
array = getattr(sec, arrayName)
|
||||
if array is not None:
|
||||
return array[y & 0xf, z & 0xf, x & 0xf]
|
||||
return default
|
||||
|
||||
def setLight(self, arrayName, x, y, z, value):
|
||||
cx = x >> 4
|
||||
cy = y >> 4
|
||||
cz = z >> 4
|
||||
if self.containsChunk(cx, cz):
|
||||
chunk = self.getChunk(cx, cz)
|
||||
sec = chunk.getSection(cy, create=True)
|
||||
if sec:
|
||||
array = getattr(sec, arrayName)
|
||||
assert array is not None
|
||||
if array is not None:
|
||||
array[y & 0xf, z & 0xf, x & 0xf] = value
|
||||
chunk.dirty = True
|
||||
|
||||
def getBlockLight(self, x, y, z, default=0):
|
||||
return self.getLight("BlockLight", x, y, z, default)
|
||||
|
||||
def setBlockLight(self, x, y, z, value):
|
||||
return self.setLight("BlockLight", x, y, z, value)
|
||||
|
||||
def getSkyLight(self, x, y, z, default=0):
|
||||
return self.getLight("SkyLight", x, y, z, default)
|
||||
|
||||
def setSkyLight(self, x, y, z, value):
|
||||
return self.setLight("SkyLight", x, y, z, value)
|
||||
|
||||
def getBiomeID(self, x, z, default=0):
|
||||
cx = x >> 4
|
||||
cz = z >> 4
|
||||
|
Reference in New Issue
Block a user