Categories
.glb blog officialstupid threejs

2D glb mix

new cooking… tiny achievement – https://mero.live/build/battle/

mixing GLB #mixamo#three.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Battle</title>
    <style>
        body { margin: 0; overflow: hidden; background-color: #000; font-family: 'Arial Black', sans-serif; }
        #ui {
            position: absolute;
            top: 20px;
            left: 20px;
            color: #fff; 
            padding: 15px; 
            text-transform: uppercase;
            z-index: 10; 
            font-size: 12px;
        }
        .key-bind { color: #ffeb3b; font-weight: bold; }
    </style>
</head>
<body>

    <div id="ui">
        <h2 style="margin:0 0 10px 0">Battle</h2>
        <div><span class="key-bind">A / D :</span> Walk</div>
        <div><span class="key-bind">Shift + A/D :</span> Run</div>
        <div><span class="key-bind">A :</span> Light Slash</div>
        <div><span class="key-bind">J :</span> Downward Hit</div>
        <div><span class="key-bind">K :</span> Combo Attack</div>
        <div><span class="key-bind">L :</span> Jump Attack</div>
        <div><span class="key-bind">N :</span> Block</div>
        <div><span class="key-bind">M :</span> Crouch (Hold)</div>
        <div><span class="key-bind">, :</span> Roll</div>
        <div><span class="key-bind">. :</span> Side Kick</div>
        <div><span class="key-bind">; :</span> Backflip Uppercut</div>
    </div>

    <script type="importmap">
        {
            "imports": {
                "three": "https://unpkg.com/three@0.160.0/build/three.module.js",
                "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
            }
        }
    </script>

    <script type="module">
        import * as THREE from 'three';
        import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
        import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';

        const ANIMATIONS_MAP = {
            idle: 'Idle-main_compressed.glb', 
            walk: 'unarmed_walk_compressed.glb', 
            run: 'run_compressed.glb',
            block: 'sword_block_compressed.glb',
            crouch: 'sword_crouch_compressed.glb',
            roll: 'run_roll_compressed.glb',
            attack1: 'sword_slash_compressed.glb',
            attack2: 'sword_down_compressed.glb',
            attack3: 'sword_combo_compressed.glb',
            attack4: 'sword_jump_compressed.glb',
            kick: 'Side Kick_compressed.glb',
            uppercut: 'Back Flip To Uppercut_compressed.glb'
        };

        // --- ADJUST THIS VALUE TO FIX THE FLOATING ---
        // If character is still floating, make this more negative (e.g., -0.8)
        // If character is inside the floor, make it closer to 0 (e.g., -0.2)
        const CROUCH_Y_OFFSET = -0.5; 

        let scene, camera, renderer, clock, mixer;
        let characterGroup, model;
        let actions = {}; 
        let activeAction;
        let isActionLocked = false; 
        let keys = {};
        let facingDirection = 1;

        init();

        async function init() {
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x111111);
            clock = new THREE.Clock();

            camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.set(0, 1.5, 4); 
            camera.lookAt(0, 1, 0);

            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.shadowMap.enabled = true;
            document.body.appendChild(renderer.domElement);

            scene.add(new THREE.HemisphereLight(0xffffff, 0x444444, 3));
            const dirLight = new THREE.DirectionalLight(0xffffff, 2);
            dirLight.position.set(5, 10, 5);
            dirLight.castShadow = true;
            scene.add(dirLight);

            const floor = new THREE.Mesh(new THREE.PlaneGeometry(100, 50), new THREE.MeshPhongMaterial({ color: 0x222222 }));
            floor.rotation.x = -Math.PI / 2;
            floor.receiveShadow = true;
            scene.add(floor);

            characterGroup = new THREE.Group(); 
            scene.add(characterGroup);

            const dracoLoader = new DRACOLoader();
            dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/');

            const loader = new GLTFLoader();
            loader.setDRACOLoader(dracoLoader);

            try {
                const gltf = await loader.loadAsync(ANIMATIONS_MAP.walk);
                model = gltf.scene;
                model.scale.set(1, 1, 1); 
                model.rotation.y = Math.PI / 2; 
                characterGroup.add(model);
                
                mixer = new THREE.AnimationMixer(model);

                for (const [key, file] of Object.entries(ANIMATIONS_MAP)) {
                    const gltfAnim = await loader.loadAsync(file);
                    let clip = gltfAnim.animations[0];
                    
                    // Remove root position to prevent popping
                    clip.tracks = clip.tracks.filter(track => !track.name.includes('.position'));

                    const action = mixer.clipAction(clip);
                    if (key.startsWith('attack') || key === 'roll' || key === 'kick' || key === 'uppercut' || key === 'crouch' || key === 'block') {
                        action.setLoop(THREE.LoopOnce);
                        action.clampWhenFinished = true;
                    } else {
                        action.setLoop(THREE.LoopRepeat);
                    }
                    actions[key] = action;
                }
                snapToAction('idle');
            } catch (e) { console.error("Loading error:", e); }

            window.addEventListener('keydown', (e) => {
                const k = e.key.toLowerCase();
                keys[k] = true;
                handleCombat(k);
            });
            window.addEventListener('keyup', (e) => {
                keys[e.key.toLowerCase()] = false;
            });

            animate();
        }

        function handleCombat(key) {
            if (isActionLocked) return;
            if (key === 'j') playOneShot('attack2');
            else if (key === 'k') playOneShot('attack3');
            else if (key === 'l') playOneShot('attack4');
            else if (key === 'n') snapToAction('block');
            else if (key === 'm') snapToAction('crouch'); 
            else if (key === ',') playOneShot('roll');
            else if (key === '.') playOneShot('kick');
            else if (key === ';') playOneShot('uppercut');
            else if (key === 'a' && !keys['d']) playOneShot('attack1');
        }

        function playOneShot(name) {
            isActionLocked = true; 
            const action = actions[name];
            if(activeAction) activeAction.stop();
            action.reset().play();
            activeAction = action;

            const onFinished = (e) => {
                if (e.action === action) {
                    isActionLocked = false; 
                    if (keys['m']) snapToAction('crouch');
                    else if (keys['n']) snapToAction('block');
                    else snapToAction('idle');
                    mixer.removeEventListener('finished', onFinished);
                }
            };
            mixer.addEventListener('finished', onFinished);
        }

        function snapToAction(name) {
            if (isActionLocked && name !== 'idle') return; 
            const action = actions[name];
            if (activeAction !== action) {
                if (activeAction && !isActionLocked) activeAction.fadeOut(0.1);
                action.reset().setEffectiveWeight(1).fadeIn(0.1).play();
                activeAction = action;
            }
        }

        function updateMovement(delta) {
            if (!characterGroup) return;

            // FORCE GROUND POSITION
            characterGroup.position.y = 0;

            // DYNAMIC OFFSET FIX: 
            // If the current active animation is 'crouch', we physically push 
            // the model down relative to the group to compensate for the floating pose.
            if (activeAction === actions['crouch']) {
                model.position.y = CROUCH_Y_OFFSET;
            } else {
                model.position.y = 0;
            }

            if (!isActionLocked) {
                const walkSpeed = 1.8; 
                const runSpeed = 4.0; 
                const currentSpeed = keys['shift'] ? runSpeed : walkSpeed;
                const currentAnim = keys['shift'] ? 'run' : 'walk';

                if (keys['d']) { 
                    facingDirection = 1;
                    characterGroup.rotation.y = 0; 
                    characterGroup.position.x += currentSpeed * delta;
                    snapToAction(currentAnim);
                } else if (keys['a']) { 
                    facingDirection = -1;
                    characterGroup.rotation.y = Math.PI; 
                    characterGroup.position.x -= currentSpeed * delta;
                    snapToAction(currentAnim);
                } 
                else if (!keys['m'] && !keys['n']) {
                    snapToAction('idle');
                } 
                else if (keys['m']) {
                    snapToAction('crouch');
                }
                else {
                    snapToAction('block');
                }
            }

            characterGroup.position.x = Math.max(-10, Math.min(10, characterGroup.position.x));
        }

        function animate() {
            requestAnimationFrame(animate);
            const delta = clock.getDelta();
            if (mixer) mixer.update(delta);
            updateMovement(delta);
            renderer.render(scene, camera);
        }

        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });
    </script>
