[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.

fur 1

Florian (Feb 4, 2025, 6:57 PM +0100) f2076f31 0bef5b8b

+295 -68
+6
package-lock.json
··· 14 14 "@threlte/studio": "^0.1.3", 15 15 "@types/three": "^0.173.0", 16 16 "pixi.js": "^8.7.3", 17 + "stats.js": "^0.17.0", 17 18 "three": "^0.173.0", 18 19 "wiggle": "^0.0.17" 19 20 }, ··· 4214 4215 "engines": { 4215 4216 "node": ">=0.10.0" 4216 4217 } 4218 + }, 4219 + "node_modules/stats.js": { 4220 + "version": "0.17.0", 4221 + "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz", 4222 + "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==" 4217 4223 }, 4218 4224 "node_modules/string-width": { 4219 4225 "version": "5.1.2",
+1
package.json
··· 43 43 "@threlte/studio": "^0.1.3", 44 44 "@types/three": "^0.173.0", 45 45 "pixi.js": "^8.7.3", 46 + "stats.js": "^0.17.0", 46 47 "three": "^0.173.0", 47 48 "wiggle": "^0.0.17" 48 49 }
+128 -19
src/lib/Creature.svelte
··· 1 1 <script lang="ts"> 2 2 import { GLTFLoader } from 'three/examples/jsm/Addons.js'; 3 - import * as THREE from 'three'; 3 + import * as THREE from 'three/webgpu'; 4 4 // @ts-ignore 5 5 import { WiggleBone } from 'wiggle'; 6 6 import { onMount } from 'svelte'; 7 7 import { createFaceTexture, updateFace } from './face/face'; 8 8 import { T, useTask, useThrelte } from '@threlte/core'; 9 9 10 - const { scene } = useThrelte(); 11 - 12 10 let rootBone: THREE.Bone; 13 11 const wiggleBones: WiggleBone[] = []; 14 12 let creature: THREE.Group; 15 13 16 - let texture: THREE.CanvasTexture; 14 + let canvasTexture: THREE.CanvasTexture; 15 + import { 16 + color, 17 + float, 18 + Fn, 19 + If, 20 + mx_noise_float, 21 + normalGeometry, 22 + normalLocal, 23 + normalView, 24 + oscSine, 25 + positionGeometry, 26 + positionLocal, 27 + select, 28 + texture, 29 + uniform, 30 + uv, 31 + vec3 32 + } from 'three/tsl'; 33 + 34 + const uniforms = []; 35 + 36 + const mouse = new THREE.Vector2(); 17 37 18 38 onMount(async () => { 39 + // add mouse movement 40 + window.addEventListener('mousemove', (event) => { 41 + // set to be between 0 and 1 42 + mouse.set(event.clientX / window.innerWidth, event.clientY / window.innerHeight); 43 + }); 44 + 19 45 const { app } = await createFaceTexture({ 20 - width: 512, 21 - height: 512 46 + width: 1024, 47 + height: 1024 22 48 }); 23 49 24 - texture = new THREE.CanvasTexture(app.canvas); 50 + canvasTexture = new THREE.CanvasTexture(app.canvas); 25 51 26 52 const loader = new GLTFLoader(); 27 53 ··· 30 56 if (!mesh) return; 31 57 32 58 creature = obj; 59 + let scale = 1.4; 60 + creature.scale.set(scale, scale, scale); 61 + 62 + if (mesh instanceof THREE.Mesh) { 63 + // mesh.material = new THREE.MeshStandardMaterial({ 64 + // map: canvasTexture, 65 + // }); 33 66 34 - creature.position.y = 0.5; 67 + // const detail = texture( canvasTexture, uv().mul( 10 ) ); 68 + 69 + const material = new THREE.MeshStandardNodeMaterial(); 70 + // material.colorNode = positionGeometry.sub(positionLocal); //texture(canvasTexture); 71 + // material.colorNode = color( 0x0066ff ); 72 + material.colorNode = texture(canvasTexture); 73 + 74 + // const limitPosition = Fn(({ position }) => { 75 + // const limit = oscSine(); 76 + 77 + // // Convert to variable using `.toVar()` to be able to use assignments. 78 + // const result = position.toVec3().toVar(); 79 + 80 + // result.y = select( result.y.greaterThan( limit ), limit, result.y ); 81 + 82 + // return result; 83 + // }); 84 + 85 + // material.positionNode = limitPosition({ position: positionLocal }); 86 + 87 + mesh.material = material; 88 + 89 + const nodes = []; 90 + 91 + let total = 16; 92 + for (let i = 0; i < total; i++) { 93 + const material = new THREE.MeshStandardNodeMaterial({ 94 + transparent: true, 95 + opacity: 1, 96 + side: THREE.DoubleSide 97 + }); 98 + 99 + const movement = uniform(vec3(0, 0, 0)); 100 + 101 + uniforms.push(movement); 102 + 103 + material.positionNode = positionGeometry 104 + .add(normalGeometry.mul(i * 0.003)) 105 + .add(movement.add(vec3(0, -0.001, 0)).mul(i * 1)); 106 + 107 + material.colorNode = color(0xffffff).mul((i / total) * 0.8 + 0.2); 108 + 109 + material.alphaTestNode = float(0.5); 110 + 111 + material.opacityNode = select( 112 + texture(canvasTexture).g.greaterThan(0.8).and(texture(canvasTexture).b.lessThan(0.5)), 35 113 36 - if (mesh instanceof THREE.Mesh) { 37 - mesh.material = new THREE.MeshStandardMaterial({ 38 - map: texture, 39 - }); 114 + select( 115 + mx_noise_float(positionGeometry.mul(200)).greaterThan((i / total) * 2 - 1), 116 + 1.0, 117 + 0.0 118 + ), 119 + 0.0 120 + ); 121 + 122 + const grass = mesh.clone(); 123 + grass.material = material; 124 + 125 + // grass.position.y = i * 0.01; 126 + nodes.push(grass); 127 + } 128 + 129 + mesh.add(...nodes); 40 130 } 41 131 42 132 // obj.rotation.y = -Math.PI / 2; 43 133 // obj.lookAt(camera.position); 44 134 45 135 if (mesh instanceof THREE.SkinnedMesh) { 46 - 47 136 mesh.skeleton.bones.forEach((bone) => { 48 137 if (!(bone.parent instanceof THREE.Bone) || !bone.parent?.isBone) { 49 138 rootBone = bone; 50 139 51 - rootBone.rotation.y = -Math.PI / 2; 140 + // rootBone.rotation.y = -Math.PI / 2; 52 141 } else { 53 142 const wiggleBone = new WiggleBone(bone, { 54 143 velocity: 0.4 ··· 60 149 }); 61 150 }); 62 151 152 + let lastPosition = new THREE.Vector3(); 63 153 let total = 0; 64 154 useTask((dt) => { 65 155 total += dt; ··· 67 157 wiggleBone.update(); 68 158 }); 69 159 70 - if (texture) { 160 + if (canvasTexture) { 71 161 updateFace(dt, 0, 0); 72 - texture.needsUpdate = true; 162 + canvasTexture.needsUpdate = true; 73 163 } 74 164 75 - if (rootBone) rootBone.position.x = Math.sin(total * 5) * 0.5; 165 + if (rootBone) { 166 + rootBone.position.z = Math.sin(total * 2) * 0.5; //-(mouse.x - 0.5) * 0.8 + rootBone.position.z * 0.2; 167 + 168 + // uniforms.forEach((uniform) => { 169 + // uniform.value.set( 170 + // (lastPosition.x - rootBone.position.x) * dt, 171 + // (lastPosition.y - rootBone.position.y) * dt, 172 + // (lastPosition.z - rootBone.position.z) * dt 173 + // ); 174 + // }); 175 + 176 + // console.log( 177 + // (lastPosition.x - rootBone.position.x) * dt, 178 + // (lastPosition.y - rootBone.position.y) * dt, 179 + // (lastPosition.z - rootBone.position.z) * dt 180 + // ); 181 + 182 + lastPosition.copy(rootBone.position); 183 + } 76 184 }); 77 185 </script> 78 186 79 - 80 - <T is={creature} /> 187 + {#if creature} 188 + <T is={creature} /> 189 + {/if}
+34
src/lib/Grass.svelte
··· 1 + <script lang="ts"> 2 + import { GLTFLoader } from 'three/examples/jsm/Addons.js'; 3 + import * as THREE from 'three/webgpu'; 4 + 5 + import { onMount } from 'svelte'; 6 + import { T, useTask, useThrelte } from '@threlte/core'; 7 + import { Fn, If, normalView, oscSine, positionGeometry, positionLocal, select, texture, uv, mx_noise_float, color, normalLocal } from 'three/tsl'; 8 + 9 + 10 + const nodes = []; 11 + 12 + let total = 32; 13 + for(let i = 0; i < total; i++) { 14 + const material = new THREE.MeshBasicNodeMaterial({transparent: true, opacity: 1}); 15 + const geometry = new THREE.PlaneGeometry(10, 10); 16 + 17 + material.positionNode = positionLocal.add(normalLocal.mul(i * 0.01)); 18 + 19 + material.colorNode = color(0.2, 0.8, 0.2).mul((i / total) * 0.8 + 0.2); 20 + 21 + material.opacityNode = select( mx_noise_float(uv().mul(500)).greaterThan( (i / total) * 2 - 1 ), 1.0, 0.0 ); 22 + 23 + const grass = new THREE.Mesh(geometry, material); 24 + 25 + grass.rotation.x = -Math.PI / 2; 26 + // grass.position.y = i * 0.01; 27 + nodes.push(grass); 28 + } 29 + 30 + </script> 31 + 32 + {#each nodes as node} 33 + <T is={node} /> 34 + {/each}
+44 -27
src/lib/Room.svelte
··· 4 4 --> 5 5 6 6 <script lang="ts"> 7 - import * as THREE from 'three' 7 + import type * as THREE from 'three' 8 8 9 9 import type { Snippet } from 'svelte' 10 10 import { T, type Props } from '@threlte/core' ··· 28 28 Cube: THREE.Mesh 29 29 couch001: THREE.Mesh 30 30 armchair001: THREE.Mesh 31 - chair_C001: THREE.Mesh 32 31 cabinet_medium_decorated001: THREE.Mesh 33 32 table_small001: THREE.Mesh 34 33 lamp_desk001: THREE.Mesh ··· 38 37 pictureframe_medium001: THREE.Mesh 39 38 pictureframe_large_B001: THREE.Mesh 40 39 lamp_standing001: THREE.Mesh 40 + lamp_standing002: THREE.Mesh 41 41 } 42 42 materials: { 43 43 furniture_texture: THREE.MeshStandardMaterial ··· 55 55 {#await gltf} 56 56 {@render fallback?.()} 57 57 {:then gltf} 58 + <T.PointLight 59 + intensity={10} 60 + decay={2} 61 + position={[2.65, 2.17, -2.57]} 62 + rotation={[-Math.PI / 2, 0, 0]} 63 + color="#ffaa44" 64 + /> 65 + <T.SpotLight 66 + intensity={50} 67 + angle={Math.PI / 9} 68 + penumbra={0.15} 69 + decay={2} 70 + position={[2.34, 2.05, 1.93]} 71 + rotation={[-1.48, 0.31, 0.07]} 72 + color="#ffaa44" 73 + /> 58 74 <T.Mesh 59 75 castShadow 60 76 receiveShadow 61 77 geometry={gltf.nodes.Cube.geometry} 62 - position={[0, 2, 0]} 63 - scale={[1, 0.5, 1]} 64 - > 65 - <T.MeshStandardMaterial side={THREE.DoubleSide} envMapIntensity={0.1} /> 66 - </T.Mesh> 78 + material={gltf.nodes.Cube.material} 79 + position={[0, 1.56, 0]} 80 + scale={[0.78, 0.39, 0.78]} 81 + /> 67 82 <T.Mesh 68 83 castShadow 69 84 receiveShadow 70 85 geometry={gltf.nodes.couch001.geometry} 71 86 material={gltf.materials.furniture_texture} 72 - position={[3.08, 0, -0.64]} 87 + position={[2.33, 0, -0.53]} 73 88 rotation={[0, -1.53, 0]} 74 89 /> 75 90 <T.Mesh ··· 77 92 receiveShadow 78 93 geometry={gltf.nodes.armchair001.geometry} 79 94 material={gltf.materials.furniture_texture} 80 - position={[1.4, 0, -3.1]} 81 - rotation={[0, -0.23, 0]} 82 - /> 83 - <T.Mesh 84 - castShadow 85 - receiveShadow 86 - geometry={gltf.nodes.chair_C001.geometry} 87 - material={gltf.materials.furniture_texture} 88 - position={[-3.11, 0, -3.01]} 89 - rotation={[0, 0.47, 0]} 95 + position={[0.51, 0, -2.45]} 96 + rotation={[0, 0.32, 0]} 90 97 /> 91 98 <T.Mesh 92 99 castShadow 93 100 receiveShadow 94 101 geometry={gltf.nodes.cabinet_medium_decorated001.geometry} 95 102 material={gltf.materials.furniture_texture} 96 - position={[-1.02, 0, -3.56]} 103 + position={[-1.85, 0, -2.7]} 97 104 /> 98 105 <T.Mesh 99 106 castShadow 100 107 receiveShadow 101 108 geometry={gltf.nodes.table_small001.geometry} 102 109 material={gltf.materials.furniture_texture} 103 - position={[3.2, 0, 1.64]} 110 + position={[2.72, 0, 2.01]} 104 111 /> 105 112 <T.Mesh 106 113 castShadow 107 114 receiveShadow 108 115 geometry={gltf.nodes.lamp_desk001.geometry} 109 116 material={gltf.materials.furniture_texture} 110 - position={[3.13, 0.97, 1.67]} 117 + position={[2.65, 0.97, 2.04]} 111 118 rotation={[Math.PI, -1.17, Math.PI]} 112 119 /> 113 120 <T.Mesh ··· 115 122 receiveShadow 116 123 geometry={gltf.nodes.rug_rectangle_stripes_A001.geometry} 117 124 material={gltf.materials.furniture_texture} 118 - position={[-0.64, 0, 1.37]} 125 + position={[-0.87, 0, 1.06]} 119 126 rotation={[0, -0.64, 0]} 120 127 /> 121 128 <T.Mesh ··· 123 130 receiveShadow 124 131 geometry={gltf.nodes.shelf_B_small_decorated001.geometry} 125 132 material={gltf.materials.furniture_texture} 126 - position={[4, 2.25, 2.73]} 133 + position={[3.14, 2.25, 2.35]} 127 134 rotation={[0, -Math.PI / 2, 0]} 128 135 /> 129 136 <T.Mesh ··· 131 138 receiveShadow 132 139 geometry={gltf.nodes.pictureframe_large_A001.geometry} 133 140 material={gltf.materials.furniture_texture} 134 - position={[-2.61, 2.38, -4.02]} 141 + position={[-2.49, 2.38, -3.14]} 135 142 /> 136 143 <T.Mesh 137 144 castShadow 138 145 receiveShadow 139 146 geometry={gltf.nodes.pictureframe_medium001.geometry} 140 147 material={gltf.materials.furniture_texture} 141 - position={[-1.28, 2.57, -4.03]} 148 + position={[-1.15, 2.57, -3.13]} 142 149 /> 143 150 <T.Mesh 144 151 castShadow 145 152 receiveShadow 146 153 geometry={gltf.nodes.pictureframe_large_B001.geometry} 147 154 material={gltf.materials.furniture_texture} 148 - position={[4, 2.77, -0.17]} 155 + position={[3.12, 2.41, -0.07]} 149 156 rotation={[0, -Math.PI / 2, 0]} 150 157 /> 151 158 <T.Mesh ··· 153 160 receiveShadow 154 161 geometry={gltf.nodes.lamp_standing001.geometry} 155 162 material={gltf.materials.furniture_texture} 156 - position={[3.22, 0, -3.24]} 163 + position={[2.64, 0, -2.58]} 164 + /> 165 + <T.Mesh 166 + castShadow 167 + receiveShadow 168 + geometry={gltf.nodes.lamp_standing002.geometry} 169 + material={gltf.materials.furniture_texture.clone()} 170 + position={[2.64, 0, -2.58]} 171 + material.color="#ffaa44" 172 + material.emissive="#ffaa44" 173 + material.emissiveIntensity={3} 157 174 /> 158 175 {:catch err} 159 176 {@render error?.({ error: err })}
+44 -5
src/lib/Scene.svelte
··· 1 1 <script lang="ts"> 2 - import { T } from '@threlte/core'; 2 + import { T, useStage, useTask, useThrelte } from '@threlte/core'; 3 3 import Creature from './Creature.svelte'; 4 - import { Environment, OrbitControls, TransformControls, VirtualEnvironment } from '@threlte/extras'; 4 + import { 5 + Environment, 6 + OrbitControls, 7 + Sky, 8 + TransformControls, 9 + VirtualEnvironment 10 + } from '@threlte/extras'; 5 11 import Room from './Room.svelte'; 6 12 import { DoubleSide } from 'three'; 13 + import { onMount } from 'svelte'; 14 + import * as THREE from 'three'; 15 + import Grass from './Grass.svelte'; 16 + let debug = $state(false); 7 17 8 - let debug = $state(false); 18 + const { renderer } = useThrelte(); 19 + import Stats from 'stats.js'; 20 + var stats = new Stats(); 21 + 22 + onMount(() => { 23 + renderer.toneMapping = THREE.ACESFilmicToneMapping; 24 + renderer.toneMappingExposure = 0.4; 25 + 26 + stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom 27 + document.body.appendChild(stats.dom); 28 + }); 29 + 30 + useTask((delta) => { 31 + stats.begin(); 32 + }); 33 + const { renderStage } = useThrelte(); 34 + 35 + const afterRenderStage = useStage('after-render', { 36 + after: renderStage 37 + }); 38 + useTask( 39 + (delta) => { 40 + stats.end(); 41 + }, 42 + { stage: afterRenderStage } 43 + ); 9 44 </script> 10 45 11 - <T.PerspectiveCamera makeDefault position={[-10, 10, 10]}> 46 + <T.PerspectiveCamera makeDefault position={[5, 1, 0]}> 12 47 <OrbitControls /> 13 48 </T.PerspectiveCamera> 14 49 ··· 16 51 17 52 <Environment isBackground={false} url={'/tiny-creature/workshop.hdr'} /> 18 53 19 - <Room /> 54 + <!-- <Grass /> --> 55 + 56 + <!-- <Room /> --> 20 57 21 58 {#snippet lightformer( 22 59 color: string, ··· 41 78 {/snippet} 42 79 </T.Group> 43 80 {/snippet} 81 + 82 + <!-- <Sky /> --> 44 83 <VirtualEnvironment visible={debug}> 45 84 {@render lightformer('#FFfFfF', 'plane', 20, [0, 0, -20], debug)} 46 85 {@render lightformer('#FFD0CB', 'circle', 5, [0, 5, 0], debug)}
+19 -10
src/lib/face/face.ts
··· 1 - import { Application, type Renderer } from 'pixi.js'; 1 + import { Application, 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 18 const app = new Application(); 18 19 19 20 await app.init({ 20 21 width, 21 22 height, 22 - background: '#db2777' 23 + background: '#ffff00' 23 24 }); 24 25 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); 33 + 25 34 rightEye = new Eye({ 26 - x: width * 0.5 + 75, 27 - y: height * 0.5 + 100, 28 - size: 50 35 + x: width * 0.5 + 75 * scale, 36 + y: height * 0.5 + 50 * scale, 37 + size: 50 * scale 29 38 }); 30 39 31 40 leftEye = new Eye({ 32 - x: width * 0.5 - 75, 33 - y: height * 0.5 + 100, 34 - size: 50 41 + x: width * 0.5 - 75 * scale, 42 + y: height * 0.5 + 50 * scale, 43 + size: 50 * scale 35 44 }); 36 45 37 46 app.stage.addChild(rightEye.container); ··· 39 48 40 49 mouth = new Mouth({ 41 50 x: width * 0.5, 42 - y: height * 0.5 - 20, 43 - size: 50 51 + y: height * 0.5 - 70 * scale, 52 + size: 50 * scale 44 53 }); 45 54 app.stage.addChild(mouth.container); 46 55
+17 -6
src/routes/+page.svelte
··· 1 - <script> 1 + <script lang="ts"> 2 2 import { Canvas } from '@threlte/core'; 3 3 import Scene from '$lib/Scene.svelte'; 4 + import { Studio } from '@threlte/studio'; 5 + import { WebGPURenderer } from 'three/webgpu' 6 + import { PerfMonitor } from '@threlte/extras'; 7 + 4 8 </script> 5 9 6 10 <div class="h-screen w-screen"> 7 - <Canvas> 8 - <Scene /> 9 - </Canvas> 10 - 11 - </div> 11 + <Canvas createRenderer={(canvas) => { 12 + return new WebGPURenderer({ 13 + canvas, 14 + antialias: true, 15 + forceWebGL: false 16 + }) 17 + }}> 18 + <Studio> 19 + <Scene /> 20 + </Studio> 21 + </Canvas> 22 + </div>
static/favicon.png

This is a binary file and will not be displayed.

static/room-transformed.glb

This is a binary file and will not be displayed.

static/room.glb

This is a binary file and will not be displayed.

+2 -1
vite.config.ts
··· 1 1 import { sveltekit } from '@sveltejs/kit/vite'; 2 2 import { defineConfig } from 'vite'; 3 + import { threlteStudio } from '@threlte/studio/vite'; 3 4 4 5 export default defineConfig({ 5 - plugins: [sveltekit()] 6 + plugins: [threlteStudio(), sveltekit()] 6 7 });