Commit 4485971c authored by hucy's avatar hucy

fix:向量

parent 3f2391a4
......@@ -136,4 +136,11 @@ export const MenuList = [
link: '/vue-konva',
active: false,
},
{
title: '向量',
caption: 'JavaScript 线性代数:向量',
icon: require('./menuListIcons/amis.svg'),
link: '/vector',
active: false,
},
];
<!--
* JavaScript 线性代数:向量
* https://juejin.cn/post/6844903859689619469
-->
<script setup lang="ts">
import { reactive, onMounted } from 'vue';
import { Vector } from './utils';
interface Point {
x: number;
y: number;
gap?: number;
[proppName: string]: any;
}
const stageSize = reactive({
width: 1200,
height: 800,
gridGap: 100,
});
const state = reactive({
canvasDom: null as any,
pen: null as any,
});
onMounted(() => {
state.canvasDom = document.getElementById('canvas-grid');
state.pen = state.canvasDom.getContext('2d');
drawGrid();
const pointO = { x: 500, y: 300, name: 'O' };
const pointA = { x: 600, y: 200, name: 'A' };
const pointB = { x: 400, y: 200, name: 'B' };
drawVector(pointO, pointA);
drawVector(pointO, pointB);
// 加法
const pointC = new Vector(pointO, pointA).add(pointB);
const len = new Vector(pointO, pointB).length;
console.log('OB向量长度', len);
drawVector(pointO, pointC, '#21BA45');
// 减
// const pointD = new Vector(pointO, pointB).subtract(pointA);
// console.log('pointD', pointD);
// drawVector(pointO, pointD, '#FFA000');
// 求夹角
const angle = new Vector(pointO, pointA).dotProduct(pointB);
console.log('夹角', angle);
});
function drawVector(pointX: Point, pointY: Point, color = '#F44336') {
const pen = state.pen;
pen.save();
pen.beginPath();
pen.moveTo(pointX.x, pointX.y);
pen.lineTo(pointY.x, pointY.y);
pen.lineWidth = 4;
pen.strokeStyle = color;
pen.stroke();
pen.fillText(pointX.name, pointX.x, pointX.y);
pen.fillText(pointY.name, pointY.x, pointY.y);
pen.restore();
}
// 网格线
function drawGrid() {
let canvas: any = document.getElementById('canvas-grid');
let pen = canvas.getContext('2d');
// 绘制网格
const step = stageSize.gridGap;
const h = stageSize.height;
const w = stageSize.width;
const w_l = w / step;
const h_l = h / step;
// 横着的线
pen.save();
for (let i = 0; i <= h_l; i++) {
pen.beginPath();
pen.moveTo(0, i * step);
pen.lineTo(w, i * step);
pen.stroke();
}
// 竖着的线
for (let i = 0; i <= w_l; i++) {
pen.beginPath();
pen.moveTo(i * step, 0);
pen.lineTo(i * step, h);
pen.stroke();
}
pen.restore();
}
</script>
<template>
<div class="center">
<div class="canvas-box">
<canvas
id="canvas-grid"
:width="stageSize.width"
:height="stageSize.height"
style="position: absolute"
></canvas>
</div>
</div>
</template>
<style lang="scss" scoped>
.canvas-box {
box-sizing: border-box;
width: 1200px;
height: 800px;
border: 1px solid #000;
position: relative;
}
</style>
export default [
{
path: 'vector',
name: 'VECTOR',
component: () => import('./IndexPage.vue'),
meta: {
title: '向量',
permission: ['*'],
keepalive: true,
},
},
];
interface Point {
x: number;
y: number;
[proppName: string]: any;
}
/**
* 创建一个2维度向量类
*/
export class Vector {
components: any;
length: number;
constructor(...components: any) {
this.components = components;
const pointO = components[0];
const pointA = components[1];
const diffX = pointA.x - pointO.x;
const diffY = pointA.y - pointO.y;
this.length = Math.sqrt(diffX * diffX + diffY * diffY);
}
// 加法
add(components: any) {
const pointO = this.components[0];
const pointA = this.components[1];
const pointB = components;
return {
x: pointA.x + pointB.x - pointO.x,
y: pointA.y + pointB.y - pointO.y,
};
}
// 减
subtract(components: any) {
const pointO = this.components[0];
const pointA = this.components[1];
const pointC = components;
return {
x: pointA.x - pointC.x + pointO.x,
y: pointA.y - pointC.y + pointO.y,
};
}
// 向量点乘:(内积)
// https://zhuanlan.zhihu.com/p/359975221
dotProduct(components: any) {
const pointO = this.components[0];
const pointA = this.components[1];
const pointB = components;
const _pointA = { x: pointA.x - pointO.x, y: pointA.y - pointO.y };
const _pointB = { x: pointB.x - pointO.x, y: pointB.y - pointO.y };
// 求向量OA与OB的夹角
// AB向量 = OB向量 - OA向量
// const ab_x = _pointB.x - _pointA.x;
// const ab_y = _pointB.y - _pointA.y;
// OA向量 * OB向量 > 0,夹角在0~90度之间
// OA向量 * OB向量 = 0,正交,相互垂直
// OA向量 * OB向量 < 0,夹角在90度~180度之间
//
// OA向量 * OB向量 = x1*x2 + y1*y2;
const abMultiplication = _pointA.x * _pointB.x + _pointA.y * _pointB.y;
// OA向量的模
const abs_OA = new Vector({ x: 0, y: 0 }, _pointA).length;
// OB向量的模
const abs_OB = new Vector({ x: 0, y: 0 }, _pointB).length;
// 得到弧度值
let result = Math.acos(abMultiplication / (abs_OA * abs_OB));
// 转为角度值
result = toAngle(result);
const arr = [];
arr.push(_pointA);
arr.push({ x: 0, y: 0 });
arr.push(_pointB);
const is_clockwise = isClockwise(arr);
if (is_clockwise) {
// 顺时针方向 大于180度
result = 360 - result;
} else {
}
return result;
}
}
/**
* 判断坐标数组是否顺时针(默认为false)
* @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
* @returns {boolean} 是否顺时针
*/
export function isClockwise(points: Point[]) {
// 三个点可以判断矢量是顺时针旋转还是逆时针旋转的,但由于可能存在凹边,所以并不是任意三点都可以正确反映多边形的走向
// 因此需要取多边形中绝对是凸边的点来判断,
// 多边形中的极值点(x最大或x最小或y最大或y最小)它与相邻两点构成的边必然是凸边,因此我们先取出多边形中的极值点,再由极值点和其前后两点去判断矢量的走向,从而判断出多边形的走向。
if (!Array.isArray(points) || points.length < 3) {
console.error('多边形坐标集合不能少于3个');
return false;
}
let coords = JSON.parse(JSON.stringify(points));
if (coords[0] === coords[coords.length - 1]) {
coords = coords.slice(0, coords.length - 1);
}
coords = coords.reverse();
let maxXIndex = 0;
let maxX = parseFloat(coords[maxXIndex].x);
let c1;
let c2;
let c3;
for (let i = 0; i < coords.length; i++) {
if (parseFloat(coords[i].x) > maxX) {
maxX = parseFloat(coords[i].x);
maxXIndex = i;
}
}
if (maxXIndex === 0) {
c1 = coords[coords.length - 1];
c2 = coords[maxXIndex];
c3 = coords[maxXIndex + 1];
} else if (maxXIndex === coords.length - 1) {
c1 = coords[maxXIndex - 1];
c2 = coords[maxXIndex];
c3 = coords[0];
} else {
c1 = coords[maxXIndex - 1];
c2 = coords[maxXIndex];
c3 = coords[maxXIndex + 1];
}
const x1 = parseFloat(c1.x);
const y1 = parseFloat(c1.y);
const x2 = parseFloat(c2.x);
const y2 = parseFloat(c2.y);
const x3 = parseFloat(c3.x);
const y3 = parseFloat(c3.y);
const s = (x1 - x3) * (y2 - y3) - (x2 - x3) * (y1 - y3);
return s < 0;
}
/**
* 转为弧度值
*/
export function toRadian(val: number) {
return (val * Math.PI) / 180;
}
/**
* 转角度值
*/
export function toAngle(val: number) {
return (val * 180) / Math.PI;
}
......@@ -4,15 +4,18 @@
* @Date: 2023-03-29
-->
<script setup lang="ts">
import { reactive, onMounted } from 'vue';
import { ref, reactive, onMounted } from 'vue';
import { getBoundingBox } from 'src/common/utils';
import { scalePolygon } from './utils';
import Konva from 'konva';
import Decimal from 'decimal.js';
const startSelected = ref(true);
const state = reactive({
stage: null as any,
layer: null as any,
group: null as any,
group: new Map(),
myMap: new Map(),
// shape: null as any,
// boundingBox: null as any,
// box: null as any,
......@@ -20,33 +23,47 @@ const state = reactive({
{
name: 'test1',
color: '#00D2FF',
selected: true,
path: [
{ x: 100, y: 100 },
{ x: 200, y: 100 },
{ x: 200, y: 200 },
{ x: 0, y: 300 },
],
},
{
name: 'test2',
color: '#4CAF50',
path: [
{ x: 300, y: 200 },
{ x: 200, y: 400 },
{ x: 100, y: 600 },
{ x: 400, y: 600 },
{ x: 500, y: 200 },
],
},
{
name: 'test3',
color: '#FF8A80',
path: [
{ x: 600, y: 200 },
{ x: 500, y: 400 },
{ x: 300, y: 100 },
{ x: 100, y: 100, gap: 20 },
{ x: 200, y: 100, gap: 10 },
{ x: 200, y: 200, gap: 40 },
{ x: 0, y: 300, gap: 10 },
],
},
// {
// name: 'test2',
// color: '#4CAF50',
// selected: true,
// path: [
// { x: 300, y: 200 },
// { x: 200, y: 400 },
// { x: 100, y: 600 },
// { x: 400, y: 600 },
// { x: 500, y: 200 },
// ],
// },
// {
// name: 'test3',
// color: '#FF8A80',
// selected: true,
// path: [
// { x: 600, y: 200 },
// { x: 500, y: 400 },
// { x: 300, y: 100 },
// ],
// },
// {
// name: 'test4',
// color: '#FFC107',
// selected: true,
// path: [
// { x: 1000, y: 0 },
// { x: 1100, y: 0 },
// { x: 1100, y: 100 },
// { x: 1000, y: 100 },
// ],
// },
] as any[],
});
......@@ -74,11 +91,16 @@ onMounted(() => {
state.layer = new Konva.Layer();
state.stage.add(state.layer);
let index = 0;
for (const item of state.shapePath) {
let shape = createShape(item);
if (shape) {
state.layer.add(shape);
state.group.set(shape._id, index); // 储存 state.shapePath的下标
state.myMap.set(item.name, shape._id);
}
index++;
}
state.layer.on('dragmove', layerDragmove);
......@@ -107,7 +129,7 @@ function createShape(itemData: any) {
height: boundingBox.height,
stroke: 'red',
// fill: 'grey',
strokeWidth: 4,
strokeWidth: 2,
});
// 如果添加到group,则坐标的相对位置时基于group的位置
......@@ -136,21 +158,71 @@ function createShape(itemData: any) {
// stroke: 'white',
// strokeWidth: 10,
});
// 向外扩大 start
const extraPath = scalePolygon(path) as any[];
const shape2: any = new Konva.Shape({
sceneFunc: function (context, shape) {
context.beginPath();
let index = 0;
for (const i of extraPath) {
const x = i.x;
const y = i.y;
if (index === 0) {
context.moveTo(x, y);
} else {
context.lineTo(x, y);
}
// context.quadraticCurveTo(150, 100, 260, 170); 弧线
index++;
}
context.closePath();
// (!) Konva specific method, it is very important
context.fillStrokeShape(shape);
},
// fill: fillColor,
name,
stroke: '#21BA45',
strokeWidth: 2,
});
// 向外扩大 end
// 先添加的边界框矩形,再添加的自定义图像,
// 顺序和下面layerDragmove时获取children的顺序保持一致
group.add(rectBox);
group.add(shape);
group.add(shape2);
return group;
}
}
function layerDragmove(e: any) {
let target = e.target;
state.layer.children.forEach(function (group: any) {
// do not check intersection with itself
// 不检查与自身相交
if (group._id == target._id) {
return;
}
});
const target = e.target;
const targetRectAttr = target.children[0].attrs;
// const targetShapeAttr = target.children[1].attrs;
const diffX = target.attrs.x;
const diffY = target.attrs.y;
const taIndex = state.group.get(target._id);
const ta = state.shapePath[taIndex];
const _boundingBox = ta.boundingBox;
_boundingBox.min_x = targetRectAttr.x + diffX;
_boundingBox.min_y = targetRectAttr.y + diffY;
// _boundingBox.max_x += diffX;
// _boundingBox.min_y += diffY;
// _boundingBox.max_y += diffY;
// state.layer.children.forEach(function (group: any) {
// // do not check intersection with itself
// // 不检查与自身相交
// if (group._id == target._id) {
// return;
// }
// });
}
function handleData() {
......@@ -160,32 +232,78 @@ function handleData() {
}
}
// 一条线上有多个点,只保留起点和终点
// 1.一条线上有多个点,只保留起点和终点
// 2.计算边距上的点
// 注意!path的最后一个点不与第一个点重合
function removeAngleRepeat(path: any[]) {
let length = path.length;
if (length >= 2) {
let myMap = new Map();
// 计算A点两条邻边相交的点A'
// 计算过A点垂直于边距线上的点,这样的点计算两个,也就是A点两条邻边上各一点
//
let index = 1;
for (const item of path) {
let x1, y1, x2, y2;
// let last_x, last_y, gap_1, gap_2;
if (index < length) {
x1 = item.x;
y1 = item.y;
x2 = path[index].x;
y2 = path[index].y;
// 边距 start1
// gap_1 = item.gap || 0;
// if (index === 1) {
// // 第一个点,另一条邻边是与最后一个点组成
// last_x = path[length - 1].x;
// last_y = path[length - 1].y;
// gap_2 = path[length - 1].gap;
// } else {
// last_x = path[index - 2].x;
// last_y = path[index - 2].y;
// gap_2 = path[index - 2].gap;
// }
// 边距 end1
} else {
// 最后一个点连第一个点
x1 = item.x;
y1 = item.y;
x2 = path[0].x;
y2 = path[0].y;
// 边距 start2
// gap_1 = item.gap || 0;
// gap_2 = path[index - 2].gap;
// last_x = path[index - 2].x;
// last_y = path[index - 2].y;
// 边距 end2
}
// toAngle:false弧度 true角度
let du = getAngle(x1, y1, x2, y2, true);
item.du = du;
if (!myMap.has(du)) {
myMap.set(du, item);
// 边距 start3
// const du2 = getAngle(x1, y1, last_x, last_y, true);
// const gapParams = {
// x1,
// y1,
// gap_1,
// x2,
// y2,
// last_x,
// last_y,
// gap_2,
// du1: du,
// du2,
// };
// handleGap(gapParams);
// 边距 end3
}
// console.log('du', du);
......@@ -199,18 +317,19 @@ function removeAngleRepeat(path: any[]) {
return newArr;
} else {
console.warn('小于两个点,不能形成夹角');
return path;
}
}
/**
* 计算从x1y1到x2y2的直线,与水平线形成的夹角
* 计算规则为顺时针从左侧0°到与该直线形成的夹角
* 计算从A点[x1,y1]到B点[x2,y2]的直线,与水平线形成的夹角
* 计算规则为以A为旋转点,将AB线顺时针旋转到-X轴形成的夹角
* @param {Object} x1
* @param {Object} y1
* @param {Object} x2
* @param {Object} y2
* @param {Boolean} toAngle 是否转换为角度值,默认false
* @param {Boolean} toAngle 默认false【弧度值】,true【角度值】
*/
function getAngle(
x1: number,
......@@ -230,7 +349,7 @@ function getAngle(
let res;
// 角度
// 角度
// let angle = (180 + (Math.atan2(-y, -x) * 180) / Math.PI + 360) % 360;
let _atan2 = Decimal.atan2(y.negated(), x.negated());
let _angle = Decimal.div(Decimal.mul(_atan2, 180), Math.PI);
......@@ -239,11 +358,36 @@ function getAngle(
res = Decimal.sub(360, angle);
if (!toAngle) {
// 弧度值
res = Decimal.mul(res, Math.PI).div(180);
}
return res.toNumber();
}
/**
* 计算边距
* param里面包含3个点的坐标,及2条边距
* A [x1,y1] 当前目标点
* B [x2,y2] 在A点下一条邻边上的点
* C [last_x,last_y] 在A点上一条邻边上的点
* gap_1 AB上的边距; du1 AB与-X轴的夹角,传入角度值
* gap_2 AC上的边距; du2 AC与-X轴的夹角,传入角度值
*/
// function handleGap(param: any) {
// const { x1, y1, x2, y2, last_x, last_y, gap_1, gap_2, du1, du2 } = param;
// // console.log('du1', du1);
// }
// 批量选择
function batchSelection(value: boolean) {
if (value) {
startSelected.value = true;
} else {
startSelected.value = false;
state.shapePath.forEach((i) => (i.selected = true));
}
}
// 网格线
function drawGrid() {
let canvas: any = document.getElementById('canvas-grid');
......@@ -274,7 +418,13 @@ function drawGrid() {
</script>
<template>
<div class="konva-main-page container-height center">
<div>666</div>
<div class="column">
<q-toggle
v-model="startSelected"
label="批量选择"
@update:model-value="batchSelection"
/>
</div>
<div class="canvas-box">
<canvas
id="canvas-grid"
......@@ -282,7 +432,20 @@ function drawGrid() {
:height="stageSize.height"
style="position: absolute"
></canvas>
<div id="stage-container"></div>
<template v-for="(item, index) in state.shapePath" :key="index">
<q-checkbox
v-model="item.selected"
dense
class="my-checkbox"
v-if="startSelected && item.boundingBox"
:style="{
left: item.boundingBox.min_x + 'px',
top: item.boundingBox.min_y + 'px',
}"
/>
</template>
</div>
</div>
</template>
......@@ -296,5 +459,9 @@ function drawGrid() {
height: 800px;
border: 1px solid #000;
// background: pink;
position: relative;
}
.my-checkbox {
position: absolute;
}
</style>
/*
* @Description: 多边形核心算法
* 源码Gitee:https://gitee.com/dhzx/js-polygon-algorithm
*/
interface Point {
x: number;
y: number;
gap?: number;
[proppName: string]: any;
}
/**
* 获取多边形中心点
* @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
*/
export function getPolygonCenter(points: Point[]) {
if (!Array.isArray(points) || points.length < 3) {
console.error('多边形坐标集合不能少于3个');
return;
}
const result = { x: 0, y: 0 };
points.forEach((p) => {
result.x += p.x;
result.y += p.y;
});
result.x /= points.length;
result.y /= points.length;
return result;
}
/**
* 获取多边形重心(质心)
* @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
*/
export function getPolygonBaryCenter(points: Point[]) {
if (!Array.isArray(points) || points.length < 3) {
console.error('多边形坐标集合不能少于3个');
return;
}
const result = { x: 0, y: 0 };
let area = 0;
for (let i = 1; i <= points.length; i++) {
const curX = points[i % points.length].x;
const curY = points[i % points.length].y;
const nextX = points[i - 1].x;
const nextY = points[i - 1].y;
const temp = (curX * nextY - curY * nextX) / 2;
area += temp;
result.x += (temp * (curX + nextX)) / 3;
result.y += (temp * (curY + nextY)) / 3;
}
result.x /= area;
result.y /= area;
return result;
}
/**
* 判断点是否在多边形内部
* @param {Point} point 点坐标
* @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
* @returns
*/
export function isInPolygon(point: Point, points: Point[]) {
if (!Array.isArray(points) || points.length < 3) {
console.error('多边形坐标集合不能少于3个');
return;
}
const n = points.length;
let nCross = 0;
for (let i = 0; i < n; i++) {
const p1 = points[i];
const p2 = points[(i + 1) % n];
// 求解 y=p.y 与 p1 p2 的交点
// p1p2 与 y=p0.y平行
if (p1.y === p2.y) continue;
// 交点在p1p2延长线上
if (point.y < Math.min(p1.y, p2.y)) continue;
// 交点在p1p2延长线上
if (point.y >= Math.max(p1.y, p2.y)) continue;
// 求交点的 X 坐标
const x = ((point.y - p1.y) * (p2.x - p1.x)) / (p2.y - p1.y) + p1.x;
// 只统计单边交点
if (x > point.x) nCross++;
}
return nCross % 2 === 1;
}
/**
* 缩放多边形坐标
* @decoration 需配合顺时针判断方法一起使用
* @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
* @param {number} extra 外延大小。为正: 向外扩; 为负: 向内缩
* @return {Point[]} 扩展或缩小后的多边形点坐标数组
*/
export function scalePolygon2(points: Point[], extra: number) {
if (!Array.isArray(points) || points.length < 3) {
console.error('多边形坐标集合不能少于3个');
return;
}
const ps = points;
// 通过顺时针判断取正值还是负值
const extra0 = isClockwise(ps) ? -extra : extra;
const norm = (x: number, y: number) => Math.sqrt(x * x + y * y);
const len = ps.length;
const polygon = [];
for (let i = 0; i < len; i++) {
const point = ps[i];
const point1 = ps[i === 0 ? len - 1 : i - 1];
const point2 = ps[i === len - 1 ? 0 : i + 1];
// 向量PP1
const vectorX1 = point1.x - point.x; // 向量PP1 横坐标
const vectorY1 = point1.y - point.y; // 向量PP1 纵坐标
const n1 = norm(vectorX1, vectorY1); // 向量的平方根 为了对向量PP1做单位化
let vectorUnitX1 = vectorX1 / n1; // 向量单位化 横坐标
let vectorUnitY1 = vectorY1 / n1; // 向量单位化 纵坐标
// 向量PP2
const vectorX2 = point2.x - point.x; // 向量PP2 横坐标
const vectorY2 = point2.y - point.y; // 向量PP2 纵坐标
const n2 = norm(vectorX2, vectorY2); // 向量的平方根 为了对向量PP1做单位化
let vectorUnitX2 = vectorX2 / n2; // 向量单位化 横坐标
let vectorUnitY2 = vectorY2 / n2; // 向量单位化 纵坐标
// PQ距离
const vectorLen =
-extra0 /
Math.sqrt(
(1 - (vectorUnitX1 * vectorUnitX2 + vectorUnitY1 * vectorUnitY2)) / 2
);
// 根据向量的叉乘积来判断角是凹角还是凸角
if (vectorX1 * vectorY2 + -1 * vectorY1 * vectorX2 < 0) {
vectorUnitX2 *= -1;
vectorUnitY2 *= -1;
vectorUnitX1 *= -1;
vectorUnitY1 *= -1;
}
// PQ的方向
const vectorX = vectorUnitX1 + vectorUnitX2;
const vectorY = vectorUnitY1 + vectorUnitY2;
const n = vectorLen / norm(vectorX, vectorY);
const vectorUnitX = vectorX * n;
const vectorUnitY = vectorY * n;
const polygonX = vectorUnitX + point.x;
const polygonY = vectorUnitY + point.y;
polygon[i] = { x: polygonX, y: polygonY };
}
return polygon;
}
/**
* 缩放多边形坐标
* @decoration 需配合顺时针判断方法一起使用
* @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
* @param {number} extra 外延大小。为正: 向外扩; 为负: 向内缩
* @return {Point[]} 扩展或缩小后的多边形点坐标数组
*/
export function scalePolygon(points: Point[], extra = 0) {
if (!Array.isArray(points) || points.length < 3) {
console.error('多边形坐标集合不能少于3个');
return;
}
const ps = points;
// 通过顺时针判断取正值还是负值
// const extra0 = isClockwise(ps) ? -extra : extra;
const norm = (x: number, y: number) => Math.sqrt(x * x + y * y);
const len = ps.length;
const polygon = [];
for (let i = 0; i < len; i++) {
let extra0;
if (extra) {
extra0 = isClockwise(ps) ? -extra : extra;
} else {
const _gap = ps[i].gap || 0;
extra0 = isClockwise(ps) ? -_gap : _gap;
}
const point = ps[i];
const point1 = ps[i === 0 ? len - 1 : i - 1];
const point2 = ps[i === len - 1 ? 0 : i + 1];
// 向量PP1
const vectorX1 = point1.x - point.x; // 向量PP1 横坐标
const vectorY1 = point1.y - point.y; // 向量PP1 纵坐标
const n1 = norm(vectorX1, vectorY1); // 向量的平方根 为了对向量PP1做单位化
let vectorUnitX1 = vectorX1 / n1; // 向量单位化 横坐标
let vectorUnitY1 = vectorY1 / n1; // 向量单位化 纵坐标
// 向量PP2
const vectorX2 = point2.x - point.x; // 向量PP2 横坐标
const vectorY2 = point2.y - point.y; // 向量PP2 纵坐标
const n2 = norm(vectorX2, vectorY2); // 向量的平方根 为了对向量PP1做单位化
let vectorUnitX2 = vectorX2 / n2; // 向量单位化 横坐标
let vectorUnitY2 = vectorY2 / n2; // 向量单位化 纵坐标
// PQ距离
const vectorLen =
-extra0 /
Math.sqrt(
(1 - (vectorUnitX1 * vectorUnitX2 + vectorUnitY1 * vectorUnitY2)) / 2
);
// 根据向量的叉乘积来判断角是凹角还是凸角
if (vectorX1 * vectorY2 + -1 * vectorY1 * vectorX2 < 0) {
vectorUnitX2 *= -1;
vectorUnitY2 *= -1;
vectorUnitX1 *= -1;
vectorUnitY1 *= -1;
}
// PQ的方向
const vectorX = vectorUnitX1 + vectorUnitX2;
const vectorY = vectorUnitY1 + vectorUnitY2;
const n = vectorLen / norm(vectorX, vectorY);
const vectorUnitX = vectorX * n;
const vectorUnitY = vectorY * n;
const polygonX = vectorUnitX + point.x;
const polygonY = vectorUnitY + point.y;
polygon[i] = { x: polygonX, y: polygonY };
}
return polygon;
}
/**
* 判断坐标数组是否顺时针(默认为false)
* @param {Point[]} points 点坐标数组 [{x:0,y:0}...]
* @returns {boolean} 是否顺时针
*/
export function isClockwise(points: Point[]) {
// 三个点可以判断矢量是顺时针旋转还是逆时针旋转的,但由于可能存在凹边,所以并不是任意三点都可以正确反映多边形的走向
// 因此需要取多边形中绝对是凸边的点来判断,
// 多边形中的极值点(x最大或x最小或y最大或y最小)它与相邻两点构成的边必然是凸边,因此我们先取出多边形中的极值点,再由极值点和其前后两点去判断矢量的走向,从而判断出多边形的走向。
if (!Array.isArray(points) || points.length < 3) {
console.error('多边形坐标集合不能少于3个');
return false;
}
let coords = JSON.parse(JSON.stringify(points));
if (coords[0] === coords[coords.length - 1]) {
coords = coords.slice(0, coords.length - 1);
}
coords = coords.reverse();
let maxXIndex = 0;
let maxX = parseFloat(coords[maxXIndex].x);
let c1;
let c2;
let c3;
for (let i = 0; i < coords.length; i++) {
if (parseFloat(coords[i].x) > maxX) {
maxX = parseFloat(coords[i].x);
maxXIndex = i;
}
}
if (maxXIndex === 0) {
c1 = coords[coords.length - 1];
c2 = coords[maxXIndex];
c3 = coords[maxXIndex + 1];
} else if (maxXIndex === coords.length - 1) {
c1 = coords[maxXIndex - 1];
c2 = coords[maxXIndex];
c3 = coords[0];
} else {
c1 = coords[maxXIndex - 1];
c2 = coords[maxXIndex];
c3 = coords[maxXIndex + 1];
}
const x1 = parseFloat(c1.x);
const y1 = parseFloat(c1.y);
const x2 = parseFloat(c2.x);
const y2 = parseFloat(c2.y);
const x3 = parseFloat(c3.x);
const y3 = parseFloat(c3.y);
const s = (x1 - x3) * (y2 - y3) - (x2 - x3) * (y1 - y3);
return s < 0;
}
......@@ -5,6 +5,7 @@ import TREE from '../modules/tree/route';
import AMIS from '../modules/amis/route';
import VUE_STUDY from '../modules/vue-study/route';
import VUE_KONVA from '../modules/vue-konva/route';
import VECTOR from '../modules/vector/route';
const routes: RouteRecordRaw[] = [
{
......@@ -133,6 +134,7 @@ const routes: RouteRecordRaw[] = [
...AMIS,
...VUE_STUDY,
...VUE_KONVA,
...VECTOR,
],
},
],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment