diff --git a/tools/editortool/map/camera.py b/tools/editortool/map/camera.py index 936ddab..288642e 100644 --- a/tools/editortool/map/camera.py +++ b/tools/editortool/map/camera.py @@ -1,28 +1,55 @@ import math +import time from OpenGL.GL import * from OpenGL.GLU import * from dusk.defs import defs +from editortool.map.map import map -pixelsPerUnit = float(defs.get('RPG_CAMERA_PIXELS_PER_UNIT')) -yOffset = float(defs.get('RPG_CAMERA_Z_OFFSET')) -fov = float(defs.get('RPG_CAMERA_FOV')) -scale = 8.0 +class Camera: + def __init__(self): + self.pixelsPerUnit = float(defs.get('RPG_CAMERA_PIXELS_PER_UNIT')) + self.yOffset = float(defs.get('RPG_CAMERA_Z_OFFSET')) + self.fov = float(defs.get('RPG_CAMERA_FOV')) + self.scale = 8.0 + self.lastTime = time.time() + self.lookAtTarget = [0.0, 0.0, 0.0] -def setupCamera(vw, vh): - z = (vh / 2.0) / ( - (pixelsPerUnit * scale) * math.tan(math.radians(fov / 2.0)) - ) - lookAt = ( 0, 0, 0 ) - aspectRatio = vw / vh - position = lookAt[0], lookAt[1] - yOffset, lookAt[2] + z + def setup(self, vw, vh, duration=0.1): + now = time.time() + delta = now - self.lastTime + self.lastTime = now + # Calculate ease factor for exponential smoothing over 'duration' seconds + ease = 1 - math.exp(-delta / duration) - glMatrixMode(GL_PROJECTION) - glLoadIdentity() - gluPerspective(fov, aspectRatio, 0.1, 1000.0) - glMatrixMode(GL_MODELVIEW) - glLoadIdentity() - gluLookAt( - position[0], position[1], position[2], - lookAt[0], lookAt[1], lookAt[2], - 0.0, 1.0, 0.0 - ) \ No newline at end of file + z = (vh / 2.0) / ( + (self.pixelsPerUnit * self.scale) * math.tan(math.radians(self.fov / 2.0)) + ) + lookAt = [ + map.position[0] * float(defs.get('TILE_WIDTH')), + map.position[1] * float(defs.get('TILE_HEIGHT')), + map.position[2] * float(defs.get('TILE_DEPTH')), + ] + aspectRatio = vw / vh + + + # Ease the lookAt target + for i in range(3): + self.lookAtTarget[i] += (lookAt[i] - self.lookAtTarget[i]) * ease + + # Camera position is now based on the eased lookAtTarget + cameraPosition = ( + self.lookAtTarget[0], + self.lookAtTarget[1] - self.yOffset, + self.lookAtTarget[2] + z + ) + + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + gluPerspective(self.fov, aspectRatio, 0.1, 1000.0) + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + gluLookAt( + cameraPosition[0], cameraPosition[1], cameraPosition[2], + self.lookAtTarget[0], self.lookAtTarget[1], self.lookAtTarget[2], + 0.0, 1.0, 0.0 + ) \ No newline at end of file diff --git a/tools/editortool/map/chunk.py b/tools/editortool/map/chunk.py index e69de29..1cfe038 100644 --- a/tools/editortool/map/chunk.py +++ b/tools/editortool/map/chunk.py @@ -0,0 +1,14 @@ +from editortool.map.chunk import Tile + +class Chunk: + def __init__(self, x=0, y=0, z=0, size_x=1, size_y=1, size_z=1): + self.x = x + self.y = y + self.z = z + self.data = [[[Tile() for _ in range(size_z)] for _ in range(size_y)] for _ in range(size_x)] + + def draw(self): + for x in range(len(self.data)): + for y in range(len(self.data[x])): + for z in range(len(self.data[x][y])): + self.data[x][y][z].draw(x, y, z) \ No newline at end of file diff --git a/tools/editortool/map/chunkpanel.py b/tools/editortool/map/chunkpanel.py new file mode 100644 index 0000000..3f57162 --- /dev/null +++ b/tools/editortool/map/chunkpanel.py @@ -0,0 +1,36 @@ +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QGridLayout +from editortool.map.map import map + +class ChunkPanel(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + layout = QVBoxLayout(self) + self.chunk_info_label = QLabel("Tile Information") + layout.addWidget(self.chunk_info_label) + self.move_label = QLabel("Move Selection") + layout.addWidget(self.move_label) + + grid = QGridLayout() + self.btn_up = QPushButton("U") + self.btn_n = QPushButton("N") + self.btn_down = QPushButton("D") + self.btn_w = QPushButton("W") + self.btn_s = QPushButton("S") + self.btn_e = QPushButton("E") + + # Arrange buttons: U N D on top row, W S E on bottom row + grid.addWidget(self.btn_up, 0, 0) + grid.addWidget(self.btn_n, 0, 1) + grid.addWidget(self.btn_down, 0, 2) + grid.addWidget(self.btn_w, 1, 0) + grid.addWidget(self.btn_s, 1, 1) + grid.addWidget(self.btn_e, 1, 2) + layout.addLayout(grid) + layout.addStretch() + + self.btn_n.clicked.connect(lambda: map.moveRelative(0, 1, 0)) + self.btn_s.clicked.connect(lambda: map.moveRelative(0, -1, 0)) + self.btn_e.clicked.connect(lambda: map.moveRelative(1, 0, 0)) + self.btn_w.clicked.connect(lambda: map.moveRelative(-1, 0, 0)) + self.btn_up.clicked.connect(lambda: map.moveRelative(0, 0, 1)) + self.btn_down.clicked.connect(lambda: map.moveRelative(0, 0, -1)) \ No newline at end of file diff --git a/tools/editortool/map/glwidget.py b/tools/editortool/map/glwidget.py index 49001e4..c8e17ae 100644 --- a/tools/editortool/map/glwidget.py +++ b/tools/editortool/map/glwidget.py @@ -3,8 +3,8 @@ from PyQt5.QtWidgets import QOpenGLWidget from OpenGL.GL import * from OpenGL.GLU import * from editortool.map.selectbox import drawSelectBox -from editortool.map.tile import drawTile -from editortool.map.camera import setupCamera +from editortool.map.camera import Camera +from editortool.map.grid import Grid class GLWidget(QOpenGLWidget): def __init__(self, parent=None): @@ -12,6 +12,8 @@ class GLWidget(QOpenGLWidget): self.timer = QTimer(self) self.timer.timeout.connect(self.update) self.timer.start(16) # ~60 FPS + self.camera = Camera() + self.grid = Grid() def initializeGL(self): glClearColor(0.392, 0.584, 0.929, 1.0) @@ -20,7 +22,6 @@ class GLWidget(QOpenGLWidget): def resizeGL(self, w, h): pass - def paintGL(self): glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glLoadIdentity() @@ -33,8 +34,7 @@ class GLWidget(QOpenGLWidget): w = 1 glViewport(0, 0, w, h) - setupCamera(self.width(), self.height()) # <-- Add this here + self.camera.setup(w, h) - # Draw test quad at 0,0,0 to 1,1,0 - drawTile(0, 0, 0) - drawSelectBox(0, 0, 0) \ No newline at end of file + self.grid.draw() + drawSelectBox() \ No newline at end of file diff --git a/tools/editortool/map/grid.py b/tools/editortool/map/grid.py new file mode 100644 index 0000000..41277ca --- /dev/null +++ b/tools/editortool/map/grid.py @@ -0,0 +1,46 @@ +from OpenGL.GL import * +from dusk.defs import defs + +class Grid: + def __init__(self, lines=1000): + self.cellWidth = float(defs.get('TILE_WIDTH')) + self.cellHeight = float(defs.get('TILE_HEIGHT')) + self.lines = lines + self.enabled = True + + def draw(self): + if not self.enabled: + return + center = [0, 0, 0] + halfWidth = self.cellWidth * self.lines // 2 + halfHeight = self.cellHeight * self.lines // 2 + # Draw origin axes + glBegin(GL_LINES) + + # X axis - RED + glColor3f(1.0, 0.0, 0.0) + glVertex3f(center[0] - halfWidth, center[1], center[2]) + glVertex3f(center[0] + halfWidth, center[1], center[2]) + + # Y axis - GREEN + glColor3f(0.0, 1.0, 0.0) + glVertex3f(center[0], center[1] - halfHeight, center[2]) + glVertex3f(center[0], center[1] + halfHeight, center[2]) + + # Z axis - BLUE + glColor3f(0.0, 0.0, 1.0) + glVertex3f(center[0], center[1], center[2] - halfWidth) + glVertex3f(center[0], center[1], center[2] + halfWidth) + glEnd() + + # Draw grid + glColor3f(0.8, 0.8, 0.8) + glBegin(GL_LINES) + for i in range(-self.lines // 2, self.lines // 2 + 1): + # Vertical lines + glVertex3f(center[0] + i * self.cellWidth, center[1] - halfHeight, center[2]) + glVertex3f(center[0] + i * self.cellWidth, center[1] + halfHeight, center[2]) + # Horizontal lines + glVertex3f(center[0] - halfWidth, center[1] + i * self.cellHeight, center[2]) + glVertex3f(center[0] + halfWidth, center[1] + i * self.cellHeight, center[2]) + glEnd() \ No newline at end of file diff --git a/tools/editortool/map/map.py b/tools/editortool/map/map.py new file mode 100644 index 0000000..ed72e29 --- /dev/null +++ b/tools/editortool/map/map.py @@ -0,0 +1,37 @@ +class Map: + def __init__(self): + self._position = [0, 0, 0] # x, y, z + self.listeners = [] + self.chunks = [] + self.width = 5 + self.height = 5 + self.depth = 3 + + @property + def position(self): + return self._position + + @position.setter + def position(self, value): + self._position = value + self.notifyPositionListeners() + + def addPositionListener(self, listener): + self.listeners.append(listener) + + def notifyPositionListeners(self): + for listener in self.listeners: + listener(self._position) + + def moveTo(self, x, y, z): + self.position = [x, y, z] + + def moveRelative(self, x, y, z): + self.moveTo( + self._position[0] + x, + self._position[1] + y, + self._position[2] + z + ) + +# Singleton instance +map = Map() \ No newline at end of file diff --git a/tools/editortool/map/mapinfopanel.py b/tools/editortool/map/mapinfopanel.py new file mode 100644 index 0000000..5ad8fa4 --- /dev/null +++ b/tools/editortool/map/mapinfopanel.py @@ -0,0 +1,68 @@ +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton, QHBoxLayout, QMessageBox +from editortool.map.map import map +from dusk.defs import defs + +CHUNK_WIDTH = int(defs.get('CHUNK_WIDTH')) +CHUNK_HEIGHT = int(defs.get('CHUNK_HEIGHT')) +CHUNK_DEPTH = int(defs.get('CHUNK_DEPTH')) + +class MapInfoPanel(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + + layout = QVBoxLayout() + map_info_label = QLabel("Map Information") + layout.addWidget(map_info_label) + + map_title_label = QLabel("Map Title") + self.map_title_input = QLineEdit() + layout.addWidget(map_title_label) + layout.addWidget(self.map_title_input) + + tilePositionLabel = QLabel("Tile Position") + layout.addWidget(tilePositionLabel) + tilePositionRow = QHBoxLayout() + self.tileXInput = QLineEdit() + self.tileXInput.setPlaceholderText("X") + tilePositionRow.addWidget(self.tileXInput) + self.tileYInput = QLineEdit() + self.tileYInput.setPlaceholderText("Y") + tilePositionRow.addWidget(self.tileYInput) + self.tileZInput = QLineEdit() + self.tileZInput.setPlaceholderText("Z") + tilePositionRow.addWidget(self.tileZInput) + self.tileGo = QPushButton("Go") + tilePositionRow.addWidget(self.tileGo) + layout.addLayout(tilePositionRow) + + self.chunkPosLabel = QLabel("Chunk Position: 0, 0, 0") + layout.addWidget(self.chunkPosLabel) + self.chunkLabel = QLabel("Chunk: 0, 0, 0") + layout.addWidget(self.chunkLabel) + + layout.addStretch() + self.setLayout(layout) + + self.tileGo.clicked.connect(self.goToPosition) + self.tileXInput.returnPressed.connect(self.goToPosition) + self.tileYInput.returnPressed.connect(self.goToPosition) + self.tileZInput.returnPressed.connect(self.goToPosition) + + self.updatePositionLabels(map.position) + map.addPositionListener(self.updatePositionLabels) + + def goToPosition(self): + try: + x = int(self.tileXInput.text()) + y = int(self.tileYInput.text()) + z = int(self.tileZInput.text()) + map.moveTo(x, y, z) + except ValueError: + QMessageBox.warning(self, "Invalid Input", "Please enter valid integer coordinates.") + + def updatePositionLabels(self, pos): + self.tileXInput.setText(str(pos[0])) + self.tileYInput.setText(str(pos[1])) + self.tileZInput.setText(str(pos[2])) + self.chunkPosLabel.setText(f"Chunk Position: {pos[0] % CHUNK_WIDTH}, {pos[1] % CHUNK_HEIGHT}, {pos[2] % CHUNK_DEPTH}") + self.chunkLabel.setText(f"Chunk: {pos[0] // CHUNK_WIDTH}, {pos[1] // CHUNK_HEIGHT}, {pos[2] // CHUNK_DEPTH}") \ No newline at end of file diff --git a/tools/editortool/map/selectbox.py b/tools/editortool/map/selectbox.py index 9df2625..dd1c094 100644 --- a/tools/editortool/map/selectbox.py +++ b/tools/editortool/map/selectbox.py @@ -1,38 +1,36 @@ import OpenGL.GL as gl from dusk.defs import defs import colorsys +from editortool.map.map import map hue = [0.0] # Mutable container for static hue -def drawSelectBox(x, y, z): +def drawSelectBox(): w = float(defs.get('TILE_WIDTH')) h = float(defs.get('TILE_HEIGHT')) d = float(defs.get('TILE_DEPTH')) - - x = x * w - y = y * h - z = z * d - - # Center box. - x -= w / 2.0 - y -= h / 2.0 - z -= d / 2.0 + position = [ + map.position[0] * w - (w / 2.0), + map.position[1] * h - (h / 2.0), + map.position[2] * d - (d / 2.0) + ] # Define the 8 vertices of the cube with w=h=d=1 vertices = [ - (x, y, z), # 0: min corner - (x+w, y, z), # 1 - (x+w, y+h, z), # 2 - (x, y+h, z), # 3 - (x, y, z+d), # 4 - (x+w, y, z+d), # 5 - (x+w, y+h, z+d), # 6 - (x, y+h, z+d) # 7 + (position[0], position[1], position[2]), # 0: min corner + (position[0]+w, position[1], position[2]), # 1 + (position[0]+w, position[1]+h, position[2]), # 2 + (position[0], position[1]+h, position[2]), # 3 + (position[0], position[1], position[2]+d), # 4 + (position[0]+w, position[1], position[2]+d), # 5 + (position[0]+w, position[1]+h, position[2]+d), # 6 + (position[0], position[1]+h, position[2]+d) # 7 ] # List of edges as pairs of vertex indices edges = [ (0, 1), (1, 2), (2, 3), (3, 0), # bottom face (4, 5), (5, 6), (6, 7), (7, 4), # top face + (4, 5), (5, 6), (6, 7), (7, 4), # top face (0, 4), (1, 5), (2, 6), (3, 7) # vertical edges ] diff --git a/tools/editortool/map/tile.py b/tools/editortool/map/tile.py index 9d551ef..5cf9cf4 100644 --- a/tools/editortool/map/tile.py +++ b/tools/editortool/map/tile.py @@ -1,25 +1,29 @@ from OpenGL.GL import * from dusk.defs import defs -def drawTile(x, y, z): - w = float(defs.get('TILE_WIDTH')) - h = float(defs.get('TILE_HEIGHT')) - d = float(defs.get('TILE_DEPTH')) +class Tile: + def __init__(self): + self.tile_id = 0 - x = x * w - y = y * h - z = z * d + def draw(self, x, y, z): + w = float(defs.get('TILE_WIDTH')) + h = float(defs.get('TILE_HEIGHT')) + d = float(defs.get('TILE_DEPTH')) - # Center tile. - x -= w / 2.0 - y -= h / 2.0 - z -= d / 2.0 + x = x * w + y = y * h + z = z * d - # Draw the tile as a flat square on the X-Y plane at depth z. - glColor3f(1.0, 0.0, 0.0) # Red color - glBegin(GL_QUADS) - glVertex3f(x, y, z) # Bottom-left - glVertex3f(x + w, y, z) # Bottom-right - glVertex3f(x + w, y + h, z) # Top-right - glVertex3f(x, y + h, z) # Top-left - glEnd() \ No newline at end of file + # Center tile. + x -= w / 2.0 + y -= h / 2.0 + z -= d / 2.0 + + # Draw the tile as a flat square on the X-Y plane at depth z. + glColor3f(1.0, 0.0, 0.0) # Red color + glBegin(GL_QUADS) + glVertex3f(x, y, z) # Bottom-left + glVertex3f(x + w, y, z) # Bottom-right + glVertex3f(x + w, y + h, z) # Top-right + glVertex3f(x, y + h, z) # Top-left + glEnd() \ No newline at end of file diff --git a/tools/editortool/maptool.py b/tools/editortool/maptool.py index c82423e..9207ae6 100644 --- a/tools/editortool/maptool.py +++ b/tools/editortool/maptool.py @@ -9,6 +9,8 @@ from PyQt5.QtWidgets import ( QAction, QFileDialog, QLineEdit, QMessageBox ) from editortool.map.glwidget import GLWidget +from editortool.map.chunkpanel import ChunkPanel +from editortool.map.mapinfopanel import MapInfoPanel class MapWindow(QMainWindow): def __init__(self): @@ -45,41 +47,10 @@ class MapWindow(QMainWindow): main_layout = QHBoxLayout(central) - # Left panel - left_panel = QVBoxLayout() - chunk_info_label = QLabel("CHUNK INFO") - left_panel.addWidget(chunk_info_label) - - # Movement controls - move_label = QLabel("Move Selection") - left_panel.addWidget(move_label) - btn_n = QPushButton("N") - btn_s = QPushButton("S") - btn_e = QPushButton("E") - btn_w = QPushButton("W") - btn_up = QPushButton("Up") - btn_down = QPushButton("Down") - left_panel.addWidget(btn_n) - left_panel.addWidget(btn_s) - left_panel.addWidget(btn_e) - left_panel.addWidget(btn_w) - left_panel.addWidget(btn_up) - left_panel.addWidget(btn_down) - left_panel.addStretch() - - # Right panel - right_panel = QVBoxLayout() - map_info_label = QLabel("Map Information") - right_panel.addWidget(map_info_label) - map_title_label = QLabel("Map Title") - self.map_title_input = QLineEdit() - right_panel.addWidget(map_title_label) - right_panel.addWidget(self.map_title_input) - right_panel.addStretch() - - # Add panels to main layout + # Left panel (ChunkPanel) + self.chunk_panel = ChunkPanel() left_widget = QWidget() - left_widget.setLayout(left_panel) + left_widget.setLayout(self.chunk_panel.layout()) left_widget.setFixedWidth(200) main_layout.addWidget(left_widget) @@ -87,21 +58,15 @@ class MapWindow(QMainWindow): self.gl_widget = GLWidget(self) main_layout.addWidget(self.gl_widget, stretch=3) - right_widget = QWidget() - right_widget.setLayout(right_panel) + # Right panel (MapInfoPanel) + self.map_info_panel = MapInfoPanel() + right_widget = self.map_info_panel right_widget.setFixedWidth(250) main_layout.addWidget(right_widget) + self.map_title_input = self.map_info_panel.map_title_input self.map_title_input.textChanged.connect(self._on_map_title_changed) - # Connect movement buttons - btn_n.clicked.connect(lambda: self.move_highlight(0, 1, 0)) - btn_s.clicked.connect(lambda: self.move_highlight(0, -1, 0)) - btn_e.clicked.connect(lambda: self.move_highlight(1, 0, 0)) - btn_w.clicked.connect(lambda: self.move_highlight(-1, 0, 0)) - btn_up.clicked.connect(lambda: self.move_highlight(0, 0, 1)) - btn_down.clicked.connect(lambda: self.move_highlight(0, 0, -1)) - def _on_map_title_changed(self): self.dirty = True @@ -109,7 +74,6 @@ class MapWindow(QMainWindow): self.current_file = None self.map_title_input.setText("") self.dirty = False - # ...clear relevant data... def openFile(self): default_dir = os.path.join(os.path.dirname(__file__), '../../assets/map/') @@ -123,7 +87,7 @@ class MapWindow(QMainWindow): self.dirty = False except Exception as e: self.map_title_input.setText("") - # Optionally show error dialog + QMessageBox.critical(self, "Error", f"Failed to load file:\n{e}") def saveFile(self): if self.current_file: @@ -147,15 +111,7 @@ class MapWindow(QMainWindow): json.dump(data, f, indent=2) self.dirty = False except Exception as e: - # Optionally show error dialog - pass - - def move_highlight(self, dx, dy, dz): - self.highlighted_pos[0] += dx - self.highlighted_pos[1] += dy - self.highlighted_pos[2] += dz - self.gl_widget.set_highlighted_pos(self.highlighted_pos) - self.dirty = True + QMessageBox.critical(self, "Error", f"Failed to save file:\n{e}") def closeEvent(self, event): if self.dirty: