<!DOCTYPE html> <html lang="en"> <head> <title>three.js webgl - LDrawLoader</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> <link type="text/css" rel="stylesheet" href="main.css"> <style> body { color: #444; } a { color: #08f; } </style> </head> <body> <div id="info"> <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - LDrawLoader </div> <!-- Import maps polyfill --> <!-- Remove this when import maps will be widely supported --> <script async src="https://unpkg.com/es-module-shims@1.8.0/dist/es-module-shims.js"></script> <script type="importmap"> { "imports": { "three": "../build/three.module.js", "three/addons/": "./jsm/" } } </script> <script type="module"> import * as THREE from 'three'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js'; import { LDrawLoader } from 'three/addons/loaders/LDrawLoader.js'; import { LDrawUtils } from 'three/addons/utils/LDrawUtils.js'; let container, progressBarDiv; let camera, scene, renderer, controls, gui, guiData; let model; const ldrawPath = 'models/ldraw/officialLibrary/'; const modelFileList = { 'Car': 'models/car.ldr_Packed.mpd', 'Lunar Vehicle': 'models/1621-1-LunarMPVVehicle.mpd_Packed.mpd', 'Radar Truck': 'models/889-1-RadarTruck.mpd_Packed.mpd', 'Trailer': 'models/4838-1-MiniVehicles.mpd_Packed.mpd', 'Bulldozer': 'models/4915-1-MiniConstruction.mpd_Packed.mpd', 'Helicopter': 'models/4918-1-MiniFlyers.mpd_Packed.mpd', 'Plane': 'models/5935-1-IslandHopper.mpd_Packed.mpd', 'Lighthouse': 'models/30023-1-Lighthouse.ldr_Packed.mpd', 'X-Wing mini': 'models/30051-1-X-wingFighter-Mini.mpd_Packed.mpd', 'AT-ST mini': 'models/30054-1-AT-ST-Mini.mpd_Packed.mpd', 'AT-AT mini': 'models/4489-1-AT-AT-Mini.mpd_Packed.mpd', 'Shuttle': 'models/4494-1-Imperial Shuttle-Mini.mpd_Packed.mpd', 'TIE Interceptor': 'models/6965-1-TIEIntercep_4h4MXk5.mpd_Packed.mpd', 'Star fighter': 'models/6966-1-JediStarfighter-Mini.mpd_Packed.mpd', 'X-Wing': 'models/7140-1-X-wingFighter.mpd_Packed.mpd', 'AT-ST': 'models/10174-1-ImperialAT-ST-UCS.mpd_Packed.mpd' }; init(); animate(); function init() { container = document.createElement( 'div' ); document.body.appendChild( container ); camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 ); camera.position.set( 150, 200, 250 ); // renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.toneMapping = THREE.ACESFilmicToneMapping; container.appendChild( renderer.domElement ); // scene const pmremGenerator = new THREE.PMREMGenerator( renderer ); scene = new THREE.Scene(); scene.background = new THREE.Color( 0xdeebed ); scene.environment = pmremGenerator.fromScene( new RoomEnvironment( renderer ) ).texture; controls = new OrbitControls( camera, renderer.domElement ); controls.enableDamping = true; // guiData = { modelFileName: modelFileList[ 'Car' ], displayLines: true, conditionalLines: true, smoothNormals: true, buildingStep: 0, noBuildingSteps: 'No steps.', flatColors: false, mergeModel: false }; window.addEventListener( 'resize', onWindowResize ); progressBarDiv = document.createElement( 'div' ); progressBarDiv.innerText = 'Loading...'; progressBarDiv.style.fontSize = '3em'; progressBarDiv.style.color = '#888'; progressBarDiv.style.display = 'block'; progressBarDiv.style.position = 'absolute'; progressBarDiv.style.top = '50%'; progressBarDiv.style.width = '100%'; progressBarDiv.style.textAlign = 'center'; // load materials and then the model reloadObject( true ); } function updateObjectsVisibility() { model.traverse( c => { if ( c.isLineSegments ) { if ( c.isConditionalLine ) { c.visible = guiData.conditionalLines; } else { c.visible = guiData.displayLines; } } else if ( c.isGroup ) { // Hide objects with building step > gui setting c.visible = c.userData.buildingStep <= guiData.buildingStep; } } ); } function reloadObject( resetCamera ) { if ( model ) { scene.remove( model ); } model = null; updateProgressBar( 0 ); showProgressBar(); // only smooth when not rendering with flat colors to improve processing time const lDrawLoader = new LDrawLoader(); lDrawLoader.smoothNormals = guiData.smoothNormals && ! guiData.flatColors; lDrawLoader .setPath( ldrawPath ) .load( guiData.modelFileName, function ( group2 ) { if ( model ) { scene.remove( model ); } model = group2; // demonstrate how to use convert to flat colors to better mimic the lego instructions look if ( guiData.flatColors ) { function convertMaterial( material ) { const newMaterial = new THREE.MeshBasicMaterial(); newMaterial.color.copy( material.color ); newMaterial.polygonOffset = material.polygonOffset; newMaterial.polygonOffsetUnits = material.polygonOffsetUnits; newMaterial.polygonOffsetFactor = material.polygonOffsetFactor; newMaterial.opacity = material.opacity; newMaterial.transparent = material.transparent; newMaterial.depthWrite = material.depthWrite; newMaterial.toneMapping = false; return newMaterial; } model.traverse( c => { if ( c.isMesh ) { if ( Array.isArray( c.material ) ) { c.material = c.material.map( convertMaterial ); } else { c.material = convertMaterial( c.material ); } } } ); } // Merge model geometries by material if ( guiData.mergeModel ) model = LDrawUtils.mergeObject( model ); // Convert from LDraw coordinates: rotate 180 degrees around OX model.rotation.x = Math.PI; scene.add( model ); guiData.buildingStep = model.userData.numBuildingSteps - 1; updateObjectsVisibility(); // Adjust camera and light const bbox = new THREE.Box3().setFromObject( model ); const size = bbox.getSize( new THREE.Vector3() ); const radius = Math.max( size.x, Math.max( size.y, size.z ) ) * 0.5; if ( resetCamera ) { controls.target0.copy( bbox.getCenter( new THREE.Vector3() ) ); controls.position0.set( - 2.3, 1, 2 ).multiplyScalar( radius ).add( controls.target0 ); controls.reset(); } createGUI(); hideProgressBar(); }, onProgress, onError ); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); } function createGUI() { if ( gui ) { gui.destroy(); } gui = new GUI(); gui.add( guiData, 'modelFileName', modelFileList ).name( 'Model' ).onFinishChange( function () { reloadObject( true ); } ); gui.add( guiData, 'flatColors' ).name( 'Flat Colors' ).onChange( function () { reloadObject( false ); } ); gui.add( guiData, 'mergeModel' ).name( 'Merge model' ).onChange( function () { reloadObject( false ); } ); if ( model.userData.numBuildingSteps > 1 ) { gui.add( guiData, 'buildingStep', 0, model.userData.numBuildingSteps - 1 ).step( 1 ).name( 'Building step' ).onChange( updateObjectsVisibility ); } else { gui.add( guiData, 'noBuildingSteps' ).name( 'Building step' ).onChange( updateObjectsVisibility ); } gui.add( guiData, 'smoothNormals' ).name( 'Smooth Normals' ).onChange( function changeNormals() { reloadObject( false ); } ); gui.add( guiData, 'displayLines' ).name( 'Display Lines' ).onChange( updateObjectsVisibility ); gui.add( guiData, 'conditionalLines' ).name( 'Conditional Lines' ).onChange( updateObjectsVisibility ); } // function animate() { requestAnimationFrame( animate ); controls.update(); render(); } function render() { renderer.render( scene, camera ); } function onProgress( xhr ) { if ( xhr.lengthComputable ) { updateProgressBar( xhr.loaded / xhr.total ); console.log( Math.round( xhr.loaded / xhr.total * 100, 2 ) + '% downloaded' ); } } function onError( error ) { const message = 'Error loading model'; progressBarDiv.innerText = message; console.log( message ); console.error( error ); } function showProgressBar() { document.body.appendChild( progressBarDiv ); } function hideProgressBar() { document.body.removeChild( progressBarDiv ); } function updateProgressBar( fraction ) { progressBarDiv.innerText = 'Loading... ' + Math.round( fraction * 100, 2 ) + '%'; } </script> <!-- LDraw.org CC BY 2.0 Parts Library attribution --> <div style="display: block; position: absolute; bottom: 8px; left: 8px; width: 160px; padding: 10px; background-color: #F3F7F8;"> <center> <a href="http://www.ldraw.org"><img style="width: 145px" src="models/ldraw/ldraw_org_logo/Stamp145.png"></a> <br /> <a href="http://www.ldraw.org/">This software uses the LDraw Parts Library</a> </center> </div> </body> </html>