From f9a4ccec5c4bcca3041d5f990fd364367999d686 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 18 Feb 2025 03:48:37 +0300 Subject: [PATCH 1/2] fix bedrock entities format rendering (like arrow) --- renderer/viewer/lib/entity/EntityMesh.ts | 71 +++++++++++++++--- renderer/viewer/lib/entity/testEntities.ts | 83 ++++++++++++++++++++++ 2 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 renderer/viewer/lib/entity/testEntities.ts diff --git a/renderer/viewer/lib/entity/EntityMesh.ts b/renderer/viewer/lib/entity/EntityMesh.ts index cb9dd2257..a32504be1 100644 --- a/renderer/viewer/lib/entity/EntityMesh.ts +++ b/renderer/viewer/lib/entity/EntityMesh.ts @@ -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 = { + 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 + + const uvCoords = currentFace ? getFaceUV(cube, currentFace) : cube.uv + if (!uvCoords) { + errors.push(`Missing UV coordinates for face ${currentFace || 'unknown'}`) + continue + } + if (sameTextureForAllFaces) { - u = (cube.uv[0] + pos[3] * cube.size[0]) / texWidth - v = (cube.uv[1] + pos[4] * cube.size[1]) / texHeight + u = (uvCoords[0] + pos[3] * cube.size[0]) / texWidth + v = (uvCoords[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 + 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 } diff --git a/renderer/viewer/lib/entity/testEntities.ts b/renderer/viewer/lib/entity/testEntities.ts new file mode 100644 index 000000000..108308e31 --- /dev/null +++ b/renderer/viewer/lib/entity/testEntities.ts @@ -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 From 1ed7041ba68a26d0fd1254ecfb3aadbf11fe0912 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 18 Feb 2025 03:51:02 +0300 Subject: [PATCH 2/2] add arrow obj --- renderer/viewer/lib/entity/models/Arrow.obj | 60 +++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 renderer/viewer/lib/entity/models/Arrow.obj diff --git a/renderer/viewer/lib/entity/models/Arrow.obj b/renderer/viewer/lib/entity/models/Arrow.obj new file mode 100644 index 000000000..469dd6cd3 --- /dev/null +++ b/renderer/viewer/lib/entity/models/Arrow.obj @@ -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 +