Răsfoiți Sursa

Merge pull request #20535 from Mcgode/edge-split-modifier

Examples: Add EdgeSplitModifier
Mr.doob 4 ani în urmă
părinte
comite
a62e7f1e2d

+ 190 - 0
examples/js/modifiers/EdgeSplitModifier.js

@@ -0,0 +1,190 @@
+console.warn( "THREE.EdgeSplitModifier: As part of the transition to ES6 Modules, the files in 'examples/js' were deprecated in May 2020 (r117) and will be deleted in December 2020 (r124). You can find more information about developing using ES6 Modules in https://threejs.org/docs/#manual/en/introduction/Installation." );
+
+
+THREE.EdgeSplitModifier = function () {
+
+	const A = new THREE.Vector3();
+	const B = new THREE.Vector3();
+	const C = new THREE.Vector3();
+
+	let positions, normals;
+	let indexes;
+	let pointToIndexMap, splitIndexes;
+
+
+	function computeNormals() {
+
+		normals = new Float32Array( indexes.length * 3 );
+
+		for ( let i = 0; i < indexes.length; i += 3 ) {
+
+			let index = indexes[ i ];
+			A.set(
+				positions[ 3 * index ],
+				positions[ 3 * index + 1 ],
+				positions[ 3 * index + 2 ] );
+
+			index = indexes[ i + 1 ];
+			B.set(
+				positions[ 3 * index ],
+				positions[ 3 * index + 1 ],
+				positions[ 3 * index + 2 ] );
+
+			index = indexes[ i + 2 ];
+			C.set(
+				positions[ 3 * index ],
+				positions[ 3 * index + 1 ],
+				positions[ 3 * index + 2 ] );
+
+			C.sub( B );
+			A.sub( B );
+
+			const normal = C.cross( A ).normalize();
+
+			for ( let j = 0; j < 3; j ++ ) {
+
+				normals[ 3 * ( i + j ) ] = normal.x;
+				normals[ 3 * ( i + j ) + 1 ] = normal.y;
+				normals[ 3 * ( i + j ) + 2 ] = normal.z;
+
+			}
+
+		}
+
+	}
+
+
+	function mapPositionsToIndexes() {
+
+		pointToIndexMap = Array( positions.length / 3 );
+
+		for ( let i = 0; i < indexes.length; i ++ ) {
+
+			const index = indexes[ i ];
+
+			if ( pointToIndexMap[ index ] == null )
+				pointToIndexMap[ index ] = [];
+
+			pointToIndexMap[ index ].push( i );
+
+		}
+
+	}
+
+
+	function edgeSplitToGroups( indexes, cutOff, firstIndex ) {
+
+		A.set( normals[ 3 * firstIndex ], normals[ 3 * firstIndex + 1 ], normals[ 3 * firstIndex + 2 ] )
+			.normalize();
+
+		const result = {
+			splitGroup: [],
+			currentGroup: [ firstIndex ]
+		};
+
+		for ( const j of indexes ) {
+
+			if ( j !== firstIndex ) {
+
+				B.set( normals[ 3 * j ], normals[ 3 * j + 1 ], normals[ 3 * j + 2 ] )
+					.normalize();
+
+				if ( B.dot( A ) < cutOff )
+					result.splitGroup.push( j );
+
+				else
+					result.currentGroup.push( j );
+
+			}
+
+		}
+
+		return result;
+
+	}
+
+
+	function edgeSplit( indexes, cutOff, original = null ) {
+
+		if ( indexes.length === 0 )
+			return;
+
+		const groupResults = [];
+		for ( const index of indexes )
+			groupResults.push( edgeSplitToGroups( indexes, cutOff, index ) );
+
+		let result = groupResults[ 0 ];
+		for ( const groupResult of groupResults )
+			if ( groupResult.currentGroup.length > result.currentGroup.length )
+				result = groupResult;
+
+		if ( original != null )
+			splitIndexes.push( {
+				original: original,
+				indexes: result.currentGroup
+			} );
+
+		if ( result.splitGroup.length )
+			edgeSplit( result.splitGroup, cutOff, original || result.currentGroup[ 0 ] );
+
+	}
+
+
+	this.modify = function ( geometry, cutOffAngle ) {
+
+		if ( ! geometry.isBufferGeometry ) {
+
+			geometry = new THREE.BufferGeometry().fromGeometry( geometry );
+
+		}
+
+
+		if ( geometry.index == null )
+			geometry = THREE.BufferGeometryUtils.mergeVertices( geometry );
+
+
+		indexes = geometry.index.array;
+		positions = geometry.getAttribute( "position" ).array;
+
+		computeNormals();
+
+
+		mapPositionsToIndexes();
+
+
+		splitIndexes = [];
+
+		for ( const vertexIndexes of pointToIndexMap )
+			edgeSplit( vertexIndexes, Math.cos( cutOffAngle ) - 0.001 );
+
+
+		const newPositions = new Float32Array( positions.length + 3 * splitIndexes.length );
+		newPositions.set( positions );
+		const offset = positions.length;
+
+		const newIndexes = new Uint32Array( indexes.length );
+		newIndexes.set( indexes );
+
+		for ( let i = 0; i < splitIndexes.length; i ++ ) {
+
+			const split = splitIndexes[ i ];
+			const index = indexes[ split.original ];
+
+			newPositions[ offset + 3 * i ] = positions[ 3 * index ];
+			newPositions[ offset + 3 * i + 1 ] = positions[ 3 * index + 1 ];
+			newPositions[ offset + 3 * i + 2 ] = positions[ 3 * index + 2 ];
+
+			for ( const j of split.indexes )
+				newIndexes[ j ] = offset / 3 + i;
+
+		}
+
+		geometry = new THREE.BufferGeometry();
+		geometry.setAttribute( 'position', new THREE.BufferAttribute( newPositions, 3, true ) );
+		geometry.setIndex( new THREE.BufferAttribute( newIndexes, 1 ) );
+
+		return geometry;
+
+	};
+
+};

