Как найти точки пересечения прямых?
Известны координаты точек: "A", "B", "S". К отрезкам AB и BS построены перпендикуляры и на них от точек A и S найдены точки "с", "d", "u", "v";
Как теперь найти точки: f, e, которые лежат на пересечении прямых, параллельных AB и BS?
Расстояния между точками должны быть равны: Ac = Ad = Sv = Su.
Ответы (3 шт):
- Найти величину угла, образованного векторами BA и BS (привести к диапазону [0, 2Pi])
- Поделить пополам
- Нормализовать вектор BA и повернуть на угол (2)
- Поделить половину ширины полосы на косинус ( (2) - 90 градусов )
- Задать вектору (3) длину (4)
Мы нашли f
Чтобы найти е нужно f * -1
Прибавим B к f и e - точки найдены.
Пример:
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/[email protected]";
import { OrbitControls } from "https://cdn.skypack.dev/[email protected]/examples/jsm/controls/OrbitControls";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
let renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
let controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.enablePan = false;
let helper = new THREE.GridHelper(10, 10, 0x808080, 0x404040);
helper.rotation.x = Math.PI * 0.5;
helper.position.z = -.001;
scene.add(helper);
const desiredWidth = 1.5;
let ptsBase = [
/*
new THREE.Vector2(-3, 1), //A
new THREE.Vector2(0, 1), //B
new THREE.Vector2(4, -3) //S
*/
new THREE.Vector2(-3, -3), //A
new THREE.Vector2(0, 1), //B
new THREE.Vector2(4, -3) //S
];
let lineBase = new THREE.Line(
new THREE.BufferGeometry().setFromPoints(ptsBase),
new THREE.LineBasicMaterial({ color: "yellow" })
);
scene.add(lineBase);
// all the magic is here //////////////////////////////////////////////////////////////////////////////////////////
let a = new THREE.Vector2().subVectors(ptsBase[0], ptsBase[1]); //BA
let b = new THREE.Vector2().subVectors(ptsBase[2], ptsBase[1]); //BS
let angle = (Math.PI - b.angle()) - (Math.PI - a.angle()); // [0..PI2]
let halfAngle = angle * 0.5;
let scaleVal = Math.cos(Math.abs(halfAngle - (Math.PI * 0.5)));
let f = a
.clone()
.setLength(desiredWidth / scaleVal)
.rotateAround(new THREE.Vector2(), -halfAngle);
let e = f.clone().negate();
//////////////////////////////////////////////////////////////////////////////////////////
let cornerLine = new THREE.Line(
new THREE.BufferGeometry()
.setFromPoints([f.clone().add(ptsBase[1]), e.clone().add(ptsBase[1])])
.setAttribute(
"color",
new THREE.Float32BufferAttribute([0, 1, 1, 0, 1, 0], 3)
),
new THREE.LineBasicMaterial({ vertexColors: true })
);
scene.add(cornerLine);
let lineTop = new THREE.Line(
new THREE.BufferGeometry().setFromPoints(
ptsBase.map((p) => {
return p.clone().add(f);
})
),
new THREE.LineBasicMaterial({ color: 0x00ffff })
);
let lineBottom = new THREE.Line(
new THREE.BufferGeometry().setFromPoints(
ptsBase.map((p) => {
return p.clone().add(e);
})
),
new THREE.LineBasicMaterial({ color: 0x00ff00 })
);
scene.add(lineTop, lineBottom);
renderer.setAnimationLoop(() => {
controls.update();
renderer.render(scene, camera);
});
</script>
Функция thickLine принимает координаты трёх точек и смещение, возвращает координаты трёх пар точек: левая и правая соседки для первой точки, левая и правая соседки для второй точки, левая и правая соседки для третьей точки.
Математика для крайних точек простая: направление отрезка поворачивается на девяносто градусов ((x, y) -> (-y, x)), длина нормализуется. Имея направление легко вычислить соседок точек.
Поворот вектора v обозначим как v^. Тогда левая соседка точки a относительно отрезка (a, b) на расстоянии w будет:
na = (b - a)^ - нормаль к отрезку.
al = a + w na - левая соседка.
Левая соседка точки c относительно отрезка (b, c):
nc = (c - b)^ - нормаль к отрезку.
cl = c + w nc - левая соседка.
Левая соседка точки b. Направление в котором нужно двигаться вычисляется как биссектриса направлений na и nc:
nb = na + nc.
Сама левая соседка:
bl = b + (2 w / ‖nb‖2) nb.
Покажем что отрезок (al, bl) параллелен (a, b). Для этого он должен быть перпендикулярен na, то есть надо проверить что скалярное произведение равно нулю:
na (bl - al) =
= na (b + (2 w / ‖nb‖2) nb - (a + w na)) =
= na ((b - a) + (2 w / ‖nb‖2) nb - w na) =
= na (b - a) + na ((2 w / ‖nb‖2) nb - w na) =
= 0 + na (w / ‖nb‖2) (2 nb - ‖nb‖2 na) =
= na (w / ‖nb‖2) (2 (na + nc) - ‖na + nc‖2 na) =
= (w / ‖nb‖2) (2 (na na + na nc) - ‖na + nc‖2 na na) =
= (w / ‖nb‖2) (2 (na na + na nc) - ((na + nc) (na + nc)) na na) =
= (w / ‖nb‖2) (2 (1 + na nc) - (na + nc) (na + nc)) =
= (w / ‖nb‖2) (2 (1 + na nc) - (na na + 2 na nc + nc nc)) =
= (w / ‖nb‖2) (2 (1 + na nc) - (1 + 2 na nc + 1)) =
= (w / ‖nb‖2) 0 =
= 0
Никакой тригонометрии не нужно. Самые сложные операции - деление и извлечение корня. Код может выдавать бесконечности и NaN если точки совпадают или поворот на 180 градусов. В таких ситуациях задача неразрешима в принципе - нельзя определить нормали к отрезкам или определить места средних соседок:
// a, b, c - points, w - half width
const thickLine = (a, b, c, w) => {
const normal = (a, b) => {
const x = b[0] - a[0];
const y = b[1] - a[1];
const f = 1 / Math.sqrt(x ** 2 + y ** 2);
return [-y * f, x * f];
};
const na = normal(a, b);
const nc = normal(b, c);
const d = [na[0] + nc[0], na[1] + nc[1]];
const f = w * 2 / (d[0] ** 2 + d[1] ** 2);
const shift = (p, dir, w) => [
p[0] + dir[0] * w, p[1] + dir[1] * w
];
return [
shift(a, na, w),
shift(a, na, -w),
shift(b, d , f),
shift(b, d , -f),
shift(c, nc, w),
shift(c, nc, -w)
];
};
Полный работающий пример. Красные точки можно таскать:
// a, b, c - points, w - half width
const thickLine = (a, b, c, w) => {
const normal = (a, b) => {
const x = b[0] - a[0];
const y = b[1] - a[1];
const f = 1 / Math.sqrt(x ** 2 + y ** 2);
return [-y * f, x * f];
};
const na = normal(a, b);
const nc = normal(b, c);
const d = [na[0] + nc[0], na[1] + nc[1]];
const f = w * 2 / (d[0] ** 2 + d[1] ** 2);
const shift = (p, dir, w) => [
p[0] + dir[0] * w, p[1] + dir[1] * w
];
return [
shift(a, na, w),
shift(a, na, -w),
shift(b, d , f),
shift(b, d , -f),
shift(c, nc, w),
shift(c, nc, -w)
];
};
const makePoint = svg => {
const point = document.createElementNS(
'http://www.w3.org/2000/svg',
'circle'
);
point.setAttribute('r', 4);
point.setAttribute('stroke', 'blue');
point.setAttribute('fill', 'blue');
svg.appendChild(point);
const movePoint = (x, y) => {
if (!isFinite(x) || !isFinite(y)) {
return;
}
const ctm = point.getCTM();
ctm.e = x;
ctm.f = y;
const t = svg.createSVGTransform();
t.setMatrix(ctm);
point.transform.baseVal.initialize(t);
};
return {
'move': movePoint
};
};
const makeDraggablePoint = svg => {
const subscribers = [];
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
svg.appendChild(group);
const p = () => {
const ctm = group.getCTM();
return [ctm.e, ctm.f];
};
const moveGroup = (x, y) => {
const ctm = group.getCTM();
ctm.e = x;
ctm.f = y;
const t = svg.createSVGTransform();
t.setMatrix(ctm);
group.transform.baseVal.initialize(t);
subscribers.forEach(e => e());
};
const handle = document.createElementNS(
'http://www.w3.org/2000/svg',
'circle'
);
handle.setAttribute('r', 20);
handle.setAttribute('stroke', 'transparent');
handle.setAttribute('fill', 'transparent');
handle.style.cursor = 'grab';
group.appendChild(handle);
const point = document.createElementNS(
'http://www.w3.org/2000/svg',
'circle'
);
point.setAttribute('r', 4);
point.setAttribute('stroke', 'red');
point.setAttribute('fill', 'red');
point.style.cursor = 'grab';
group.appendChild(point);
const onPointerDown = e => {
e.preventDefault();
const [x, y] = p();
const offset_x = x - e.clientX;
const offset_y = y - e.clientY;
group.setPointerCapture(e.pointerId);
const oldMove = group.onpointermove;
const oldUp = group.onpointerup;
group.onpointermove = e => {
e.preventDefault();
const sx = e.clientX + offset_x;
const sy = e.clientY + offset_y;
moveGroup(sx, sy);
};
group.onpointerup = e => {
e.preventDefault();
group.onpointerup = oldUp;
group.onpointermove = oldMove;
group.releasePointerCapture(e.pointerId);
};
};
handle.onpointerdown = onPointerDown;
point.onpointerdown = onPointerDown;
return {
'subscribe': cb => subscribers.push(cb),
'p': p,
'move': moveGroup,
};
};
const makeSegment = svg => {
// <line x1="10" x2="50" y1="110" y2="150" stroke="orange" stroke-width="5"/>
const line = document.createElementNS(
'http://www.w3.org/2000/svg',
'line'
);
svg.appendChild(line);
line.setAttribute('stroke', 'black');
line.setAttribute('stroke-width', '2');
return {
'p1': () => [line.getAttribute('x1'), line.getAttribute('y1')],
'p2': () => [line.getAttribute('x2'), line.getAttribute('y2')],
'move1': (x, y) => {
if (isFinite(x) && isFinite(y)) {
line.setAttribute('x1', x);
line.setAttribute('y1', y);
}
},
'move2': (x, y) => {
if (isFinite(x) && isFinite(y)) {
line.setAttribute('x2', x);
line.setAttribute('y2', y);
}
}
};
};
(() => {
const body = document.getElementsByTagName('body')[0];
const svg = document.createElementNS(
'http://www.w3.org/2000/svg',
'svg'
);
svg.setAttribute('width', 400);
svg.setAttribute('height', 200);
body.appendChild(svg);
const s12 = makeSegment(svg);
const s23 = makeSegment(svg);
const s12l = makeSegment(svg);
const s12r = makeSegment(svg);
const s23l = makeSegment(svg);
const s23r = makeSegment(svg);
const p1l = makePoint(svg);
const p1r = makePoint(svg);
const p2l = makePoint(svg);
const p2r = makePoint(svg);
const p3l = makePoint(svg);
const p3r = makePoint(svg);
const p1 = makeDraggablePoint(svg);
const p2 = makeDraggablePoint(svg);
const p3 = makeDraggablePoint(svg);
const halfWidth = 20;
const updateLine = () => {
const points = thickLine(p1.p(), p2.p(), p3.p(), halfWidth);
p1l .move (...points[0]);
s12l.move1(...points[0]);
p1r .move (...points[1]);
s12r.move1(...points[1]);
p2l .move (...points[2]);
s12l.move2(...points[2]);
s23l.move1(...points[2]);
p2r .move (...points[3]);
s12r.move2(...points[3]);
s23r.move1(...points[3]);
p3l .move (...points[4]);
s23l.move2(...points[4]);
p3r .move (...points[5]);
s23r.move2(...points[5]);
};
p1.subscribe(() => {
s12.move1(...p1.p());
updateLine();
});
p2.subscribe(() => {
const [x, y] = p2.p();
s12.move2(x, y);
s23.move1(x, y);
updateLine();
});
p3.subscribe(() => {
s23.move2(...p3.p());
updateLine();
});
p1.move(100, 50);
p2.move(200, 50);
p3.move(200, 100);
})();
Для решения данной задачи потребуется выстроить от середины отрезка AB ещë один перпендикуляр, которыц будет задавать направление функции вычисления координат пересечения второй линии по другую сторону
function calculateIntersection_sec(p1, p2, p3, p4) {
var c2x = p3.x - p4.x; // (x3 - x4)
var c3x = p1.x - p2.x; // (x1 - x2)
var c2z = p3.z - p4.z; // (y3 - y4)
var c3z = p1.z - p2.z; // (y1 - y2)
// down part of intersection point formula
var d = c3x * c2z - c3z * c2x;
if (d == 0) {
throw new Error('Number of intersection points is zero or infinity.');
}
// upper part of intersection point formula
var u1 = p1.x * p2.z - p1.z * p2.x; // (x1 * y2 - y1 * x2)
var u4 = p3.x * p4.z - p3.z * p4.x; // (x3 * y4 - y3 * x4)
// intersection point formula
var px = (u1 * c2x - c3x * u4) / d;
var pz = (u1 * c2z - c3z * u4) / d;
var p = { x: px, z: pz };
return p;
}
// Usage example:
// line 1
var p1 = { x: new_it_1, z: new_it_2 }; // P1: (x1, y1)
var p2 = { x: new_it_3, z: new_it_4 }; // P2: (x2, y2)
// line 2
var p3 = { x: new_it_5, z: new_it_6 }; // P3: (x4, y4)
var p4 = { x: new_it_7, z: new_it_8 }; // P4: (x4, y4)
var p = calculateIntersection_sec(p1, p2, p3, p4); // intersection point
cube_set_pos_projecct_12.position.x = p.x;
cube_set_pos_projecct_12.position.z = p.z;
