Skip to content

Commit cb3c67f

Browse files
authored
refactor(voxel-renderer): implement inverted shape using rotation flipY (#235)
1 parent 09a961c commit cb3c67f

File tree

20 files changed

+200
-623
lines changed

20 files changed

+200
-623
lines changed

.changeset/eleven-tires-doubt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@jolly-pixel/voxel.renderer": minor
3+
---
4+
5+
Implement inverted shape using flipY rotatation

packages/voxel-renderer/docs/Blocks.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,11 @@ type BlockShapeID =
5050
| "poleZ"
5151
| "poleCross"
5252
| "ramp"
53-
| "rampFlip"
5453
| "rampCornerInner"
5554
| "rampCornerOuter"
56-
| "rampCornerInnerFlip"
57-
| "rampCornerOuterFlip"
5855
| "stair"
5956
| "stairCornerInner"
6057
| "stairCornerOuter"
61-
| "stairFlip"
62-
| "stairCornerInnerFlip"
63-
| "stairCornerOuterFlip"
6458
| (string & {}); // custom shapes registered at runtime
6559
```
6660

packages/voxel-renderer/docs/BuiltInShapes.md

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,8 @@ All ramp shapes use **collisionHint**: [trimesh](./Collision.md).
4949
| Shape ID | Occludes |
5050
|---:|---|
5151
| `ramp` | `-Y`, `+Z` |
52-
| `rampFlip` | `+Y`, `-Y`, `+Z` |
5352
| `rampCornerInner` | `-Y`, `+Z`, `+X` |
5453
| `rampCornerOuter` | `-Y` |
55-
| `rampCornerInnerFlip` | `+Y`, `+Z`, `+X` |
56-
| `rampCornerOuterFlip` | `+Y` |
5754

5855
### Stairs
5956

@@ -64,9 +61,32 @@ All stair shapes use **collisionHint**: [trimesh](./Collision.md).
6461
| `stair` | `-Y`, `+Z` |
6562
| `stairCornerInner` | `-Y`, `+Z`, `+X` |
6663
| `stairCornerOuter` | `-Y` |
67-
| `stairFlip` | `+Y`, `+Z` |
68-
| `stairCornerInnerFlip` | `+Y`, `+Z`, `+X` |
69-
| `stairCornerOuterFlip` | `+Y` |
64+
65+
### Inverted / Upside-Down Shapes (`flipY`)
66+
67+
Ceiling ramps, inverted stairs, and similar shapes are produced by setting `flipY: true`
68+
on any voxel rather than using a dedicated shape class. `flipY` mirrors the block geometry
69+
around `y = 0.5`, reverses face winding to preserve correct lighting, and swaps
70+
the `+Y`/`-Y` occlusion directions so face culling against neighbours remains accurate.
71+
72+
```ts
73+
// Ceiling ramp — same geometry as "ramp" but mounted upside-down
74+
vr.setVoxel("Ceiling", {
75+
position: { x: 2, y: 4, z: 0 },
76+
blockId: myRampBlock,
77+
flipY: true
78+
});
79+
80+
// Inverted inner-corner stair
81+
vr.setVoxel("Ceiling", {
82+
position: { x: 3, y: 4, z: 0 },
83+
blockId: myStairBlock,
84+
rotation: VoxelRotation.CW90,
85+
flipY: true
86+
});
87+
```
88+
89+
`flipY` can be combined freely with `rotation`, `flipX`, and `flipZ`.
7090

7191
---
7292

packages/voxel-renderer/docs/VoxelRenderer.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const vr = actor.addComponentAndGet(VoxelRenderer, {
1010
{
1111
id: 1,
1212
name: "Grass",
13-
shapeId: "fullCube",
13+
shapeId: "cube",
1414
collidable: true,
1515
faceTextures: {},
1616
defaultTexture: {
@@ -40,6 +40,12 @@ vr.setVoxel("Ground", {
4040
flipZ: false
4141
});
4242

43+
vr.setVoxel("Ground", {
44+
position: { x: 2, y: 0, z: 0 },
45+
blockId: 1,
46+
flipY: true
47+
});
48+
4349
const entry = vr.getVoxel({
4450
x: 0, y: 0, z: 0
4551
});
@@ -208,6 +214,8 @@ interface VoxelSetOptions {
208214
flipX?: boolean;
209215
/** Mirror the block on the Z axis. Default: `false`. */
210216
flipZ?: boolean;
217+
/** Mirror the block geometry around y = 0.5 (upside-down). */
218+
flipY?: boolean;
211219
}
212220
```
213221

packages/voxel-renderer/examples/scripts/demo-shapes.ts

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,14 @@ import { PoleY } from "../../src/blocks/shapes/PoleY.ts";
1919
import { Pole } from "../../src/blocks/shapes/Pole.ts";
2020
import { PoleCross } from "../../src/blocks/shapes/PoleCross.ts";
2121
import { Ramp } from "../../src/blocks/shapes/Ramp.ts";
22-
import { RampFlip } from "../../src/blocks/shapes/RampFlip.ts";
2322
import {
2423
RampCornerInner,
25-
RampCornerOuter,
26-
RampCornerInnerFlip,
27-
RampCornerOuterFlip
24+
RampCornerOuter
2825
} from "../../src/blocks/shapes/RampCorner.ts";
2926
import {
3027
Stair,
3128
StairCornerInner,
32-
StairCornerOuter,
33-
StairFlip,
34-
StairCornerInnerFlip,
35-
StairCornerOuterFlip
29+
StairCornerOuter
3630
} from "../../src/blocks/shapes/Stair.ts";
3731

3832
// CONSTANTS
@@ -91,11 +85,6 @@ const kShapes: ShapeEntry[] = [
9185
label: "ramp",
9286
color: "#f5a623"
9387
},
94-
{
95-
shape: new RampFlip(),
96-
label: "rampFlip",
97-
color: "#f9a825"
98-
},
9988
{
10089
shape: new RampCornerInner(),
10190
label: "rampCornerInner",
@@ -106,16 +95,6 @@ const kShapes: ShapeEntry[] = [
10695
label: "rampCornerOuter",
10796
color: "#9c27b0"
10897
},
109-
{
110-
shape: new RampCornerInnerFlip(),
111-
label: "rampCornerInnerFlip",
112-
color: "#c62828"
113-
},
114-
{
115-
shape: new RampCornerOuterFlip(),
116-
label: "rampCornerOuterFlip",
117-
color: "#6a1b9a"
118-
},
11998

12099
// ── Stair ───────────────────────────────────────────────────────────────────
121100
{
@@ -132,21 +111,6 @@ const kShapes: ShapeEntry[] = [
132111
shape: new StairCornerOuter(),
133112
label: "stairCornerOuter",
134113
color: "#00897b"
135-
},
136-
{
137-
shape: new StairFlip(),
138-
label: "stairFlip",
139-
color: "#3949ab"
140-
},
141-
{
142-
shape: new StairCornerInnerFlip(),
143-
label: "stairCornerInnerFlip",
144-
color: "#bf360c"
145-
},
146-
{
147-
shape: new StairCornerOuterFlip(),
148-
label: "stairCornerOuterFlip",
149-
color: "#00695c"
150114
}
151115
];
152116

packages/voxel-renderer/src/VoxelRenderer.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ export interface VoxelSetOptions {
7474
flipX?: boolean;
7575
/** Mirror the block around z = 0.5. Default: false */
7676
flipZ?: boolean;
77+
/** Mirror the block around y = 0.5. Default: false */
78+
flipY?: boolean;
7779
}
7880

7981
export interface VoxelRemoveOptions {
@@ -316,9 +318,10 @@ export class VoxelRenderer extends ActorComponent {
316318
blockId,
317319
rotation = 0,
318320
flipX = false,
319-
flipZ = false
321+
flipZ = false,
322+
flipY = false
320323
} = options;
321-
const transform = packTransform(rotation, flipX, flipZ);
324+
const transform = packTransform(rotation, flipX, flipZ, flipY);
322325

323326
this.world.setVoxelAt(
324327
layerName,
@@ -328,7 +331,7 @@ export class VoxelRenderer extends ActorComponent {
328331
this.#onLayerUpdated?.({
329332
action: "voxel-set",
330333
layerName,
331-
metadata: { position, blockId, rotation, flipX, flipZ }
334+
metadata: { position, blockId, rotation, flipX, flipZ, flipY }
332335
});
333336
}
334337

@@ -353,12 +356,13 @@ export class VoxelRenderer extends ActorComponent {
353356
blockId,
354357
rotation = 0,
355358
flipX = false,
356-
flipZ = false
359+
flipZ = false,
360+
flipY = false
357361
} of entries) {
358362
this.world.setVoxelAt(
359363
layerName,
360364
position,
361-
{ blockId, transform: packTransform(rotation, flipX, flipZ) }
365+
{ blockId, transform: packTransform(rotation, flipX, flipZ, flipY) }
362366
);
363367
}
364368
}

