Finally merged map asset and map tool
Some checks failed
Build Dusk / build-linux (push) Failing after 39s
Build Dusk / build-psp (push) Failing after 54s

This commit is contained in:
2025-11-19 20:25:25 -06:00
parent 8740c2b165
commit 51a1077fda
7 changed files with 94 additions and 134 deletions

View File

@@ -6,148 +6,83 @@ from assetstool.args import args
from assetstool.assetcache import assetCache, assetGetCache
from assetstool.assethelpers import getAssetRelativePath
from dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, CHUNK_TILE_COUNT
from dusk.map import Map
from dusk.chunk import Chunk
def processTile(tileIndex, x=0, y=0, z=0, chunkX=0, chunkY=0, chunkZ=0):
vertices = []
indices = []
tileType = tileIndex
# Placement X, Y, Z
px = (x * TILE_WIDTH) + (chunkX * CHUNK_WIDTH * TILE_WIDTH)
py = (y * TILE_HEIGHT) + (chunkY * CHUNK_HEIGHT * TILE_HEIGHT)
pz = (z * TILE_DEPTH) + (chunkZ * CHUNK_DEPTH * TILE_DEPTH)
if tileIndex == 0:
# Tile 0, nothing
return None
elif tileIndex == 5:
# Tile 2, ramp up
color = (255,0,0)
vertices = [
{'position': (px, py, pz + TILE_DEPTH), 'color': color, 'uv': (0,0)}, # 0,0
{'position': (px + TILE_WIDTH, py, pz + TILE_DEPTH), 'color': color, 'uv': (1,0)}, # 1,0
{'position': (px + TILE_WIDTH, py + TILE_HEIGHT, pz), 'color': color, 'uv': (1,1)}, # 1,1
{'position': (px, py, pz + TILE_DEPTH), 'color': color, 'uv': (0,0)}, # 0,0 (repeat)
{'position': (px + TILE_WIDTH, py + TILE_HEIGHT, pz), 'color': color, 'uv': (1,1)}, # 1,1 (repeat)
{'position': (px, py + TILE_HEIGHT, pz), 'color': color, 'uv': (0,1)} # 0,1
]
indices = [0, 1, 2, 3, 4, 5]
else:
# Determine color for checkerboard pattern
if tileIndex == 1:
color = (255, 255, 255)
else:
color = (0, 0, 255)
vertices = [
{'position': (px, py, pz), 'color': color, 'uv': (0,0)}, # 0,0
{'position': (px + TILE_WIDTH, py, pz), 'color': color, 'uv': (1,0)}, # 1,0
{'position': (px + TILE_WIDTH, py + TILE_HEIGHT, pz), 'color': color, 'uv': (1,1)}, # 1,1
{'position': (px, py, pz), 'color': color, 'uv': (0,0)}, # 0,0 (repeat)
{'position': (px + TILE_WIDTH, py + TILE_HEIGHT, pz), 'color': color, 'uv': (1,1)}, # 1,1 (repeat)
{'position': (px, py + TILE_HEIGHT, pz), 'color': color, 'uv': (0,1)} # 0,1
]
indices = [0, 1, 2, 3, 4, 5]
def convertModelData(modelData):
# TLDR; Model data stores things efficiently with indices, but we buffer it
# out to 6 vertex quads for simplicity.
outVertices = []
outUVs = []
outColors = []
for indice in modelData['indices']:
vertex = modelData['vertices'][indice]
uv = modelData['uvs'][indice]
color = modelData['colors'][indice]
outVertices.append(vertex)
outUVs.append(uv)
outColors.append(color)
return {
'vertices': vertices,
'indices': indices,
'tileType': tileType
'vertices': outVertices,
'uvs': outUVs,
'colors': outColors
}
def processChunk(path):
cache = assetGetCache(path)
def processChunk(chunk):
cache = assetGetCache(chunk.getFilename())
if cache:
return cache
# Read input file as JSON
with open(path, 'r') as f:
inData = json.load(f)
# Filename must contain chunk coordinates as X_Y_Z
fileName = os.path.basename(path)
nameParts = os.path.splitext(fileName)[0].split('_')
if len(nameParts) != 3:
print(f"Error: Chunk filename {fileName} does not contain valid chunk coordinates.")
sys.exit(1)
chunk = {
'chunkX': int(nameParts[0]),
'chunkY': int(nameParts[1]),
'chunkZ': int(nameParts[2]),
'tiles': [0] * CHUNK_TILE_COUNT,
'models': []
}
baseModel = {
'vertices': [],
'indices': [],
'vertexCount': 0,
'indexCount': 0
'colors': [],
'uvs': []
}
models = [ baseModel ]
# Append the model to chunk.models
chunk['models'].append(baseModel)
for tileIndex, tile in chunk.tiles.items():
tileBase = tile.getBaseTileModel()
for i, tile in enumerate(inData['shapes']):
# Set to chunk
# Calculate x, y, z from i
x = i % CHUNK_WIDTH
y = (i // CHUNK_WIDTH) % CHUNK_HEIGHT
z = i // (CHUNK_WIDTH * CHUNK_HEIGHT)
# Add tile 3D model
result = processTile(tile, x, y, z, chunk['chunkX'], chunk['chunkY'], chunk['chunkZ'])
if result is not None and len(result['vertices']) > 0:
base = len(baseModel['vertices'])
quad_indices = [base + idx for idx in result['indices']]
baseModel['vertices'].extend(result['vertices'])
baseModel['indices'].extend(quad_indices)
baseModel['vertexCount'] = len(baseModel['vertices'])
baseModel['indexCount'] = len(baseModel['indices'])
chunk['tiles'][i] = result['tileType']
convertedBase = convertModelData(tileBase)
baseModel['vertices'].extend(convertedBase['vertices'])
baseModel['colors'].extend(convertedBase['colors'])
baseModel['uvs'].extend(convertedBase['uvs'])
# Generate binary buffer for efficient output
buffer = bytearray()
buffer.extend(b'DCF')# Header
buffer.extend(len(chunk['tiles']).to_bytes(4, 'little')) # Number of tiles
buffer.extend(len(chunk['models']).to_bytes(1, 'little')) # Number of models
buffer.extend(len(chunk.tiles).to_bytes(4, 'little')) # Number of tiles
buffer.extend(len(models).to_bytes(1, 'little')) # Number of models
# Buffer tile data as array of uint8_t
for tileIndex in chunk['tiles']:
buffer.append(tileIndex.to_bytes(1, 'little')[0])
for tileIndex, tile in chunk.tiles.items():
buffer.extend(tile.shape.to_bytes(1, 'little'))
# For each model
for model in chunk['models']:
# Write vertex count and index count
buffer.extend(model['vertexCount'].to_bytes(4, 'little'))
# For each vertex
for vertex in model['vertices']:
# This is not tightly packed in memory.
# R G B A U V X Y Z
# Color is 4 bytes (RGBA)
# Rest is floats
r, g, b = vertex['color']
a = 255
buffer.extend(r.to_bytes(1, 'little'))
buffer.extend(g.to_bytes(1, 'little'))
buffer.extend(b.to_bytes(1, 'little'))
buffer.extend(a.to_bytes(1, 'little'))
u, v = vertex['uv']
buffer.extend(bytearray(struct.pack('<f', u)))
buffer.extend(bytearray(struct.pack('<f', v)))
x, y, z = vertex['position']
buffer.extend(bytearray(struct.pack('<f', x)))
buffer.extend(bytearray(struct.pack('<f', y)))
buffer.extend(bytearray(struct.pack('<f', z)))
# # For each model
for model in models:
vertexCount = len(model['vertices'])
buffer.extend(vertexCount.to_bytes(4, 'little'))
for i in range(vertexCount):
vertex = model['vertices'][i]
uv = model['uvs'][i]
color = model['colors'][i]
buffer.extend(color[0].to_bytes(1, 'little'))
buffer.extend(color[1].to_bytes(1, 'little'))
buffer.extend(color[2].to_bytes(1, 'little'))
buffer.extend(color[3].to_bytes(1, 'little'))
buffer.extend(bytearray(struct.pack('<f', uv[0])))
buffer.extend(bytearray(struct.pack('<f', uv[1])))
buffer.extend(bytearray(struct.pack('<f', vertex[0])))
buffer.extend(bytearray(struct.pack('<f', vertex[1])))
buffer.extend(bytearray(struct.pack('<f', vertex[2])))
# Write out map file
relative = getAssetRelativePath(path)
fileNameWithoutExt = os.path.splitext(os.path.basename(path))[0]
relative = getAssetRelativePath(chunk.getFilename())
fileNameWithoutExt = os.path.splitext(os.path.basename(relative))[0]
outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dcf")
outputFilePath = os.path.join(args.output_assets, outputFileRelative)
os.makedirs(os.path.dirname(outputFilePath), exist_ok=True)
@@ -158,20 +93,42 @@ def processChunk(path):
'files': [ outputFilePath ],
'chunk': chunk
}
return assetCache(path, outChunk)
return assetCache(chunk.getFilename(), outChunk)
def processMap(asset):
cache = assetGetCache(asset['path'])
if cache is not None:
return cache
# Path provided should be a directory.
if not os.path.isdir(asset['path']):
print(f"Error: Asset path {asset['path']} is not a directory.")
map = Map(None)
map.load(asset['path'])
dir = map.getMapDirectory()
files = os.listdir(dir)
if len(files) == 0:
print(f"Error: No chunk files found in map directory {dir}.")
sys.exit(1)
chunkFiles = []
for fileName in files:
if not fileName.endswith('.json'):
continue
fNameNoExt = os.path.splitext(fileName)[0]
fnPieces = fNameNoExt.split('_')
if len(fnPieces) != 3:
print(f"Error: Chunk filename {fileName} does not contain valid chunk coordinates.")
sys.exit(1)
chunk = Chunk(map, int(fnPieces[0]), int(fnPieces[1]), int(fnPieces[2]))
chunk.load()
result = processChunk(chunk)
chunkFiles.extend(result['files'])
outMap = {
'files': chunkFiles
}
return assetCache(asset['path'], outMap)
# List files
chunkFiles = []
for fileName in os.listdir(asset['path']):

