@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
227 lines (173 loc) • 7.01 kB
JavaScript
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
);
}