packages/voxel-renderer/src/blocks/BlockShape.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,11 @@ export type BlockShapeID =
3131
| "poleZ"
3232
| "poleCross"
3333
| "ramp"
34-
| "rampFlip"
3534
| "rampCornerInner"
3635
| "rampCornerOuter"
37-
| "rampCornerInnerFlip"
38-
| "rampCornerOuterFlip"
3936
| "stair"
4037
| "stairCornerInner"
4138
| "stairCornerOuter"
42-
| "stairFlip"
43-
| "stairCornerInnerFlip"
44-
| "stairCornerOuterFlip"
4539
| (string & {});
4640

4741
/**

packages/voxel-renderer/src/blocks/BlockShapeRegistry.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,14 @@ import type { BlockShape } from "./BlockShape.ts";
33
import { Cube } from "./shapes/Cube.ts";
44
import { Slab } from "./shapes/Slab.ts";
55
import { Ramp } from "./shapes/Ramp.ts";
6-
import { RampCornerInner, RampCornerOuter, RampCornerInnerFlip, RampCornerOuterFlip } from "./shapes/RampCorner.ts";
6+
import { RampCornerInner, RampCornerOuter } from "./shapes/RampCorner.ts";
77
import { PoleY } from "./shapes/PoleY.ts";
88
import { Pole } from "./shapes/Pole.ts";
99
import { PoleCross } from "./shapes/PoleCross.ts";
10-
import { RampFlip } from "./shapes/RampFlip.ts";
1110
import {
1211
Stair,
1312
StairCornerInner,
14-
StairCornerOuter,
15-
StairFlip,
16-
StairCornerInnerFlip,
17-
StairCornerOuterFlip
13+
StairCornerOuter
1814
} from "./shapes/Stair.ts";
1915

2016
/**
@@ -56,17 +52,11 @@ export class BlockShapeRegistry {
5652
.register(new Pole("z"))
5753
.register(new PoleCross())
5854
.register(new Ramp())
59-
.register(new RampFlip())
6055
.register(new RampCornerInner())
6156
.register(new RampCornerOuter())
62-
.register(new RampCornerInnerFlip())
63-
.register(new RampCornerOuterFlip())
6457
.register(new Stair())
6558
.register(new StairCornerInner())
66-
.register(new StairCornerOuter())
67-
.register(new StairFlip())
68-
.register(new StairCornerInnerFlip())
69-
.register(new StairCornerOuterFlip());
59+
.register(new StairCornerOuter());
7060

7161
return registry;
7262
}

packages/voxel-renderer/src/blocks/shapes/RampCorner.ts

Lines changed: 0 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import type {
77
FaceDefinition
88
} from "../BlockShape.ts";
99
import {
10-
type Vec2,
11-
type Vec3,
1210
FACE
1311
} from "../../utils/math.ts";
1412
import {
@@ -174,79 +172,3 @@ export class RampCornerOuter implements BlockShape {
174172
}
175173
}
176174

177-
// -- Reverse variants (Y-flipped) -------------------------------------------
178-
179-
function yFlipFace(fd: FaceDefinition): FaceDefinition {
180-
const flippedAndReversed = fd.vertices
181-
.map(([x, y, z]) => [x, 1 - y, z] as Vec3)
182-
.reverse() as Vec3[];
183-
const reversedUvs = [...fd.uvs].reverse() as Vec2[];
184-
185-
let flippedFace: FACE;
186-
if (fd.face === FACE.NegY) {
187-
flippedFace = FACE.PosY;
188-
}
189-
else if (fd.face === FACE.PosY) {
190-
flippedFace = FACE.NegY;
191-
}
192-
else {
193-
flippedFace = fd.face;
194-
}
195-
196-
const flippedNormal: Vec3 = [fd.normal[0], -fd.normal[1], fd.normal[2]];
197-
198-
return { face: flippedFace, normal: flippedNormal, vertices: flippedAndReversed, uvs: reversedUvs };
199-
}
200-
201-
const kRampCornerInnerFlipFaces = new RampCornerInner().faces.map(yFlipFace);
202-
const kRampCornerOuterFlipFaces = new RampCornerOuter().faces.map(yFlipFace);
203-
204-
/**
205-
* RampCornerInnerFlip — Y-flipped RampCornerInner (hangs from ceiling).
206-
* Flat ceiling at PosY, full walls PosZ + PosX, diagonal slope descends downward.
207-
*
208-
* Occludes: PosY, PosZ, PosX.
209-
*/
210-
export class RampCornerInnerFlip implements BlockShape {
211-
readonly id: BlockShapeID;
212-
readonly collisionHint: BlockCollisionHint = "trimesh";
213-
214-
constructor(
215-
id: BlockShapeID = "rampCornerInnerFlip"
216-
) {
217-
this.id = id;
218-
}
219-
220-
readonly faces: readonly FaceDefinition[] = kRampCornerInnerFlipFaces;
221-
222-
occludes(
223-
face: FACE
224-
): boolean {
225-
return face === FACE.PosY || face === FACE.PosZ || face === FACE.PosX;
226-
}
227-
}
228-
229-
/**
230-
* RampCornerOuterFlip — Y-flipped RampCornerOuter (quarter-pyramid hanging from ceiling).
231-
* Flat ceiling at PosY, slope descends downward.
232-
*
233-
* Occludes: PosY only.
234-
*/
235-
export class RampCornerOuterFlip implements BlockShape {
236-
readonly id: BlockShapeID;
237-
readonly collisionHint: BlockCollisionHint = "trimesh";
238-
239-
constructor(
240-
id: BlockShapeID = "rampCornerOuterFlip"
241-
) {
242-
this.id = id;
243-
}
244-
245-
readonly faces: readonly FaceDefinition[] = kRampCornerOuterFlipFaces;
246-
247-
occludes(
248-
face: FACE
249-
): boolean {
250-
return face === FACE.PosY;
251-
}
252-
}

0 commit comments

Comments
 (0)