View File

@@ -2,7 +2,7 @@ import json
import os
from dusk.event import Event
from dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, CHUNK_VERTEX_COUNT_MAX, TILE_SHAPE_NULL
from editortool.map.tile import Tile
from dusk.tile import Tile
from editortool.map.vertexbuffer import VertexBuffer
from OpenGL.GL import *

View File

@@ -3,11 +3,11 @@ from dusk.event import Event
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5.QtCore import QTimer
import os
from editortool.map.chunk import Chunk
from dusk.chunk import Chunk
from dusk.defs import MAP_WIDTH, MAP_HEIGHT, MAP_DEPTH, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH
import traceback
MAP_DEFAULT_PATH = os.path.join(os.path.dirname(__file__), '../../../assets/map/')
MAP_DEFAULT_PATH = os.path.join(os.path.dirname(__file__), '../../assets/map/')
EDITOR_CONFIG_PATH = os.path.join(os.path.dirname(__file__), '.editor')
class Map:
@@ -28,7 +28,10 @@ class Map:
for z in range(MAP_DEPTH):
self.chunks[index] = Chunk(self, x, y, z)
index += 1
QTimer.singleShot(16, self.loadLastFile)
# Only in editor instances:
if parent is not None:
QTimer.singleShot(16, self.loadLastFile)
def loadLastFile(self):
if not os.path.exists(EDITOR_CONFIG_PATH):

View File

@@ -1,7 +1,7 @@
import os
from PyQt5.QtWidgets import QAction, QMenuBar, QFileDialog
from PyQt5.QtGui import QKeySequence
from editortool.map.map import MAP_DEFAULT_PATH
from dusk.map import MAP_DEFAULT_PATH
class MapMenubar:
def __init__(self, parent):

View File

@@ -6,7 +6,7 @@ from editortool.map.chunkpanel import ChunkPanel
from editortool.map.mapinfopanel import MapInfoPanel
from editortool.map.menubar import MapMenubar
from editortool.map.statusbar import StatusBar
from editortool.map.map import Map
from dusk.map import Map
from dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_SHAPE_NULL, TILE_SHAPE_FLOOR
from editortool.map.selectbox import SelectBox
from editortool.map.camera import Camera