From 0efd476676a4a4a525799975f4da15db0c2535f6 Mon Sep 17 00:00:00 2001 From: Teriuihi Date: Sun, 22 Jun 2025 19:53:27 +0200 Subject: [PATCH] Initial attempt at textures --- frontend/public/img/skins/akastijn.png | Bin 0 -> 2308 bytes .../render-container.component.ts | 6 +- .../services/player-model.service.ts | 239 +++++++++++++++++- 3 files changed, 235 insertions(+), 10 deletions(-) create mode 100644 frontend/public/img/skins/akastijn.png diff --git a/frontend/public/img/skins/akastijn.png b/frontend/public/img/skins/akastijn.png new file mode 100644 index 0000000000000000000000000000000000000000..2b645ec9afca37b5568d833db397f3028017bc9c GIT binary patch literal 2308 zcmX}uX;>228USG2Sp-COMUYiwS8)l@zLL;rB-HBwwR68z zILWx|Cd{r4)MJ~>b4vOcm&(BwG(tVdp=8y|Al+?@&N=b?N zgW!NtXeh_E9Pi6lomk|U6Wv?Gy7u1GsqB+%8> zm5cAr{(J>%8227{Z85j@)17={s3^)q}pdKC`o-R;NBGg+x*sk*eOr1?K~{dLQYzh4wY4%XW_wIb zLw0sUbL)wM0;6ut@bK`xd-uk+Z+!Xk<;<}y9}cMI=jRu$R4mNQEcZ5i?Qi}%wBIy( z%=DzgWHQ|-R8N_&2w4{=j|Z3=Y;FL@TvsnYQLrK-Gczko`Q3&M`8r)u@zBIV(+=8c z0KgU#5iCnc>6=aZl8#D5wLWXQIKGN?+Bm4Oa$8rF=gn`47Q6_yI+T1@HIhh7s>y?+ z5_aBCXy@qf<_(6a;eC!dSn|$IYoF>De-M2ddOLHKXQ)^@xcTo3t$W3VuMfOCP@X-s zBE7D3@lx2k%DJY!hT>H=W1D$zvmWIEZh6M*ktqC!`Y+;lB}?5c+0QD=|7vcPw?4d7 z*ni*prvgh&5d@dHTK9Ma{&dOcKm5eBxW&*N@aMDiKMlj%$^DzZCWrOxvlkHYNZ`Uh z@jLwAUmFyh$9P{*%S~~u`w+@`$f19uaqQLO--J=WOJX0i}Oz@ zo>T<+QwJs)P+8EUtC_8a#R|1$N9m1I@;ccAXk=;csfyV#?iX>o(Wf0ih05rsu|ucq zjtv)gcYp0J>tpksW04NtaiC`_jfbhsjZYLs?H#A)yLP8lRz7?D?$_Q=7ymt}nP2M8 z{5+lHn^!pMb-N?vd!6!a_dVTsh&(nT^VV*?|A~)@d0m}*dP+^CmX@mG#xC99%CxaN z+6pnp{cEj(Hazff?6m9Nb&t0} zEqtxs|Ky#rA;D+EzMH@AvzCqAjoII&?7A_%#45tqojTJun$){e({VQ74{h;8gUz|y z&DXd2)5XD~<*!%AP-r02( z+d6o&jQ3N!Y9MD8x{(GfC>EkBf4MM_QZgHOzgIKZDb_r{LH`)?!+hbVO_TibxWES$ z!SdCsA6&WJiVI$xL8w(fhK7a36zYUd+$G9MiCAx0>-PP!0q=!MxLmGDYQO6S_)>VrdHn6#KXK={kv{VXikdz|R!o?iZ z=Xs1py*L})m{?lio-s3@!8tgkCe4v$u_tMrxt98DL^V=SqraBfb9}Bh*npm~ici$9 zwDX!&plq~KB{J9Ptf3z$Or?fe7F9o%tb&|XBNqX+(hqc`6mVG(9t5x!F|6ND^`<38 zFy15Bx2*ab0;SzjP|dH^$$w8DCAvmdqe)t_i#~L{0QVyzx4D{L?`AJ~T2q@aY6sgI z@cR}&i%1F(3abkYvLcJFT3B1IIyk4<=J25-azX{%$$zEi%LD?UP+)$hZB#-&t+T?O zRS_{3lR<#$Xf2VLF$E%!gW7s2UM<|*i)M|qL}m8P-Dv9Oq;f8n*YtIMUL3}cfpuq=&y{j|g*$2wdhyoW7I|W7_sH9>AI{Xa z1D*6bCO=6@<{$dQQ3l%z%%H&9JO=*Rq^@ba*Ccn{B|B>Em?TZ@Jls`! zZ~z>SJ(3dBRvBQxKbL|R{Mx#XJ+Db_^b6dxy}jF~=^jQO>dZCTRP{Rl@=9Jt4L_hu z%T*-krmW~d_2%RI8Hl#Yj%FSZqsQ#9xaJWm?FYpI!8cQ2Oj)kt(0SQUNCh1D{jOaP zewnry1z81kr=9Fs7t6#?TaJPnv}^d+K2}FqTmHS}XOiP6Xg9SxZR;D0DB@wnSe4af WKkaf#KcMQ{k0T;P9&89)SMxv7w~+(@ literal 0 HcmV?d00001 diff --git a/frontend/src/app/particles/components/render-container/render-container.component.ts b/frontend/src/app/particles/components/render-container/render-container.component.ts index 6fd5913..c3d5fb1 100644 --- a/frontend/src/app/particles/components/render-container/render-container.component.ts +++ b/frontend/src/app/particles/components/render-container/render-container.component.ts @@ -49,7 +49,11 @@ export class RenderContainerComponent implements AfterViewInit, OnDestroy { */ private initializeScene(): void { this.rendererService.initializeRenderer(this.rendererContainer); - this.playerModelService.createPlayerModel(); + this.playerModelService.loadSkinTexture('/public/img/skins/akastijn.png') + .then(() => { + // Then create the player model with the texture applied + this.playerModelService.createPlayerModel(); + }); this.intersectionPlaneService.createIntersectionPlane(); this.inputHandlerService.initializeInputHandlers(this.rendererService.renderer.domElement); } diff --git a/frontend/src/app/particles/services/player-model.service.ts b/frontend/src/app/particles/services/player-model.service.ts index d50112a..8f3ae77 100644 --- a/frontend/src/app/particles/services/player-model.service.ts +++ b/frontend/src/app/particles/services/player-model.service.ts @@ -1,24 +1,68 @@ -import { Injectable } from '@angular/core'; +import {Injectable} from '@angular/core'; import * as THREE from 'three'; -import { RendererService } from './renderer.service'; +import {RendererService} from './renderer.service'; -/** - * Service responsible for creating and managing the player model - */ @Injectable({ providedIn: 'root' }) export class PlayerModelService { private playerModel!: THREE.Group; + private skinTexture!: THREE.Texture; + private textureLoaded = false; - constructor(private rendererService: RendererService) {} + constructor(private rendererService: RendererService) { + } /** - * Creates a simple player model and adds it to the scene + * 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}); @@ -56,10 +100,187 @@ export class PlayerModelService { const rightLeg = new THREE.Mesh(legGeometry, legMaterial); rightLeg.position.set(0.125, 0.15, 0); this.playerModel.add(rightLeg); + } - this.rendererService.scene.add(this.playerModel); + /** + * Creates a textured player model using the Minecraft skin + */ + private createTexturedPlayerModel(): void { + // Create the player with properly mapped textures - return this.playerModel; + // 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; } /**