[READ-ONLY] Mirror of https://github.com/flo-bit/tiny-creature. three.js journey #16 - tamagotchi flo-bit.dev/tiny-creature/
0

Configure Feed

Select the types of activity you want to include in your feed.

more fur stuff

Florian (Feb 5, 2025, 12:35 AM +0100) e16185a8 f2076f31

+146 -39
-1
src/app.html
··· 2 2 <html lang="en"> 3 3 <head> 4 4 <meta charset="utf-8" /> 5 - <link rel="icon" href="%sveltekit.assets%/favicon.png" /> 6 5 <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 6 %sveltekit.head% 8 7 </head>
static/model.glb

This is a binary file and will not be displayed.

+49 -12
src/lib/Creature.svelte
··· 4 4 // @ts-ignore 5 5 import { WiggleBone } from 'wiggle'; 6 6 import { onMount } from 'svelte'; 7 - import { createFaceTexture, updateFace } from './face/face'; 7 + import { createFaceTexture, createFurTexture, updateFace } from './face/face'; 8 8 import { T, useTask, useThrelte } from '@threlte/core'; 9 9 10 10 let rootBone: THREE.Bone; ··· 12 12 let creature: THREE.Group; 13 13 14 14 let canvasTexture: THREE.CanvasTexture; 15 + let furTexture: THREE.CanvasTexture; 16 + 15 17 import { 16 18 color, 17 19 float, 18 20 Fn, 19 21 If, 22 + mix, 20 23 mx_noise_float, 21 24 normalGeometry, 22 25 normalLocal, ··· 42 45 mouse.set(event.clientX / window.innerWidth, event.clientY / window.innerHeight); 43 46 }); 44 47 48 + // jujmp on space 49 + window.addEventListener('keydown', (event) => { 50 + if (event.key === ' ') { 51 + ySpeed = 5; 52 + } 53 + }); 54 + 45 55 const { app } = await createFaceTexture({ 46 56 width: 1024, 47 57 height: 1024 ··· 49 59 50 60 canvasTexture = new THREE.CanvasTexture(app.canvas); 51 61 62 + const fur = await createFurTexture({ 63 + width: 512, 64 + height: 512 65 + }); 66 + 67 + furTexture = new THREE.CanvasTexture(fur.app.canvas); 68 + 52 69 const loader = new GLTFLoader(); 53 70 54 71 loader.load('/tiny-creature/model.glb', ({ scene: obj }) => { 72 + console.log(obj); 55 73 const mesh = obj.getObjectByName('Icosphere'); 56 74 if (!mesh) return; 57 75 ··· 101 119 uniforms.push(movement); 102 120 103 121 material.positionNode = positionGeometry 104 - .add(normalGeometry.mul(i * 0.003)) 105 - .add(movement.add(vec3(0, -0.001, 0)).mul(i * 1)); 122 + .add( 123 + normalGeometry.mul( 124 + float(i) 125 + .mul(0.01) 126 + .sub(float(1).sub(texture(furTexture).a).mul(0.2)) 127 + ) 128 + ) 129 + .add(movement.add(vec3(0, -0.003, 0)).mul(i * 1)); 106 130 107 - material.colorNode = color(0xffffff).mul((i / total) * 0.8 + 0.2); 131 + material.colorNode = mix( 132 + color(0xec4899), 133 + color(0xfb923c), 134 + mx_noise_float(positionGeometry.mul(10).mul(0.5).add(0.5)) 135 + ).mul((i / total) * 0.8 + 0.2); 108 136 109 137 material.alphaTestNode = float(0.5); 110 138 111 139 material.opacityNode = select( 112 - texture(canvasTexture).g.greaterThan(0.8).and(texture(canvasTexture).b.lessThan(0.5)), 113 - 114 - select( 115 - mx_noise_float(positionGeometry.mul(200)).greaterThan((i / total) * 2 - 1), 116 - 1.0, 117 - 0.0 118 - ), 140 + mx_noise_float(positionGeometry.mul(100)).greaterThan((i / total) * 2 - 1), 141 + 1.0, 119 142 0.0 120 143 ); 121 144 ··· 137 160 if (!(bone.parent instanceof THREE.Bone) || !bone.parent?.isBone) { 138 161 rootBone = bone; 139 162 163 + console.log('found root bone', rootBone); 164 + 140 165 // rootBone.rotation.y = -Math.PI / 2; 141 166 } else { 142 167 const wiggleBone = new WiggleBone(bone, { 143 - velocity: 0.4 168 + velocity: 0.5 144 169 }); 145 170 wiggleBones.push(wiggleBone); 171 + 172 + console.log('found wiggle bone', wiggleBone); 146 173 } 147 174 }); 148 175 } ··· 151 178 152 179 let lastPosition = new THREE.Vector3(); 153 180 let total = 0; 181 + 182 + let ySpeed = 0; 154 183 useTask((dt) => { 155 184 total += dt; 156 185 wiggleBones.forEach((wiggleBone) => { ··· 162 191 canvasTexture.needsUpdate = true; 163 192 } 164 193 194 + if (furTexture) { 195 + // furTexture.needsUpdate = true; 196 + } 197 + 165 198 if (rootBone) { 166 199 rootBone.position.z = Math.sin(total * 2) * 0.5; //-(mouse.x - 0.5) * 0.8 + rootBone.position.z * 0.2; 167 200 201 + rootBone.position.y += ySpeed * dt; 202 + rootBone.position.y = Math.max(rootBone.position.y, -0.5); 203 + 204 + ySpeed -= 10 * dt; 168 205 // uniforms.forEach((uniform) => { 169 206 // uniform.value.set( 170 207 // (lastPosition.x - rootBone.position.x) * dt,
+2 -2
src/routes/+page.svelte
··· 15 15 forceWebGL: false 16 16 }) 17 17 }}> 18 - <Studio> 18 + <!-- <Studio> --> 19 19 <Scene /> 20 - </Studio> 20 + <!-- </Studio> --> 21 21 </Canvas> 22 22 </div>
+2 -2
src/lib/face/eye.ts
··· 101 101 this.eyeContainer.addChild(this.mask); 102 102 this.eyeContainer.mask = this.mask; 103 103 104 - let eyebrowWidth = opts?.eyebrow?.width ?? 2; 105 - let eyebrowCurve = opts?.eyebrow?.curve ?? -0.2; 104 + const eyebrowWidth = opts?.eyebrow?.width ?? 2; 105 + const eyebrowCurve = opts?.eyebrow?.curve ?? 0.2; 106 106 107 107 const eyebrow = new PIXI.Graphics() 108 108 .moveTo((-eyebrowWidth / 2) * this.size, 0)
+93 -22
src/lib/face/face.ts
··· 1 - import { Application, Graphics, type Renderer } from 'pixi.js'; 1 + import { Application, Container, Graphics, type Renderer } from 'pixi.js'; 2 2 import Eye from './eye'; 3 3 import Mouth from './mouth'; 4 4 ··· 14 14 width?: number; 15 15 height?: number; 16 16 }): Promise<{ app: Application<Renderer> }> { 17 - const scale = 1.6; 17 + const xScale = 0.4; 18 + const yScale = 1.7; 18 19 const app = new Application(); 19 20 20 21 await app.init({ 21 22 width, 22 23 height, 23 - background: '#ffff00' 24 + background: '#be185d' 24 25 }); 25 26 26 - // add face circle 27 - const face = new Graphics(); 28 - face.circle(0, 0, 140 * scale); 29 - face.x = width * 0.5; 30 - face.y = height * 0.5 + 30 * scale; 31 - face.fill({ color: '#db2777' }); 32 - app.stage.addChild(face); 27 + const container = new Container(); 28 + container.x = width * 0.5; 29 + container.y = height * 0.625; 30 + container.scale.set(xScale, yScale); 31 + app.stage.addChild(container); 33 32 34 33 rightEye = new Eye({ 35 - x: width * 0.5 + 75 * scale, 36 - y: height * 0.5 + 50 * scale, 37 - size: 50 * scale 34 + x: 75, 35 + y: 0, 36 + size: 50 38 37 }); 39 38 40 39 leftEye = new Eye({ 41 - x: width * 0.5 - 75 * scale, 42 - y: height * 0.5 + 50 * scale, 43 - size: 50 * scale 40 + x: -75, 41 + y: 0, 42 + size: 50 44 43 }); 45 44 46 - app.stage.addChild(rightEye.container); 47 - app.stage.addChild(leftEye.container); 45 + container.addChild(rightEye.container); 46 + container.addChild(leftEye.container); 48 47 49 48 mouth = new Mouth({ 50 - x: width * 0.5, 51 - y: height * 0.5 - 70 * scale, 52 - size: 50 * scale 49 + x: 0, 50 + y: -130, 51 + size: 50 53 52 }); 54 - app.stage.addChild(mouth.container); 53 + container.addChild(mouth.container); 54 + 55 + return { 56 + app 57 + }; 58 + } 59 + 60 + export async function createFurTexture({ 61 + width = 512, 62 + height = 512 63 + }: { 64 + width?: number; 65 + height?: number; 66 + }): Promise<{ app: Application<Renderer> }> { 67 + const xScale = 0.4; 68 + const yScale = 1.45; 69 + const app = new Application(); 70 + 71 + await app.init({ 72 + width, 73 + height, 74 + backgroundAlpha: 0.0 75 + }); 76 + 77 + const container = new Container(); 78 + container.x = width * 0.5; 79 + container.y = height * 0.625; 80 + container.scale.set(xScale, yScale); 81 + app.stage.addChild(container); 82 + 83 + const background = new Graphics(); 84 + background.rect(0, 0, width * 5, height * 5); 85 + background.pivot.set(width * 2.5, height * 2.5); 86 + background.fill({ color: '#000000' }); 87 + container.addChild(background); 88 + 89 + // add circle mask 90 + const circleMask = new Graphics(); 91 + circleMask.circle(0, 0, 100); 92 + circleMask.blendMode = 'erase'; 93 + 94 + circleMask.fill({ color: '#ffffff' }); 95 + container.addChild(circleMask); 96 + 97 + // for (let i = 0; i < 21; i++) { 98 + // // add circle 99 + // const circle = new Graphics(); 100 + // circle.circle(0, 0, 3 * i + 40); 101 + // // more and more green 102 + // circle.stroke({ color: { r: 0, g: 0, b: 0, a: i / 20 }, width: 3 }); 103 + // container.addChild(circle); 104 + // } 105 + 106 + const circle = new Graphics(); 107 + circle.circle(0, 0, 100); 108 + circle.fill({ color: { r: 0, g: 0, b: 0, a: 0.65 } }); 109 + container.addChild(circle); 110 + 111 + const eyeMaskLeft = new Graphics(); 112 + eyeMaskLeft.circle(0, 0, 25); 113 + eyeMaskLeft.x = -75 / 2; 114 + eyeMaskLeft.y = 0; 115 + eyeMaskLeft.blendMode = 'erase'; 116 + eyeMaskLeft.fill({ color: '#ffffff' }); 117 + container.addChild(eyeMaskLeft); 118 + 119 + const eyeMaskRight = new Graphics(); 120 + eyeMaskRight.circle(0, 0, 25); 121 + eyeMaskRight.x = 75 / 2; 122 + eyeMaskRight.y = 0; 123 + eyeMaskRight.blendMode = 'erase'; 124 + eyeMaskRight.fill({ color: '#ffffff' }); 125 + container.addChild(eyeMaskRight); 55 126 56 127 return { 57 128 app