UNPKG

@xtor/cga.js

Version:

Xtor Compute Geometry Algorithm Libary 计算几何算法库

411 lines (369 loc) 12.6 kB
import { v3 } from "../../math/Vector3"; import { gPrecision, approximateEqual } from "../../math/Math"; import { Orientation } from "./type" export class Segment extends Array { /** * 线段 * @param {Point|Vector3} p0 * @param {Point|Vector3} p1 */ constructor(p0, p1) { super(); this.push(p0 || v3(), p1 || v3()); this.center = this.p0 .clone() .add(this.p1) .multiplyScalar(0.5); this.lenDirection = this.p1.clone().sub(this.p0); this.lenSq = this.lenDirection.lengthSq(); this.len = Math.sqrt(this.lenSq); this.direction = this.lenDirection.clone().normalize(); } get p0() { return this[0]; } set p0(value) { this[0] = value; } get p1() { return this[1]; } set p1(value) { this[1] = value; } /** * @param {Vector3} amount * @param {Vector3 target */ at(amount, target) { if (target === undefined) target = new Vector3(); return target .subVectors(this.end, this.start) .multiplyScalar(amount) .add(this.start); } equals(segment) { return (this.p0.equals(segment.p0) && this.p1.equals(segment.p1)) || (this.p1.equals(segment.p0) && this.p1.equals(segment.p0)) } clone() { return new Segment(this.p0.clone(), this.p1.clone()); } //---Distance--------------------------------------------- /** * 点是否在线段上 * @param {Vector3} point */ containPoint(point) { return point.inside(this); } /** * 点与线段的距离 * @param {Vector3} point */ distancePoint(point) { return point.distanceSegment(this); } /** * 线段到线段的距离 * @param {Segment} segment */ distanceSegment(segment) { var result = { parameters: [], closests: [] }; function GetClampedRoot(slope, h0, h1) { var r; if (h0 < 0) { if (h1 > 0) { r = -h0 / slope; if (r > 1) { r = 0.5; } // The slope is positive and -h0 is positive, so there is no // need to test for a negative value and clamp it. } else { r = 1; } } else { r = 0; } return r; } function ComputevarIntersection(sValue, classify, edge, end) { if (classify[0] < 0) { edge[0] = 0; end[0][0] = 0; end[0][1] = mF00 / mB; if (end[0][1] < 0 || end[0][1] > 1) { end[0][1] = 0.5; } if (classify[1] == 0) { edge[1] = 3; end[1][0] = sValue[1]; end[1][1] = 1; } else // classify[1] > 0 { edge[1] = 1; end[1][0] = 1; end[1][1] = mF10 / mB; if (end[1][1] < 0 || end[1][1] > 1) { end[1][1] = 0.5; } } } else if (classify[0] == 0) { edge[0] = 2; end[0][0] = sValue[0]; end[0][1] = 0; if (classify[1] < 0) { edge[1] = 0; end[1][0] = 0; end[1][1] = mF00 / mB; if (end[1][1] < 0 || end[1][1] > 1) { end[1][1] = 0.5; } } else if (classify[1] == 0) { edge[1] = 3; end[1][0] = sValue[1]; end[1][1] = 1; } else { edge[1] = 1; end[1][0] = 1; end[1][1] = mF10 / mB; if (end[1][1] < 0 || end[1][1] > 1) { end[1][1] = 0.5; } } } else // classify[0] > 0 { edge[0] = 1; end[0][0] = 1; end[0][1] = mF10 / mB; if (end[0][1] < 0 || end[0][1] > 1) { end[0][1] = 0.5; } if (classify[1] == 0) { edge[1] = 3; end[1][0] = sValue[1]; end[1][1] = 1; } else { edge[1] = 0; end[1][0] = 0; end[1][1] = mF00 / mB; if (end[1][1] < 0 || end[1][1] > 1) { end[1][1] = 0.5; } } } } function ComputeMinimumParameters(edge, end, parameters) { var delta = end[1][1] - end[0][1]; var h0 = delta * (-mB * end[0][0] + mC * end[0][1] - mE); if (h0 >= 0) { if (edge[0] == 0) { parameters[0] = 0; parameters[1] = GetClampedRoot(mC, mG00, mG01); } else if (edge[0] == 1) { parameters[0] = 1; parameters[1] = GetClampedRoot(mC, mG10, mG11); } else { parameters[0] = end[0][0]; parameters[1] = end[0][1]; } } else { var h1 = delta * (-mB * end[1][0] + mC * end[1][1] - mE); if (h1 <= 0) { if (edge[1] == 0) { parameters[0] = 0; parameters[1] = GetClampedRoot(mC, mG00, mG01); } else if (edge[1] == 1) { parameters[0] = 1; parameters[1] = GetClampedRoot(mC, mG10, mG11); } else { parameters[0] = end[1][0]; parameters[1] = end[1][1]; } } else // h0 < 0 and h1 > 0 { var z = Math.min(Math.max(h0 / (h0 - h1), 0), 1); var omz = 1 - z; parameters[0] = omz * end[0][0] + z * end[1][0]; parameters[1] = omz * end[0][1] + z * end[1][1]; } } } var seg0Dir = this.p1.clone().sub(this.p0); var seg1Dir = segment.p1.clone().sub(segment.p0); var segDiff = this.p0.clone().sub(segment.p0); var mA = seg0Dir.dot(seg0Dir); var mB = seg0Dir.dot(seg1Dir); var mC = seg1Dir.dot(seg1Dir); var mD = seg0Dir.dot(segDiff); var mE = seg1Dir.dot(segDiff); var mF00 = mD; var mF10 = mF00 + mA; var mF01 = mF00 - mB; var mF11 = mF10 - mB; var mG00 = -mE; var mG10 = mG00 - mB; var mG01 = mG00 + mC; var mG11 = mG10 + mC; if (mA > 0 && mC > 0) { var sValue = []; sValue[0] = GetClampedRoot(mA, mF00, mF10); sValue[1] = GetClampedRoot(mA, mF01, mF11); var classify = []; for (var i = 0; i < 2; ++i) { if (sValue[i] <= 0) { classify[i] = -1; } else if (sValue[i] >= 1) { classify[i] = +1; } else { classify[i] = 0; } } if (classify[0] == -1 && classify[1] == -1) { // The minimum must occur on s = 0 for 0 <= t <= 1. result.parameters[0] = 0; result.parameters[1] = GetClampedRoot(mC, mG00, mG01); } else if (classify[0] == +1 && classify[1] == +1) { // The minimum must occur on s = 1 for 0 <= t <= 1. result.parameters[0] = 1; result.parameters[1] = GetClampedRoot(mC, mG10, mG11); } else { // The line dR/ds = 0 varersects the domain [0,1]^2 in a // nondegenerate segment. Compute the endpoints of that segment, // end[0] and end[1]. The edge[i] flag tells you on which domain // edge end[i] lives: 0 (s=0), 1 (s=1), 2 (t=0), 3 (t=1). var edge = []; var end = new Array(2) for (let i = 0; i < end.length; i++) end[i] = new Array(2); ComputevarIntersection(sValue, classify, edge, end); // The directional derivative of R along the segment of // varersection is // H(z) = (end[1][1]-end[1][0])*dR/dt((1-z)*end[0] + z*end[1]) // for z in [0,1]. The formula uses the fact that dR/ds = 0 on // the segment. Compute the minimum of H on [0,1]. ComputeMinimumParameters(edge, end, result.parameters); } } else { if (mA > 0) { // The Q-segment is degenerate ( segment.point0 and segment.p0 are the same point) and // the quadratic is R(s,0) = a*s^2 + 2*d*s + f and has (half) // first derivative F(t) = a*s + d. The closests P-point is // varerior to the P-segment when F(0) < 0 and F(1) > 0. result.parameters[0] = GetClampedRoot(mA, mF00, mF10); result.parameters[1] = 0; } else if (mC > 0) { // The P-segment is degenerate ( this.point0 and this.p0 are the same point) and // the quadratic is R(0,t) = c*t^2 - 2*e*t + f and has (half) // first derivative G(t) = c*t - e. The closests Q-point is // varerior to the Q-segment when G(0) < 0 and G(1) > 0. result.parameters[0] = 0; result.parameters[1] = GetClampedRoot(mC, mG00, mG01); } else { // P-segment and Q-segment are degenerate. result.parameters[0] = 0; result.parameters[1] = 0; } } result.closests[0] = this.p0.clone().multiplyScalar(1 - result.parameters[0]).add( this.p1.clone().multiplyScalar(result.parameters[0])); result.closests[1] = segment.p0.clone().multiplyScalar(1 - result.parameters[1]).add( segment.p1.clone().multiplyScalar(result.parameters[1])); var diff = result.closests[0].clone().sub(result.closests[1]); result.sqrDistance = diff.dot(diff); result.distance = Math.sqrt(result.sqrDistance); return result; } /** * 射线到射线的距离 * @param {Ray} ray */ distanceRay(ray) { } /** * 线段到直线的距离 * @param {Ray} ray */ distanceLine(line) { } //---Intersect--------------------------------------------------- intersectSegment(segment) { const result = this.distanceSegment(segment); const resultLine = this.distanceLine(segment); result.interserct = false; if (!approximateEqual(this.normal.dot(segment.normal), 1, gPrecision)) { // 平行 if (resultLine.distance >= gPrecision) { //平行或共线不重叠 } else { //共线重叠 if (this.equals(segment)) { //# 相等 result.equals = true; } else if (result.parameters.every(o => o === 0 || o === 1)) { //只是端点相交 } else { //# 包含 被包含不用切割 包含被切割三段 //# 部分重叠 } } } else { if (result.distance > gPrecision) return result; result.interserct = true //相交 if (result.parameters.every(o => o === 0 || o === 1)) { //都是端点碰触 } else if (result.parameters[0] === 0 || result.parameters[0] === 1) { // this线段 在端点上 result.splitSegs = [[this], [new Segment(segment.p0, result.closests[1]), new Segment(result.closests[1], segment.p1)]] } else if (result.parameters[1] === 0 || result.parameters[1] === 1) { //segment线段在端点上 result.splitSegs = [[new Segment(this.p0, result.closests[0]), new Segment(result.closests[0], this.p1)], segment]; } else { // 两个都不在端点上 result.splitSegs = [[new Segment(this.p0, result.closests[0]), new Segment(result.closests[0], this.p1)], [new Segment(segment.p0, result.closests[1]), new Segment(result.closests[1], segment.p1)]]; } } return result; } //---Offset------------------------------------------------------ /** * 线段偏移 * @param {Vector3} binormal 偏移平面法线 * @param {Vector3} direction 偏移方向 * @param {Number} distance 偏移距离 */ offset(binormal, direction, distance) { binormal = binormal || new Vector3(0, 1, 0); var direction = p1.clone().sub(p0).normalize(); var tandir = direction.clone().cross(binormal).normalize(); var result = {} result.arr = [ tandir.clone().multiplyScalar(distance).add(p0), tandir.clone().multiplyScalar(distance).add(p1) ]; result.direction = direction result.tandir = tandir return result } //---方位--------------------- orientationPoint(point, normal = Vector3.UnitY) { var binormal = this.direction.clone().cross(normal); if (this.distanceLine(point).distance < gPrecision) return Orientation.Common; return point.clone().sub(this.origin).dot(binormal) > 0 ? Orientation.Positive : Orientation.Negative; } orientationSegment(segment, normal = Vector3.UnitY) { var or0 = this.orientationPoint(segment.p0,normal); var or1 = this.orientationPoint(segment.p1,normal); return or0 | or1; } } export function segment(p0, p1) { return new Segment(p0, p1); }