</body>
</html>
Categories
.glb blog threejs world

world – new glb

Categories
blog officialstupid threejs

Pebble: Getting Back Into Three.js Monkey Business

https://officialstupid.me/workshop/pebble

Water physics. Trying to master water! 😄

Categories
game threejs

world

Screenshot 2026 06 03 at 13.50.34

working on new tiny open world game for fun and learning!!

notes or progress or log or ideas : https://www.reddit.com/r/officialstupid/

Categories
blog cms threejs

Yes – I’m happy!!

After 5 days of digging, I finally stopped my madness over the text editor—dragging, cloning, sorting, blah blah blah—and got back to the core part.

Three.js cross-origin ā€˜Russian doll’ effect at the end.

Categories
blog officialstupid threejs toy box toydoka

ToyDoka – profile

So, here we go again.

I really tried to stop this time. After finally giving this ā€œtoyboxā€ a proper domain toydoka.com, I thought maybe that would be enough—a small sense of closure. But somehow, I’ve found myself right back in it. Same loop, same energy… here we go again.

Over the past couple of days – yesterday and the day before – I’ve already poured more than two full days into this. The homepage is done, the core structure is in place, and the marketplace is up and running.

Right now, I’m working on the profile system and the profile edit features. It’s one of those parts that seems simple at first, but quickly turns into something deeper, because profiles aren’t just forms, they shape how people exist inside the platform.

There’s something strange but exciting about this phase. It’s messy, a bit chaotic, but also very alive. Even when I try to step away, I keep coming back to tweak, improve, and build just a little more.

Maybe that’s the nature of creating something from scratch. You don’t really ā€œstopā€ you just pause, and then eventually return.

Anyway, if you’re curious, here’s where it’s at right now:
https://toydoka.com/me/officialstupid

Let’s see where this goes next.

Categories
blog threejs toy box

Toybox found its home – https://toydoka.com

Screenshot 2026 04 07 at 01.09.23

https://toydoka.com new home for toybox finally I made up my mind to use domain…

Categories
blog threejs toy box

TOYBOX – update

Screenshot 2026 04 05 at 02.18.03

16+ hours

  • community
  • build and save
  • quick – sign up & login
  • export and import json
  • like
  • auto load new build at homepage
  • whole new light UI with less buttons and I think best UX


https://officialstupid.com/toybox/

02:25 AM, Sun 5 Apr

Categories
.glb blog threejs

Text – update

  • image
  • color
  • background color
  • export png
  • export glb
  • right click – stand text
  • arrange
Categories
blog officialstupid threejs

3d text

base on talk at r/threejs officialstupid.com/text/

Categories
blog game threejs

Cannon Solo and VS mode game

https://cannon.officialstupid.com