@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
276 lines (227 loc) • 8.45 kB
JavaScript
import { Vector3 } from "three";
import { m3_multiply } from "../../../../../../core/geom/mat3/m3_multiply.js";
import { m3_rm_compose_transform } from "../../../../../../core/geom/mat3/m3_rm_compose_transform.js";
import { v2_distance } from "../../../../../../core/geom/vec2/v2_distance.js";
import { v2_length } from "../../../../../../core/geom/vec2/v2_length.js";
import { max2 } from "../../../../../../core/math/max2.js";
import { CapType } from "../CapType.js";
import { make_ring_faces } from "./make_ring_faces.js";
import { make_ring_vertices } from "./make_ring_vertices.js";
/**
*
* @param {number} radial_segments
* @returns {number}
*/
function compute_cap_round_segment_count(radial_segments) {
return max2(3, Math.ceil(radial_segments / 4));
}
/**
*
* @param {number[]} shape
* @param {number} shape_length
* @param {number} cx
* @param {number} cy
* @returns {number}
*/
function compute_shape_radius(shape, shape_length, cx = 0, cy = 0) {
let max_distance = 0;
for (let i = 0; i < shape_length; i++) {
const i2 = i * 2;
const x = shape[i2];
const y = shape[i2 + 1];
// NOTE: can be optimized by delaying SQRT
const distance = v2_distance(x, y, cx, cy);
if (distance > max_distance) {
max_distance = distance;
}
}
return max_distance;
}
/**
*
* @param {number} index
* @param {Float32Array|number[]} in_positions
* @param {Vector3[]} in_normals
* @param {Vector3[]} in_binormals
* @param {Vector3[]} in_tangents
* @param {StreamGeometryBuilder} out
* @param {number[]} shape
* @param {number[]|Float32Array} shape_normal
* @param {number} shape_length
* @param {number[]} shape_transform
* @param {number} direction
*/
function make_cap_round(
out,
index,
in_positions, in_normals, in_binormals, in_tangents,
shape, shape_normal, shape_length, shape_transform, direction
) {
// how many radial segments will be placed laterally to make up the cap
const cap_segment_count = compute_cap_round_segment_count(shape_length);
const i3 = index * 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[index];
const B = in_binormals[index];
const T = in_tangents[index];
const tangent = new Vector3();
tangent.crossVectors(N, B);
tangent.normalize();
tangent.multiplyScalar(-direction);
const normal = direction > 0 ? N : N.clone().negate();
const binormal = direction > 0 ? B : B.clone().negate();
const angular_step_i = (Math.PI / 2) / cap_segment_count;
const uv_u = index / (in_positions.length / 3 - 1);
let i;
const index_offset = out.cursor_vertices;
const m3 = new Float32Array(9);
const radius_raw = compute_shape_radius(shape, shape_length);
// extract scale from shape matrix
const scale_x = v2_length(shape_transform[0], shape_transform[3]);
const scale_y = v2_length(shape_transform[1], shape_transform[4]);
const radius = radius_raw * max2(scale_x, scale_y);
for (i = 0; i <= cap_segment_count; i++) {
const j = direction > 0 ? i : cap_segment_count - i;
const angle_b = j * angular_step_i;
const cos_b = Math.cos(angle_b);
const sin_b = Math.sin(angle_b);
// build shape transform matrix that scales original shape down
m3_rm_compose_transform(m3, 0, 0, sin_b, sin_b, 0, 0, 0);
m3_multiply(m3, m3, shape_transform);
const _d = radius;
// compute offset for ring center
const _px = Px + tangent.x * cos_b * _d;
const _py = Py + tangent.y * cos_b * _d;
const _pz = Pz + tangent.z * cos_b * _d;
// build ring
// TODO normals on produced geometry are incorrect as they need to be bent along the cap
make_ring_vertices(
out,
_px, _py, _pz,
normal, binormal, tangent,
uv_u, v4_no_bend,
shape, shape_normal, shape_length, m3
);
}
make_ring_faces(out, index_offset, cap_segment_count, shape_length);
}
const m3_zero = new Float32Array(9);
const v4_array = new Float32Array(4);
const v4_no_bend = new Float32Array([0, 1, 0, 0]);
/**
*
* @param {number} index
* @param {Float32Array|number[]} in_positions
* @param {Vector3[]} in_normals
* @param {Vector3[]} in_binormals
* @param {Vector3[]} in_tangents
* @param {StreamGeometryBuilder} out
* @param {number[]} shape
* @param {number[]|Float32Array} shape_normal
* @param {number} shape_length
* @param {number[]} shape_transform
* @param {number} direction
*/
function make_cap_flat(
out,
index,
in_positions, in_normals, in_binormals, in_tangents,
shape, shape_normal, shape_length, shape_transform, direction
) {
// how many radial segments will be placed laterally to make up the cap
const i3 = index * 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[index];
const B = in_binormals[index];
const tangent = new Vector3();
tangent.crossVectors(N, B);
tangent.normalize();
const normal = direction > 0 ? N.clone().negate() : N;
const uv_u = index / (in_positions.length / 3 - 1);
const index_offset = out.cursor_vertices;
make_ring_vertices(
out,
Px, Py, Pz,
normal, B, in_tangents[index],
uv_u, v4_no_bend,
shape, shape_normal, shape_length, shape_transform
);
make_ring_vertices(
out,
Px, Py, Pz,
normal, B, in_tangents[index],
uv_u, v4_no_bend,
shape, shape_normal, shape_length, m3_zero
);
make_ring_faces(out, index_offset, 1, shape_length);
}
/**
*
* @param {number} index
* @param {Float32Array|number[]} in_positions
* @param {Vector3[]} in_normals
* @param {Vector3[]} in_binormals
* @param {StreamGeometryBuilder} out
* @param {Vector3[]} in_tangents
* @param {number[]} shape
* @param {number[]|Float32Array} shape_normal
* @param {number} shape_length
* @param {number[]|Float32Array} shape_transform
* @param {number} direction
* @param {CapType} type
*/
export function make_cap(
out,
index,
in_positions, in_normals, in_binormals, in_tangents,
shape, shape_normal, shape_length, shape_transform, direction, type
) {
if (type === CapType.None) {
// do nothing
} else if (type === CapType.Round) {
make_cap_round(
out,
index,
in_positions, in_normals, in_binormals, in_tangents,
shape, shape_normal, shape_length, shape_transform,
direction
);
} else if (type === CapType.Flat) {
make_cap_flat(
out,
index,
in_positions, in_normals, in_binormals, in_tangents,
shape, shape_normal, shape_length, shape_transform,
direction
);
} else {
throw new Error(`Unsupported cap type '${type}'`);
}
}
/**
* Increases target geometry buffers to accommodate a given number of caps
* @param {number} count how many caps
* @param {{polygon_count:number, vertex_count:number}} out where to increment
* @param {number} radial_segments number of lines that make up profile of the extruded shape
* @param {CapType} type
*/
export function append_compute_cap_geometry_size(count, out, radial_segments, type) {
if (type === CapType.None) {
// do nothing
} else if (type === CapType.Round) {
const cap_segment_count = compute_cap_round_segment_count(radial_segments);
out.vertex_count += count * (cap_segment_count + 1) * (radial_segments + 1);
out.polygon_count += count * cap_segment_count * (radial_segments) * 2;
} else if (type === CapType.Flat) {
out.vertex_count += count * (2) * (radial_segments + 1);
out.polygon_count += count * (radial_segments) * 2;
} else {
throw new Error(`Unsupported cap type '${type}'`);
}
}