import * as THREE from 'three'; import { threejsLessonUtils } from './threejs-lesson-utils.js'; { function makeSphere( widthDivisions, heightDivisions ) { const radius = 7; return new THREE.SphereGeometry( radius, widthDivisions, heightDivisions ); } const highPolySphereGeometry = function () { const widthDivisions = 100; const heightDivisions = 50; return makeSphere( widthDivisions, heightDivisions ); }(); const lowPolySphereGeometry = function () { const widthDivisions = 12; const heightDivisions = 9; return makeSphere( widthDivisions, heightDivisions ); }(); function smoothOrFlat( flatShading, radius = 7 ) { const widthDivisions = 12; const heightDivisions = 9; const geometry = new THREE.SphereGeometry( radius, widthDivisions, heightDivisions ); const material = new THREE.MeshPhongMaterial( { flatShading, color: 'hsl(300,50%,50%)', } ); return new THREE.Mesh( geometry, material ); } function basicLambertPhongExample( MaterialCtor, lowPoly, params = {} ) { const geometry = lowPoly ? lowPolySphereGeometry : highPolySphereGeometry; const material = new MaterialCtor( { color: 'hsl(210,50%,50%)', ...params, } ); return { obj3D: new THREE.Mesh( geometry, material ), trackball: lowPoly, }; } function sideExample( side ) { const base = new THREE.Object3D(); const size = 6; const geometry = new THREE.PlaneGeometry( size, size ); [ { position: [ - 1, 0, 0 ], up: [ 0, 1, 0 ], }, { position: [ 1, 0, 0 ], up: [ 0, - 1, 0 ], }, { position: [ 0, - 1, 0 ], up: [ 0, 0, - 1 ], }, { position: [ 0, 1, 0 ], up: [ 0, 0, 1 ], }, { position: [ 0, 0, - 1 ], up: [ 1, 0, 0 ], }, { position: [ 0, 0, 1 ], up: [ - 1, 0, 0 ], }, ].forEach( ( settings, ndx ) => { const material = new THREE.MeshBasicMaterial( { side } ); material.color.setHSL( ndx / 6, .5, .5 ); const mesh = new THREE.Mesh( geometry, material ); mesh.up.set( ...settings.up ); mesh.lookAt( ...settings.position ); mesh.position.set( ...settings.position ).multiplyScalar( size * .75 ); base.add( mesh ); } ); return base; } function makeStandardPhysicalMaterialGrid( elem, physical, update ) { const numMetal = 5; const numRough = 7; const meshes = []; const MatCtor = physical ? THREE.MeshPhysicalMaterial : THREE.MeshStandardMaterial; const color = physical ? 'hsl(160,50%,50%)' : 'hsl(140,50%,50%)'; for ( let m = 0; m < numMetal; ++ m ) { const row = []; for ( let r = 0; r < numRough; ++ r ) { const material = new MatCtor( { color, roughness: r / ( numRough - 1 ), metalness: 1 - m / ( numMetal - 1 ), } ); const mesh = new THREE.Mesh( highPolySphereGeometry, material ); row.push( mesh ); } meshes.push( row ); } return { obj3D: null, trackball: false, render( renderInfo ) { const { camera, scene, renderer } = renderInfo; const rect = elem.getBoundingClientRect(); const width = ( rect.right - rect.left ) * renderInfo.pixelRatio; const height = ( rect.bottom - rect.top ) * renderInfo.pixelRatio; const left = rect.left * renderInfo.pixelRatio; const bottom = ( renderer.domElement.clientHeight - rect.bottom ) * renderInfo.pixelRatio; const cellSize = Math.min( width / numRough, height / numMetal ) | 0; const xOff = ( width - cellSize * numRough ) / 2; const yOff = ( height - cellSize * numMetal ) / 2; camera.aspect = 1; camera.updateProjectionMatrix(); if ( update ) { update( meshes ); } for ( let m = 0; m < numMetal; ++ m ) { for ( let r = 0; r < numRough; ++ r ) { const x = left + xOff + r * cellSize; const y = bottom + yOff + m * cellSize; renderer.setViewport( x, y, cellSize, cellSize ); renderer.setScissor( x, y, cellSize, cellSize ); const mesh = meshes[ m ][ r ]; scene.add( mesh ); renderer.render( scene, camera ); scene.remove( mesh ); } } }, }; } threejsLessonUtils.addDiagrams( { smoothShading: { create() { return smoothOrFlat( false ); }, }, flatShading: { create() { return smoothOrFlat( true ); }, }, MeshBasicMaterial: { create() { return basicLambertPhongExample( THREE.MeshBasicMaterial ); }, }, MeshLambertMaterial: { create() { return basicLambertPhongExample( THREE.MeshLambertMaterial ); }, }, MeshPhongMaterial: { create() { return basicLambertPhongExample( THREE.MeshPhongMaterial ); }, }, MeshBasicMaterialLowPoly: { create() { return basicLambertPhongExample( THREE.MeshBasicMaterial, true ); }, }, MeshLambertMaterialLowPoly: { create() { return basicLambertPhongExample( THREE.MeshLambertMaterial, true ); }, }, MeshPhongMaterialLowPoly: { create() { return basicLambertPhongExample( THREE.MeshPhongMaterial, true ); }, }, MeshPhongMaterialShininess0: { create() { return basicLambertPhongExample( THREE.MeshPhongMaterial, false, { color: 'red', shininess: 0, } ); }, }, MeshPhongMaterialShininess30: { create() { return basicLambertPhongExample( THREE.MeshPhongMaterial, false, { color: 'red', shininess: 30, } ); }, }, MeshPhongMaterialShininess150: { create() { return basicLambertPhongExample( THREE.MeshPhongMaterial, false, { color: 'red', shininess: 150, } ); }, }, MeshBasicMaterialCompare: { create() { return basicLambertPhongExample( THREE.MeshBasicMaterial, false, { color: 'purple', } ); }, }, MeshLambertMaterialCompare: { create() { return basicLambertPhongExample( THREE.MeshLambertMaterial, false, { color: 'black', emissive: 'purple', } ); }, }, MeshPhongMaterialCompare: { create() { return basicLambertPhongExample( THREE.MeshPhongMaterial, false, { color: 'black', emissive: 'purple', shininess: 0, } ); }, }, MeshToonMaterial: { create() { return basicLambertPhongExample( THREE.MeshToonMaterial ); }, }, MeshStandardMaterial: { create( props ) { return makeStandardPhysicalMaterialGrid( props.renderInfo.elem, false ); }, }, MeshPhysicalMaterial: { create( props ) { const settings = { clearcoat: .5, clearcoatRoughness: 0, }; function addElem( parent, type, style = {} ) { const elem = document.createElement( type ); Object.assign( elem.style, style ); parent.appendChild( elem ); return elem; } function addRange( elem, obj, prop, min, max ) { const outer = addElem( elem, 'div', { width: '100%', textAlign: 'center', 'font-family': 'monospace', } ); const div = addElem( outer, 'div', { textAlign: 'left', display: 'inline-block', } ); const label = addElem( div, 'label', { display: 'inline-block', width: '12em', } ); label.textContent = prop; const num = addElem( div, 'div', { display: 'inline-block', width: '3em', } ); function updateNum() { num.textContent = obj[ prop ].toFixed( 2 ); } updateNum(); const input = addElem( div, 'input', { } ); Object.assign( input, { type: 'range', min: 0, max: 100, value: ( obj[ prop ] - min ) / ( max - min ) * 100, } ); input.addEventListener( 'input', () => { obj[ prop ] = min + ( max - min ) * input.value / 100; updateNum(); } ); } const { elem } = props.renderInfo; addRange( elem, settings, 'clearcoat', 0, 1 ); addRange( elem, settings, 'clearcoatRoughness', 0, 1 ); const area = addElem( elem, 'div', { width: '100%', height: '400px', } ); return makeStandardPhysicalMaterialGrid( area, true, ( meshes ) => { meshes.forEach( row => row.forEach( mesh => { mesh.material.clearcoat = settings.clearcoat; mesh.material.clearcoatRoughness = settings.clearcoatRoughness; } ) ); } ); }, }, MeshDepthMaterial: { create( props ) { const { camera } = props; const radius = 4; const tube = 1.5; const radialSegments = 8; const tubularSegments = 64; const p = 2; const q = 3; const geometry = new THREE.TorusKnotGeometry( radius, tube, tubularSegments, radialSegments, p, q ); const material = new THREE.MeshDepthMaterial(); camera.near = 7; camera.far = 20; return new THREE.Mesh( geometry, material ); }, }, MeshNormalMaterial: { create() { const radius = 4; const tube = 1.5; const radialSegments = 8; const tubularSegments = 64; const p = 2; const q = 3; const geometry = new THREE.TorusKnotGeometry( radius, tube, tubularSegments, radialSegments, p, q ); const material = new THREE.MeshNormalMaterial(); return new THREE.Mesh( geometry, material ); }, }, sideDefault: { create() { return sideExample( THREE.FrontSide ); }, }, sideDouble: { create() { return sideExample( THREE.DoubleSide ); }, }, } ); }