UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

276 lines (227 loc) • 8.45 kB
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}'`); } }