Created
February 24, 2026 16:53
-
-
Save drawby/b54d874182cf05748eebe97e030ebc85 to your computer and use it in GitHub Desktop.
Untitled
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>☃️ Snowman Builder</title> | |
| <link rel="stylesheet" href="style.css"> | |
| </head> | |
| <body> | |
| <h1>☃️ Build Your Snowman!</h1> | |
| <div id="container"> | |
| <div id="ui"> | |
| <h2>Head</h2> | |
| <button onclick="loadPart('head','head1.glb')">Happy</button> | |
| <button onclick="loadPart('head','head2.glb')">Cool</button> | |
| <h2>Torso</h2> | |
| <button onclick="loadPart('torso','torso1.glb')">Round</button> | |
| <button onclick="loadPart('torso','torso2.glb')">Fat</button> | |
| <h2>Bottom</h2> | |
| <button onclick="loadPart('bottom','bottom1.glb')">Big</button> | |
| <button onclick="loadPart('bottom','bottom2.glb')">Huge</button> | |
| <h2>Resize Head</h2> | |
| <input type="range" min="0.5" max="2" | |
| step="0.1" | |
| value="1" | |
| oninput="resizePart('head', this.value)"> | |
| <button onclick="exportSTL()">Export STL</button> | |
| </div> | |
| <canvas id="canvas"></canvas> | |
| </div> | |
| <script type="module" src="app.js"></script> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import * as THREE from 'https://unpkg.com/three@0.160.0/build/three.module.js'; | |
| import { OrbitControls } | |
| from 'https://unpkg.com/three@0.160.0/examples/jsm/controls/OrbitControls.js'; | |
| import { GLTFLoader } | |
| from 'https://unpkg.com/three@0.160.0/examples/jsm/loaders/GLTFLoader.js'; | |
| import { STLExporter } | |
| from 'https://unpkg.com/three@0.160.0/examples/jsm/exporters/STLExporter.js'; | |
| let scene, camera, renderer, controls; | |
| let snowman; | |
| let parts = { | |
| head:null, | |
| torso:null, | |
| bottom:null | |
| }; | |
| const loader = new GLTFLoader(); | |
| init(); | |
| animate(); | |
| function init(){ | |
| scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x87CEEB); | |
| camera = new THREE.PerspectiveCamera( | |
| 75, | |
| (window.innerWidth-300)/window.innerHeight, | |
| 0.1, | |
| 1000 | |
| ); | |
| camera.position.set(0,3,6); | |
| renderer = new THREE.WebGLRenderer({ | |
| canvas:document.getElementById("canvas"), | |
| antialias:true | |
| }); | |
| renderer.setSize(window.innerWidth-300,window.innerHeight); | |
| controls = new OrbitControls(camera,renderer.domElement); | |
| const light = new THREE.DirectionalLight(0xffffff,1); | |
| light.position.set(5,10,5); | |
| scene.add(light); | |
| const ambient = new THREE.AmbientLight(0xffffff,0.6); | |
| scene.add(ambient); | |
| snowman = new THREE.Group(); | |
| scene.add(snowman); | |
| } | |
| window.loadPart = function(type,file){ | |
| const path = `models/${type}s/${file}`; | |
| loader.load(path,function(gltf){ | |
| const model = gltf.scene; | |
| model.traverse(child=>{ | |
| if(child.isMesh){ | |
| child.castShadow=true; | |
| child.receiveShadow=true; | |
| } | |
| }); | |
| if(parts[type]) | |
| snowman.remove(parts[type]); | |
| parts[type]=model; | |
| snowman.add(model); | |
| snapParts(); | |
| }); | |
| }; | |
| function snapParts(){ | |
| if(parts.bottom){ | |
| const bottomBox = | |
| new THREE.Box3().setFromObject(parts.bottom); | |
| const bottomHeight = | |
| bottomBox.max.y-bottomBox.min.y; | |
| parts.bottom.position.y = 0; | |
| if(parts.torso){ | |
| const torsoBox = | |
| new THREE.Box3().setFromObject(parts.torso); | |
| const torsoHeight = | |
| torsoBox.max.y-torsoBox.min.y; | |
| parts.torso.position.y = | |
| bottomHeight; | |
| if(parts.head){ | |
| const headBox = | |
| new THREE.Box3().setFromObject(parts.head); | |
| const headHeight = | |
| headBox.max.y-headBox.min.y; | |
| parts.head.position.y = | |
| bottomHeight + torsoHeight; | |
| } | |
| } | |
| } | |
| } | |
| window.resizePart=function(type,scale){ | |
| if(parts[type]){ | |
| parts[type].scale.set(scale,scale,scale); | |
| snapParts(); | |
| } | |
| }; | |
| window.exportSTL=function(){ | |
| const exporter=new STLExporter(); | |
| const clone=snowman.clone(true); | |
| clone.updateMatrixWorld(true); | |
| const result=exporter.parse(clone); | |
| const blob=new Blob([result],{type:'text/plain'}); | |
| const link=document.createElement("a"); | |
| link.href=URL.createObjectURL(blob); | |
| link.download="snowman.stl"; | |
| link.click(); | |
| }; | |
| function animate(){ | |
| requestAnimationFrame(animate); | |
| snowman.rotation.y+=0.005; | |
| renderer.render(scene,camera); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| body{ | |
| margin:0; | |
| font-family: Comic Sans MS, Arial; | |
| background:#87CEEB; | |
| text-align:center; | |
| } | |
| h1{ | |
| font-size:40px; | |
| } | |
| #container{ | |
| display:flex; | |
| } | |
| #ui{ | |
| width:300px; | |
| background:white; | |
| padding:20px; | |
| } | |
| button{ | |
| font-size:20px; | |
| padding:10px; | |
| margin:5px; | |
| width:100%; | |
| border-radius:10px; | |
| border:none; | |
| background:#ffcc00; | |
| cursor:pointer; | |
| } | |
| button:hover{ | |
| background:#ffaa00; | |
| } | |
| input[type="range"]{ | |
| width:100%; | |
| } | |
| canvas{ | |
| flex-grow:1; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment