Fix tile rendering dimensions and alignment, update tile definitions to use height
This commit is contained in:
@@ -412,11 +412,18 @@ export class GameRenderer {
|
||||
mesh.rotation.y = angle;
|
||||
|
||||
// Store door data for interaction (new doors always start closed)
|
||||
// Convert numeric direction to string for generator compatibility
|
||||
const dirMap = { 0: 'N', 1: 'E', 2: 'S', 3: 'W' };
|
||||
mesh.userData = {
|
||||
isDoor: true,
|
||||
isOpen: false,
|
||||
cells: [d1, d2],
|
||||
direction: dir
|
||||
direction: dir,
|
||||
exitData: {
|
||||
x: d1.x,
|
||||
y: d1.y,
|
||||
direction: dirMap[dir] || 'N'
|
||||
}
|
||||
};
|
||||
mesh.name = `door_${idx}`;
|
||||
|
||||
@@ -480,14 +487,38 @@ export class GameRenderer {
|
||||
|
||||
// Load texture with callback
|
||||
this.getTexture(texturePath, (texture) => {
|
||||
const w = tileDef.width;
|
||||
const l = tileDef.length;
|
||||
|
||||
// Create Plane
|
||||
const geometry = new THREE.PlaneGeometry(w, l);
|
||||
// --- NEW LOGIC: Calculate center based on DIMENSIONS, not CELLS ---
|
||||
|
||||
// 1. Get the specific variant for this rotation to know the VISUAL bounds
|
||||
// (The shape the grid sees: e.g. 4x2 for East)
|
||||
const currentVariant = tileDef.variants[tileInstance.rotation];
|
||||
|
||||
if (!currentVariant) {
|
||||
console.error(`[GameRenderer] Missing variant for rotation ${tileInstance.rotation}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const rotWidth = currentVariant.width;
|
||||
const rotHeight = currentVariant.height;
|
||||
|
||||
// 2. Calculate the Geometric Center of the tile relative to the anchor
|
||||
// Formula: anchor + (dimension - 1) / 2
|
||||
// (Subtract 1 because width 1 is just offset 0)
|
||||
const cx = tileInstance.x + (rotWidth - 1) / 2;
|
||||
const cy = tileInstance.y + (rotHeight - 1) / 2;
|
||||
|
||||
console.log(`[GameRenderer] Dimensions (Rotated): ${rotWidth}x${rotHeight}`);
|
||||
console.log(`[GameRenderer] Calculated Center: (${cx}, ${cy})`);
|
||||
|
||||
// 3. Use BASE dimensions from NORTH variant for the Plane
|
||||
// (Since we are rotating the plane itself, we start with the un-rotated image size)
|
||||
const baseWidth = tileDef.variants.N.width;
|
||||
const baseHeight = tileDef.variants.N.height;
|
||||
|
||||
// Create Plane with BASE dimensions
|
||||
const geometry = new THREE.PlaneGeometry(baseWidth, baseHeight);
|
||||
|
||||
// SWITCH TO BASIC MATERIAL FOR DEBUGGING TEXTURE VISIBILITY
|
||||
// Standard material heavily depends on lights. If light is not hitting correctly, it looks black.
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
transparent: true,
|
||||
@@ -496,42 +527,32 @@ export class GameRenderer {
|
||||
});
|
||||
const plane = new THREE.Mesh(geometry, material);
|
||||
|
||||
|
||||
|
||||
// Initial Rotation: Plane X-Y to X-Z
|
||||
// Initial Rotation: Plane X-Y to X-Z (Flat on ground)
|
||||
plane.rotation.x = -Math.PI / 2;
|
||||
|
||||
// Handle Rotation safely (Support both 0-3 and N-W)
|
||||
const rotMap = { 'N': 0, '0': 0, 0: 0, 'E': 1, '1': 1, 1: 1, 'S': 2, '2': 2, 2: 2, 'W': 3, '3': 3, 3: 3 };
|
||||
// Handle Rotation
|
||||
const rotMap = { 'N': 0, 'E': 1, 'S': 2, 'W': 3 };
|
||||
const r = rotMap[tileInstance.rotation] !== undefined ? rotMap[tileInstance.rotation] : 0;
|
||||
|
||||
// Apply Tile Rotation
|
||||
// Apply Tile Rotation (Z-axis is Up in this local frame before X-rotation? No, after X-rot)
|
||||
// Actually, standard hierarchy: Rotate Z first?
|
||||
// ThreeJS rotation order XYZ.
|
||||
// We want to rotate around the Y axis of the world (which is Z of the plane before x-rotation?)
|
||||
// Simplest: Rotate Z of the plane, which corresponds to world Y.
|
||||
// Note: We use negative rotation because ThreeJS is CCW, but our grid might be different,
|
||||
// but usually -r * PI/2 works for this setup.
|
||||
plane.rotation.z = -r * (Math.PI / 2);
|
||||
|
||||
// Calculate Center Offset for Positioning
|
||||
const midX = (tileDef.width - 1) / 2;
|
||||
const midY = (tileDef.length - 1) / 2;
|
||||
|
||||
// Rotate the offset vector based on tile rotation
|
||||
let dx, dy;
|
||||
|
||||
if (r === 0) { dx = midX; dy = midY; }
|
||||
else if (r === 1) { dx = midY; dy = -midX; }
|
||||
else if (r === 2) { dx = -midX; dy = -midY; }
|
||||
else if (r === 3) { dx = -midY; dy = midX; }
|
||||
|
||||
const centerX = tileInstance.x + dx;
|
||||
const centerY = tileInstance.y + dy;
|
||||
|
||||
// Set at almost 0 height to avoid z-fighting with grid helper, but effectively on floor
|
||||
plane.position.set(centerX, 0.01, -centerY);
|
||||
// Position at the calculated center
|
||||
// Notice: World Z is -Grid Y
|
||||
plane.position.set(cx, 0.01, -cy);
|
||||
plane.receiveShadow = true;
|
||||
|
||||
this.scene.add(plane);
|
||||
console.log(`[GameRenderer] ✓ Tile plane added at (${centerX}, 0.01, ${-centerY}) for ${tileDef.id}`);
|
||||
console.log(`[GameRenderer] ✓ Tile plane added at (${cx}, 0.01, ${-cy})`);
|
||||
});
|
||||
} else {
|
||||
console.warn(`[GameRenderer] details missing for texture render. def: ${!!tileDef}, inst: ${!!tileInstance}, tex: ${tileDef?.textures?.length}`);
|
||||
console.warn(`[GameRenderer] details missing for texture render. def: ${!!tileDef}, inst: ${!!tileInstance}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,4 +602,156 @@ export class GameRenderer {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========== MANUAL PLACEMENT SYSTEM ==========
|
||||
|
||||
enableDoorSelection(enabled) {
|
||||
this.doorSelectionEnabled = enabled;
|
||||
|
||||
if (enabled) {
|
||||
// Highlight available exits
|
||||
this.highlightAvailableExits();
|
||||
} else {
|
||||
// Remove highlights
|
||||
if (this.exitHighlightGroup) {
|
||||
this.exitHighlightGroup.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlightAvailableExits() {
|
||||
if (!this.exitHighlightGroup) {
|
||||
this.exitHighlightGroup = new THREE.Group();
|
||||
this.scene.add(this.exitHighlightGroup);
|
||||
}
|
||||
|
||||
this.exitHighlightGroup.clear();
|
||||
|
||||
// Highlight each exit door with a pulsing glow
|
||||
if (this.exitGroup) {
|
||||
this.exitGroup.children.forEach(doorMesh => {
|
||||
if (doorMesh.userData.isDoor && !doorMesh.userData.isOpen) {
|
||||
// Create highlight ring
|
||||
const ringGeom = new THREE.RingGeometry(1.2, 1.4, 32);
|
||||
const ringMat = new THREE.MeshBasicMaterial({
|
||||
color: 0x00ff00,
|
||||
side: THREE.DoubleSide,
|
||||
transparent: true,
|
||||
opacity: 0.6
|
||||
});
|
||||
const ring = new THREE.Mesh(ringGeom, ringMat);
|
||||
ring.rotation.x = -Math.PI / 2;
|
||||
ring.position.copy(doorMesh.position);
|
||||
ring.position.y = 0.05;
|
||||
|
||||
|
||||
// Store reference to door for click handling
|
||||
doorMesh.userData.isExit = true;
|
||||
// Create proper exit data with all required fields
|
||||
const firstCell = doorMesh.userData.cells[0];
|
||||
// Convert numeric direction (0,1,2,3) to string ('N','E','S','W')
|
||||
const dirMap = { 0: 'N', 1: 'E', 2: 'S', 3: 'W' };
|
||||
doorMesh.userData.exitData = {
|
||||
x: firstCell.x,
|
||||
y: firstCell.y,
|
||||
direction: dirMap[doorMesh.userData.direction] || 'N'
|
||||
};
|
||||
|
||||
this.exitHighlightGroup.add(ring);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showPlacementPreview(preview) {
|
||||
if (!preview) {
|
||||
this.hidePlacementPreview();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create preview groups if they don't exist
|
||||
if (!this.previewGroup) {
|
||||
this.previewGroup = new THREE.Group();
|
||||
this.scene.add(this.previewGroup);
|
||||
}
|
||||
|
||||
if (!this.projectionGroup) {
|
||||
this.projectionGroup = new THREE.Group();
|
||||
this.scene.add(this.projectionGroup);
|
||||
}
|
||||
|
||||
// Clear previous preview
|
||||
this.previewGroup.clear();
|
||||
this.projectionGroup.clear();
|
||||
|
||||
const { card, cells, isValid, x, y, rotation } = preview;
|
||||
|
||||
// Calculate bounds for tile - OLD LOGIC (Removed)
|
||||
// Note: We ignore 'cells' for positioning the texture, but keep them for the Ground Projection (Green/Red squares)
|
||||
|
||||
// 1. FLOATING TILE (Y = 3)
|
||||
if (card.textures && card.textures.length > 0) {
|
||||
this.getTexture(card.textures[0], (texture) => {
|
||||
|
||||
// Get Current Rotation Variant for Dimensions
|
||||
const currentVariant = card.variants[rotation];
|
||||
const rotWidth = currentVariant.width;
|
||||
const rotHeight = currentVariant.height;
|
||||
|
||||
// Calculate Center based on Anchor (x, y) and Dimensions
|
||||
const cx = x + (rotWidth - 1) / 2;
|
||||
const cy = y + (rotHeight - 1) / 2;
|
||||
|
||||
// Use BASE dimensions from NORTH variant
|
||||
const baseWidth = card.variants.N.width;
|
||||
const baseHeight = card.variants.N.height;
|
||||
|
||||
const geometry = new THREE.PlaneGeometry(baseWidth, baseHeight);
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
transparent: true,
|
||||
opacity: 0.8,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
const floatingTile = new THREE.Mesh(geometry, material);
|
||||
|
||||
floatingTile.rotation.x = -Math.PI / 2;
|
||||
|
||||
// Apply Z rotation
|
||||
const rotMap = { 'N': 0, 'E': 1, 'S': 2, 'W': 3 };
|
||||
const r = rotMap[rotation] !== undefined ? rotMap[rotation] : 0;
|
||||
floatingTile.rotation.z = -r * (Math.PI / 2);
|
||||
|
||||
console.log(`[Preview] Rotation: ${rotation}, Center: (${cx}, ${cy})`);
|
||||
|
||||
floatingTile.position.set(cx, 3, -cy);
|
||||
this.previewGroup.add(floatingTile);
|
||||
});
|
||||
}
|
||||
|
||||
// 2. GROUND PROJECTION (Green/Red)
|
||||
const projectionColor = isValid ? 0x00ff00 : 0xff0000;
|
||||
cells.forEach(cell => {
|
||||
const geometry = new THREE.PlaneGeometry(0.95, 0.95);
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
color: projectionColor,
|
||||
transparent: true,
|
||||
opacity: 0.5,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
const projection = new THREE.Mesh(geometry, material);
|
||||
projection.rotation.x = -Math.PI / 2;
|
||||
projection.position.set(cell.x, 0.02, -cell.y);
|
||||
this.projectionGroup.add(projection);
|
||||
});
|
||||
}
|
||||
|
||||
hidePlacementPreview() {
|
||||
if (this.previewGroup) {
|
||||
this.previewGroup.clear();
|
||||
}
|
||||
if (this.projectionGroup) {
|
||||
this.projectionGroup.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user