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.

355 lines
9.5 KiB

2 years ago
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - geometry - spline extrusion</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<style>
body {
background-color: #f0f0f0;
color: #444;
}
a {
color: #08f;
}
</style>
</head>
<body>
<div id="container"></div>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - spline extrusion examples
</div>
<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import Stats from 'three/addons/libs/stats.module.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import * as Curves from 'three/addons/curves/CurveExtras.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
let container, stats;
let camera, scene, renderer, splineCamera, cameraHelper, cameraEye;
const direction = new THREE.Vector3();
const binormal = new THREE.Vector3();
const normal = new THREE.Vector3();
const position = new THREE.Vector3();
const lookAt = new THREE.Vector3();
const pipeSpline = new THREE.CatmullRomCurve3( [
new THREE.Vector3( 0, 10, - 10 ), new THREE.Vector3( 10, 0, - 10 ),
new THREE.Vector3( 20, 0, 0 ), new THREE.Vector3( 30, 0, 10 ),
new THREE.Vector3( 30, 0, 20 ), new THREE.Vector3( 20, 0, 30 ),
new THREE.Vector3( 10, 0, 30 ), new THREE.Vector3( 0, 0, 30 ),
new THREE.Vector3( - 10, 10, 30 ), new THREE.Vector3( - 10, 20, 30 ),
new THREE.Vector3( 0, 30, 30 ), new THREE.Vector3( 10, 30, 30 ),
new THREE.Vector3( 20, 30, 15 ), new THREE.Vector3( 10, 30, 10 ),
new THREE.Vector3( 0, 30, 10 ), new THREE.Vector3( - 10, 20, 10 ),
new THREE.Vector3( - 10, 10, 10 ), new THREE.Vector3( 0, 0, 10 ),
new THREE.Vector3( 10, - 10, 10 ), new THREE.Vector3( 20, - 15, 10 ),
new THREE.Vector3( 30, - 15, 10 ), new THREE.Vector3( 40, - 15, 10 ),
new THREE.Vector3( 50, - 15, 10 ), new THREE.Vector3( 60, 0, 10 ),
new THREE.Vector3( 70, 0, 0 ), new THREE.Vector3( 80, 0, 0 ),
new THREE.Vector3( 90, 0, 0 ), new THREE.Vector3( 100, 0, 0 )
] );
const sampleClosedSpline = new THREE.CatmullRomCurve3( [
new THREE.Vector3( 0, - 40, - 40 ),
new THREE.Vector3( 0, 40, - 40 ),
new THREE.Vector3( 0, 140, - 40 ),
new THREE.Vector3( 0, 40, 40 ),
new THREE.Vector3( 0, - 40, 40 )
] );
sampleClosedSpline.curveType = 'catmullrom';
sampleClosedSpline.closed = true;
// Keep a dictionary of Curve instances
const splines = {
GrannyKnot: new Curves.GrannyKnot(),
HeartCurve: new Curves.HeartCurve( 3.5 ),
VivianiCurve: new Curves.VivianiCurve( 70 ),
KnotCurve: new Curves.KnotCurve(),
HelixCurve: new Curves.HelixCurve(),
TrefoilKnot: new Curves.TrefoilKnot(),
TorusKnot: new Curves.TorusKnot( 20 ),
CinquefoilKnot: new Curves.CinquefoilKnot( 20 ),
TrefoilPolynomialKnot: new Curves.TrefoilPolynomialKnot( 14 ),
FigureEightPolynomialKnot: new Curves.FigureEightPolynomialKnot(),
DecoratedTorusKnot4a: new Curves.DecoratedTorusKnot4a(),
DecoratedTorusKnot4b: new Curves.DecoratedTorusKnot4b(),
DecoratedTorusKnot5a: new Curves.DecoratedTorusKnot5a(),
DecoratedTorusKnot5c: new Curves.DecoratedTorusKnot5c(),
PipeSpline: pipeSpline,
SampleClosedSpline: sampleClosedSpline
};
let parent, tubeGeometry, mesh;
const params = {
spline: 'GrannyKnot',
scale: 4,
extrusionSegments: 100,
radiusSegments: 3,
closed: true,
animationView: false,
lookAhead: false,
cameraHelper: false,
};
const material = new THREE.MeshLambertMaterial( { color: 0xff00ff } );
const wireframeMaterial = new THREE.MeshBasicMaterial( { color: 0x000000, opacity: 0.3, wireframe: true, transparent: true } );
function addTube() {
if ( mesh !== undefined ) {
parent.remove( mesh );
mesh.geometry.dispose();
}
const extrudePath = splines[ params.spline ];
tubeGeometry = new THREE.TubeGeometry( extrudePath, params.extrusionSegments, 2, params.radiusSegments, params.closed );
addGeometry( tubeGeometry );
setScale();
}
function setScale() {
mesh.scale.set( params.scale, params.scale, params.scale );
}
function addGeometry( geometry ) {
// 3D shape
mesh = new THREE.Mesh( geometry, material );
const wireframe = new THREE.Mesh( geometry, wireframeMaterial );
mesh.add( wireframe );
parent.add( mesh );
}
function animateCamera() {
cameraHelper.visible = params.cameraHelper;
cameraEye.visible = params.cameraHelper;
}
init();
animate();
function init() {
container = document.getElementById( 'container' );
// camera
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.01, 10000 );
camera.position.set( 0, 50, 500 );
// scene
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf0f0f0 );
// light
const light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 0, 0, 1 );
scene.add( light );
// tube
parent = new THREE.Object3D();
scene.add( parent );
splineCamera = new THREE.PerspectiveCamera( 84, window.innerWidth / window.innerHeight, 0.01, 1000 );
parent.add( splineCamera );
cameraHelper = new THREE.CameraHelper( splineCamera );
scene.add( cameraHelper );
addTube();
// debug camera
cameraEye = new THREE.Mesh( new THREE.SphereGeometry( 5 ), new THREE.MeshBasicMaterial( { color: 0xdddddd } ) );
parent.add( cameraEye );
cameraHelper.visible = params.cameraHelper;
cameraEye.visible = params.cameraHelper;
// renderer
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
// stats
stats = new Stats();
container.appendChild( stats.dom );
// dat.GUI
const gui = new GUI( { width: 285 } );
const folderGeometry = gui.addFolder( 'Geometry' );
folderGeometry.add( params, 'spline', Object.keys( splines ) ).onChange( function () {
addTube();
} );
folderGeometry.add( params, 'scale', 2, 10 ).step( 2 ).onChange( function () {
setScale();
} );
folderGeometry.add( params, 'extrusionSegments', 50, 500 ).step( 50 ).onChange( function () {
addTube();
} );
folderGeometry.add( params, 'radiusSegments', 2, 12 ).step( 1 ).onChange( function () {
addTube();
} );
folderGeometry.add( params, 'closed' ).onChange( function () {
addTube();
} );
folderGeometry.open();
const folderCamera = gui.addFolder( 'Camera' );
folderCamera.add( params, 'animationView' ).onChange( function () {
animateCamera();
} );
folderCamera.add( params, 'lookAhead' ).onChange( function () {
animateCamera();
} );
folderCamera.add( params, 'cameraHelper' ).onChange( function () {
animateCamera();
} );
folderCamera.open();
const controls = new OrbitControls( camera, renderer.domElement );
controls.minDistance = 100;
controls.maxDistance = 2000;
window.addEventListener( 'resize', onWindowResize );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
//
function animate() {
requestAnimationFrame( animate );
render();
stats.update();
}
function render() {
// animate camera along spline
const time = Date.now();
const looptime = 20 * 1000;
const t = ( time % looptime ) / looptime;
tubeGeometry.parameters.path.getPointAt( t, position );
position.multiplyScalar( params.scale );
// interpolation
const segments = tubeGeometry.tangents.length;
const pickt = t * segments;
const pick = Math.floor( pickt );
const pickNext = ( pick + 1 ) % segments;
binormal.subVectors( tubeGeometry.binormals[ pickNext ], tubeGeometry.binormals[ pick ] );
binormal.multiplyScalar( pickt - pick ).add( tubeGeometry.binormals[ pick ] );
tubeGeometry.parameters.path.getTangentAt( t, direction );
const offset = 15;
normal.copy( binormal ).cross( direction );
// we move on a offset on its binormal
position.add( normal.clone().multiplyScalar( offset ) );
splineCamera.position.copy( position );
cameraEye.position.copy( position );
// using arclength for stablization in look ahead
tubeGeometry.parameters.path.getPointAt( ( t + 30 / tubeGeometry.parameters.path.getLength() ) % 1, lookAt );
lookAt.multiplyScalar( params.scale );
// camera orientation 2 - up orientation via normal
if ( ! params.lookAhead ) lookAt.copy( position ).add( direction );
splineCamera.matrix.lookAt( splineCamera.position, lookAt, normal );
splineCamera.quaternion.setFromRotationMatrix( splineCamera.matrix );
cameraHelper.update();
renderer.render( scene, params.animationView === true ? splineCamera : camera );
}
</script>
</body>
</html>