fixes
This commit is contained in:
+7
-17
@@ -1,12 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive } from 'vue'
|
import { computed, reactive } from 'vue'
|
||||||
import BlockCanvas from './components/BlockCanvas.vue'
|
import BlockCanvas from './components/BlockCanvas.vue'
|
||||||
import { generateShape, summarize, type ShapeMode } from './composables/useShapeGenerator'
|
import { generateShape, summarize } from './composables/useShapeGenerator'
|
||||||
|
|
||||||
interface FormState {
|
interface FormState {
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
mode: ShapeMode
|
|
||||||
useSlabs: boolean
|
useSlabs: boolean
|
||||||
useStairs: boolean
|
useStairs: boolean
|
||||||
cellSize: number
|
cellSize: number
|
||||||
@@ -15,7 +14,6 @@ interface FormState {
|
|||||||
const form = reactive<FormState>({
|
const form = reactive<FormState>({
|
||||||
width: 12,
|
width: 12,
|
||||||
height: 8,
|
height: 8,
|
||||||
mode: 'arch',
|
|
||||||
useSlabs: true,
|
useSlabs: true,
|
||||||
useStairs: true,
|
useStairs: true,
|
||||||
cellSize: 20,
|
cellSize: 20,
|
||||||
@@ -35,7 +33,6 @@ const shape = computed(() => {
|
|||||||
return generateShape({
|
return generateShape({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
mode: form.mode,
|
|
||||||
useSlabs: form.useSlabs,
|
useSlabs: form.useSlabs,
|
||||||
useStairs: form.useStairs,
|
useStairs: form.useStairs,
|
||||||
})
|
})
|
||||||
@@ -47,12 +44,13 @@ const stats = computed(() => summarize(shape.value))
|
|||||||
<template>
|
<template>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<header>
|
<header>
|
||||||
<h1>Minecraft Arch / Ellipse Generator</h1>
|
<h1>Minecraft Arch Generator</h1>
|
||||||
<p class="subtitle">
|
<p class="subtitle">
|
||||||
Enter a width and height (in blocks) for the wall panel. An arch or
|
Enter a width and height (in blocks) for the wall panel. An arch
|
||||||
oval opening is cut out of it, smoothed with slabs (half-block steps)
|
opening is cut out of it from the ground up, smoothed with slabs
|
||||||
and stairs (beveled corners) instead of a fully jagged hole outline.
|
(half-block steps) and stairs (beveled corners) instead of a fully
|
||||||
The result is the wall blocks you need to place, not the opening.
|
jagged hole outline. The result is the wall blocks you need to
|
||||||
|
place, not the opening.
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</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" />
|
<input type="number" v-model.number="form.height" :min="MIN_SIZE" :max="MAX_SIZE" />
|
||||||
</label>
|
</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">
|
<label class="checkbox">
|
||||||
<input type="checkbox" v-model="form.useSlabs" />
|
<input type="checkbox" v-model="form.useSlabs" />
|
||||||
Use slabs (half-block smoothing)
|
Use slabs (half-block smoothing)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Builds a Minecraft elevation-view (front-facing) block list for a wall
|
// 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
|
// 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
|
// 2x2 grid) against the true ellipse boundary, so the resulting shape is
|
||||||
@@ -41,12 +41,9 @@ export interface Shape {
|
|||||||
columns: Column[]
|
columns: Column[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ShapeMode = 'arch' | 'oval'
|
|
||||||
|
|
||||||
export interface GenerateOptions {
|
export interface GenerateOptions {
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
mode: ShapeMode
|
|
||||||
useSlabs: boolean
|
useSlabs: boolean
|
||||||
useStairs: 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
|
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. */
|
/** 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, mode: ShapeMode, height: number): number {
|
function rowCenterY(r: number): number {
|
||||||
return mode === 'oval' ? r - (height - 1) / 2 : r + 0.5
|
return r + 0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
function classifyCell(tl: boolean, tr: boolean, bl: boolean, br: boolean, useSlabs: boolean, useStairs: boolean): Cell | null {
|
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 {
|
export function generateShape(opts: GenerateOptions): Shape {
|
||||||
const width = Math.max(1, Math.round(opts.width))
|
const width = Math.max(1, Math.round(opts.width))
|
||||||
const height = Math.max(1, Math.round(opts.height))
|
const height = Math.max(1, Math.round(opts.height))
|
||||||
const { mode, useSlabs, useStairs } = opts
|
const { useSlabs, useStairs } = opts
|
||||||
|
|
||||||
const a = width / 2
|
const a = width / 2
|
||||||
const b = mode === 'oval' ? height / 2 : height
|
const b = height
|
||||||
const centerOffsetX = (width - 1) / 2
|
const centerOffsetX = (width - 1) / 2
|
||||||
const useSubSampling = useSlabs || useStairs
|
const useSubSampling = useSlabs || useStairs
|
||||||
|
|
||||||
@@ -98,7 +95,7 @@ export function generateShape(opts: GenerateOptions): Shape {
|
|||||||
const col: Column = []
|
const col: Column = []
|
||||||
|
|
||||||
for (let r = 0; r < height; r++) {
|
for (let r = 0; r < height; r++) {
|
||||||
const cy = rowCenterY(r, mode, height)
|
const cy = rowCenterY(r)
|
||||||
|
|
||||||
if (!useSubSampling) {
|
if (!useSubSampling) {
|
||||||
col.push(insideOpening(cx, cy, a, b) ? null : { type: 'full' })
|
col.push(insideOpening(cx, cy, a, b) ? null : { type: 'full' })
|
||||||
|
|||||||
Reference in New Issue
Block a user