Iso diamond tiles
Cut a seamless texture into isometric ground tiles
- image
- ui_widget
- tiles
- atlas
- receipt
- tile_list
You generated a nice cobblestone texture. You want a game-ready isometric terrain tile set. LF_IsoDiamondTiles is the step between those two sentences, and it's a much better idea than hand-rotating squares in an image editor.
The insight is almost embarrassingly simple: an isometric diamond is a top-down square rotated 45 degrees and squashed 2:1. So if your source texture already tiles, every diamond you cut out of it tiles with every other one - and you can generate variants just by sampling at different offsets.
How it works
The node takes one seamless top-down texture, doubles it (so sampling wraps instead of clamping at the edges), and for each variant does a GPU-side affine grid sample: the diamond's pixel grid is transformed into texture space with that 45°/2:1 projection, plus a seeded random offset in U and V. Bilinear filtering, border padding, and an antialiased diamond coverage mask for the transparent corners.
That doubling is the whole trick. Because the source wraps, a diamond that runs off one edge continues on the other side - which is the property you need for tiles that sit next to each other in a game without a visible grid.
texture_scale controls how much of the texture one diamond spans along its diagonal. At the 0.5 default you get a clean readable tile; push it up and you're zooming into the texture, down and you're zooming out to more repetition per tile. Randomise seed and raise variants for variation - with variants at 1 you're just getting the same crop four times over in a different place, which is fine if that's the point.
The inputs and outputs that matter
image- one seamless top-down texture. Only the first image in a batch is used, so don't expect a batch of textures to come back as a batch of tile sets.tile_width/tile_height- default 128 × 64. That 2:1 is the isometric convention; deviate and you get a stretched diamond, which is occasionally what you want for a specific engine.variants- how many differently-offset diamonds to cut. Default 4.seed- the offsets are seeded, so the same seed gives the same tiles. Reproducible tile sets, which matters more than it sounds when you're shipping.texture_scale- 0.05 to 4, default 0.5.
Outputs: tiles (all variants as one RGBA batch at the requested size), atlas (every variant side by side in a single row - the thing you actually drag into an engine or a sprite sheet), tile_list (the same tiles as a list), and receipt - a JSON payload with tile geometry, the seeded offsets and the atlas dimensions. Keep the receipt. When a tile set looks wrong three days later, the seed and offsets are how you reproduce it.
How to install it
ComfyUI Manager → search LF Nodes → install → restart. Manual:
cd ComfyUI/custom_nodes
git clone https://github.com/lucafoscili/lf-nodes
python -m pip install -r lf-nodes/requirements.txt
Pip with ComfyUI's interpreter. The pack is 154 nodes wide and its requirements are correspondingly broad (torch, opencv, rembg, av, transformers), so a fresh install is heavier than the node you're after. Also: lucafoscili/comfyui-lf is the archived old repo - the live one is lf-nodes.
There's a shipped example worth running: the pack's Workflow Runner includes an Iso Ground Tiles card that chains this after LF_SeamlessTile, so a texture that doesn't tile yet gets fixed first. It's opt-in - set WORKFLOW_RUNNER_ENABLED=true before starting ComfyUI if you want to see it.
Common issues & troubleshooting
Visible grid lines between tiles. Your texture isn't actually seamless. The projection math assumes it wraps; if the source has a hard edge, every diamond carries that edge, just rotated. Feed it through LF_SeamlessTile first, or generate tileable with circular padding in the first place.
All variants look identical. They will, at low texture_scale with a very repetitive source - the offsets are there, but there's nothing different to sample. Raise variants and texture_scale together.
Corners are black instead of transparent. They shouldn't be - the tiles come out RGBA with an antialiased coverage mask - but check what you're saving with. A JPEG or a flattened PNG export kills the alpha, and then you get black diamonds in a grid.
The atlas is enormous. It's a single row: variants × tile_width wide. At 128px and 64 variants that's an 8192px image. Cut the variants, or split the atlas yourself.
It only processed one texture. Documented behavior - first image only. Run the node per texture.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | One seamless top-down texture; only the first image is used. | |
| tile_width | INT | 1288–1024 | Diamond tile width in pixels. |
| tile_height | INT | 644–1024 | Diamond tile height in pixels (half the width for 2:1). |
| variants | INT | 41–64 | How many differently offset diamonds to cut. |
| seed | INT | 420–2147483647 | Seed for the variant offsets. |
| texture_scale | FLOAT | 0.500.05–4 | Fraction of the texture one diamond spans along its diagonal. |
| ui_widgetopt | LF_MASONRY | [object Object] | — |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| tiles | IMAGE | RGBA diamond tiles, one per variant, at the requested tile size. |
| atlas | IMAGE | All variants side by side in one RGBA atlas row. |
| receipt | JSON | Receipt with tile geometry, variant offsets and atlas size. |
| tile_list | IMAGE | The same tiles as an authoritative list. |