UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

227 lines (173 loc) • 7.01 kB
import { v3_dot } from "../../../../../../core/geom/vec3/v3_dot.js"; import { v3_length } from "../../../../../../core/geom/vec3/v3_length.js"; import { clamp } from "../../../../../../core/math/clamp.js"; import { max2 } from "../../../../../../core/math/max2.js"; import { computeNonuniformCaltmullRomSplineDerivative } from "../../../../../../core/math/spline/computeNonuniformCaltmullRomSplineDerivative.js"; import { PathNormalType } from "../PathNormalType.js"; import { computeFrenetFrames } from "./computeFrenetFrames.js"; import { makeTubeGeometry } from "./makeTubeGeometry.js"; const scratch_array_0 = []; const scratch_array_1 = []; const scratch_array_2 = []; const scratch_array_3 = []; /** * * @param {number[]} positions * @param {number[]} derivatives * @param {number} result_offset * @param {Path} path * @param {number} offset */ function sample_path(positions, derivatives, result_offset, path, offset) { if (!path.find_index_and_normalized_distance(scratch_array_0, offset)) { return 0; } /** * * @type {number} */ const i1 = scratch_array_0[0]; /** * * @type {number} */ const t = scratch_array_0[1]; const input_length = path.getPointCount(); const max_index = input_length - 1; const i0 = clamp(i1 - 1, 0, max_index); const i2 = clamp(i1 + 1, 0, max_index); const i3 = clamp(i1 + 2, 0, max_index); path.readPositionToArray(i0, scratch_array_0, 0); path.readPositionToArray(i1, scratch_array_1, 0); path.readPositionToArray(i2, scratch_array_2, 0); path.readPositionToArray(i3, scratch_array_3, 0); computeNonuniformCaltmullRomSplineDerivative( positions, result_offset, derivatives, result_offset, scratch_array_0, scratch_array_1, scratch_array_2, scratch_array_3, 3, t, 0.5 ); // normalize derivative const dx = derivatives[result_offset]; const dy = derivatives[result_offset + 1]; const dz = derivatives[result_offset + 2]; const mag = v3_length(dx, dy, dz); if (mag !== 0) { const inv_mag = 1 / mag; derivatives[result_offset] = dx * inv_mag; derivatives[result_offset + 1] = dy * inv_mag; derivatives[result_offset + 2] = dz * inv_mag; } return i1; } /** * * @param {Path} path * @param {TubePathStyle} style * @param {number[]} shape * @param {number[]|Float32Array} shape_normal * @param {number[]} shape_transform * @param {number} segment_start * @param {number} segment_end * @return {THREE.BufferGeometry} */ export function build_geometry_catmullrom( path, style, shape, shape_normal, shape_transform, segment_start, segment_end ) { const point_count = path.getPointCount(); // resample curve const total_points = Math.ceil(point_count * style.resolution); const reference_step_size = 1 / max2(0.00001, style.resolution); const reference_min_step_size = reference_step_size * 0.25; let step_size = reference_step_size; const path_length = path.length; const sample_positions_f32 = []; const sample_derivatives_f32 = []; let added_points = 0; // initial segment let last_knot_index = sample_path( sample_positions_f32, sample_derivatives_f32, added_points * 3, path, segment_start * path_length ); added_points++; let current_offset = segment_start * path_length; let step_resized_direction = 0; const absolute_end_offset = segment_end * path_length; for (; current_offset < absolute_end_offset; current_offset += step_size) { const point_address = added_points * 3; const knot_index = sample_path( sample_positions_f32, sample_derivatives_f32, point_address, path, current_offset ); // check difference with previous derivative const previous_point_index = added_points - 1; const previous_point_address = previous_point_index * 3; // derivatives are basically normals, and dot product is has Cosine value of angle between two vectors const dot = v3_dot( sample_derivatives_f32[previous_point_address], sample_derivatives_f32[previous_point_address + 1], sample_derivatives_f32[previous_point_address + 2], sample_derivatives_f32[point_address], sample_derivatives_f32[point_address + 1], sample_derivatives_f32[point_address + 2] ); // angular difference results in larger visual error for longer runs const error_size = (1 - dot) * step_size; if ( (step_size > reference_min_step_size) && ( ( // check if we jumped over a knot, so that we can sample down to get closer to it knot_index > last_knot_index && step_size > reference_min_step_size ) || ( error_size * 10 > reference_min_step_size && step_resized_direction <= 0 ) || ( // check if we're getting too close to the end of the segment, this lets us create a nice end current_offset + step_size > absolute_end_offset ) ) ) { // step looks to be too large current_offset -= step_size; step_size *= 0.5; step_resized_direction = -1; continue; // retry } else if ( error_size < 0.02 && step_resized_direction >= 0 ) { // step looks to be too small current_offset -= step_size; step_size *= 2; step_resized_direction = 1; continue; // retry } // reset adaptive step direction step_resized_direction = 0; // remember the last knot, so we know when we get to the end of the segment last_knot_index = knot_index; added_points++ } if ((absolute_end_offset - current_offset + step_size) > 0.0001) { // end sample_path(sample_positions_f32, sample_derivatives_f32, added_points * 3, path, absolute_end_offset); added_points++; } // console.log(`Total Points ${added_points}`) // DEBUG info const normal_hint = style.path_normal_type === PathNormalType.FixedStart ? style.path_normal : undefined; const frames = computeFrenetFrames(sample_positions_f32, false, normal_hint); return makeTubeGeometry( sample_positions_f32, frames.normals, frames.binormals, frames.tangents, shape, shape_normal, shape.length / 2, shape_transform, false, style.cap_type ); }