Browse Source

Use a list of missing multires tiles to avoid trying load them.

This avoids the 404 errors that would previously result.

A compact encoding is used, with `!` plus the face letter used to specify a
new face, `>` plus a base83-encoded number used to specify a new level, and a
list of base83-encoded numbers used to specify x and y tile coordinates, where
the base83 encoding uses the minimum number of characters to encode the
maximum tile number for the level in question. The base83 encoding is the same
as the one used for SHT hashes.
pull/1210/head
Matthew Petroff 9 months ago
parent
commit
150dfae33b
3 changed files with 88 additions and 13 deletions
  1. +6
    -0
      doc/json-config-parameters.md
  2. +28
    -0
      src/js/libpannellum.js
  3. +54
    -13
      utils/multires/generate.py

+ 6
- 0
doc/json-config-parameters.md View File

@@ -506,6 +506,12 @@ used, the image size should be kept small, since it needs to be loaded with the
configuration parameters.


#### `missingTiles` (string)

This specifies tiles that are missing and should not be loaded. A compact
encoding is used for these data.



## Dynamic content specific options



+ 28
- 0
src/js/libpannellum.js View File

@@ -658,6 +658,28 @@ function Renderer(container, context) {
gl.vertexAttribPointer(program.vertPosLocation, 3, gl.FLOAT, false, 0, 0);
gl.useProgram(program);
}

// Parse missing tiles list, if it exists
if (image.missingTiles) {
var missingTiles = [];
var perSide = image.missingTiles.split('!');
var level = -1;
for (var i = 1; i < perSide.length; i++) {
var side = perSide[i].at(0);
var perLevel = perSide[i].indexOf('>') < 0 ? [side, perSide[i].slice(1)] : perSide[i].split('>');
for (var j = 1; j < perLevel.length; j++) {
if (perSide[i].indexOf('>') >= 0)
var level = shtB83decode(perLevel[j].at(0), 1)[0];
var maxTileNum = Math.ceil(image.cubeResolution /
Math.pow(2, image.maxLevel - level) / image.tileResolution) - 1;
var numTileDigits = Math.ceil(Math.log(maxTileNum + 1) / Math.log(83));
var tiles = perLevel[j].slice(1).length > 0 ? shtB83decode(perLevel[j].slice(1), numTileDigits) : [0, 0];
for (var k = 0; k < tiles.length / 2; k++)
missingTiles.push([side, level, tiles[k * 2], tiles[k * 2 + 1]].toString());
}
}
image.missingTileList = missingTiles;
}
}

