<script type="importmap">
			{
				"imports": {
					"three": "https://unpkg.com/three@0.135.0/build/three.module.js"
				}
			}
</script>
import * as THREE from "https://threejs.org/build/three.module.js";

import { USDZExporter } from "https://threejs.org/examples/jsm/exporters/USDZExporter.js";


import { FontLoader } from "https://threejs.org/examples/jsm/loaders/FontLoader.js";


import { TextGeometry } from "https://threejs.org/examples/jsm/geometries/TextGeometry.js";


import { HDRCubeTextureLoader } from "https://threejs.org/examples/jsm/loaders/HDRCubeTextureLoader.js";



class App {
  
  
    initHDR() {
        this.renderer.physicallyCorrectLights = true;
        this.renderer.toneMapping = THREE.LinearToneMapping;
        this.renderer.outputEncoding = THREE.sRGBEncoding;


    
        var pmremGenerator = new THREE.PMREMGenerator( this.renderer );

        var hdrUrls = [ 'px.hdr', 'nx.hdr', 'py.hdr', 'ny.hdr', 'pz.hdr', 'nz.hdr' ];
				this.hdrCubeMap = new HDRCubeTextureLoader()
					.setPath( 'https://threejs.org/examples/textures/cube/pisaHDR/' )
					.setDataType( THREE.HalfFloatType )
					.load( hdrUrls,  ()=> {

						this.hdrCubeRenderTarget = pmremGenerator.fromCubemap( this.hdrCubeMap );

						this.hdrCubeMap.magFilter = THREE.LinearFilter;
						this.hdrCubeMap.needsUpdate = true;
          
          this.scene.background = this.hdrCubeMap;
          this.worldEnvMap = this.hdrCubeRenderTarget.texture;
        
            this.scene.traverse(el=>{
              if(!el.material) return;
              el.material.envMap = this.hdrCubeRenderTarget.texture;
              el.material.needsUpdate = true;
             
            });
        } );
    
      }
 
  constructor() {
    
    this.renderer = new THREE.WebGLRenderer();
    this.scene = new THREE.Scene();
    this.objects = new THREE.Scene();
    this.scene.add(this.objects);
    this.camera = new THREE.PerspectiveCamera(45, innerWidth/(innerHeight), .01, 10);
    
    this.renderer.setSize(innerWidth, innerHeight);
    this.renderer.setClearColor(0x404040);
    this.renderer.setAnimationLoop(e=> this.update(e));
    this.renderer.setPixelRatio(devicePixelRatio);
    document.body.appendChild(this.renderer.domElement);
    
    this.addSticks();
    
    this.initHDR();
    
    Object.assign(this.renderer.domElement.style, {
      position:"fixed",
      top:0,
      left:0
    });    

//     for(var i =-3;i<3;i++) {
//     const b = this.makeBottle();
//       b.position.x = i/10.
//       this.scene.add(b);
      
//     }
    this.addUSDZButton();
    this.init();
    this.addInput();
  }
  
  makeBottle() {


    const bottle = new THREE.Object3D();
    const smoothstep = (low,high, v) =>{
        v = (v-low)/(high-low);
        v = Math.max(0, Math.min(1, v));
        return v * v * (3-2*v);
    }

    const capVerts = [];
    const bottleVerts = [];
    const capTop = Math.random();
    const lowBottle = Math.random();
    const highBottle = Math.random();
    const capHeight = Math.random()+0.2;
    capVerts.push(new THREE.Vector2(0,0));
    for(var y=0;y<=1;y+=1/30) {
        capVerts.push(
            new THREE.Vector2(smoothstep(0,0.2,y) * smoothstep(1, capTop, y)*0.3 ,y*capHeight)
        );
    
        bottleVerts.push(
            new THREE.Vector2(smoothstep(0,highBottle,y) * smoothstep(1, lowBottle, y)*0.5+0.3 ,y)
        );
    }
    capVerts.push(new THREE.Vector2(0,1));

    const bottleGeo = new THREE.LatheGeometry(bottleVerts,32);
    const capGeo = new THREE.LatheGeometry(capVerts,32);
    const capMesh = new THREE.Mesh(capGeo,
        new THREE.MeshStandardMaterial({
            metalness:(Math.random()>0.5?0:1),
            color:Math.random()*0xffffff,
            roughness:0.05
        }));

    const bottleMesh = new THREE.Mesh(bottleGeo,
        new THREE.MeshStandardMaterial({
            metalness:0,
            color:Math.random()*0xffffff,
            roughness:0.0
        }));
        bottle.add(capMesh);
        bottle.add(bottleMesh);
        capMesh.position.y+=capHeight/2+0.25;
        bottle.scale.multiplyScalar(0.05);
        return bottle;
}
  
