Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
Vitaly Turovsky
1ed7041ba6 add arrow obj 2025-02-18 03:51:02 +03:00
Vitaly Turovsky
f9a4ccec5c fix bedrock entities format rendering (like arrow) 2025-02-18 03:48:37 +03:00
3 changed files with 206 additions and 8 deletions

View file

@ -29,6 +29,19 @@ interface GeoData {
skinWeights: number[]
}
interface UVFace {
uv: [number, number]
}
interface CubeFaces {
north?: UVFace
south?: UVFace
east?: UVFace
west?: UVFace
up?: UVFace
down?: UVFace
}
interface JsonBone {
name: string
pivot?: [number, number, number]
@ -42,7 +55,7 @@ interface JsonBone {
interface JsonCube {
origin: [number, number, number]
size: [number, number, number]
uv: [number, number]
uv?: [number, number] | CubeFaces
inflate?: number
rotation?: [number, number, number]
}
@ -143,6 +156,21 @@ function dot (a: number[], b: number[]): number {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
function getFaceUV (cube: JsonCube, face: keyof CubeFaces): [number, number] | undefined {
// Handle per-face UV format (new format)
if (typeof cube.uv === 'object' && !Array.isArray(cube.uv)) {
const faceUV = cube.uv[face.toLowerCase()]
if (faceUV?.uv) {
return faceUV.uv
}
}
// Handle legacy format (array format)
if (Array.isArray(cube.uv)) {
return cube.uv
}
return undefined
}
function addCube (
attr: GeoData,
boneId: number,
@ -160,27 +188,54 @@ function addCube (
cubeRotation.y = -cube.rotation[1] * Math.PI / 180
cubeRotation.z = -cube.rotation[2] * Math.PI / 180
}
const faceToDirection: Record<keyof CubeFaces, string> = {
up: 'up',
down: 'down',
north: 'north',
south: 'south',
east: 'east',
west: 'west'
}
for (const { dir, corners, u0, v0, u1, v1 } of Object.values(elemFaces)) {
const ndx = Math.floor(attr.positions.length / 3)
const eastOrWest = dir[0] !== 0
// Determine which face we're processing based on direction
let currentFace: keyof CubeFaces | undefined
for (const [face, direction] of Object.entries(faceToDirection)) {
if (direction === Object.keys(elemFaces)[Object.values(elemFaces).indexOf({ dir, corners, u0, v0, u1, v1 })]) {
currentFace = face
break
}
}
const faceUvs: number[] = []
for (const pos of corners) {
let u: number
let v: number
if (sameTextureForAllFaces) {
u = (cube.uv[0] + pos[3] * cube.size[0]) / texWidth
v = (cube.uv[1] + pos[4] * cube.size[1]) / texHeight
} else {
u = (cube.uv[0] + dot(pos[3] ? u1 : u0, cube.size)) / texWidth
v = (cube.uv[1] + dot(pos[4] ? v1 : v0, cube.size)) / texHeight
const uvCoords = currentFace ? getFaceUV(cube, currentFace) : cube.uv
if (!uvCoords) {
errors.push(`Missing UV coordinates for face ${currentFace || 'unknown'}`)
continue
}
if (sameTextureForAllFaces) {
u = (uvCoords[0] + pos[3] * cube.size[0]) / texWidth
v = (uvCoords[1] + pos[4] * cube.size[1]) / texHeight
} else {
u = (uvCoords[0] + dot(pos[3] ? u1 : u0, cube.size)) / texWidth
v = (uvCoords[1] + dot(pos[4] ? v1 : v0, cube.size)) / texHeight
}
if (isNaN(u) || isNaN(v)) {
errors.push(`NaN u: ${u}, v: ${v}`)
errors.push(`NaN u: ${u}, v: ${v} for face ${currentFace || 'unknown'}`)
continue
}
if (u < 0 || u > 1 || v < 0 || v > 1) {
errors.push(`u: ${u}, v: ${v} out of range`)
errors.push(`u: ${u}, v: ${v} out of range for face ${currentFace || 'unknown'}`)
continue
}

View file

@ -0,0 +1,60 @@
# Aspose.3D Wavefront OBJ Exporter
# Copyright 2004-2024 Aspose Pty Ltd.
# File created: 02/12/2025 20:01:28
mtllib material.lib
g Arrow
#
# object Arrow
#
v -160 8.146034E-06 50
v 160 8.146034E-06 50
v -160 -8.146034E-06 -50
v 160 -8.146034E-06 -50
v -160 -50 1.1920929E-05
v 160 -50 1.1920929E-05
v -160 50 -1.1920929E-05
v 160 50 -1.1920929E-05
v -140 -49.999992 50.000008
v -140 50.000008 49.999992
v -140 -50.000008 -49.999992
v -140 49.999992 -50.000008
# 12 vertices
vn 0 1 -1.6292068E-07
vn 0 1 -1.6292068E-07
vn 0 1 -1.6292068E-07
vn 0 1 -1.6292068E-07
vn 0 3.1391647E-07 1
vn 0 3.1391647E-07 1
vn 0 3.1391647E-07 1
vn 0 3.1391647E-07 1
vn -1 0 0
vn -1 0 0
vn -1 0 0
vn -1 0 0
# 12 vertex normals
vt 0 0.84375 0
vt 0.5 1 0
vt 0.5 1 0
vt 0.5 0.84375 0
vt 0 1 0
vt 0.15625 0.84375 0
vt 0.15625 0.6875 0
vt 0 0.84375 0
vt 0.5 0.84375 0
vt 0 1 0
vt 0 0.6875 0
vt 0 0.84375 0
# 12 texture coords
usemtl Arrow
s 1
f 1/1/1 2/9/2 4/2/3 3/10/4
f 5/8/5 6/4/6 8/3/7 7/5/8
f 9/11/9 10/7/10 12/6/11 11/12/12
#3 polygons

View file

@ -0,0 +1,83 @@
import { z } from 'zod'
import entities from './entities.json'
// Define Zod schemas matching the TypeScript interfaces
const ElemFaceSchema = z.object({
dir: z.tuple([z.number(), z.number(), z.number()]),
u0: z.tuple([z.number(), z.number(), z.number()]),
v0: z.tuple([z.number(), z.number(), z.number()]),
u1: z.tuple([z.number(), z.number(), z.number()]),
v1: z.tuple([z.number(), z.number(), z.number()]),
corners: z.array(z.tuple([z.number(), z.number(), z.number(), z.number(), z.number()]))
})
const UVFaceSchema = z.object({
uv: z.tuple([z.number(), z.number()])
})
const CubeFacesSchema = z.object({
north: UVFaceSchema.optional(),
south: UVFaceSchema.optional(),
east: UVFaceSchema.optional(),
west: UVFaceSchema.optional(),
up: UVFaceSchema.optional(),
down: UVFaceSchema.optional()
})
const JsonCubeSchema = z.object({
origin: z.tuple([z.number(), z.number(), z.number()]),
size: z.tuple([z.number(), z.number(), z.number()]),
uv: z.union([
z.tuple([z.number(), z.number()]),
z.object({
north: z.object({ uv: z.tuple([z.number(), z.number()]) }).optional(),
south: z.object({ uv: z.tuple([z.number(), z.number()]) }).optional(),
east: z.object({ uv: z.tuple([z.number(), z.number()]) }).optional(),
west: z.object({ uv: z.tuple([z.number(), z.number()]) }).optional(),
up: z.object({ uv: z.tuple([z.number(), z.number()]) }).optional(),
down: z.object({ uv: z.tuple([z.number(), z.number()]) }).optional()
})
]).optional(),
inflate: z.number().optional(),
rotation: z.tuple([z.number(), z.number(), z.number()]).optional()
})
const JsonBoneSchema = z.object({
name: z.string(),
pivot: z.tuple([z.number(), z.number(), z.number()]).optional(),
bind_pose_rotation: z.tuple([z.number(), z.number(), z.number()]).optional(),
rotation: z.tuple([z.number(), z.number(), z.number()]).optional(),
parent: z.string().optional(),
cubes: z.array(JsonCubeSchema).optional(),
mirror: z.boolean().optional()
})
const JsonModelSchema = z.object({
texturewidth: z.number().optional(),
textureheight: z.number().optional(),
bones: z.array(JsonBoneSchema)
})
const EntityGeometrySchema = z.record(JsonModelSchema)
const EntitiesSchema = z.record(z.object({
geometry: EntityGeometrySchema,
textures: z.record(z.string())
}))
// Validate entities.json against schema
let validatedEntities
try {
validatedEntities = EntitiesSchema.parse(entities)
} catch (error) {
if (error instanceof z.ZodError) {
console.error('Validation errors:')
for (const err of error.errors) {
console.error(`- Path: ${err.path.join('.')}`)
console.error(` Error: ${err.message}`)
}
}
throw error
}
export default validatedEntities