// Check if there was an error
@@ -1069,6 +1091,7 @@ function Renderer(container, context) {
// Clear canvas
if (clear)
gl.clear(gl.COLOR_BUFFER_BIT);

// Determine tiles that need to be drawn
var node_paths = {};
for (var i = 0; i < program.currentNodes.length; i++) {
@@ -1134,6 +1157,11 @@ function Renderer(container, context) {
* @param {number} hfov - Horizontal field of view to check at.
*/
function testMultiresNode(rotPersp, rotPerspNoClip, node, pitch, yaw, hfov) {
// Don't try to load missing tiles (I wish there were a better way to check than `toString`)
if (image.missingTileList !== undefined &&
image.missingTileList.indexOf([node.side, node.level, node.x, node.y].toString()) >= 0)
return;

if (checkSquareInView(rotPersp, node.vertices)) {
// In order to determine if this tile resolution needs to be loaded
// for this node, start by calculating positions of node corners


+ 54
- 13
utils/multires/generate.py View File

@@ -61,6 +61,14 @@ try:
except:
sys.stderr.write("Unable to import pyshtools. Not generating SHT preview.\n")

b83chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"
def b83encode(vals, length):
result = ""
for val in vals:
for i in range(1, length + 1):
result += b83chars[int(val // (83 ** (length - i))) % 83]
return result

def img2shtHash(img, lmax=5):
'''
Create spherical harmonic transform (SHT) hash preview.
@@ -74,15 +82,6 @@ def img2shtHash(img, lmax=5):
quantB = encodeFloat(b / maxVal, 9)
return quantR * 19 ** 2 + quantG * 19 + quantB

b83chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"

def b83encode(vals, length):
result = ""
for val in vals:
for i in range(1, length + 1):
result += b83chars[int(val // (83 ** (length - i))) % 83]
return result

# Calculate SHT coefficients
r = pysh.expand.SHExpandDH(img[..., 0], sampling=2, lmax_calc=lmax)
g = pysh.expand.SHExpandDH(img[..., 1], sampling=2, lmax_calc=lmax)
@@ -210,6 +209,10 @@ partialPano = True if args.haov != -1 and args.vaov != -1 else False
colorList = ast.literal_eval(args.backgroundColor)
colorTuple = (int(colorList[0]*255), int(colorList[1]*255), int(colorList[2]*255))

# Don't generate preview for partial panoramas
if haov < 360 or vaov < 180:
genPreview = False

if args.debug:
print('maxLevel: '+ str(levels))
print('tileResolution: '+ str(tileSize))
@@ -243,6 +246,7 @@ faces = ['face0000.tif', 'face0001.tif', 'face0002.tif', 'face0003.tif', 'face00

# Generate tiles
print('Generating tiles...')
missingTiles = []
for f in range(0, 6):
size = cubeSize
faceExists = os.path.exists(os.path.join(args.output, faces[f]))
@@ -271,8 +275,45 @@ for f in range(0, 6):
background = Image.new(tile.mode[:-1], tile.size, colorTuple)
background.paste(tile, tile.split()[-1])
tile = background
tile.save(os.path.join(args.output, str(level), faceLetters[f] + str(i) + '_' + str(j) + extension), quality=args.quality)
colors = tile.getcolors(1)
if not genPreview and colors is not None and colors[0][1] == colorTuple:
missingTiles.append((f, level, j, i))
else:
tile.save(os.path.join(args.output, str(level), faceLetters[f] + str(i) + '_' + str(j) + extension), quality=args.quality)
else:
missingTiles.append((f, level, j, i))
size = int(size / 2)
else:
missingTiles.append((f, level, 0, 0))

# Tell viewer not to load missing tiles
if len(missingTiles) > 0:
# Remove children of missing tiles, since they won't be loaded anyway
tilesToRemove = []
for t in missingTiles:
tilesToRemove.append((t[0], t[1] + 1, t[2] * 2, t[3] * 2))
tilesToRemove.append((t[0], t[1] + 1, t[2] * 2, t[3] * 2 + 1))
tilesToRemove.append((t[0], t[1] + 1, t[2] * 2 + 1, t[3] * 2))
tilesToRemove.append((t[0], t[1] + 1, t[2] * 2 + 1, t[3] * 2 + 1))
for t in tilesToRemove:
if t in missingTiles:
missingTiles.pop(missingTiles.index(t))
# Encode missing tile list as string
missingTilesStr = ''
prevFace = prevLevel = None
for missingTile in sorted(missingTiles):
face = missingTile[0]
level = missingTile[1]
if face != prevFace:
missingTilesStr += '!' + faceLetters[face]
#prevLevel = None
if level != prevLevel:
missingTilesStr += '>' + b83encode([level], 1)
maxTileNum = math.ceil(cubeSize / 2**(levels - level) / tileSize) - 1
numTileDigits = math.ceil(math.log(maxTileNum + 1, 83))
missingTilesStr += b83encode(missingTile[2:], numTileDigits)
prevFace = face
prevLevel = level

# Generate fallback tiles
if args.fallbackSize > 0:
@@ -297,8 +338,6 @@ if not args.debug:
os.remove(os.path.join(args.output, face))

# Generate preview (but not for partial panoramas)
if haov < 360 or vaov < 180:
genPreview = False
if genPreview:
# Generate SHT-hash preview
shtHash = img2shtHash(np.array(Image.open(args.inputFile).resize((1024, 512))))
@@ -328,7 +367,7 @@ if vaov < 180:
text.append(' "pitch": ' + str( args.vOffset)+ ',')
text.append(' "maxPitch": ' + str(+vaov/2+args.vOffset)+ ',')
if colorTuple != (0, 0, 0):
text.append(' "backgroundColor": "' + args.backgroundColor+ '",')
text.append(' "backgroundColor": ' + args.backgroundColor+ ',')
if args.avoidbackground and (haov < 360 or vaov < 180):
text.append(' "avoidShowingBackground": true,')
if args.autoload:
@@ -339,6 +378,8 @@ if genPreview:
text.append(' "shtHash": "' + shtHash + '",')
if args.thumbnailSize > 0:
text.append(' "equirectangularThumbnail": "' + equiPreview + '",')
if len(missingTiles) > 0:
text.append(' "missingTiles": "' + missingTilesStr + '",')
text.append(' "path": "/%l/%s%y_%x",')
if args.fallbackSize > 0:
text.append(' "fallbackPath": "/fallback/%s",')


Loading…
Cancel
Save