import {Injectable} from '@angular/core'; import * as THREE from 'three'; import {RendererService} from './renderer.service'; @Injectable({ providedIn: 'root' }) export class PlayerModelService { private playerModel!: THREE.Group; private skinTexture!: THREE.Texture; private textureLoaded = false; constructor(private rendererService: RendererService) { } /** * Loads a Minecraft skin texture from a URL * @param textureUrl The URL of the skin texture to load * @returns A promise that resolves when the texture is loaded */ loadSkinTexture(textureUrl: string): Promise { return new Promise((resolve) => { const loader = new THREE.TextureLoader(); loader.load(textureUrl, (texture) => { // Set texture parameters texture.magFilter = THREE.NearestFilter; texture.minFilter = THREE.NearestFilter; this.skinTexture = texture; this.textureLoaded = true; // If the player model already exists, rebuild it with textures if (this.playerModel) { // Remove old model this.rendererService.scene.remove(this.playerModel); // Create new model with textures this.createPlayerModel(); } resolve(); }); }); } /** * Creates a player model with Minecraft-style textures and adds it to the scene */ createPlayerModel(): THREE.Group { this.playerModel = new THREE.Group(); if (this.textureLoaded) { // Create textured model if texture is loaded this.createTexturedPlayerModel(); } else { // Create simple colored model if no texture is loaded this.createSimplePlayerModel(); } this.rendererService.scene.add(this.playerModel); return this.playerModel; } /** * Creates a simple colored player model (without textures) */ private createSimplePlayerModel(): void { // Head const headGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5); const headMaterial = new THREE.MeshLambertMaterial({color: 0xffccaa}); const head = new THREE.Mesh(headGeometry, headMaterial); head.position.y = 1.35; this.playerModel.add(head); // Body const bodyGeometry = new THREE.BoxGeometry(0.5, 0.7, 0.25); const bodyMaterial = new THREE.MeshLambertMaterial({color: 0x0000ff}); const body = new THREE.Mesh(bodyGeometry, bodyMaterial); body.position.y = 0.75; this.playerModel.add(body); // Arms const armGeometry = new THREE.BoxGeometry(0.2, 0.7, 0.25); const armMaterial = new THREE.MeshLambertMaterial({color: 0xffccaa}); const leftArm = new THREE.Mesh(armGeometry, armMaterial); leftArm.position.set(-0.35, 0.75, 0); this.playerModel.add(leftArm); const rightArm = new THREE.Mesh(armGeometry, armMaterial); rightArm.position.set(0.35, 0.75, 0); this.playerModel.add(rightArm); // Legs const legGeometry = new THREE.BoxGeometry(0.25, 0.7, 0.25); const legMaterial = new THREE.MeshLambertMaterial({color: 0x000000}); const leftLeg = new THREE.Mesh(legGeometry, legMaterial); leftLeg.position.set(-0.125, 0.15, 0); this.playerModel.add(leftLeg); const rightLeg = new THREE.Mesh(legGeometry, legMaterial); rightLeg.position.set(0.125, 0.15, 0); this.playerModel.add(rightLeg); } /** * Creates a textured player model using the Minecraft skin */ private createTexturedPlayerModel(): void { // Create the player with properly mapped textures // Head - 8x8x8 pixels in the texture this.playerModel.add(this.createBoxWithUvMapping( 0.5, 0.5, 0.5, // width, height, depth [ {x: 8, y: 0, w: 8, h: 8}, // top {x: 16, y: 0, w: 8, h: 8}, // bottom {x: 16, y: 8, w: 8, h: 8}, // right {x: 0, y: 8, w: 8, h: 8}, // left {x: 8, y: 8, w: 8, h: 8}, // front {x: 24, y: 8, w: 8, h: 8} // back ], {x: 0, y: 1.35, z: 0} // position )); // Body - 8x12x4 pixels in the texture this.playerModel.add(this.createBoxWithUvMapping( 0.5, 0.7, 0.25, // width, height, depth [ {x: 20, y: 16, w: 8, h: 4}, // top {x: 28, y: 16, w: 8, h: 4}, // bottom {x: 28, y: 20, w: 4, h: 12}, // right {x: 16, y: 20, w: 4, h: 12}, // left {x: 20, y: 20, w: 8, h: 12}, // front {x: 32, y: 20, w: 8, h: 12} // back ], {x: 0, y: 0.75, z: 0} // position )); // Left Arm - 4x12x4 pixels in the texture this.playerModel.add(this.createBoxWithUvMapping( 0.2, 0.7, 0.25, // width, height, depth [ {x: 44, y: 16, w: 4, h: 4}, // top {x: 48, y: 16, w: 4, h: 4}, // bottom {x: 48, y: 20, w: 4, h: 12}, // right {x: 40, y: 20, w: 4, h: 12}, // left {x: 44, y: 20, w: 4, h: 12}, // front {x: 52, y: 20, w: 4, h: 12} // back ], {x: -0.35, y: 0.75, z: 0} // position )); // Right Arm - 4x12x4 pixels in the texture this.playerModel.add(this.createBoxWithUvMapping( 0.2, 0.7, 0.25, // width, height, depth [ {x: 44, y: 16, w: 4, h: 4}, // top - mirror of left arm {x: 48, y: 16, w: 4, h: 4}, // bottom - mirror of left arm {x: 40, y: 20, w: 4, h: 12}, // right - mirror of left arm's left {x: 48, y: 20, w: 4, h: 12}, // left - mirror of left arm's right {x: 44, y: 20, w: 4, h: 12}, // front - same as left arm {x: 52, y: 20, w: 4, h: 12} // back - same as left arm ], {x: 0.35, y: 0.75, z: 0} // position )); // Left Leg - 4x12x4 pixels in the texture this.playerModel.add(this.createBoxWithUvMapping( 0.25, 0.7, 0.25, // width, height, depth [ {x: 4, y: 16, w: 4, h: 4}, // top {x: 8, y: 16, w: 4, h: 4}, // bottom {x: 8, y: 20, w: 4, h: 12}, // right {x: 0, y: 20, w: 4, h: 12}, // left {x: 4, y: 20, w: 4, h: 12}, // front {x: 12, y: 20, w: 4, h: 12} // back ], {x: -0.125, y: 0.15, z: 0} // position )); // Right Leg - 4x12x4 pixels in the texture this.playerModel.add(this.createBoxWithUvMapping( 0.25, 0.7, 0.25, // width, height, depth [ {x: 4, y: 16, w: 4, h: 4}, // top - mirror of left leg {x: 8, y: 16, w: 4, h: 4}, // bottom - mirror of left leg {x: 0, y: 20, w: 4, h: 12}, // right - mirror of left leg's left {x: 8, y: 20, w: 4, h: 12}, // left - mirror of left leg's right {x: 4, y: 20, w: 4, h: 12}, // front - same as left leg {x: 12, y: 20, w: 4, h: 12} // back - same as left leg ], {x: 0.125, y: 0.15, z: 0} // position )); } /** * Creates a box with proper UV mapping for a Minecraft character part * @param width Width of the box * @param height Height of the box * @param depth Depth of the box * @param uvMapping Array of UV coordinates for each face (top, bottom, right, left, front, back) * @param position Position of the box * @returns THREE.Mesh with properly mapped textures */ private createBoxWithUvMapping( width: number, height: number, depth: number, uvMapping: Array<{ x: number, y: number, w: number, h: number }>, position: { x: number, y: number, z: number } ): THREE.Mesh { // Create box geometry const geometry = new THREE.BoxGeometry(width, height, depth); // Texture dimensions (Minecraft skins are 64x64) const textureWidth = 64; const textureHeight = 64; // Calculate UV coordinates for each face // Three.js creates 2 triangles per face, so we need to set UVs for 6 faces * 2 triangles * 3 vertices const uvs = []; // Order of faces in BoxGeometry: px, nx, py, ny, pz, nz (right, left, top, bottom, front, back) // However, our order is: top, bottom, right, left, front, back // So we need to remap const remappedUVs = [ uvMapping[2], // right (px) uvMapping[3], // left (nx) uvMapping[0], // top (py) uvMapping[1], // bottom (ny) uvMapping[4], // front (pz) uvMapping[5], // back (nz) ]; // Set UVs for all faces let faceUVs: number[][] = []; remappedUVs.forEach(face => { // Calculate corner coordinates (normalized from 0-1) const x1 = face.x / textureWidth; const y1 = 1 - (face.y / textureHeight); const x2 = (face.x + face.w) / textureWidth; const y2 = 1 - ((face.y + face.h) / textureHeight); // Each face has 2 triangles with 3 vertices each // First triangle: top-left, bottom-right, bottom-left // Second triangle: top-left, top-right, bottom-right // Triangle 1 faceUVs.push([x1, y1]); // top-left faceUVs.push([x2, y2]); // bottom-right faceUVs.push([x1, y2]); // bottom-left // Triangle 2 faceUVs.push([x1, y1]); // top-left faceUVs.push([x2, y1]); // top-right faceUVs.push([x2, y2]); // bottom-right }); // Flatten the UV array const uvArray = new Float32Array(faceUVs.flat()); // Set the UV attribute geometry.setAttribute('uv', new THREE.BufferAttribute(uvArray, 2)); // Create material with the skin texture const material = new THREE.MeshBasicMaterial({ map: this.skinTexture, transparent: true, }); // Create mesh and set position const mesh = new THREE.Mesh(geometry, material); const wireframeMaterial = new THREE.MeshBasicMaterial({ wireframe: true, color: 0xff0000 }); const wireframe = new THREE.Mesh(geometry, wireframeMaterial); mesh.add(wireframe); mesh.position.set(position.x, position.y, position.z); return mesh; } /** * Gets the player model */ getPlayerModel(): THREE.Group { return this.playerModel; } }