+ 8 - 0
examples/jsm/modifiers/EdgeSplitModifier.d.ts

@@ -0,0 +1,8 @@
+import { BufferGeometry, Geometry } from "../../../src/Three";
+
+export class EdgeSplitModifier {
+
+	constructor();
+	modify( geometry: Geometry, cutOffPoint: number ): BufferGeometry;
+
+}

+ 191 - 0
examples/jsm/modifiers/EdgeSplitModifier.js

@@ -0,0 +1,191 @@
+import { BufferAttribute, BufferGeometry, Vector3 } from "../../../build/three.module.js";
+import { BufferGeometryUtils } from "../utils/BufferGeometryUtils.js";
+
+
+export function EdgeSplitModifier() {
+
+	const A = new Vector3();
+	const B = new Vector3();
+	const C = new Vector3();
+
+	let positions, normals;
+	let indexes;
+	let pointToIndexMap, splitIndexes;
+
+
+	function computeNormals() {
+
+		normals = new Float32Array( indexes.length * 3 );
+
+		for ( let i = 0; i < indexes.length; i += 3 ) {
+
+			let index = indexes[ i ];
+			A.set(
+				positions[ 3 * index ],
+				positions[ 3 * index + 1 ],
+				positions[ 3 * index + 2 ] );
+
+			index = indexes[ i + 1 ];
+			B.set(
+				positions[ 3 * index ],
+				positions[ 3 * index + 1 ],
+				positions[ 3 * index + 2 ] );
+
+			index = indexes[ i + 2 ];
+			C.set(
+				positions[ 3 * index ],
+				positions[ 3 * index + 1 ],
+				positions[ 3 * index + 2 ] );
+
+			C.sub( B );
+			A.sub( B );
+
+			const normal = C.cross( A ).normalize();
+
+			for ( let j = 0; j < 3; j ++ ) {
+
+				normals[ 3 * ( i + j ) ] = normal.x;
+				normals[ 3 * ( i + j ) + 1 ] = normal.y;
+				normals[ 3 * ( i + j ) + 2 ] = normal.z;
+
+			}
+
+		}
+
+	}
+
+
+	function mapPositionsToIndexes() {
+
+		pointToIndexMap = Array( positions.length / 3 );
+
+		for ( let i = 0; i < indexes.length; i ++ ) {
+
+			const index = indexes[ i ];
+
+			if ( pointToIndexMap[ index ] == null )
+				pointToIndexMap[ index ] = [];
+
+			pointToIndexMap[ index ].push( i );
+
+		}
+
+	}
+
+
+	function edgeSplitToGroups( indexes, cutOff, firstIndex ) {
+
+		A.set( normals[ 3 * firstIndex ], normals[ 3 * firstIndex + 1 ], normals[ 3 * firstIndex + 2 ] )
+			.normalize();
+
+		const result = {
+			splitGroup: [],
+			currentGroup: [ firstIndex ]
+		};
+
+		for ( const j of indexes ) {
+
+			if ( j !== firstIndex ) {
+
+				B.set( normals[ 3 * j ], normals[ 3 * j + 1 ], normals[ 3 * j + 2 ] )
+					.normalize();
+
+				if ( B.dot( A ) < cutOff )
+					result.splitGroup.push( j );
+
+				else
+					result.currentGroup.push( j );
+
+			}
+
+		}
+
+		return result;
+
+	}
+
+
+	function edgeSplit( indexes, cutOff, original = null ) {
+
+		if ( indexes.length === 0 )
+			return;
+
+		const groupResults = [];
+		for ( const index of indexes )
+			groupResults.push( edgeSplitToGroups( indexes, cutOff, index ) );
+
+		let result = groupResults[ 0 ];
+		for ( const groupResult of groupResults )
+			if ( groupResult.currentGroup.length > result.currentGroup.length )
+				result = groupResult;
+
+		if ( original != null )
+			splitIndexes.push( {
+				original: original,
+				indexes: result.currentGroup
+			} );
+
+		if ( result.splitGroup.length )
+			edgeSplit( result.splitGroup, cutOff, original || result.currentGroup[ 0 ] );
+
+	}
+
+
+	this.modify = function ( geometry, cutOffAngle ) {
+
+		if ( ! geometry.isBufferGeometry ) {
+
+			geometry = new BufferGeometry().fromGeometry( geometry );
+
+		}
+
+
+		if ( geometry.index == null )
+			geometry = BufferGeometryUtils.mergeVertices( geometry );
+
+
+		indexes = geometry.index.array;
+		positions = geometry.getAttribute( "position" ).array;
+
+		computeNormals();
+
+
+		mapPositionsToIndexes();
+
+
+		splitIndexes = [];
+
+		for ( const vertexIndexes of pointToIndexMap )
+			edgeSplit( vertexIndexes, Math.cos( cutOffAngle ) - 0.001 );
+
+
+		const newPositions = new Float32Array( positions.length + 3 * splitIndexes.length );
+		newPositions.set( positions );
+		const offset = positions.length;
+
+		const newIndexes = new Uint32Array( indexes.length );
+		newIndexes.set( indexes );
+
+		for ( let i = 0; i < splitIndexes.length; i ++ ) {
+
+			const split = splitIndexes[ i ];
+			const index = indexes[ split.original ];
+
+			newPositions[ offset + 3 * i ] = positions[ 3 * index ];
+			newPositions[ offset + 3 * i + 1 ] = positions[ 3 * index + 1 ];
+			newPositions[ offset + 3 * i + 2 ] = positions[ 3 * index + 2 ];
+
+			for ( const j of split.indexes )
+				newIndexes[ j ] = offset / 3 + i;
+
+		}
+
+		geometry = new BufferGeometry();
+		geometry.setAttribute( 'position', new BufferAttribute( newPositions, 3, true ) );
+		geometry.setIndex( new BufferAttribute( newIndexes, 1 ) );
+
+		return geometry;
+
+	};
+
+}

