286 lines
7.0 KiB
TypeScript
286 lines
7.0 KiB
TypeScript
import * as THREE from 'three';
|
|
import { Geometry, Vector, Vector2, Vector3, Box3 } from 'three';
|
|
import { Matrix2 } from './Matrix2';
|
|
|
|
|
|
export const cZeroVec = new THREE.Vector3();
|
|
export const cXAxis = new THREE.Vector3(1, 0, 0);
|
|
export const cYAxis = new THREE.Vector3(0, 1, 0);
|
|
export const cZAxis = new THREE.Vector3(0, 0, 1);
|
|
|
|
/**
|
|
* 旋转一个点,旋转中心在原点
|
|
*
|
|
* @export
|
|
* @param {Vector3} pt 点
|
|
* @param {number} ang 角度.
|
|
* @returns {Vector3} 返回pt不拷贝.
|
|
*/
|
|
export function rotatePoint(pt: Vector3, ang: number): Vector3
|
|
{
|
|
new Matrix2().setRotate(ang).applyVector(pt);
|
|
return pt;
|
|
}
|
|
|
|
export function equaln(v1: number, v2: number, fuzz = 1e-3)
|
|
{
|
|
return Math.abs(v1 - v2) < fuzz;
|
|
}
|
|
export function equal<T extends Vector>(v1: T, v2: T)
|
|
{
|
|
return v1.distanceToSquared(v2) < 1e-8;
|
|
}
|
|
|
|
export function fixAngle(an: number, fixAngle: number, fuzz: number = 0.1)
|
|
{
|
|
if (an < 0)
|
|
an += Math.PI * 2;
|
|
an += fuzz;
|
|
let rem = an % fixAngle;
|
|
if (rem < fuzz * 2)
|
|
{
|
|
an -= rem;
|
|
}
|
|
else
|
|
{
|
|
an -= fuzz;
|
|
}
|
|
return an;
|
|
}
|
|
|
|
/**
|
|
* 按照极坐标的方式移动一个点
|
|
*
|
|
* @export
|
|
* @template
|
|
* @param {T} v 向量(2d,3d)
|
|
* @param {number} an 角度
|
|
* @param {number} dis 距离
|
|
* @returns {T}
|
|
*/
|
|
export function polar<T extends Vector2 | Vector3>(v: T, an: number, dis: number): T
|
|
{
|
|
v.x += Math.cos(an) * dis;
|
|
v.y += Math.sin(an) * dis;
|
|
return v;
|
|
}
|
|
|
|
export function angle(v: Vector3 | Vector2)
|
|
{
|
|
if (equaln(v.y, 0) && v.x > 0)
|
|
return 0;
|
|
let angle = Math.atan2(v.y, v.x);
|
|
if (angle < 0) angle += Math.PI * 2;
|
|
return angle;
|
|
}
|
|
|
|
/**
|
|
* 求两个向量的夹角,顺时针为负,逆时针为正
|
|
*
|
|
* @param {THREE.Vector3} v1
|
|
* @param {THREE.Vector3} v2
|
|
* @param {THREE.Vector3} [ref] 参考向量,如果为世界坐标系则为0,0,1
|
|
* @returns
|
|
*/
|
|
export function angleTo(v1: THREE.Vector3, v2: THREE.Vector3, ref: THREE.Vector3 = new THREE.Vector3(0, 0, 1))
|
|
{
|
|
if (!ref.equals(new Vector3(0, 0, 1)))
|
|
{
|
|
//任意轴坐标系. 使用相机的构造矩阵.
|
|
ref.multiplyScalar(-1);
|
|
let up = getLoocAtUpVec(ref);
|
|
let refOcs = new THREE.Matrix4();
|
|
refOcs.lookAt(cZeroVec, ref, up);
|
|
let refOcsInv = new THREE.Matrix4().getInverse(refOcs);
|
|
v1.applyMatrix4(refOcsInv);
|
|
v2.applyMatrix4(refOcsInv);
|
|
v1.z = 0;
|
|
v2.z = 0;
|
|
}
|
|
if (v1.equals(cZeroVec) || v2.equals(cZeroVec))
|
|
return 0;
|
|
let cv = new Vector3().crossVectors(v1, v2).normalize();
|
|
return cv.z === 0 ? v1.angleTo(v2) : v1.angleTo(v2) * cv.z;
|
|
}
|
|
|
|
export function getLoocAtUpVec(dir: THREE.Vector3): THREE.Vector3
|
|
{
|
|
if (dir.equals(cZeroVec))
|
|
{
|
|
throw ("zero vector")
|
|
}
|
|
let norm = dir.clone().normalize();
|
|
if (norm.equals(cZAxis))
|
|
{
|
|
return new THREE.Vector3(0, 1, 0);
|
|
}
|
|
else if (norm.equals(cZAxis.clone().negate()))
|
|
{
|
|
return new THREE.Vector3(0, -1, 0);
|
|
}
|
|
else
|
|
{
|
|
let xv: THREE.Vector3 = new THREE.Vector3();
|
|
xv.crossVectors(cZAxis, norm);
|
|
|
|
let up = new THREE.Vector3();
|
|
up.crossVectors(norm, xv);
|
|
return up;
|
|
}
|
|
}
|
|
|
|
export function createLookAtMat4(dir: THREE.Vector3): THREE.Matrix4
|
|
{
|
|
let up = getLoocAtUpVec(dir);
|
|
let mat = new THREE.Matrix4();
|
|
mat.lookAt(cZeroVec, dir, up);
|
|
return mat;
|
|
}
|
|
|
|
export function isParallelTo(v1: THREE.Vector3, v2: THREE.Vector3)
|
|
{
|
|
return v1.clone().cross(v2).lengthSq() < 1e-9;
|
|
}
|
|
|
|
export function ptToString(v: THREE.Vector3, fractionDigits: number = 3): string
|
|
{
|
|
return v.toArray().map(o =>
|
|
{
|
|
return o.toFixed(fractionDigits)
|
|
}).join(",")
|
|
}
|
|
|
|
export function midPoint(v1: THREE.Vector3, v2: THREE.Vector3): THREE.Vector3
|
|
{
|
|
return v1.clone().add(v2).multiplyScalar(0.5);
|
|
}
|
|
export function midPoint2(v1: THREE.Vector2, v2: THREE.Vector2): THREE.Vector2
|
|
{
|
|
return v1.clone().add(v2).multiplyScalar(0.5);
|
|
}
|
|
|
|
export function midPtCir(v1: THREE.Vector3, v2: THREE.Vector3)
|
|
{
|
|
let baseline = new Vector3(1, 0, 0);
|
|
let outLine = v2.clone().sub(v1);
|
|
let ang = angleTo(baseline, outLine) / 2;
|
|
let midLine = rotatePoint(outLine, -ang);
|
|
return v1.clone().add(midLine);
|
|
}
|
|
|
|
/**
|
|
* 获得Three对象的包围盒.
|
|
* @param obj
|
|
* @param [updateMatrix] 是否应该更新对象矩阵
|
|
* @returns box
|
|
*/
|
|
export function GetBox(obj: THREE.Object3D, updateMatrix?: boolean): THREE.Box3
|
|
{
|
|
let box = new Box3();
|
|
if (updateMatrix) obj.updateMatrixWorld(false);
|
|
if (!obj.visible) return box;
|
|
|
|
obj.traverse(o =>
|
|
{
|
|
//因为实体Erase时,实体仍然保存在Scene中.
|
|
if (o.visible === false)
|
|
return;
|
|
|
|
//@ts-ignore
|
|
let geo = o.geometry as BufferGeometry;
|
|
if (geo)
|
|
{
|
|
if (!geo.boundingBox)
|
|
geo.computeBoundingBox();
|
|
box.union(geo.boundingBox.clone().applyMatrix4(o.matrixWorld));
|
|
}
|
|
});
|
|
|
|
return box;
|
|
}
|
|
|
|
export function GetBoxArr(arr: Array<THREE.Object3D>): THREE.Box3
|
|
{
|
|
let box = new Box3();
|
|
for (let o of arr)
|
|
{
|
|
let b = GetBox(o);
|
|
if (!b.isEmpty())
|
|
box.union(b);
|
|
}
|
|
return box;
|
|
}
|
|
|
|
export function MoveMatrix(v: THREE.Vector3): THREE.Matrix4
|
|
{
|
|
let mat = new THREE.Matrix4();
|
|
mat.makeTranslation(v.x, v.y, v.z);
|
|
return mat;
|
|
}
|
|
|
|
export function getProjectDist(v1: Vector3, v2: Vector3)
|
|
{
|
|
let ang = v1.angleTo(v2);
|
|
let dist = v1.length();
|
|
return {
|
|
h: dist * Math.cos(ang),
|
|
v: dist * Math.sin(ang)
|
|
}
|
|
}
|
|
//获得输入点在2线组成的4个区间的位置
|
|
export function getPtPostion(sp: Vector3, ep: Vector3, c: Vector3, inPt: Vector3)
|
|
{
|
|
let l1 = sp.clone().sub(c);
|
|
let l2 = ep.clone().sub(c);
|
|
let l3 = l1.clone().negate();
|
|
let l4 = l2.clone().negate();
|
|
let inputLine = inPt.clone().sub(c);
|
|
let ang1 = angleTo(l1, l2);
|
|
let ang2 = Math.PI;
|
|
let ang3 = ang2 + Math.abs(ang1);
|
|
let inputAng = angleTo(l1, inputLine);
|
|
if (ang1 * inputAng < 0)
|
|
{
|
|
inputAng = (Math.PI * 2 - Math.abs(inputAng));
|
|
}
|
|
ang1 = Math.abs(ang1);
|
|
inputAng = Math.abs(inputAng);
|
|
if (inputAng <= ang1)
|
|
{
|
|
return { sp, ep };
|
|
} else if (inputAng > ang1 && inputAng <= ang2)
|
|
{
|
|
return { sp: c.clone().add(l3), ep }
|
|
} else if (inputAng > ang2 && inputAng <= ang3)
|
|
{
|
|
return { sp: c.clone().add(l3), ep: c.clone().add(l4) }
|
|
} else
|
|
{
|
|
return { sp, ep: c.clone().add(l4) };
|
|
}
|
|
}
|
|
export function angleAndX(v: Vector3 | Vector2)
|
|
{
|
|
return v.x ? Math.atan(v.y / v.x) : Math.PI / 2;
|
|
}
|
|
/**
|
|
* 将角度调整为0-2pi之间
|
|
*
|
|
* @export
|
|
* @param {number} an
|
|
*/
|
|
export function angleTo2Pi(an: number)
|
|
{
|
|
an = an % (Math.PI * 2);
|
|
if (an < 0) an += Math.PI * 2
|
|
return an;
|
|
}
|
|
export function updateGeometry(l: THREE.Line | THREE.Mesh, geometry: Geometry)
|
|
{
|
|
let geo = l.geometry as THREE.Geometry;
|
|
geo.dispose();
|
|
l.geometry = geometry;
|
|
geometry.verticesNeedUpdate = true;
|
|
geometry.computeBoundingSphere();
|
|
}
|