import * as THREE from 'three'; import { UIBreak, UIButton, UIInteger, UIPanel, UIRow, UISelect, UIText } from './libs/ui.js'; import { ViewportPathtracer } from './Viewport.Pathtracer.js'; function SidebarProjectImage( editor ) { const strings = editor.strings; const container = new UIPanel(); container.setId( 'render' ); // Image container.add( new UIText( strings.getKey( 'sidebar/project/image' ) ).setTextTransform( 'uppercase' ) ); container.add( new UIBreak(), new UIBreak() ); // Shading const shadingRow = new UIRow(); container.add( shadingRow ); shadingRow.add( new UIText( strings.getKey( 'sidebar/project/shading' ) ).setClass( 'Label' ) ); const shadingTypeSelect = new UISelect().setOptions( { 'solid': 'SOLID', 'realistic': 'REALISTIC' } ).setWidth( '170px' ).onChange( refreshShadingRow ).setValue( 'solid' ); shadingRow.add( shadingTypeSelect ); const pathTracerMinSamples = 3; const pathTracerMaxSamples = 65536; const samplesNumber = new UIInteger( 16 ).setRange( pathTracerMinSamples, pathTracerMaxSamples ); const samplesRow = new UIRow(); samplesRow.add( new UIText( strings.getKey( 'sidebar/project/image/samples' ) ).setClass( 'Label' ) ); samplesRow.add( samplesNumber ); container.add( samplesRow ); function refreshShadingRow() { samplesRow.setHidden( shadingTypeSelect.getValue() !== 'realistic' ); } refreshShadingRow(); // Resolution const resolutionRow = new UIRow(); container.add( resolutionRow ); resolutionRow.add( new UIText( strings.getKey( 'sidebar/project/resolution' ) ).setClass( 'Label' ) ); const imageWidth = new UIInteger( 1024 ).setTextAlign( 'center' ).setWidth( '28px' ); resolutionRow.add( imageWidth ); resolutionRow.add( new UIText( '×' ).setTextAlign( 'center' ).setFontSize( '12px' ).setWidth( '12px' ) ); const imageHeight = new UIInteger( 1024 ).setTextAlign( 'center' ).setWidth( '28px' ); resolutionRow.add( imageHeight ); // Render const renderButton = new UIButton( strings.getKey( 'sidebar/project/render' ) ); renderButton.setWidth( '170px' ); renderButton.setMarginLeft( '120px' ); renderButton.onClick( async () => { if ( shadingTypeSelect.getValue() === 'realistic' ) { let isMaterialsValid = true; editor.scene.traverseVisible( ( object ) => { if ( object.isMesh ) { const materials = Array.isArray( object.material ) ? object.material : [ object.material ]; for ( let i = 0; i < materials.length; i ++ ) { const material = materials[ i ]; if ( ! material.isMeshStandardMaterial ) { isMaterialsValid = false; return; } } } } ); if ( isMaterialsValid === false ) { alert( strings.getKey( 'prompt/rendering/realistic/unsupportedMaterial' ) ); return; } } // const json = editor.toJSON(); const project = json.project; // const loader = new THREE.ObjectLoader(); const camera = loader.parse( json.camera ); camera.aspect = imageWidth.getValue() / imageHeight.getValue(); camera.updateProjectionMatrix(); camera.updateMatrixWorld(); const scene = loader.parse( json.scene ); const renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setSize( imageWidth.getValue(), imageHeight.getValue() ); if ( project.shadows !== undefined ) renderer.shadowMap.enabled = project.shadows; if ( project.shadowType !== undefined ) renderer.shadowMap.type = project.shadowType; if ( project.toneMapping !== undefined ) renderer.toneMapping = project.toneMapping; if ( project.toneMappingExposure !== undefined ) renderer.toneMappingExposure = project.toneMappingExposure; // popup const width = imageWidth.getValue() / window.devicePixelRatio; const height = imageHeight.getValue() / window.devicePixelRatio; const left = ( screen.width - width ) / 2; const top = ( screen.height - height ) / 2; const output = window.open( '', '_blank', `location=no,left=${left},top=${top},width=${width},height=${height}` ); const meta = document.createElement( 'meta' ); meta.name = 'viewport'; meta.content = 'width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'; output.document.head.appendChild( meta ); output.document.body.style.background = '#000'; output.document.body.style.margin = '0px'; output.document.body.style.overflow = 'hidden'; const canvas = renderer.domElement; canvas.style.width = width + 'px'; canvas.style.height = height + 'px'; output.document.body.appendChild( canvas ); // switch ( shadingTypeSelect.getValue() ) { case 'solid': renderer.render( scene, camera ); renderer.dispose(); break; case 'realistic': const status = document.createElement( 'div' ); status.style.position = 'absolute'; status.style.top = '10px'; status.style.left = '10px'; status.style.color = 'white'; status.style.fontFamily = 'system-ui'; status.style.fontSize = '12px'; output.document.body.appendChild( status ); const pathTracer = new ViewportPathtracer( renderer ); pathTracer.init( scene, camera ); pathTracer.setSize( imageWidth.getValue(), imageHeight.getValue() ); const maxSamples = Math.max( pathTracerMinSamples, Math.min( pathTracerMaxSamples, samplesNumber.getValue() ) ); function animate() { if ( output.closed === true ) return; const samples = Math.floor( pathTracer.getSamples() ) + 1; if ( samples < maxSamples ) { requestAnimationFrame( animate ); } pathTracer.update(); const progress = Math.floor( samples / maxSamples * 100 ); status.textContent = `${ samples } / ${ maxSamples } ( ${ progress }% )`; if ( progress === 100 ) { status.textContent += ' ✓'; } } animate(); break; } } ); container.add( renderButton ); // return container; } export { SidebarProjectImage };