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