+ 148 - 0
examples/webgl_modifier_edge_spliter.html

@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - modifier - Edge split modifier</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">
+	</head>
+	<body>
+
+		<script type="module">
+
+			import * as THREE from '../build/three.module.js';
+
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+			import { OBJLoader } from "./jsm/loaders/OBJLoader.js";
+			import { BufferGeometryUtils } from "./jsm/utils/BufferGeometryUtils.js";
+			import { EdgeSplitModifier } from "./jsm/modifiers/EdgeSplitModifier.js";
+
+			import { GUI } from "./jsm/libs/dat.gui.module.js";
+
+			let renderer, scene, camera;
+			let modifier, mesh, baseGeometry;
+
+			const params = {
+				smoothShading: false,
+				edgeSplit: false,
+				cutOffAngle: 20,
+			};
+
+			init();
+
+			function init() {
+
+				const info = document.createElement( 'div' );
+				info.style.position = 'absolute';
+				info.style.top = '10px';
+				info.style.width = '100%';
+				info.style.textAlign = 'center';
+				info.innerHTML = '<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - Indexed geometry edge splitting';
+				document.body.appendChild( info );
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				scene = new THREE.Scene();
+
+				camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight );
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.addEventListener( 'change', render ); // use if there is no animation loop
+				controls.enableDamping = true;
+				controls.dampingFactor = 0.25;
+				controls.rotateSpeed = 0.35;
+				controls.minZoom = 1;
+				camera.position.set( 0, 0, 4 );
+
+				scene.add( new THREE.HemisphereLight( 0xffffff, 0x444444 ) );
+
+				new OBJLoader().load(
+					'./models/obj/cerberus/cerberus.obj',
+					function ( group ) {
+
+						// Retrieve Cerberus vertex positions only
+						const modelGeometry = group.children[ 0 ].geometry;
+						modelGeometry.deleteAttribute( 'normal' );
+						modelGeometry.deleteAttribute( 'uv' );
+
+						modifier = new EdgeSplitModifier();
+						baseGeometry = modelGeometry;
+
+						console.log( "Loaded" );
+
+						mesh = new THREE.Mesh( getGeometry(), new THREE.MeshStandardMaterial() );
+						mesh.material.flatShading = ! params.smoothShading;
+						mesh.rotateY( - Math.PI / 2 );
+						mesh.scale.set( 3.5, 3.5, 3.5 );
+						mesh.translateZ( 1.5 );
+						scene.add( mesh );
+
+						render();
+
+					}
+				);
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+
+				const gui = new GUI( { name: "Edge split modifier parameters" } );
+
+				gui.add( params, "smoothShading" ).onFinishChange( updateMesh );
+				gui.add( params, "edgeSplit" ).onFinishChange( updateMesh );
+				gui.add( params, "cutOffAngle" ).min( 0 ).max( 180 ).onFinishChange( updateMesh );
+
+			}
+
+			function onWindowResize() {
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				render();
+
+			}
+
+
+			function getGeometry() {
+
+				let g;
+
+				if ( params.edgeSplit )
+					g = modifier.modify( baseGeometry, params.cutOffAngle * Math.PI / 180 );
+				else
+					g = BufferGeometryUtils.mergeVertices( baseGeometry );
+
+				g.computeVertexNormals();
+
+				return g;
+
+			}
+
+
+			function updateMesh() {
+
+				mesh.geometry = getGeometry();
+
+				mesh.material.flatShading = ! params.smoothShading;
+				mesh.material.needsUpdate = true;
+
+				render();
+
+			}
+
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>