@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
392 lines (253 loc) • 11.5 kB
JavaScript
import { StreamGeometryBuilder } from "../../../src/engine/graphics/ecs/path/tube/build/StreamGeometryBuilder.js";
import { make_ring_faces } from "../../../src/engine/graphics/ecs/path/tube/build/make_ring_faces.js";
import { BufferGeometry } from "three/src/core/BufferGeometry.js";
import { Float32BufferAttribute } from "three/src/core/BufferAttribute.js";
import { Vector2, Vector3 } from "three";
// Modified from https://github.com/mrdoob/three.js/blob/master/src/geometries/CylinderBufferGeometry.js
class ArrowBufferGeometry extends BufferGeometry {
constructor( {radiusTop = 1/7, radiusBottom = 1/20, height = 1, heightTop = 0.6, radialSegments = 8, heightSegments = 1, openEnded = false, heightIncludesHead = true} = {} ) {
super();
this.type = 'ArrowBufferGeometry';
this.parameters = {
radiusTop: radiusTop,
radiusBottom: radiusBottom,
height: height,
heightTop: heightTop,
radialSegments: radialSegments,
heightSegments: heightSegments,
openEnded: openEnded,
heightIncludesHead: heightIncludesHead,
};
const scope = this;
const thetaStart = 0, thetaLength = 2 * Math.PI;
radialSegments = Math.floor( radialSegments );
heightSegments = Math.floor( heightSegments );
// buffers
const indices = [];
const vertices = [];
const normals = [];
const uvs = [];
// helper variables
let index = 0;
const indexArray = [];
const halfHeight = height / 2;
const tubeHeight = heightIncludesHead ? height - heightTop : height;
let groupStart = 0;
// generate geometry
generateTorso();
generateCap( true );
generateCap( false, true );
if ( openEnded === false ) {
if ( radiusBottom > 0 ) generateCap( false );
}
// build geometry
this.setIndex( indices );
this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
function generateTorso() {
const normal = new Vector3();
const vertex = new Vector3();
let groupCount = 0;
// this will be used to calculate the normal
const slope = 0;
// generate vertices, normals and uvs
for ( let y = 0; y <= heightSegments; y ++ ) {
const indexRow = [];
const v = y / heightSegments;
// calculate the radius of the current row
const radius = radiusBottom;
for ( let x = 0; x <= radialSegments; x ++ ) {
const u = x / radialSegments;
const theta = u * Math.PI * 2;
const sinTheta = Math.sin( theta );
const cosTheta = Math.cos( theta );
// vertex
vertex.x = radius * cosTheta;
vertex.y = radius * sinTheta;
vertex.z = v * tubeHeight;
vertices.push( vertex.x, vertex.y, vertex.z );
// normal
normal.set( cosTheta, sinTheta, 0 ).normalize();
normals.push( normal.x, normal.y, normal.z );
// uv
uvs.push( u, 1 - v );
// save index of vertex in respective row
indexRow.push( index ++ );
}
// now save vertices of the row in our index array
indexArray.push( indexRow );
}
// generate indices
for ( let x = 0; x < radialSegments; x ++ ) {
for ( let y = 0; y < heightSegments; y ++ ) {
// we use the index array to access the correct indices
const a = indexArray[ y ][ x ];
const b = indexArray[ y + 1 ][ x ];
const c = indexArray[ y + 1 ][ x + 1 ];
const d = indexArray[ y ][ x + 1 ];
// faces
indices.push( b, a, d );
indices.push( c, b, d );
// update group counter
groupCount += 6;
}
}
// add a group to the geometry. this will ensure multi material support
scope.addGroup( groupStart, groupCount, 0 );
// calculate new start value for groups
groupStart += groupCount;
}
function generateCap( top, headBase = false ) {
// save the index of the first center vertex
const centerIndexStart = index;
const uv = new Vector2();
const vertex = new Vector3();
let groupCount = 0;
const radius = ( top || headBase ) ? radiusTop : radiusBottom;
const sign = ( top ) ? 1 : -1;
// first we generate the center vertex data of the cap.
// because the geometry needs one set of uvs per face,
// we must generate a center vertex per face/segment
for ( let x = 1; x <= radialSegments; x ++ ) {
// vertex
vertices.push( 0, 0, top ? tubeHeight + heightTop : (headBase ? tubeHeight : 0) );
// normal
if (top) {
const theta = (x - 1/2) / radialSegments * Math.PI * 2;
const sinTheta = Math.sin(theta), cosTheta = Math.cos(theta);
const normal = new Vector3( cosTheta, sinTheta, radiusTop / heightTop );
normal.normalize();
normals.push( normal.x, normal.y, normal.z);
} else {
normals.push( 0, 0, sign );
}
// uv
uvs.push( 0.5, 0.5 );
// increase index
index ++;
}
// save the index of the last center vertex
const centerIndexEnd = index;
// now we generate the surrounding vertices, normals and uvs
for ( let x = 0; x <= radialSegments; x ++ ) {
const u = x / radialSegments;
const theta = u * thetaLength + thetaStart;
const cosTheta = Math.cos( theta );
const sinTheta = Math.sin( theta );
// vertex
vertex.x = radius * cosTheta;
vertex.y = radius * sinTheta;
vertex.z = top || headBase ? tubeHeight : 0;
vertices.push( vertex.x, vertex.y, vertex.z );
// normal
if (top) {
const normal = new Vector3(cosTheta, sinTheta, radiusTop / heightTop );
normal.normalize();
normals.push( normal.x, normal.y, normal.z);
} else {
normals.push( 0, 0, sign );
}
// uv
uv.x = ( cosTheta * 0.5 ) + 0.5;
uv.y = ( sinTheta * 0.5 * sign ) + 0.5;
uvs.push( uv.x, uv.y );
// increase index
index ++;
}
// generate indices
for ( let x = 0; x < radialSegments; x ++ ) {
const c = centerIndexStart + x;
const i = centerIndexEnd + x;
if ( top === true ) {
// face top
indices.push( i, i + 1, c );
} else {
// face bottom
indices.push( i + 1, i, c );
}
groupCount += 3;
}
// add a group to the geometry. this will ensure multi material support
scope.addGroup( groupStart, groupCount, top === true ? 1 : 2 );
// calculate new start value for groups
groupStart += groupCount;
}
}
}
/**
*
* @param {StreamGeometryBuilder} out
* @param {number} count
* @param {number} radius
* @param {number} z
*/
function make_ring_vertices(out, count, radius, z) {
const positions = out.positions;
for (let i = 0; i < count; i++) {
const vertex_index = out.cursor_vertices++;
const fraction = i / count;
const angle = Math.PI * 2 * fraction;
const vertex_offset = vertex_index * 3;
positions[vertex_offset] = Math.cos(angle) * radius;
positions[vertex_offset + 1] = Math.sin(angle) * radius;
positions[vertex_offset + 2] = z;
}
}
/**
*
* @param {StreamGeometryBuilder} out
* @param {number} count
* @param {number} vertex_offset
* @param {number} tip_vertex_index
*/
function make_cone_indices(out, count, vertex_offset, tip_vertex_index) {
const indices = out.indices;
for (let i = 0; i < count; i++) {
const index_0 = i + vertex_offset;
const index_1 = tip_vertex_index;
const index_2 = ((i + 1) % count) + vertex_offset;
const triangle_index = out.cursor_indices++;
const triangle_offset = triangle_index * 3;
indices[triangle_offset] = index_1;
indices[triangle_offset + 1] = index_2;
indices[triangle_offset + 2] = index_0;
}
}
export function makeSolidArrowGeometry(){
return new ArrowBufferGeometry();
}
function makeSolidArrowGeometry2(
radial_resolution = 16,
pointer_length = 0.5,
pointer_width = 0.2,
stem_width = 0.1
) {
const Z_OFFSET = -0.5;
const vertex_count = radial_resolution * 3 + 1;
const triangle_count = radial_resolution //arrow cap
+ radial_resolution * 2 // under-arrow ring
+ radial_resolution * 2 // stem
+ radial_resolution //cap for base of the stem
;
const builder = new StreamGeometryBuilder();
builder.allocate(vertex_count, triangle_count);
const positions = builder.positions;
// write tip
const tip_vertex_index = builder.cursor_vertices++;
positions[tip_vertex_index * 3] = 0;
positions[tip_vertex_index * 3 + 1] = 0;
positions[tip_vertex_index * 3 + 2] = Z_OFFSET + 1;
const ring_0_start = builder.cursor_vertices;
// make cone ring - arrow cap
make_ring_vertices(builder, radial_resolution, pointer_width, Z_OFFSET + 1 - pointer_length);
make_cone_indices(builder, radial_resolution, ring_0_start, tip_vertex_index);
const ring_1_start = builder.cursor_vertices;
// make ring 1
make_ring_vertices(builder, radial_resolution, stem_width, Z_OFFSET + 1 - pointer_length);
make_ring_faces(builder, ring_1_start, 1, radial_resolution);
const ring_2_start = builder.cursor_vertices;
make_ring_vertices(builder, radial_resolution, stem_width, Z_OFFSET + 0);
make_ring_faces(builder, ring_2_start, 1, radial_resolution);
return builder.build();
}