Add real door textures with sliding-see-through rendering
The raycaster's door geometry now takes the live `doors[]` state instead of a boolean flag, so partially-open door cells let render rays pass through the already-slid portion to show what's genuinely beyond, rather than painting a flat "socket" fill. `intersectDoorMidplane` returns a slide-shifted `textureX` so the door texture visibly translates into the wall as it opens. `WolfensteinView` samples the new `wolfenstein-doors` sheet the same way it does walls (source-texel column, vertical crop, multiply shading), falling back to the flat tan placeholder. Added the doors.png/.psd assets and a `sheets.doors` entry in the art manifest. Updated sprites.md to document the new sheet, the see-through behavior, and that this stays render-only (gameplay raycasts are unaffected).
This commit is contained in:
parent
a336376a93
commit
f6081de0a8
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
|
@ -2,7 +2,8 @@
|
|||
"_readme": "Drop-in art manifest. Every entry has path: null until painted — the game renders procedurally via WolfensteinArt.js until then. Fill in a path and it lazy-loads automatically via assetManifest.js's wolfenstein entry; no code changes needed. sheets.walls expects a 4-column strip (one column per wall type in WolfensteinArt.WALL_COLORS) so WolfensteinView's per-column drawImage can sample a texture-x slice per wall type.",
|
||||
"sheets": {
|
||||
"walls": { "key": "wolfenstein-walls", "path": "assets/images/wolfenstein/walls.png", "frameWidth": 64, "frameHeight": 64 },
|
||||
"guard": { "key": "wolfenstein-guard-sheet", "path": null, "frameWidth": 128, "frameHeight": 128 }
|
||||
"guard": { "key": "wolfenstein-guard-sheet", "path": null, "frameWidth": 128, "frameHeight": 128 },
|
||||
"doors": { "key": "wolfenstein-doors", "path": "assets/images/wolfenstein/doors.png", "frameWidth": 64, "frameHeight": 64 }
|
||||
},
|
||||
"artwork": [
|
||||
{ "key": "wolfenstein-item-pistol", "path": null },
|
||||
|
|
|
|||
|
|
@ -13,11 +13,14 @@
|
|||
// "exactly at the point dir was aimed at").
|
||||
//
|
||||
// Door cells get real recessed-mid-plane geometry (see intersectDoorMidplane)
|
||||
// when a caller opts in via `doorAware` — WolfensteinView's render raycast
|
||||
// does; collision/line-of-sight/bullet callers in WolfensteinLogic don't, so
|
||||
// a door stays a simple full-cell solid for gameplay purposes exactly as
|
||||
// before (see stepDoors' note on why passability only flips at slide===1).
|
||||
// This is a rendering-only enhancement, not a change to what's walkable.
|
||||
// when a caller opts in by passing `doors` (WolfensteinView's render raycast
|
||||
// does, with state.doors — the slide-open portion genuinely isn't a hit, so
|
||||
// the DDA carries on through to render whatever's actually beyond); a
|
||||
// collision/line-of-sight/bullet caller in WolfensteinLogic omits it, so a
|
||||
// door stays a simple full-cell solid for gameplay purposes exactly as
|
||||
// before (see stepDoors' note on why passability only flips at slide===1) —
|
||||
// this is rendering-only, not a change to what's walkable/shootable/visible
|
||||
// to AI, even though the player can now visually see further than that.
|
||||
|
||||
const DEFAULT_MAX_STEPS = 256;
|
||||
|
||||
|
|
@ -41,31 +44,41 @@ export function isWallCell(map, cellX, cellY) {
|
|||
* walled off), not the doors[] `orientation` field, which nothing actually
|
||||
* sets to anything but 'vertical'.
|
||||
*
|
||||
* Returns null when the ray's own trajectory would clear the cell sideways
|
||||
* (cross into a neighboring cell) before ever reaching that mid-plane — the
|
||||
* caller should then keep stepping the DDA as if this cell were transparent
|
||||
* rather than treat it as a miss; stepping on lets the DDA reach the actual
|
||||
* flanking wall cell and hit it at its own true (nearer, unrecessed)
|
||||
* distance, which is what makes that wall's inward face visible at grazing
|
||||
* angles — the two verticals of the "H," rendered as ordinary wall hits,
|
||||
* not anything faked here.
|
||||
* Returns null in two cases the caller treats identically — keep stepping
|
||||
* the DDA as if this cell were transparent, rather than a miss:
|
||||
* - the ray's own trajectory clears the cell sideways (crosses into a
|
||||
* neighboring cell) before ever reaching the mid-plane at all — letting
|
||||
* the DDA carry on reaches the actual flanking wall cell and hits it at
|
||||
* its own true (nearer, unrecessed) distance, which is what makes that
|
||||
* wall's inward face visible at grazing angles: the two verticals of the
|
||||
* "H," rendered as ordinary wall hits, not anything faked here;
|
||||
* - the plane crossing falls in the portion of the door that has already
|
||||
* slid open (`along >= slide`, the panel having translated `slide`
|
||||
* cell-widths into its pocket) — there's genuinely nothing solid there
|
||||
* now, so the DDA carries on to whatever's actually beyond the doorway.
|
||||
*
|
||||
* For the still-covered portion, `textureX` comes back pre-shifted by
|
||||
* `-slide` — the same panel-translation math, so sampling straight from it
|
||||
* (see WolfensteinView._drawDoorColumn) makes the door texture visibly
|
||||
* slide sideways into the wall as it opens, not just get cropped in place.
|
||||
*/
|
||||
function intersectDoorMidplane(map, x, y, dirX, dirY, cellX, cellY) {
|
||||
function intersectDoorMidplane(map, x, y, dirX, dirY, cellX, cellY, slide) {
|
||||
const blocksEW = isWallCell(map, cellX, cellY - 1) && isWallCell(map, cellX, cellY + 1);
|
||||
let t, along;
|
||||
if (blocksEW) {
|
||||
if (Math.abs(dirX) < 1e-9) return null;
|
||||
const t = (cellX + 0.5 - x) / dirX;
|
||||
t = (cellX + 0.5 - x) / dirX;
|
||||
if (t <= 0) return null;
|
||||
const along = y + t * dirY - cellY;
|
||||
if (along < 0 || along > 1) return null;
|
||||
return { perpDist: t, side: 0, textureX: along };
|
||||
}
|
||||
along = y + t * dirY - cellY;
|
||||
} else {
|
||||
if (Math.abs(dirY) < 1e-9) return null;
|
||||
const t = (cellY + 0.5 - y) / dirY;
|
||||
t = (cellY + 0.5 - y) / dirY;
|
||||
if (t <= 0) return null;
|
||||
const along = x + t * dirX - cellX;
|
||||
if (along < 0 || along > 1) return null;
|
||||
return { perpDist: t, side: 1, textureX: along };
|
||||
along = x + t * dirX - cellX;
|
||||
}
|
||||
if (along < 0 || along > 1) return null; // clears the cell sideways
|
||||
if (along < slide) return null; // this point has already slid open (opens from along=0 outward)
|
||||
return { perpDist: t, side: blocksEW ? 0 : 1, textureX: along - slide };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -73,11 +86,12 @@ function intersectDoorMidplane(map, x, y, dirX, dirY, cellX, cellY) {
|
|||
* length; distances/`perpDist` come out scaled to that vector's own length —
|
||||
* until it exits the first solid cell. Returns null if it runs off the map
|
||||
* edge or exceeds maxSteps without a hit (should never happen inside a
|
||||
* validated closed level). `doorAware` opts into recessed door-plane
|
||||
* geometry (see intersectDoorMidplane) instead of treating a door cell as an
|
||||
* ordinary flush solid.
|
||||
* validated closed level). Passing `doors` (an array of `{x, y, slide}`, as
|
||||
* on WolfensteinLogic's sim state) opts a door cell into recessed,
|
||||
* partially-passable mid-plane geometry (see intersectDoorMidplane) instead
|
||||
* of treating it as an ordinary flush full-cell solid.
|
||||
*/
|
||||
export function castRay(map, x, y, dirX, dirY, maxSteps = DEFAULT_MAX_STEPS, doorAware = false) {
|
||||
export function castRay(map, x, y, dirX, dirY, maxSteps = DEFAULT_MAX_STEPS, doors = null) {
|
||||
let mapX = Math.floor(x);
|
||||
let mapY = Math.floor(y);
|
||||
|
||||
|
|
@ -100,10 +114,11 @@ export function castRay(map, x, y, dirX, dirY, maxSteps = DEFAULT_MAX_STEPS, doo
|
|||
const wallType = map.walls[mapY][mapX];
|
||||
if (wallType === 0) continue;
|
||||
|
||||
if (doorAware && wallType === DOOR_WALL_TYPE) {
|
||||
const doorHit = intersectDoorMidplane(map, x, y, dirX, dirY, mapX, mapY);
|
||||
if (doors && wallType === DOOR_WALL_TYPE) {
|
||||
const door = doors.find((d) => d.x === mapX && d.y === mapY);
|
||||
const doorHit = intersectDoorMidplane(map, x, y, dirX, dirY, mapX, mapY, door?.slide ?? 0);
|
||||
if (doorHit) return { ...doorHit, mapX, mapY, wallType };
|
||||
continue; // ray grazes past this door cell without reaching its mid-plane
|
||||
continue; // ray grazes past this door cell, or passes through its already-open portion
|
||||
}
|
||||
|
||||
const perpDist = side === 0 ? (sideDistX - deltaDistX) : (sideDistY - deltaDistY);
|
||||
|
|
@ -125,8 +140,8 @@ export function makeCamera(x, y, angle, fov) {
|
|||
return { x, y, angle, dirX, dirY, planeX: -dirY * planeScale, planeY: dirX * planeScale };
|
||||
}
|
||||
|
||||
/** One castRay per screen column, camera-space (fisheye-free). `doorAware` — see castRay — is what WolfensteinView passes to get recessed door geometry. */
|
||||
export function castColumns(map, camera, numColumns, doorAware = false) {
|
||||
/** One castRay per screen column, camera-space (fisheye-free). `doors` — see castRay — is what WolfensteinView passes (state.doors) to get recessed, slide-aware door geometry. */
|
||||
export function castColumns(map, camera, numColumns, doors = null) {
|
||||
const { x, y, dirX, dirY, planeX, planeY } = camera;
|
||||
const maxSteps = fullMapSteps(map);
|
||||
const out = new Array(numColumns);
|
||||
|
|
@ -134,7 +149,7 @@ export function castColumns(map, camera, numColumns, doorAware = false) {
|
|||
const cameraX = (2 * col) / numColumns - 1;
|
||||
const rdx = dirX + planeX * cameraX;
|
||||
const rdy = dirY + planeY * cameraX;
|
||||
out[col] = castRay(map, x, y, rdx, rdy, maxSteps, doorAware);
|
||||
out[col] = castRay(map, x, y, rdx, rdy, maxSteps, doors);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,17 +27,12 @@ export const VIEW_H = 940;
|
|||
export const NUM_COLUMNS = 480;
|
||||
|
||||
// wallType -> frame index in the `wolfenstein-walls` sheet, matching
|
||||
// WolfensteinArt.WALL_COLORS' order. No entry for type 9 (door) — doors are
|
||||
// never texture-sampled, they get their own recessed-slide treatment in
|
||||
// _drawDoorColumn regardless of whether painted wall art exists.
|
||||
// WolfensteinArt.WALL_COLORS' order. No entry for type 9 (door) — doors
|
||||
// sample their own `wolfenstein-doors` sheet (frame 0) via _drawDoorColumn,
|
||||
// which also handles their recessed/sliding geometry — not part of the
|
||||
// generic per-column wall-texture path below at all.
|
||||
const WALL_FRAME = { 1: 0, 2: 1, 3: 2, 4: 3 };
|
||||
|
||||
// A dark, unshaded fill for the portion of a door that's already slid past —
|
||||
// meant to read as a shadowed pocket the panel has receded into, not more
|
||||
// wall. Kept flat (no side/fog shading) so it reads as a distinct material,
|
||||
// not just a darker version of the door itself.
|
||||
const DOOR_SOCKET_COLOR = '#141018';
|
||||
|
||||
export default class WolfensteinView {
|
||||
constructor(scene, rules) {
|
||||
this.scene = scene;
|
||||
|
|
@ -57,6 +52,8 @@ export default class WolfensteinView {
|
|||
|
||||
this.wallTexture = scene.textures.exists('wolfenstein-walls')
|
||||
? scene.textures.get('wolfenstein-walls') : null;
|
||||
this.doorTexture = scene.textures.exists('wolfenstein-doors')
|
||||
? scene.textures.get('wolfenstein-doors') : null;
|
||||
}
|
||||
|
||||
render(state, camera) {
|
||||
|
|
@ -71,7 +68,7 @@ export default class WolfensteinView {
|
|||
ctx.fillStyle = '#4a4a4a';
|
||||
ctx.fillRect(0, VIEW_H / 2, VIEW_W, VIEW_H / 2);
|
||||
|
||||
const cols = castColumns(map, camera, NUM_COLUMNS, true);
|
||||
const cols = castColumns(map, camera, NUM_COLUMNS, doors);
|
||||
for (let i = 0; i < NUM_COLUMNS; i++) {
|
||||
const hit = cols[i];
|
||||
this.depthBuffer[i] = hit ? hit.perpDist : Infinity;
|
||||
|
|
@ -88,7 +85,7 @@ export default class WolfensteinView {
|
|||
const h = Math.ceil(drawEnd - drawStart);
|
||||
|
||||
if (hit.wallType === DOOR_WALL_TYPE) {
|
||||
this._drawDoorColumn(ctx, x, y, w, h, hit, doors);
|
||||
this._drawDoorColumn(ctx, x, y, w, h, hit);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -117,29 +114,43 @@ export default class WolfensteinView {
|
|||
}
|
||||
|
||||
/**
|
||||
* hit.perpDist already IS the recessed mid-plane distance here — the
|
||||
* render raycast opts into door-aware geometry (see castColumns(...,
|
||||
* true) above and WolfensteinRaycaster's intersectDoorMidplane), so this
|
||||
* column's (x,y,w,h), computed by the caller exactly like any other wall,
|
||||
* is already correctly smaller/farther than a flush wall would be. A
|
||||
* grazing column whose ray clears the door cell sideways instead hits the
|
||||
* real flanking wall cell at ITS true distance via the normal wall path
|
||||
* above — that's what makes the wall's inward face visible next to the
|
||||
* door, the two verticals of the "H." Nothing extra to fake here.
|
||||
*
|
||||
* `textureX` is where this column's ray crosses the door's face (0..1
|
||||
* along it, computed at the mid-plane); a door slides such that the low-
|
||||
* textureX edge stays put and the far edge recedes first, so as `slide`
|
||||
* climbs, columns flip from "still there" to "already receded" in
|
||||
* textureX order — the door visibly sliding sideways into its socket.
|
||||
* By the time a column gets here, the raycaster (see castColumns(...,
|
||||
* doors) above and WolfensteinRaycaster's intersectDoorMidplane) has
|
||||
* already resolved everything door-specific: this hit only exists because
|
||||
* that particular point on the door is still covering the doorway, at its
|
||||
* true recessed mid-plane distance — an already-open point along the same
|
||||
* ray instead passes straight through to whatever's actually beyond
|
||||
* (rendered by the ordinary wall path, not this method at all), which is
|
||||
* how you end up able to see past the door as it slides open. `textureX`
|
||||
* comes back already shifted by `-slide`, so sampling it straight makes
|
||||
* the door texture visibly slide sideways into the wall, not just crop in
|
||||
* place. So this is really just "draw a wall column, but from the
|
||||
* `wolfenstein-doors` sheet" — same technique as the main wall path
|
||||
* (source-texel column, vertical crop for close-up magnification,
|
||||
* multiply-shaded), falling back to the flat placeholder tan if no
|
||||
* sheet is loaded.
|
||||
*/
|
||||
_drawDoorColumn(ctx, x, y, w, h, hit, doors) {
|
||||
const door = doors.find((d) => d.x === hit.mapX && d.y === hit.mapY);
|
||||
const visibleFrac = 1 - (door?.slide ?? 0);
|
||||
ctx.fillStyle = hit.textureX < visibleFrac
|
||||
? shadeColor(WALL_COLORS[DOOR_WALL_TYPE] ?? 0xb08040, hit)
|
||||
: DOOR_SOCKET_COLOR;
|
||||
_drawDoorColumn(ctx, x, y, w, h, hit) {
|
||||
const frame = this.doorTexture?.frames[0];
|
||||
if (!frame) {
|
||||
ctx.fillStyle = shadeColor(WALL_COLORS[DOOR_WALL_TYPE] ?? 0xb08040, hit);
|
||||
ctx.fillRect(x, y, w, h);
|
||||
return;
|
||||
}
|
||||
|
||||
const lineHeight = VIEW_H / hit.perpDist;
|
||||
const rawStart = -lineHeight / 2 + VIEW_H / 2;
|
||||
const rawEnd = lineHeight / 2 + VIEW_H / 2;
|
||||
const drawStart = Math.max(0, rawStart);
|
||||
const drawEnd = Math.min(VIEW_H, rawEnd);
|
||||
const srcX = frame.cutX + Math.min(frame.width - 1, Math.floor(hit.textureX * frame.width));
|
||||
const srcY = frame.cutY + ((drawStart - rawStart) / lineHeight) * frame.height;
|
||||
const srcH = ((drawEnd - drawStart) / lineHeight) * frame.height;
|
||||
ctx.drawImage(frame.source.image, srcX, srcY, 1, srcH, x, y, w, h);
|
||||
ctx.globalCompositeOperation = 'multiply';
|
||||
ctx.fillStyle = shadeGrey(hit);
|
||||
ctx.fillRect(x, y, w, h);
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
}
|
||||
|
||||
_drawSprites(state, camera) {
|
||||
|
|
|
|||
|
|
@ -13,28 +13,41 @@ loaded as a plain Phaser spritesheet (frames read left-to-right, top-to-bottom
|
|||
one 64×64 tile per wall type**, in `WolfensteinArt.WALL_COLORS` insertion
|
||||
order: frame 0 = type 1 (stone), frame 1 = type 2 (wood), frame 2 = type 3
|
||||
(blue-tile), frame 3 = type 4 (green-tile). Doors (`DOOR_WALL_TYPE`, type 9)
|
||||
are **not** part of this sheet at all — `WolfensteinView._drawDoorColumn`
|
||||
(as of 2026-08-21) handles type 9 as a special case before the texture path
|
||||
even runs, rendering the flat placeholder tan color (`WALL_COLORS[9]`) for
|
||||
whatever fraction of the door hasn't yet slid open, and a flat dark "socket"
|
||||
fill for the rest (see the sliding-door doc below) — so a 5th sheet frame
|
||||
wouldn't currently be read even if added. Painting a real door texture means
|
||||
teaching `_drawDoorColumn` to sample a frame the same way the wall path
|
||||
does, not just adding a 320×64 sheet.
|
||||
are **not** part of this sheet at all — see `sheets.doors` below.
|
||||
|
||||
### Sliding, recessed doors
|
||||
## Door textures — `sheets.doors`
|
||||
|
||||
`frameWidth: 64, frameHeight: 64`, same spritesheet convention as walls.
|
||||
**Wired up as of 2026-08-21: frame 0 is the one and only door type ("Normal"
|
||||
— the editor's Door category has no other option yet) and is fully live.**
|
||||
The current painted sheet is 256×256 (a 4×4 grid, 16 frames), of which only
|
||||
frame 0 is read; the other 15 sit unused until a second door type exists
|
||||
(matching how the editor's Door dropdown would need a second `option` in
|
||||
`WolfensteinEditor.js`'s `CATEGORIES`, with the new tool id mapped to a
|
||||
frame index the same way `WALL_FRAME` maps wall types today — no such
|
||||
mapping exists yet since there's only the one type). `WolfensteinView.js`
|
||||
grabs the texture once in the constructor (`this.doorTexture`) and
|
||||
`_drawDoorColumn` samples it exactly like the wall path samples
|
||||
`sheets.walls` (1px source-texel column, drawImage-stretched, same vertical
|
||||
source-crop for close-up magnification, same `multiply`-composited
|
||||
side/fog shading) — falls back to the flat placeholder tan if the sheet
|
||||
isn't loaded. See the sliding/recessed-door notes below for how that
|
||||
texture actually gets positioned in the scene (mid-cell plane, slide-driven
|
||||
visible fraction).
|
||||
|
||||
### Sliding, recessed, see-through doors
|
||||
|
||||
Doors animate open/closed (`WolfensteinLogic.js`'s `stepDoors`, `slide` field
|
||||
0→1 over `DOOR_SLIDE_MS`) rather than popping instantly, and the cell stays
|
||||
solid for *gameplay* (collision/LOS/bullets) through the entire animation —
|
||||
it only becomes passable once `slide` reaches exactly 1 (see stepDoors'
|
||||
comment for why). That's unrelated to the visual, though: doors also render
|
||||
recessed to the middle of the wall's depth — real geometry, not a flat-color
|
||||
trick — via `WolfensteinRaycaster.js`'s `intersectDoorMidplane`, which
|
||||
`WolfensteinView`'s render-only raycast opts into with `castColumns(...,
|
||||
true)` (the `doorAware` flag; every gameplay raycast in `WolfensteinLogic.js`
|
||||
omits it and still sees a door as an ordinary flush full-cell solid, so this
|
||||
never touches what's actually walkable/shootable).
|
||||
comment for why: no squeezing through a half-open door). That's unrelated to
|
||||
the visual, though, which is real mid-cell geometry, not a projection trick:
|
||||
`WolfensteinRaycaster.js`'s `intersectDoorMidplane`, opted into by passing
|
||||
`state.doors` to `castColumns`/`castRay` (every gameplay raycast in
|
||||
`WolfensteinLogic.js` omits it and still sees a door as an ordinary flush
|
||||
full-cell solid — this never touches what's actually walkable/shootable/
|
||||
visible to AI, only what the player's screen shows).
|
||||
|
||||
A door's blocking axis is inferred from geometry (which pair of neighbor
|
||||
cells is walled off) rather than the `doors[]` `orientation` field, which
|
||||
|
|
@ -42,25 +55,34 @@ nothing in the codebase actually sets to anything but `'vertical'`. Given
|
|||
that axis, the door plane sits at the cell's exact midpoint (e.g. `x =
|
||||
doorCellX + 0.5` for a door blocking east-west travel) instead of at the
|
||||
near face like a normal wall — a straight-on ray is `perpDist`-recessed by
|
||||
0.5 relative to where a flush wall would sit. Critically, a ray whose
|
||||
trajectory would clear the door cell sideways (cross into a neighboring
|
||||
cell) *before* reaching that mid-plane isn't given a fake hit at all —
|
||||
`castRay` just keeps stepping the DDA, so the ray goes on to hit the actual
|
||||
flanking wall cell at *its own* true, unrecessed distance. That's what
|
||||
produces the "H" shape: the door is the crossbar, visibly set back between
|
||||
two verticals that are ordinary wall hits, not anything faked in
|
||||
`_drawDoorColumn` — confirmed with a sweep of rays across a doorway (see
|
||||
git history around 2026-08-21 for the script) showing a symmetric run of
|
||||
door hits at the recessed distance, flanked on both sides by wall hits at
|
||||
their own, nearer distances, with no discontinuity at the transition.
|
||||
0.5 relative to where a flush wall would sit.
|
||||
|
||||
`_drawDoorColumn` itself is now simple: `hit.perpDist`/`(x,y,w,h)` already
|
||||
reflect the recessed mid-plane (computed by the caller exactly like any
|
||||
other wall), so it only has to pick door-texture-tan vs. dark socket color
|
||||
per column, using `hit.textureX` (now measured at the mid-plane, 0..1 along
|
||||
the door's face) against `1 - slide` — the same left-to-right slide-open
|
||||
wipe as before, just riding on correct geometry instead of a projection
|
||||
trick.
|
||||
Two things make a ray pass straight through a door cell instead of hitting
|
||||
it — both handled by `intersectDoorMidplane` returning `null`, which
|
||||
`castRay` treats as "keep stepping the DDA," not a miss:
|
||||
- the ray's own trajectory clears the cell sideways (crosses into a
|
||||
neighboring cell) before ever reaching the mid-plane — the DDA carries on
|
||||
to the real flanking wall cell, hit at *its own* true, unrecessed
|
||||
distance, which is what produces the "H" shape (door as the crossbar,
|
||||
visibly set back between two verticals that are ordinary wall hits, not
|
||||
anything faked in `_drawDoorColumn`) — confirmed with a sweep of rays
|
||||
across a doorway showing a symmetric run of door hits at the recessed
|
||||
distance flanked by wall hits at their own nearer distances, no
|
||||
discontinuity at the transition;
|
||||
- the crossing point has already slid open (`along < slide`, where `along`
|
||||
is the 0..1 position along the door's face and the panel is modeled as
|
||||
having physically translated `slide` cell-widths into its pocket, opening
|
||||
from `along = 0` outward) — the DDA carries on *past the door entirely*,
|
||||
so a column here renders whatever's genuinely beyond the doorway (through
|
||||
the ordinary wall path, at that room's own true distance), not a stand-in
|
||||
color — you can see into the next room as the door opens, exactly as far
|
||||
as the geometry actually allows.
|
||||
|
||||
For a still-covered point, the returned `textureX` is `along - slide`, not
|
||||
raw `along` — sampling that directly (`_drawDoorColumn`, same source-texel
|
||||
technique the wall path uses) is what makes the door *texture* visibly
|
||||
translate sideways into the wall as it opens, rather than the closed
|
||||
portion just being a static crop that shrinks in place.
|
||||
|
||||
**Now wired up** (as of 2026-08-21): `WolfensteinView._drawWalls` reads
|
||||
`hit.textureX` (the raycaster's fractional wall-face position) and
|
||||
|
|
|
|||
Loading…
Reference in New Issue