You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

426 lines
9.0 KiB
JavaScript

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 );
},
},
} );
}