mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-23 15:17:09 +00:00
Compare commits
2 Commits
39f4ecc504
...
feature-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a74be78c51 | ||
|
|
c84772e3a8 |
@@ -4,7 +4,7 @@ import { useState } from 'preact/hooks'
|
|||||||
import { useModel } from '../../hooks/index.js'
|
import { useModel } from '../../hooks/index.js'
|
||||||
import type { VersionId } from '../../services/index.js'
|
import type { VersionId } from '../../services/index.js'
|
||||||
import { checkVersion } from '../../services/index.js'
|
import { checkVersion } from '../../services/index.js'
|
||||||
import { BiomeSourcePreview, BlockStatePreview, DecoratorPreview, DensityFunctionPreview, LootTablePreview, ModelPreview, NoisePreview, NoiseSettingsPreview, StructureSetPreview } from '../previews/index.js'
|
import { BiomeSourcePreview, BlockStatePreview, DecoratorPreview, DensityFunctionPreview, FeaturePreview, LootTablePreview, ModelPreview, NoisePreview, NoiseSettingsPreview, StructureSetPreview } from '../previews/index.js'
|
||||||
|
|
||||||
export const HasPreview = ['loot_table', 'dimension', 'worldgen/density_function', 'worldgen/noise', 'worldgen/noise_settings', 'worldgen/configured_feature', 'worldgen/placed_feature', 'worldgen/structure_set', 'block_definition', 'model']
|
export const HasPreview = ['loot_table', 'dimension', 'worldgen/density_function', 'worldgen/noise', 'worldgen/noise_settings', 'worldgen/configured_feature', 'worldgen/placed_feature', 'worldgen/structure_set', 'block_definition', 'model']
|
||||||
|
|
||||||
@@ -50,6 +50,11 @@ export function PreviewPanel({ model, version, id, shown }: PreviewPanelProps) {
|
|||||||
return <DecoratorPreview {...{ model, version, shown, data }} />
|
return <DecoratorPreview {...{ model, version, shown, data }} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (id === 'worldgen/configured_feature' && checkVersion(version, '1.18')) {
|
||||||
|
console.log('FEATURE')
|
||||||
|
return <FeaturePreview {...{ model, version, shown, data }} />
|
||||||
|
}
|
||||||
|
|
||||||
if (id === 'worldgen/structure_set' && checkVersion(version, '1.19')) {
|
if (id === 'worldgen/structure_set' && checkVersion(version, '1.19')) {
|
||||||
return <StructureSetPreview {...{ model, version, shown, data }} />
|
return <StructureSetPreview {...{ model, version, shown, data }} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { DRAFT_PROJECT, useLocale, useProject, useVersion } from '../../contexts
|
|||||||
import { AsyncCancel, useActiveTimeout, useAsync, useModel, useSearchParam } from '../../hooks/index.js'
|
import { AsyncCancel, useActiveTimeout, useAsync, useModel, useSearchParam } from '../../hooks/index.js'
|
||||||
import { getOutput } from '../../schema/transformOutput.js'
|
import { getOutput } from '../../schema/transformOutput.js'
|
||||||
import type { VersionId } from '../../services/index.js'
|
import type { VersionId } from '../../services/index.js'
|
||||||
import { checkVersion, fetchPreset, getBlockStates, getCollections, getModel, getSnippet, shareSnippet } from '../../services/index.js'
|
import { fetchPreset, getBlockStates, getCollections, getModel, getSnippet, shareSnippet } from '../../services/index.js'
|
||||||
import { Ad, Btn, BtnMenu, ErrorPanel, FileCreation, FileRenaming, Footer, HasPreview, Octicon, PreviewPanel, ProjectCreation, ProjectDeletion, ProjectPanel, SearchList, SourcePanel, TextInput, Tree, VersionSwitcher } from '../index.js'
|
import { Ad, Btn, BtnMenu, ErrorPanel, FileCreation, FileRenaming, Footer, HasPreview, Octicon, PreviewPanel, ProjectCreation, ProjectDeletion, ProjectPanel, SearchList, SourcePanel, TextInput, Tree, VersionSwitcher } from '../index.js'
|
||||||
|
|
||||||
export const SHARE_KEY = 'share'
|
export const SHARE_KEY = 'share'
|
||||||
@@ -265,7 +265,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
|
|||||||
const [copyActive, copySuccess] = useActiveTimeout()
|
const [copyActive, copySuccess] = useActiveTimeout()
|
||||||
|
|
||||||
const [previewShown, setPreviewShown] = useState(Store.getPreviewPanelOpen() ?? window.innerWidth > 800)
|
const [previewShown, setPreviewShown] = useState(Store.getPreviewPanelOpen() ?? window.innerWidth > 800)
|
||||||
const hasPreview = HasPreview.includes(gen.id) && !(gen.id === 'worldgen/configured_feature' && checkVersion(version, '1.18'))
|
const hasPreview = HasPreview.includes(gen.id)
|
||||||
if (previewShown && !hasPreview) setPreviewShown(false)
|
if (previewShown && !hasPreview) setPreviewShown(false)
|
||||||
let actionsShown = 2
|
let actionsShown = 2
|
||||||
if (hasPreview) actionsShown += 1
|
if (hasPreview) actionsShown += 1
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { DataModel } from '@mcschema/core'
|
import { DataModel } from '@mcschema/core'
|
||||||
import type { BlockPos, ChunkPos, PerlinNoise, Random } from 'deepslate/worldgen'
|
import type { BlockPos, ChunkPos, PerlinNoise, Random } from 'deepslate/worldgen'
|
||||||
import type { Color } from '../../Utils.js'
|
import type { Color } from '../../Utils.js'
|
||||||
import { clamp, isObject, stringToColor } from '../../Utils.js'
|
import { clamp, stringToColor } from '../../Utils.js'
|
||||||
import type { VersionId } from '../../services/index.js'
|
import type { VersionId } from '../../services/index.js'
|
||||||
import { checkVersion } from '../../services/index.js'
|
import { checkVersion } from '../../services/index.js'
|
||||||
|
import { normalizeId, sampleHeight, sampleInt } from './WorldgenUtils.jsx'
|
||||||
|
|
||||||
export type Placement = [BlockPos, number]
|
export type Placement = [BlockPos, number]
|
||||||
|
|
||||||
@@ -54,90 +55,10 @@ export function decorateChunk(pos: ChunkPos, state: any, ctx: PlacementContext):
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalize(id: string) {
|
|
||||||
return id.startsWith('minecraft:') ? id.slice(10) : id
|
|
||||||
}
|
|
||||||
|
|
||||||
function decorateY(pos: BlockPos, y: number): BlockPos[] {
|
function decorateY(pos: BlockPos, y: number): BlockPos[] {
|
||||||
return [[ pos[0], y, pos[2] ]]
|
return [[ pos[0], y, pos[2] ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sampleInt(value: any, ctx: PlacementContext): number {
|
|
||||||
if (typeof value === 'number') {
|
|
||||||
return value
|
|
||||||
} else if (value.base) {
|
|
||||||
return value.base ?? 1 + ctx.nextInt(1 + (value.spread ?? 0))
|
|
||||||
} else {
|
|
||||||
switch (normalize(value.type)) {
|
|
||||||
case 'constant': return value.value
|
|
||||||
case 'uniform': return value.value.min_inclusive + ctx.nextInt(value.value.max_inclusive - value.value.min_inclusive + 1)
|
|
||||||
case 'biased_to_bottom': return value.value.min_inclusive + ctx.nextInt(ctx.nextInt(value.value.max_inclusive - value.value.min_inclusive + 1) + 1)
|
|
||||||
case 'clamped': return clamp(sampleInt(value.value.source, ctx), value.value.min_inclusive, value.value.max_inclusive)
|
|
||||||
case 'clamped_normal':
|
|
||||||
const normal = value.value.mean + ctx.nextGaussian() * value.value.deviation
|
|
||||||
return Math.floor(clamp(value.value.min_inclusive, value.value.max_inclusive, normal))
|
|
||||||
case 'weighted_list':
|
|
||||||
const totalWeight = (value.distribution as any[]).reduce<number>((sum, e) => sum + e.weight, 0)
|
|
||||||
let i = ctx.nextInt(totalWeight)
|
|
||||||
for (const e of value.distribution) {
|
|
||||||
i -= e.weight
|
|
||||||
if (i < 0) return sampleInt(e.data, ctx)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveAnchor(anchor: any, _ctx: PlacementContext): number {
|
|
||||||
if (!isObject(anchor)) return 0
|
|
||||||
if (anchor.absolute !== undefined) return anchor.absolute
|
|
||||||
if (anchor.above_bottom !== undefined) return anchor.above_bottom
|
|
||||||
if (anchor.below_top !== undefined) return 256 - anchor.below_top
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function sampleHeight(height: any, ctx: PlacementContext): number {
|
|
||||||
if (!isObject(height)) throw new Error('Invalid height provider')
|
|
||||||
if (typeof height.type !== 'string') {
|
|
||||||
return resolveAnchor(height, ctx)
|
|
||||||
}
|
|
||||||
switch (normalize(height.type)) {
|
|
||||||
case 'constant': return resolveAnchor(height.value, ctx)
|
|
||||||
case 'uniform': {
|
|
||||||
const min = resolveAnchor(height.min_inclusive, ctx)
|
|
||||||
const max = resolveAnchor(height.max_inclusive, ctx)
|
|
||||||
return min + ctx.nextInt(max - min + 1)
|
|
||||||
}
|
|
||||||
case 'biased_to_bottom': {
|
|
||||||
const min = resolveAnchor(height.min_inclusive, ctx)
|
|
||||||
const max = resolveAnchor(height.max_inclusive, ctx)
|
|
||||||
const n = ctx.nextInt(max - min - (height.inner ?? 1) + 1)
|
|
||||||
return min + ctx.nextInt(n + (height.inner ?? 1))
|
|
||||||
}
|
|
||||||
case 'very_biased_to_bottom': {
|
|
||||||
const min = resolveAnchor(height.min_inclusive, ctx)
|
|
||||||
const max = resolveAnchor(height.max_inclusive, ctx)
|
|
||||||
const inner = height.inner ?? 1
|
|
||||||
const n1 = min + inner + ctx.nextInt(max - min - inner + 1)
|
|
||||||
const n2 = min + ctx.nextInt(n1 - min)
|
|
||||||
return min + ctx.nextInt(n2 - min + inner)
|
|
||||||
}
|
|
||||||
case 'trapezoid': {
|
|
||||||
const min = resolveAnchor(height.min_inclusive, ctx)
|
|
||||||
const max = resolveAnchor(height.max_inclusive, ctx)
|
|
||||||
const plateau = height.plateau ?? 0
|
|
||||||
if (plateau >= max - min) {
|
|
||||||
return min + ctx.nextInt(max - min + 1)
|
|
||||||
}
|
|
||||||
const n1 = (max - min - plateau) / 2
|
|
||||||
const n2 = (max - min) - n1
|
|
||||||
return min + ctx.nextInt(n2 + 1) + ctx.nextInt(n1 + 1)
|
|
||||||
}
|
|
||||||
default: throw new Error(`Invalid height provider ${height.type}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1.17 and before
|
// 1.17 and before
|
||||||
function useFeature(s: string, ctx: PlacementContext) {
|
function useFeature(s: string, ctx: PlacementContext) {
|
||||||
const i = ctx.features.indexOf(s)
|
const i = ctx.features.indexOf(s)
|
||||||
@@ -151,7 +72,7 @@ function getPlacements(pos: BlockPos, feature: any, ctx: PlacementContext): void
|
|||||||
ctx.placements.push([pos, useFeature(feature, ctx)])
|
ctx.placements.push([pos, useFeature(feature, ctx)])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const type = normalize(feature?.type ?? 'no_op')
|
const type = normalizeId(feature?.type ?? 'no_op')
|
||||||
const featureFn = Features[type]
|
const featureFn = Features[type]
|
||||||
if (featureFn) {
|
if (featureFn) {
|
||||||
featureFn(feature.config, pos, ctx)
|
featureFn(feature.config, pos, ctx)
|
||||||
@@ -161,7 +82,7 @@ function getPlacements(pos: BlockPos, feature: any, ctx: PlacementContext): void
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPositions(pos: BlockPos, decorator: any, ctx: PlacementContext): BlockPos[] {
|
function getPositions(pos: BlockPos, decorator: any, ctx: PlacementContext): BlockPos[] {
|
||||||
const type = normalize(decorator?.type ?? 'nope')
|
const type = normalizeId(decorator?.type ?? 'nope')
|
||||||
const decoratorFn = Decorators[type]
|
const decoratorFn = Decorators[type]
|
||||||
if (!decoratorFn) {
|
if (!decoratorFn) {
|
||||||
return [pos]
|
return [pos]
|
||||||
@@ -357,10 +278,10 @@ const Decorators: {
|
|||||||
function modifyPlacement(pos: BlockPos, placement: any[], ctx: PlacementContext) {
|
function modifyPlacement(pos: BlockPos, placement: any[], ctx: PlacementContext) {
|
||||||
let positions = [pos]
|
let positions = [pos]
|
||||||
for (const modifier of placement) {
|
for (const modifier of placement) {
|
||||||
const modifierFn = PlacementModifiers[normalize(modifier?.type ?? 'nope')]
|
const modifierFn = PlacementModifiers[normalizeId(modifier?.type ?? 'nope')]
|
||||||
if (!modifierFn) continue
|
if (!modifierFn) continue
|
||||||
positions = positions.flatMap(pos =>
|
positions = positions.flatMap(pos =>
|
||||||
PlacementModifiers[normalize(modifier.type)](modifier, pos, ctx)
|
PlacementModifiers[normalizeId(modifier.type)](modifier, pos, ctx)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
for (const pos of positions) {
|
for (const pos of positions) {
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { BlockPos, ChunkPos, LegacyRandom, PerlinNoise } from 'deepslate'
|
import { BlockPos, ChunkPos, LegacyRandom, PerlinNoise } from 'deepslate'
|
||||||
import type { mat3 } from 'gl-matrix'
|
import type { mat3 } from 'gl-matrix'
|
||||||
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
|
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
|
||||||
import { useLocale } from '../../contexts/index.js'
|
|
||||||
import { computeIfAbsent, iterateWorld2D, randomSeed } from '../../Utils.js'
|
import { computeIfAbsent, iterateWorld2D, randomSeed } from '../../Utils.js'
|
||||||
|
import { useLocale } from '../../contexts/index.js'
|
||||||
import { Btn } from '../index.js'
|
import { Btn } from '../index.js'
|
||||||
import type { PlacedFeature, PlacementContext } from './Decorator.js'
|
import type { PlacedFeature, PlacementContext } from './Decorator.js'
|
||||||
import { decorateChunk } from './Decorator.js'
|
import { decorateChunk } from './Decorator.js'
|
||||||
import type { PreviewProps } from './index.js'
|
|
||||||
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
|
import { InteractiveCanvas2D } from './InteractiveCanvas2D.jsx'
|
||||||
|
import { nextGaussian } from './WorldgenUtils.jsx'
|
||||||
|
import type { PreviewProps } from './index.js'
|
||||||
|
|
||||||
export const DecoratorPreview = ({ data, version, shown }: PreviewProps) => {
|
export const DecoratorPreview = ({ data, version, shown }: PreviewProps) => {
|
||||||
const { locale } = useLocale()
|
const { locale } = useLocale()
|
||||||
@@ -25,7 +26,7 @@ export const DecoratorPreview = ({ data, version, shown }: PreviewProps) => {
|
|||||||
version: version,
|
version: version,
|
||||||
nextFloat: () => random.nextFloat(),
|
nextFloat: () => random.nextFloat(),
|
||||||
nextInt: (max: number) => random.nextInt(max),
|
nextInt: (max: number) => random.nextInt(max),
|
||||||
nextGaussian: () => Math.sqrt(-2 * Math.log(1 - random.nextFloat())) * Math.cos(2 * Math.PI * random.nextFloat()),
|
nextGaussian: nextGaussian(random),
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
context,
|
context,
|
||||||
|
|||||||
80
src/app/components/previews/Feature.ts
Normal file
80
src/app/components/previews/Feature.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import type { Random } from 'deepslate'
|
||||||
|
import { BlockPos, BlockState } from 'deepslate'
|
||||||
|
import type { VersionId } from '../../services/index.js'
|
||||||
|
import { sampleBlockState, sampleInt } from './WorldgenUtils.jsx'
|
||||||
|
|
||||||
|
export type FeatureContext = {
|
||||||
|
version: VersionId,
|
||||||
|
random: Random,
|
||||||
|
place: (pos: BlockPos, block: string | BlockState) => void,
|
||||||
|
nextFloat(): number,
|
||||||
|
nextInt(max: number): number,
|
||||||
|
nextGaussian(): number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function placeFeature(data: any, ctx: FeatureContext) {
|
||||||
|
const type = data.type.replace(/^minecraft:/, '')
|
||||||
|
Features[type]?.(data.config, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Features: {
|
||||||
|
[key: string]: (config: any, ctx: FeatureContext) => void,
|
||||||
|
} = {
|
||||||
|
bamboo: (config, ctx) => {
|
||||||
|
const n = ctx.nextInt(12) + 5
|
||||||
|
if (ctx.nextFloat() < config?.probability ?? 0) {
|
||||||
|
const s = ctx.nextInt(4) + 1
|
||||||
|
for (let x = -s; x <= s; x += 1) {
|
||||||
|
for (let z = -s; z <= s; z += 1) {
|
||||||
|
if (x * x + z * z <= s * s) {
|
||||||
|
ctx.place([x, -1, z], new BlockState('podzol', { snowy: 'false' }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 0; i < n; i += 1) {
|
||||||
|
ctx.place([0, i, 0], new BlockState('bamboo', { age: '1', leaves: 'none', stage: '0' }))
|
||||||
|
}
|
||||||
|
ctx.place([0, n, 0], new BlockState('bamboo', { age: '1', leaves: 'large', stage: '1'}))
|
||||||
|
ctx.place([0, n-1, 0], new BlockState('bamboo', { age: '1', leaves: 'large', stage: '0'}))
|
||||||
|
ctx.place([0, n-2, 0], new BlockState('bamboo', { age: '1', leaves: 'small', stage: '0'}))
|
||||||
|
},
|
||||||
|
tree: (config, ctx) => {
|
||||||
|
const trunk = config.trunk_placer
|
||||||
|
const trunkPlacerType = trunk.type.replace(/^minecraft:/, '')
|
||||||
|
const treeHeight = trunk.base_height + ctx.nextInt(trunk.height_rand_a + 1) + ctx.nextInt(trunk.height_rand_b + 1)
|
||||||
|
|
||||||
|
function placeLog(pos: BlockPos) {
|
||||||
|
ctx.place(pos, sampleBlockState(config.trunk_provider, ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
const horizontalDirs = [[-1, 0], [0, 1], [1, 0], [0, -1]] as const
|
||||||
|
const startPos = BlockPos.ZERO // TODO: roots
|
||||||
|
switch (trunkPlacerType) {
|
||||||
|
case 'upwards_branching_trunk_placer': {
|
||||||
|
const branchProbability = trunk.place_branch_per_log_probability
|
||||||
|
const extraBranchLength = trunk.extra_branch_length
|
||||||
|
const extraBranchSteps = trunk.extra_branch_steps
|
||||||
|
for (let i = 0; i < treeHeight; i += 1) {
|
||||||
|
const y = startPos[1] + i
|
||||||
|
placeLog(BlockPos.create(startPos[0], y, startPos[2]))
|
||||||
|
if (i < treeHeight - 1 && ctx.nextFloat() < branchProbability) {
|
||||||
|
const dir = horizontalDirs[ctx.nextInt(4)]
|
||||||
|
const branchLength = Math.max(0, sampleInt(extraBranchLength, ctx) - sampleInt(extraBranchLength, ctx) - 1)
|
||||||
|
let branchSteps = sampleInt(extraBranchSteps, ctx)
|
||||||
|
let x = startPos[0]
|
||||||
|
let z = startPos[1]
|
||||||
|
for (let j = branchLength; length < treeHeight && branchSteps > 0; j += 1) {
|
||||||
|
if (j >= 1) {
|
||||||
|
x += dir[0]
|
||||||
|
z += dir[1]
|
||||||
|
placeLog([x, y + j, z])
|
||||||
|
}
|
||||||
|
branchSteps -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
79
src/app/components/previews/FeaturePreview.tsx
Normal file
79
src/app/components/previews/FeaturePreview.tsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { DataModel } from '@mcschema/core'
|
||||||
|
import { LegacyRandom, Structure, StructureRenderer } from 'deepslate'
|
||||||
|
import { BlockPos } from 'deepslate-1.18.2'
|
||||||
|
import type { mat4 } from 'gl-matrix'
|
||||||
|
import { useCallback, useMemo, useRef, useState } from 'preact/hooks'
|
||||||
|
import { randomSeed } from '../../Utils.js'
|
||||||
|
import { useLocale } from '../../contexts/index.js'
|
||||||
|
import { useAsync } from '../../hooks/useAsync.js'
|
||||||
|
import { AsyncCancel } from '../../hooks/useAsyncFn.js'
|
||||||
|
import { getResources } from '../../services/Resources.js'
|
||||||
|
import { Btn } from '../Btn.jsx'
|
||||||
|
import type { FeatureContext } from './Feature.js'
|
||||||
|
import { placeFeature } from './Feature.js'
|
||||||
|
import { InteractiveCanvas3D } from './InteractiveCanvas3D.jsx'
|
||||||
|
import { nextGaussian } from './WorldgenUtils.jsx'
|
||||||
|
import type { PreviewProps } from './index.js'
|
||||||
|
|
||||||
|
const MAX_SIZE = 45
|
||||||
|
|
||||||
|
export const FeaturePreview = ({ data, version, shown }: PreviewProps) => {
|
||||||
|
const { locale } = useLocale()
|
||||||
|
const [seed, setSeed] = useState(randomSeed())
|
||||||
|
const serializedData = JSON.stringify(data)
|
||||||
|
|
||||||
|
const { value: resources } = useAsync(async () => {
|
||||||
|
if (!shown) return AsyncCancel
|
||||||
|
return await getResources(version)
|
||||||
|
}, [shown, version, serializedData])
|
||||||
|
|
||||||
|
const { structure } = useMemo(() => {
|
||||||
|
const structure = new Structure([MAX_SIZE, MAX_SIZE, MAX_SIZE])
|
||||||
|
const random = new LegacyRandom(seed)
|
||||||
|
const placeOffset = Math.floor((MAX_SIZE - 1) / 2)
|
||||||
|
const context: FeatureContext = {
|
||||||
|
version: version,
|
||||||
|
random,
|
||||||
|
place: (pos, block) => {
|
||||||
|
const structurePos = BlockPos.offset(pos, placeOffset, placeOffset, placeOffset)
|
||||||
|
if (structurePos.some((v, i) => v < 0 || v >= structure.getSize()[i])) return
|
||||||
|
const name = typeof block === 'string' ? block : block.getName()
|
||||||
|
const properties = typeof block === 'string' ? undefined : block.getProperties()
|
||||||
|
structure.addBlock(structurePos, name, properties)
|
||||||
|
},
|
||||||
|
nextFloat: () => random.nextFloat(),
|
||||||
|
nextInt: (max: number) => random.nextInt(max),
|
||||||
|
nextGaussian: nextGaussian(random),
|
||||||
|
}
|
||||||
|
placeFeature(DataModel.unwrapLists(data), context)
|
||||||
|
return { structure }
|
||||||
|
}, [serializedData, version, seed])
|
||||||
|
|
||||||
|
const renderer = useRef<StructureRenderer | undefined>(undefined)
|
||||||
|
|
||||||
|
const onSetup = useCallback((canvas: HTMLCanvasElement) => {
|
||||||
|
if (renderer.current) {
|
||||||
|
renderer.current.setStructure(structure)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!resources || !shown) return
|
||||||
|
const gl = canvas.getContext('webgl')
|
||||||
|
if (!gl) return
|
||||||
|
renderer.current = new StructureRenderer(gl, structure, resources, { useInvisibleBlockBuffer: false })
|
||||||
|
}, [resources, shown, structure])
|
||||||
|
const onResize = useCallback((width: number, height: number) => {
|
||||||
|
renderer.current?.setViewport(0, 0, width, height)
|
||||||
|
}, [resources])
|
||||||
|
const onDraw = useCallback((transform: mat4) => {
|
||||||
|
renderer.current?.drawStructure(transform)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div class="controls preview-controls">
|
||||||
|
<Btn icon="sync" tooltip={locale('generate_new_seed')} onClick={() => setSeed(randomSeed())} />
|
||||||
|
</div>
|
||||||
|
<div class="full-preview">
|
||||||
|
<InteractiveCanvas3D onSetup={onSetup} onDraw={onDraw} onResize={onResize} startDistance={10} startPosition={[MAX_SIZE/2, MAX_SIZE/2, MAX_SIZE/2]} startYRotation={2.6} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
106
src/app/components/previews/WorldgenUtils.tsx
Normal file
106
src/app/components/previews/WorldgenUtils.tsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import type { Random } from 'deepslate'
|
||||||
|
import { BlockState } from 'deepslate'
|
||||||
|
import { clamp, isObject } from '../../Utils.js'
|
||||||
|
import type { VersionId } from '../../services/index.js'
|
||||||
|
|
||||||
|
export type WorldgenUtilsContext = {
|
||||||
|
random: Random,
|
||||||
|
version: VersionId,
|
||||||
|
nextFloat(): number,
|
||||||
|
nextInt(max: number): number,
|
||||||
|
nextGaussian(): number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nextGaussian(random: Random) {
|
||||||
|
return () => Math.sqrt(-2 * Math.log(1 - random.nextFloat())) * Math.cos(2 * Math.PI * random.nextFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeId(id: string) {
|
||||||
|
return id.startsWith('minecraft:') ? id.slice(10) : id
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sampleInt(value: any, ctx: WorldgenUtilsContext): number {
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return value
|
||||||
|
} else if (value.base) {
|
||||||
|
return value.base ?? 1 + ctx.nextInt(1 + (value.spread ?? 0))
|
||||||
|
} else {
|
||||||
|
switch (normalizeId(value.type)) {
|
||||||
|
case 'constant': return value.value
|
||||||
|
case 'uniform': return value.value.min_inclusive + ctx.nextInt(value.value.max_inclusive - value.value.min_inclusive + 1)
|
||||||
|
case 'biased_to_bottom': return value.value.min_inclusive + ctx.nextInt(ctx.nextInt(value.value.max_inclusive - value.value.min_inclusive + 1) + 1)
|
||||||
|
case 'clamped': return clamp(sampleInt(value.value.source, ctx), value.value.min_inclusive, value.value.max_inclusive)
|
||||||
|
case 'clamped_normal':
|
||||||
|
const normal = value.value.mean + ctx.nextGaussian() * value.value.deviation
|
||||||
|
return Math.floor(clamp(value.value.min_inclusive, value.value.max_inclusive, normal))
|
||||||
|
case 'weighted_list':
|
||||||
|
const totalWeight = (value.distribution as any[]).reduce<number>((sum, e) => sum + e.weight, 0)
|
||||||
|
let i = ctx.nextInt(totalWeight)
|
||||||
|
for (const e of value.distribution) {
|
||||||
|
i -= e.weight
|
||||||
|
if (i < 0) return sampleInt(e.data, ctx)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveAnchor(anchor: any, _ctx: WorldgenUtilsContext): number {
|
||||||
|
if (!isObject(anchor)) return 0
|
||||||
|
if (anchor.absolute !== undefined) return anchor.absolute
|
||||||
|
if (anchor.above_bottom !== undefined) return anchor.above_bottom
|
||||||
|
if (anchor.below_top !== undefined) return 256 - anchor.below_top
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sampleHeight(height: any, ctx: WorldgenUtilsContext): number {
|
||||||
|
if (!isObject(height)) throw new Error('Invalid height provider')
|
||||||
|
if (typeof height.type !== 'string') {
|
||||||
|
return resolveAnchor(height, ctx)
|
||||||
|
}
|
||||||
|
switch (normalizeId(height.type)) {
|
||||||
|
case 'constant': return resolveAnchor(height.value, ctx)
|
||||||
|
case 'uniform': {
|
||||||
|
const min = resolveAnchor(height.min_inclusive, ctx)
|
||||||
|
const max = resolveAnchor(height.max_inclusive, ctx)
|
||||||
|
return min + ctx.nextInt(max - min + 1)
|
||||||
|
}
|
||||||
|
case 'biased_to_bottom': {
|
||||||
|
const min = resolveAnchor(height.min_inclusive, ctx)
|
||||||
|
const max = resolveAnchor(height.max_inclusive, ctx)
|
||||||
|
const n = ctx.nextInt(max - min - (height.inner ?? 1) + 1)
|
||||||
|
return min + ctx.nextInt(n + (height.inner ?? 1))
|
||||||
|
}
|
||||||
|
case 'very_biased_to_bottom': {
|
||||||
|
const min = resolveAnchor(height.min_inclusive, ctx)
|
||||||
|
const max = resolveAnchor(height.max_inclusive, ctx)
|
||||||
|
const inner = height.inner ?? 1
|
||||||
|
const n1 = min + inner + ctx.nextInt(max - min - inner + 1)
|
||||||
|
const n2 = min + ctx.nextInt(n1 - min)
|
||||||
|
return min + ctx.nextInt(n2 - min + inner)
|
||||||
|
}
|
||||||
|
case 'trapezoid': {
|
||||||
|
const min = resolveAnchor(height.min_inclusive, ctx)
|
||||||
|
const max = resolveAnchor(height.max_inclusive, ctx)
|
||||||
|
const plateau = height.plateau ?? 0
|
||||||
|
if (plateau >= max - min) {
|
||||||
|
return min + ctx.nextInt(max - min + 1)
|
||||||
|
}
|
||||||
|
const n1 = (max - min - plateau) / 2
|
||||||
|
const n2 = (max - min) - n1
|
||||||
|
return min + ctx.nextInt(n2 + 1) + ctx.nextInt(n1 + 1)
|
||||||
|
}
|
||||||
|
default: throw new Error(`Invalid height provider ${height.type}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sampleBlockState(provider: any, _ctx: WorldgenUtilsContext): BlockState {
|
||||||
|
const type = provider.type.replace(/^minecraft:/, '')
|
||||||
|
switch (type) {
|
||||||
|
case 'simple_state_provider': {
|
||||||
|
return BlockState.fromJson(provider.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return BlockState.AIR
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ export * from './BiomeSourcePreview.js'
|
|||||||
export * from './BlockStatePreview.jsx'
|
export * from './BlockStatePreview.jsx'
|
||||||
export * from './DecoratorPreview.js'
|
export * from './DecoratorPreview.js'
|
||||||
export * from './DensityFunctionPreview.js'
|
export * from './DensityFunctionPreview.js'
|
||||||
|
export * from './FeaturePreview.jsx'
|
||||||
export * from './LootTablePreview.jsx'
|
export * from './LootTablePreview.jsx'
|
||||||
export * from './ModelPreview.jsx'
|
export * from './ModelPreview.jsx'
|
||||||
export * from './NoisePreview.js'
|
export * from './NoisePreview.js'
|
||||||
|
|||||||
Reference in New Issue
Block a user