This commit is contained in:
2026-08-04 02:06:32 +02:00
parent 00e9649afb
commit 4f8be3e56c
2 changed files with 14 additions and 27 deletions
+7 -17
View File
@@ -1,12 +1,11 @@
<script setup lang="ts">
import { computed, reactive } from 'vue'
import BlockCanvas from './components/BlockCanvas.vue'
import { generateShape, summarize, type ShapeMode } from './composables/useShapeGenerator'
import { generateShape, summarize } from './composables/useShapeGenerator'
interface FormState {
width: number
height: number
mode: ShapeMode
useSlabs: boolean
useStairs: boolean
cellSize: number
@@ -15,7 +14,6 @@ interface FormState {
const form = reactive<FormState>({
width: 12,
height: 8,
mode: 'arch',
useSlabs: true,
useStairs: true,
cellSize: 20,
@@ -35,7 +33,6 @@ const shape = computed(() => {
return generateShape({
width,
height,
mode: form.mode,
useSlabs: form.useSlabs,
useStairs: form.useStairs,
})
@@ -47,12 +44,13 @@ const stats = computed(() => summarize(shape.value))
<template>
<div class="page">
<header>
<h1>Minecraft Arch / Ellipse Generator</h1>
<h1>Minecraft Arch Generator</h1>
<p class="subtitle">
Enter a width and height (in blocks) for the wall panel. An arch or
oval opening is cut out of it, smoothed with slabs (half-block steps)
and stairs (beveled corners) instead of a fully jagged hole outline.
The result is the wall blocks you need to place, not the opening.
Enter a width and height (in blocks) for the wall panel. An arch
opening is cut out of it from the ground up, smoothed with slabs
(half-block steps) and stairs (beveled corners) instead of a fully
jagged hole outline. The result is the wall blocks you need to
place, not the opening.
</p>
</header>
@@ -68,14 +66,6 @@ const stats = computed(() => summarize(shape.value))
<input type="number" v-model.number="form.height" :min="MIN_SIZE" :max="MAX_SIZE" />
</label>
<label>
Opening shape
<select v-model="form.mode">
<option value="arch">Arch doorway (cut from the ground up)</option>
<option value="oval">Oval window (cut from the middle)</option>
</select>
</label>
<label class="checkbox">
<input type="checkbox" v-model="form.useSlabs" />
Use slabs (half-block smoothing)
+7 -10
View File
@@ -1,5 +1,5 @@
// Builds a Minecraft elevation-view (front-facing) block list for a wall
// panel with an arch- or oval-shaped opening cut out of it.
// panel with an arch-shaped opening cut out of it, grounded at the bottom.
//
// Each block is sub-sampled at 4 quarter-points (splitting the block into a
// 2x2 grid) against the true ellipse boundary, so the resulting shape is
@@ -41,12 +41,9 @@ export interface Shape {
columns: Column[]
}
export type ShapeMode = 'arch' | 'oval'
export interface GenerateOptions {
width: number
height: number
mode: ShapeMode
useSlabs: boolean
useStairs: boolean
}
@@ -56,9 +53,9 @@ function insideOpening(x: number, y: number, a: number, b: number): boolean {
return (x / a) ** 2 + (y / b) ** 2 <= 1
}
/** Continuous y-coordinate of a row's center: arch shapes sit on the ground (y=0 at the bottom edge), oval shapes are centered vertically. */
function rowCenterY(r: number, mode: ShapeMode, height: number): number {
return mode === 'oval' ? r - (height - 1) / 2 : r + 0.5
/** Continuous y-coordinate of a row's center; the arch sits on the ground, so y=0 is the bottom edge of the grid. */
function rowCenterY(r: number): number {
return r + 0.5
}
function classifyCell(tl: boolean, tr: boolean, bl: boolean, br: boolean, useSlabs: boolean, useStairs: boolean): Cell | null {
@@ -85,10 +82,10 @@ function classifyCell(tl: boolean, tr: boolean, bl: boolean, br: boolean, useSla
export function generateShape(opts: GenerateOptions): Shape {
const width = Math.max(1, Math.round(opts.width))
const height = Math.max(1, Math.round(opts.height))
const { mode, useSlabs, useStairs } = opts
const { useSlabs, useStairs } = opts
const a = width / 2
const b = mode === 'oval' ? height / 2 : height
const b = height
const centerOffsetX = (width - 1) / 2
const useSubSampling = useSlabs || useStairs
@@ -98,7 +95,7 @@ export function generateShape(opts: GenerateOptions): Shape {
const col: Column = []
for (let r = 0; r < height; r++) {
const cy = rowCenterY(r, mode, height)
const cy = rowCenterY(r)
if (!useSubSampling) {
col.push(insideOpening(cx, cy, a, b) ? null : { type: 'full' })