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

@@ -3,4 +3,4 @@
# This software is released under the MIT License. # This software is released under the MIT License.
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
add_asset(MAP map) add_asset(MAP map.json)

View File

@@ -6,148 +6,83 @@ from assetstool.args import args
from assetstool.assetcache import assetCache, assetGetCache from assetstool.assetcache import assetCache, assetGetCache
from assetstool.assethelpers import getAssetRelativePath 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.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): def convertModelData(modelData):
vertices = [] # TLDR; Model data stores things efficiently with indices, but we buffer it
indices = [] # out to 6 vertex quads for simplicity.
tileType = tileIndex outVertices = []
outUVs = []
# Placement X, Y, Z outColors = []
px = (x * TILE_WIDTH) + (chunkX * CHUNK_WIDTH * TILE_WIDTH) for indice in modelData['indices']:
py = (y * TILE_HEIGHT) + (chunkY * CHUNK_HEIGHT * TILE_HEIGHT) vertex = modelData['vertices'][indice]
pz = (z * TILE_DEPTH) + (chunkZ * CHUNK_DEPTH * TILE_DEPTH) uv = modelData['uvs'][indice]
color = modelData['colors'][indice]
if tileIndex == 0: outVertices.append(vertex)
# Tile 0, nothing outUVs.append(uv)
return None outColors.append(color)
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]
return { return {
'vertices': vertices, 'vertices': outVertices,
'indices': indices, 'uvs': outUVs,
'tileType': tileType 'colors': outColors
} }
def processChunk(path): def processChunk(chunk):
cache = assetGetCache(path) cache = assetGetCache(chunk.getFilename())
if cache: if cache:
return 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 = { baseModel = {
'vertices': [], 'vertices': [],
'indices': [], 'colors': [],
'vertexCount': 0, 'uvs': []
'indexCount': 0
} }
models = [ baseModel ]
# Append the model to chunk.models for tileIndex, tile in chunk.tiles.items():
chunk['models'].append(baseModel) tileBase = tile.getBaseTileModel()
for i, tile in enumerate(inData['shapes']): convertedBase = convertModelData(tileBase)
# Set to chunk baseModel['vertices'].extend(convertedBase['vertices'])
baseModel['colors'].extend(convertedBase['colors'])
# Calculate x, y, z from i baseModel['uvs'].extend(convertedBase['uvs'])
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']
# Generate binary buffer for efficient output # Generate binary buffer for efficient output
buffer = bytearray() buffer = bytearray()
buffer.extend(b'DCF')# Header buffer.extend(b'DCF')# Header
buffer.extend(len(chunk['tiles']).to_bytes(4, 'little')) # Number of tiles 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(models).to_bytes(1, 'little')) # Number of models
# Buffer tile data as array of uint8_t # Buffer tile data as array of uint8_t
for tileIndex in chunk['tiles']: for tileIndex, tile in chunk.tiles.items():
buffer.append(tileIndex.to_bytes(1, 'little')[0]) buffer.extend(tile.shape.to_bytes(1, 'little'))
# For each model # # For each model
for model in chunk['models']: for model in models:
# Write vertex count and index count vertexCount = len(model['vertices'])
buffer.extend(model['vertexCount'].to_bytes(4, 'little')) buffer.extend(vertexCount.to_bytes(4, 'little'))
for i in range(vertexCount):
# For each vertex vertex = model['vertices'][i]
for vertex in model['vertices']: uv = model['uvs'][i]
# This is not tightly packed in memory. color = model['colors'][i]
# R G B A U V X Y Z
# Color is 4 bytes (RGBA) buffer.extend(color[0].to_bytes(1, 'little'))
# Rest is floats buffer.extend(color[1].to_bytes(1, 'little'))
r, g, b = vertex['color'] buffer.extend(color[2].to_bytes(1, 'little'))
a = 255 buffer.extend(color[3].to_bytes(1, 'little'))
buffer.extend(r.to_bytes(1, 'little'))
buffer.extend(g.to_bytes(1, 'little')) buffer.extend(bytearray(struct.pack('<f', uv[0])))
buffer.extend(b.to_bytes(1, 'little')) buffer.extend(bytearray(struct.pack('<f', uv[1])))
buffer.extend(a.to_bytes(1, 'little'))
u, v = vertex['uv'] buffer.extend(bytearray(struct.pack('<f', vertex[0])))
buffer.extend(bytearray(struct.pack('<f', u))) buffer.extend(bytearray(struct.pack('<f', vertex[1])))
buffer.extend(bytearray(struct.pack('<f', v))) buffer.extend(bytearray(struct.pack('<f', vertex[2])))
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)))
# Write out map file # Write out map file
relative = getAssetRelativePath(path) relative = getAssetRelativePath(chunk.getFilename())
fileNameWithoutExt = os.path.splitext(os.path.basename(path))[0] fileNameWithoutExt = os.path.splitext(os.path.basename(relative))[0]
outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dcf") outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dcf")
outputFilePath = os.path.join(args.output_assets, outputFileRelative) outputFilePath = os.path.join(args.output_assets, outputFileRelative)
os.makedirs(os.path.dirname(outputFilePath), exist_ok=True) os.makedirs(os.path.dirname(outputFilePath), exist_ok=True)
@@ -158,20 +93,42 @@ def processChunk(path):
'files': [ outputFilePath ], 'files': [ outputFilePath ],
'chunk': chunk 'chunk': chunk
} }
return assetCache(chunk.getFilename(), outChunk)
return assetCache(path, outChunk)
def processMap(asset): def processMap(asset):
cache = assetGetCache(asset['path']) cache = assetGetCache(asset['path'])
if cache is not None: if cache is not None:
return cache return cache
# Path provided should be a directory. map = Map(None)
if not os.path.isdir(asset['path']): map.load(asset['path'])
print(f"Error: Asset path {asset['path']} is not a directory.") 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) 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 # List files
chunkFiles = [] chunkFiles = []
for fileName in os.listdir(asset['path']): for fileName in os.listdir(asset['path']):

View File

@@ -2,7 +2,7 @@ import json
import os import os
from dusk.event import Event from dusk.event import Event
from dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, CHUNK_VERTEX_COUNT_MAX, TILE_SHAPE_NULL 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 editortool.map.vertexbuffer import VertexBuffer
from OpenGL.GL import * from OpenGL.GL import *

View File

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

View File

@@ -1,7 +1,7 @@
import os import os
from PyQt5.QtWidgets import QAction, QMenuBar, QFileDialog from PyQt5.QtWidgets import QAction, QMenuBar, QFileDialog
from PyQt5.QtGui import QKeySequence from PyQt5.QtGui import QKeySequence
from editortool.map.map import MAP_DEFAULT_PATH from dusk.map import MAP_DEFAULT_PATH
class MapMenubar: class MapMenubar:
def __init__(self, parent): 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.mapinfopanel import MapInfoPanel
from editortool.map.menubar import MapMenubar from editortool.map.menubar import MapMenubar
from editortool.map.statusbar import StatusBar 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 dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_SHAPE_NULL, TILE_SHAPE_FLOOR
from editortool.map.selectbox import SelectBox from editortool.map.selectbox import SelectBox
from editortool.map.camera import Camera from editortool.map.camera import Camera