  addSticks() {
    
    for(let i =0;i<100;i++) {
      
      const m = new THREE.Mesh(
        new THREE.BoxGeometry(0.01, 0.01, 0.1), 
        new THREE.MeshStandardMaterial({
          color:Math.random()*0xffffff
        }
     ));
      this.objects.add(m);
      m.position.set(Math.random()*1.0-.5,Math.random()*1.0-.5,Math.random()*1.0-.5);
      m.lookAt(new THREE.Vector3());
      m.position.normalize().multiplyScalar(0.4);
      
    }
    
  }
      addInput() {
        const textInput = Object.assign(document.createElement('input'),
        
        {type:"text",
        placeholder:"type to update text"});
        textInput.addEventListener("change", e=>{
            this.drawText(textInput.value);
        });
        Object.assign(textInput.style, {
            position:'fixed',
            top:'10px',
            height:'20px',
            zIndex:'99',
          
            right:'30px',
            width:'50%',
          padding:'10px',
          borderRadius:'20px'
        });
        document.body.appendChild(textInput);
    }

  
  addUSDZButton() {
        
    this.button = document.createElement('div');
    this.button.addEventListener('click', e=>this.saveFile(e));
    document.body.appendChild(this.button);
    Object.assign(this.button.style, {
      position:'fixed',
      top:'10px',
      left:'10px',
      border:'1px solid black',
      backgroundColor:'white',
      padding:'9px',
      fontFamily:'Arial, sans-serif',
      borderRadius:'5px',
      cursor:'pointer'
    });
  this.button.innerHTML = "View in AR";

  }
  
  
  async init() {
     
     
    await this.drawText();
    
    
    this.addUSDZButton();
}
  
  update(evt) {
    this.camera.lookAt(new THREE.Vector3());
    this.camera.position.set(
      0.5*Math.sin(Date.now()/3000), 
      0.3, 
      0.5*Math.cos(Date.now()/2000));
    
    this.renderer.render(this.scene, this.camera);
  }  
  
  
      async drawText(text="It works!") {

        const font = await this.loadFont('https://threejs.org/examples/fonts/optimer_bold.typeface.json');

        // const text = "Yes";
        const textGeo = new TextGeometry( text, {
            font, size: 0.1, 
           bevelEnabled: true,
            bevelThickness: .002,
            bevelSize: .002,
            // bevelOffset: -0.0025,
            bevelSegments: 1,
          height: 0.01 } );

        const m = new THREE.Mesh(textGeo, new THREE.MeshStandardMaterial({
            color:0xc0e0ff,
          metalness:1,
          envMap:this.worldEnvMap,
          roughness:0.1
        }));

        if(this.textMesh!=null) this.objects.remove(this.textMesh);
        this.textMesh = m;
        this.objects.add(m);
        m.position.x -=0.1*text.length/3;
    }
    
  
  makeImage() {
    const c = document.createElement('canvas');
    
    const i = new Image();
    i.src = c.toDataURL();
    return i;
  }
  async saveFile() {
     const exporter = new USDZExporter();
        const data = await exporter.parse( this.scene );
    
const blob = new Blob( [ data ], { type: 'application/octet-stream' });
    const a = Object.assign(document.createElement('a'), {
            download:'model.usdz',
          rel:"ar",
            href:URL.createObjectURL(blob )
        });
    const i = this.makeImage();
    a.appendChild(i);

        a.click();
    }


    
    async loadFont(url) {
        return new Promise(resolve=>{ new FontLoader().load( url, result=>resolve(result))})}

  
}

const app = new App();

Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.