<script>
var conditionalLineVertShader = /* glsl */`
attribute vec3 control0;
attribute vec3 control1;
attribute vec3 direction;
attribute float collapse;
attribute vec3 instPos;
#include <common>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
void main() {
#include <color_vertex>
// Transform the line segment ends and control points into camera clip space
vec4 c0 = projectionMatrix * modelViewMatrix * vec4( control0 + instPos, 1.0 );
vec4 c1 = projectionMatrix * modelViewMatrix * vec4( control1 + instPos, 1.0 );
vec4 p0 = projectionMatrix * modelViewMatrix * vec4( position + instPos, 1.0 );
vec4 p1 = projectionMatrix * modelViewMatrix * vec4( position + instPos + direction, 1.0 );
c0.xy /= c0.w;
c1.xy /= c1.w;
p0.xy /= p0.w;
p1.xy /= p1.w;
// Get the direction of the segment and an orthogonal vector
vec2 dir = p1.xy - p0.xy;
vec2 norm = vec2( -dir.y, dir.x );
// Get control point directions from the line
vec2 c0dir = c0.xy - p1.xy;
vec2 c1dir = c1.xy - p1.xy;
// If the vectors to the controls points are pointed in different directions away
// from the line segment then the line should not be drawn.
float d0 = dot( normalize( norm ), normalize( c0dir ) );
float d1 = dot( normalize( norm ), normalize( c1dir ) );
float discardFlag = float( sign( d0 ) != sign( d1 ) );
vec3 p = position + instPos + ((discardFlag > 0.5) ? direction * collapse : vec3(0));
vec4 mvPosition = modelViewMatrix * vec4( p, 1.0 );
gl_Position = projectionMatrix * mvPosition;
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
#include <fog_vertex>
}
`;
var conditionalLineFragShader = /* glsl */`
uniform vec3 diffuse;
uniform float opacity;
#include <common>
#include <color_pars_fragment>
#include <fog_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>
void main() {
#include <clipping_planes_fragment>
vec3 outgoingLight = vec3( 0.0 );
vec4 diffuseColor = vec4( diffuse, opacity );
#include <logdepthbuf_fragment>
#include <color_fragment>
outgoingLight = diffuseColor.rgb; // simple shader
gl_FragColor = vec4( outgoingLight, diffuseColor.a );
#include <tonemapping_fragment>
#include <encodings_fragment>
#include <fog_fragment>
#include <premultiplied_alpha_fragment>
}
`;
</script>
body{
overflow: hidden;
margin: 0;
}
import * as THREE from 'https://cdn.skypack.dev/[email protected]';
import {OrbitControls} from 'https://cdn.skypack.dev/[email protected]/examples/jsm/controls/OrbitControls.js';
import {GLTFLoader} from 'https://cdn.skypack.dev/[email protected]/examples/jsm/loaders/GLTFLoader.js';
import {GUI} from 'https://cdn.skypack.dev/[email protected]/examples/jsm/libs/dat.gui.module.js';
function EdgesGeometry( geometry, thresholdAngle ) {
let g = new THREE.InstancedBufferGeometry();
g.type = 'EdgesGeometry';
g.parameters = {
thresholdAngle: thresholdAngle
};
thresholdAngle = ( thresholdAngle !== undefined ) ? thresholdAngle : 1;
// buffer
const vertices = [];
const control0 = [];
const control1 = [];
const direction = [];
const collapse = [];
// helper variables
const thresholdDot = Math.cos( THREE.MathUtils.DEG2RAD * thresholdAngle );
const edge = [ 0, 0 ], edges = {};
let edge1, edge2, key;
const keys = [ 'a', 'b', 'c' ];
// prepare source geometry
let geometry2;
if ( geometry.isBufferGeometry ) {
geometry2 = new THREE.Geometry();
geometry2.fromBufferGeometry( geometry );
} else {
geometry2 = geometry.clone();
}
geometry2.mergeVertices();
geometry2.computeFaceNormals();
const sourceVertices = geometry2.vertices;
const faces = geometry2.faces;
// now create a data structure where each entry represents an edge with its adjoining faces
for ( let i = 0, l = faces.length; i < l; i ++ ) {
const face = faces[ i ];
for ( let j = 0; j < 3; j ++ ) {
edge1 = face[ keys[ j ] ];
edge2 = face[ keys[ ( j + 1 ) % 3 ] ];
edge[ 0 ] = Math.min( edge1, edge2 );
edge[ 1 ] = Math.max( edge1, edge2 );
key = edge[ 0 ] + ',' + edge[ 1 ];
if ( edges[ key ] === undefined ) {
edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ], face1: i, face2: undefined };
} else {
edges[ key ].face2 = i;
}
}
}
// generate vertices
const v3 = new THREE.Vector3();
const n = new THREE.Vector3();
const n1 = new THREE.Vector3();
const n2 = new THREE.Vector3();
const d = new THREE.Vector3();
for ( key in edges ) {
const e = edges[ key ];
// an edge is only rendered if the angle (in degrees) between the face normals of the adjoining faces exceeds this value. default = 1 degree.
if ( e.face2 === undefined || faces[ e.face1 ].normal.dot( faces[ e.face2 ].normal ) <= thresholdDot ) {
let vertex1 = sourceVertices[ e.index1 ];
let vertex2 = sourceVertices[ e.index2 ];
vertices.push( vertex1.x, vertex1.y, vertex1.z );
vertices.push( vertex2.x, vertex2.y, vertex2.z );
d.subVectors(vertex2, vertex1);
collapse.push(0, 1);
n.copy(d).normalize();
direction.push(d.x, d.y, d.z);
n1.copy(faces[ e.face1 ].normal);
n1.crossVectors(n, n1);
d.subVectors(vertex1, vertex2);
n.copy(d).normalize();
n2.copy(faces[ e.face2 ].normal);
n2.crossVectors(n, n2);
direction.push(d.x, d.y, d.z);
v3.copy(vertex1).add(n1); // control0
control0.push(v3.x, v3.y, v3.z);
v3.copy(vertex1).add(n2); // control1
control1.push(v3.x, v3.y, v3.z);
v3.copy(vertex2).add(n1); // control0
control0.push(v3.x, v3.y, v3.z);
v3.copy(vertex2).add(n2); // control1
control1.push(v3.x, v3.y, v3.z);
}
}
// build geometry
g.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
g.setAttribute( 'control0', new THREE.Float32BufferAttribute( control0, 3 ) );
g.setAttribute( 'control1', new THREE.Float32BufferAttribute( control1, 3 ) );
g.setAttribute( 'direction', new THREE.Float32BufferAttribute( direction, 3 ) );
g.setAttribute( 'collapse', new THREE.Float32BufferAttribute( collapse, 1 ) );
return g;
}
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 10000);
camera.position.set(300, 80, 0);
camera.lookAt(scene.position);
let renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setClearColor(0x444444);
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
let controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 80, 0);
controls.update();
var light = new THREE.DirectionalLight(0xffffff, 1);
light.position.setScalar(10);
scene.add(light);
scene.add(new THREE.DirectionalLight(0xffffff, 0.5));
var loader = new GLTFLoader();
loader.load( "https://threejs.org/examples/models/gltf/Horse.glb", function ( gltf ) {
let container = new THREE.Group();
let mesh = gltf.scene.children[ 0 ];
mesh.material.color.set(0x222222);
mesh.material.vertexColors = false;
mesh.material.polygonOffset = true;
mesh.material.polygonOffsetFactor = 1;
let outline = createOutlineSegments(mesh.geometry, 0xffffff);
let instPos = [];
var instanceObj = new THREE.InstancedMesh( mesh.geometry, mesh.material, 20 )
const matrix = new THREE.Matrix4();
instanceObj.instanceMatrix.setUsage( THREE.DynamicDrawUsage );
for ( let bm = 0; bm < 20 ; bm ++ ) {
randomizeMatrixA( matrix, instPos );
instanceObj.setMatrixAt( bm, matrix );
}
console.log(instPos);
outline.geometry.setAttribute("instPos", new THREE.InstancedBufferAttribute(new Float32Array(instPos), 3));
console.log(outline.geometry);
container.add(instanceObj);
container.add(outline);
//container.add(outline);
//container.add(mesh);
scene.add(container);
console.log(mesh);
let gui = new GUI();
gui.add(mesh, "visible").name("mesh");
gui.add(outline, "visible").name("outline");
} );
function randomizeMatrixA( matrix, instPos ) {
var trashPosition = new THREE.Vector3();
var scale = new THREE.Vector3()
const quaternion = new THREE.Quaternion();
trashPosition.x = Math.random() * 800 - 400;
trashPosition.y = Math.random() * 800 - 400;
trashPosition.z = Math.random() * 800 - 400;
instPos.push(trashPosition.x, trashPosition.y, trashPosition.z);
scale.x = scale.y = scale.z = 1;
matrix.compose( trashPosition, quaternion, scale );
}
renderer.setAnimationLoop(()=>{
renderer.render(scene, camera);
});
function createOutlineSegments(geometry, color){
let eg = EdgesGeometry(geometry);
let m = new THREE.ShaderMaterial({
vertexShader: conditionalLineVertShader,
fragmentShader: conditionalLineFragShader,
uniforms: {
diffuse: {
value: new THREE.Color(color)
},
opacity: {
value: 0
}
},
transparent: false
});
let o = new THREE.LineSegments(eg, m);
return o;
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.