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
426 lines
9.0 KiB
JavaScript
2 months ago
|
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 );
|
||
|
|
||
|
},
|
||
|
},
|
||
|
} );
|
||
|
|
||
|
}
|
||
|
|