@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
203 lines (154 loc) • 5.91 kB
JavaScript
import { BufferGeometry, Vector3 } from "three";
import { assert } from "../../../../../../core/assert.js";
import { v3_angle_cos_between } from "../../../../../../core/geom/vec3/v3_angle_cos_between.js";
import { v3_length } from "../../../../../../core/geom/vec3/v3_length.js";
import { CapType } from "../CapType.js";
import { append_compute_cap_geometry_size, make_cap } from "./make_cap.js";
import { make_ring_faces } from "./make_ring_faces.js";
import { make_ring_vertices } from "./make_ring_vertices.js";
import { StreamGeometryBuilder } from "./StreamGeometryBuilder.js";
const v4_array = new Float32Array(4);
/**
* @see https://github.com/mrdoob/three.js/blob/master/src/geometries/TubeGeometry.js
* @see https://github.com/hofk/THREEg.js/blob/488f1128a25321a76888aa1fa19db64750318444/THREEg.js#L3483
* @param {Float32Array|number[]} in_positions
* @param {Vector3[]} in_normals
* @param {Vector3[]} in_binormals
* @param {Vector3[]} in_tangents
* @param {number[]} shape
* @param {number[]|Float32Array} shape_normal
* @param {number} shape_length
* @param {number[]|Float32Array} shape_transform
* @param {boolean} [closed]
* @param {CapType} [cap_type]
* @returns {BufferGeometry}
*/
export function makeTubeGeometry(
in_positions, in_normals, in_binormals, in_tangents,
shape, shape_normal, shape_length, shape_transform, closed = false, cap_type = CapType.Round
) {
assert.enum(cap_type, CapType, 'cap_type');
assert.isBoolean(closed, 'closed');
assert.isNumber(shape_length, 'shape_length');
assert.isArrayLike(shape, 'shape');
const out = new StreamGeometryBuilder();
// helper variables
const point_count = in_positions.length / 3;
const tubular_segments = point_count - 1;
const geometry_size = {
vertex_count: (tubular_segments + 1) * (shape_length + 1),
polygon_count: tubular_segments * shape_length * 2
};
if (!closed) {
append_compute_cap_geometry_size(2, geometry_size, shape_length, cap_type);
}
out.allocate(
geometry_size.vertex_count,
geometry_size.polygon_count
);
// create buffer data
if (!closed) {
// start cap
make_cap(
out, 0,
in_positions, in_normals, in_binormals, in_tangents,
shape, shape_normal, shape_length, shape_transform, 1, cap_type
);
}
const index_offset = out.cursor_vertices;
for (let i = 0; i < tubular_segments; i++) {
generateSegment(i);
}
// if the geometry is not closed, generate the last row of vertices and normals
// at the regular position on the given path
//
// if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ)
generateSegment((closed === false) ? tubular_segments : 0);
// finally create faces
make_ring_faces(out, index_offset, tubular_segments, shape_length);
if (!closed) {
// end cap
make_cap(
out, point_count - 1,
in_positions, in_normals, in_binormals, in_tangents,
shape, shape_normal, shape_length, shape_transform, -1, cap_type
);
}
/**
*
* @param {number} i
*/
function generateSegment(i) {
// we use getPointAt to sample evenly distributed points from the given path
const i3 = i * 3;
const Px = in_positions[i3];
const Py = in_positions[i3 + 1];
const Pz = in_positions[i3 + 2];
// retrieve corresponding normal and binormal
const N = in_normals[i];
const B = in_binormals[i];
// generate normals and vertices for the current segment
compute_bend_normal(v4_array, i, tubular_segments, in_positions);
make_ring_vertices(
out,
Px, Py, Pz,
N, B, in_tangents[i],
i / tubular_segments, v4_array,
shape, shape_normal, shape_length, shape_transform
);
}
return out.build();
}
/**
*
* @param {number[]|Float32Array} out
* @param {number} index
* @param {number} index_count
* @param {number[]|Float32Array} positions
*/
function compute_bend_normal(
out,
index,
index_count,
positions
) {
if (index <= 0 || index >= index_count - 1) {
// end points, no bending
out[0] = 0;
out[1] = 1;
out[2] = 0;
out[3] = 0;
return;
}
const index_next = index + 1;
const index_prev = index - 1;
const address_current = index * 3;
const address_next = index_next * 3;
const address_prev = index_prev * 3;
const i0_x = positions[address_prev];
const i0_y = positions[address_prev + 1];
const i0_z = positions[address_prev + 2];
const i1_x = positions[address_current];
const i1_y = positions[address_current + 1];
const i1_z = positions[address_current + 2];
const i2_x = positions[address_next];
const i2_y = positions[address_next + 1];
const i2_z = positions[address_next + 2];
const d0_x = i0_x - i1_x;
const d0_y = i0_y - i1_y;
const d0_z = i0_z - i1_z;
const d1_x = i1_x - i2_x;
const d1_y = i1_y - i2_y;
const d1_z = i1_z - i2_z;
// compute rotation axis
const cross_x = d0_y * d1_z - d0_z * d1_y;
const cross_y = d0_z * d1_x - d0_x * d1_z;
const cross_z = d0_x * d1_y - d0_y * d1_x;
const angle = v3_angle_cos_between(d0_x, d0_y, d0_z, d1_x, d1_y, d1_z);
const length_inv = 1 / v3_length(cross_x, cross_y, cross_z);
out[0] = cross_x * length_inv;
out[1] = cross_y * length_inv;
out[2] = cross_z * length_inv;
// bend amount
out[3] = (1 - Math.abs(angle)) * 0.5;
}