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

338 lines
9.7 KiB

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