@eturnity/eturnity_maths 9.7.0 → 9.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/coords.js +44 -11
- package/src/geometry.js +47 -14
- package/src/geometryShortDistance.js +442 -0
- package/src/index.js +3 -0
- package/src/intersectionCheck.js +165 -0
- package/src/intersectionPolygon.js +24 -1
- package/src/matrix.js +15 -1
- package/src/objects/Circle.js +55 -5
- package/src/objects/Line.js +530 -264
- package/src/objects/Measurement.js +335 -0
- package/src/objects/Plane.js +176 -0
- package/src/objects/Point.js +16 -7
- package/src/objects/Polygon.js +130 -2
- package/src/objects/derivedState/nodesEdgesCreation.js +112 -35
- package/src/objects/derivedState/updateComputedGeometryPolygon.js +1 -0
- package/src/objects/graph/graphCreation.js +5 -5
- package/src/objects/hydrate.js +229 -0
- package/src/objects/index.js +2 -0
- package/src/plane.js +76 -0
- package/src/splitMergePolygons.js +8 -8
- package/src/tests/Line/isTouchingLine.spec.js +233 -0
- package/src/tests/Plane/CoordinateChange.spec.js +32 -0
- package/src/tests/coords/toRealityRefFunction.spec.js +25 -0
- package/src/tests/geometry/getAngleInDegFrom2DENUVector.spec.js +60 -0
- package/src/tests/geometry/projectionOnPlaneFollowingVector.spec.js +215 -0
- package/src/tests/geometryShortDistance/areObjectsIntersecting.spec.js +426 -0
- package/src/tests/geometryShortDistance/findClosestPoint.spec.js +53 -0
- package/src/tests/geometryShortDistance/getShortestSegmentCircleCircle.spec.js +126 -0
- package/src/tests/geometryShortDistance/getShortestSegmentLineCircle.spec.js +124 -0
- package/src/tests/geometryShortDistance/getShortestSegmentPointLine.spec.js +133 -0
- package/src/tests/geometryShortDistance/getShortestSegmentPointPoint.spec.js +115 -0
package/src/objects/hydrate.js
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
import { Polygon } from './Polygon'
|
|
2
|
+
import { Circle } from './Circle'
|
|
3
|
+
import { Line } from './Line'
|
|
4
|
+
import { Point } from './Point'
|
|
5
|
+
import { Measurement } from './Measurement'
|
|
6
|
+
import { Plane, defaultPlane } from './Plane'
|
|
7
|
+
import { updateComputedGeometryPolygon } from './derivedState/updateComputedGeometryPolygon'
|
|
8
|
+
export function hydrateItem(serializedItem) {
|
|
9
|
+
if (serializedItem.type == 'circle') {
|
|
10
|
+
return hydrateCircle(serializedItem)
|
|
11
|
+
} else if (serializedItem.type == 'polygon') {
|
|
12
|
+
return hydratePolygon(serializedItem)
|
|
13
|
+
} else if (serializedItem.type == 'line') {
|
|
14
|
+
return hydrateLine(serializedItem)
|
|
15
|
+
} else if (serializedItem.type == 'point') {
|
|
16
|
+
return hydratePoint(serializedItem)
|
|
17
|
+
} else if (serializedItem.type == 'measurement') {
|
|
18
|
+
return hydrateMeasurement(serializedItem)
|
|
19
|
+
} else if (serializedItem.type == 'plane') {
|
|
20
|
+
return hydratePlane(serializedItem)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
2
23
|
export function hydratePolygon(serializedPolygon) {
|
|
3
24
|
const layer = serializedPolygon.layer
|
|
4
25
|
let polygon = new Polygon(serializedPolygon.outline, layer)
|
|
@@ -24,6 +45,214 @@ export function hydratePolygon(serializedPolygon) {
|
|
|
24
45
|
polygon.row_index = serializedPolygon.row_index
|
|
25
46
|
polygon.col_index = serializedPolygon.col_index
|
|
26
47
|
polygon.moduleField = serializedPolygon.moduleField
|
|
48
|
+
} else if (layer == 'roof_plan_item') {
|
|
49
|
+
polygon.shape = 'polygon'
|
|
50
|
+
polygon.colorIndex = serializedPolygon.colorIndex
|
|
51
|
+
polygon.hasFillColor = serializedPolygon.hasFillColor
|
|
27
52
|
}
|
|
53
|
+
updateComputedGeometryPolygon(polygon)
|
|
28
54
|
return polygon
|
|
29
55
|
}
|
|
56
|
+
export function hydrateCircle(serializedCircle) {
|
|
57
|
+
const layer = serializedCircle.layer
|
|
58
|
+
let circle = new Circle(
|
|
59
|
+
serializedCircle.center,
|
|
60
|
+
serializedCircle.radius,
|
|
61
|
+
layer
|
|
62
|
+
)
|
|
63
|
+
circle.id = serializedCircle.id
|
|
64
|
+
circle.version = serializedCircle.version
|
|
65
|
+
circle.name = serializedCircle.name
|
|
66
|
+
circle.margins = serializedCircle.margins
|
|
67
|
+
if (layer == 'roof_plan_item') {
|
|
68
|
+
circle.shape = 'circle'
|
|
69
|
+
circle.colorIndex = serializedCircle.colorIndex
|
|
70
|
+
circle.hasFillColor = serializedCircle.hasFillColor
|
|
71
|
+
}
|
|
72
|
+
return circle
|
|
73
|
+
}
|
|
74
|
+
export function hydrateLine(serializedLine) {
|
|
75
|
+
// Convert outline points to Point instances (handles both 2D and 3D)
|
|
76
|
+
const firstPoint =
|
|
77
|
+
serializedLine.outline && serializedLine.outline[0]
|
|
78
|
+
? new Point(
|
|
79
|
+
serializedLine.outline[0].x,
|
|
80
|
+
serializedLine.outline[0].y,
|
|
81
|
+
serializedLine.outline[0].z !== undefined
|
|
82
|
+
? serializedLine.outline[0].z
|
|
83
|
+
: 0,
|
|
84
|
+
serializedLine.layer
|
|
85
|
+
)
|
|
86
|
+
: null
|
|
87
|
+
const lastPoint =
|
|
88
|
+
serializedLine.outline && serializedLine.outline[1]
|
|
89
|
+
? new Point(
|
|
90
|
+
serializedLine.outline[1].x,
|
|
91
|
+
serializedLine.outline[1].y,
|
|
92
|
+
serializedLine.outline[1].z !== undefined
|
|
93
|
+
? serializedLine.outline[1].z
|
|
94
|
+
: 0,
|
|
95
|
+
serializedLine.layer
|
|
96
|
+
)
|
|
97
|
+
: null
|
|
98
|
+
|
|
99
|
+
const layer = serializedLine.layer
|
|
100
|
+
let line = new Line(
|
|
101
|
+
firstPoint,
|
|
102
|
+
lastPoint,
|
|
103
|
+
layer,
|
|
104
|
+
serializedLine.infiniteLine || false
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
// Restore belongsTo, hydrating items if they are serialized
|
|
108
|
+
if (serializedLine.belongsTo && Array.isArray(serializedLine.belongsTo)) {
|
|
109
|
+
line.belongsTo = serializedLine.belongsTo.map((belongsToEntry) => {
|
|
110
|
+
const belongsToItem = {
|
|
111
|
+
itemId: belongsToEntry.itemId,
|
|
112
|
+
index: belongsToEntry.index,
|
|
113
|
+
}
|
|
114
|
+
// If item is serialized, hydrate it based on type
|
|
115
|
+
if (belongsToEntry.item && belongsToEntry.item.type) {
|
|
116
|
+
belongsToItem.item =
|
|
117
|
+
belongsToEntry.item.type === 'circle'
|
|
118
|
+
? hydrateCircle(belongsToEntry.item)
|
|
119
|
+
: hydratePolygon(belongsToEntry.item)
|
|
120
|
+
} else {
|
|
121
|
+
belongsToItem.item = belongsToEntry.item
|
|
122
|
+
}
|
|
123
|
+
return belongsToItem
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Restore itemType (node2D, node3D, edge2D, edge3D, etc.)
|
|
128
|
+
if (serializedLine.itemType) {
|
|
129
|
+
line.itemType = serializedLine.itemType
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Restore other properties if present
|
|
133
|
+
if (serializedLine.id !== undefined) {
|
|
134
|
+
line.id = serializedLine.id
|
|
135
|
+
}
|
|
136
|
+
if (serializedLine.version !== undefined) {
|
|
137
|
+
line.version = serializedLine.version
|
|
138
|
+
}
|
|
139
|
+
if (serializedLine.infiniteLine !== undefined) {
|
|
140
|
+
line.infiniteLine = serializedLine.infiniteLine
|
|
141
|
+
}
|
|
142
|
+
if (serializedLine.selected !== undefined) {
|
|
143
|
+
line.selected = serializedLine.selected
|
|
144
|
+
}
|
|
145
|
+
if (serializedLine.colorIndex !== undefined) {
|
|
146
|
+
line.colorIndex = serializedLine.colorIndex
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return line
|
|
150
|
+
}
|
|
151
|
+
export function hydratePoint(serializedPoint) {
|
|
152
|
+
const layer = serializedPoint.layer
|
|
153
|
+
let point = new Point(
|
|
154
|
+
serializedPoint.x,
|
|
155
|
+
serializedPoint.y,
|
|
156
|
+
serializedPoint.z !== undefined ? serializedPoint.z : 0,
|
|
157
|
+
layer
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
// Restore belongsTo, hydrating items if they are serialized
|
|
161
|
+
if (serializedPoint.belongsTo && Array.isArray(serializedPoint.belongsTo)) {
|
|
162
|
+
point.belongsTo = serializedPoint.belongsTo.map((belongsToEntry) => {
|
|
163
|
+
const belongsToItem = {
|
|
164
|
+
itemId: belongsToEntry.itemId, // fallback for backward compatibility
|
|
165
|
+
index: belongsToEntry.index,
|
|
166
|
+
}
|
|
167
|
+
// If item is serialized, hydrate it based on type
|
|
168
|
+
if (belongsToEntry.item && belongsToEntry.item.type) {
|
|
169
|
+
belongsToItem.item =
|
|
170
|
+
belongsToEntry.item.type === 'circle'
|
|
171
|
+
? hydrateCircle(belongsToEntry.item)
|
|
172
|
+
: hydratePolygon(belongsToEntry.item)
|
|
173
|
+
} else {
|
|
174
|
+
belongsToItem.item = belongsToEntry.item
|
|
175
|
+
}
|
|
176
|
+
return belongsToItem
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Restore itemType (node2D, node3D, edge2D, edge3D, etc.)
|
|
181
|
+
if (serializedPoint.itemType) {
|
|
182
|
+
point.itemType = serializedPoint.itemType
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Restore other properties if present
|
|
186
|
+
if (serializedPoint.id !== undefined) {
|
|
187
|
+
point.id = serializedPoint.id
|
|
188
|
+
}
|
|
189
|
+
if (serializedPoint.version !== undefined) {
|
|
190
|
+
point.version = serializedPoint.version
|
|
191
|
+
}
|
|
192
|
+
if (serializedPoint.open !== undefined) {
|
|
193
|
+
point.open = serializedPoint.open
|
|
194
|
+
}
|
|
195
|
+
if (serializedPoint.selected !== undefined) {
|
|
196
|
+
point.selected = serializedPoint.selected
|
|
197
|
+
}
|
|
198
|
+
if (serializedPoint.visible !== undefined) {
|
|
199
|
+
point.visible = serializedPoint.visible
|
|
200
|
+
}
|
|
201
|
+
if (serializedPoint.hasWarning !== undefined) {
|
|
202
|
+
point.hasWarning = serializedPoint.hasWarning
|
|
203
|
+
}
|
|
204
|
+
if (serializedPoint.colorIndex !== undefined) {
|
|
205
|
+
point.colorIndex = serializedPoint.colorIndex
|
|
206
|
+
}
|
|
207
|
+
if (serializedPoint.masterHandle !== undefined) {
|
|
208
|
+
point.masterHandle = serializedPoint.masterHandle
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return point
|
|
212
|
+
}
|
|
213
|
+
export function hydrateMeasurement(serializedMeasurement) {
|
|
214
|
+
const measurement = new Measurement({
|
|
215
|
+
references: serializedMeasurement.references.map((ref) => hydrateItem(ref)),
|
|
216
|
+
refPoint: serializedMeasurement.refPoint
|
|
217
|
+
? hydrateItem(serializedMeasurement.refPoint)
|
|
218
|
+
: null,
|
|
219
|
+
projectionPlane: serializedMeasurement.projectionPlane
|
|
220
|
+
? hydratePlane(serializedMeasurement.projectionPlane)
|
|
221
|
+
: defaultPlane,
|
|
222
|
+
})
|
|
223
|
+
if (serializedMeasurement.id !== undefined) {
|
|
224
|
+
measurement.id = serializedMeasurement.id
|
|
225
|
+
}
|
|
226
|
+
measurement.offsetMm = serializedMeasurement.offsetMm
|
|
227
|
+
measurement.customLength = serializedMeasurement.customLength
|
|
228
|
+
measurement.projectionPlane = serializedMeasurement.projectionPlane
|
|
229
|
+
? hydratePlane(serializedMeasurement.projectionPlane)
|
|
230
|
+
: null
|
|
231
|
+
measurement.update()
|
|
232
|
+
return measurement
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function hydratePlane(serializedPlane) {
|
|
236
|
+
// Handle case where serializedPlane is already a Plane instance
|
|
237
|
+
if (serializedPlane instanceof Plane) {
|
|
238
|
+
return serializedPlane
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Handle case where serializedPlane is a plain object (backward compatibility)
|
|
242
|
+
const plane = new Plane({
|
|
243
|
+
id: serializedPlane.id,
|
|
244
|
+
origin: serializedPlane.origin,
|
|
245
|
+
normalVector: serializedPlane.normalVector,
|
|
246
|
+
horizontalVector: serializedPlane.horizontalVector,
|
|
247
|
+
upVector: serializedPlane.upVector,
|
|
248
|
+
matrixRealityToPlane: serializedPlane.matrixRealityToPlane,
|
|
249
|
+
matrixPlaneToReality: serializedPlane.matrixPlaneToReality,
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
// Restore version if present
|
|
253
|
+
if (serializedPlane.version !== undefined) {
|
|
254
|
+
plane.version = serializedPlane.version
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return plane
|
|
258
|
+
}
|
package/src/objects/index.js
CHANGED
package/src/plane.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { inverse3x3Matrix } from './matrix'
|
|
2
|
+
import {
|
|
3
|
+
get3DDistanceBetweenPoints,
|
|
4
|
+
pointProjectionOnPlane,
|
|
5
|
+
isClockWise,
|
|
6
|
+
} from './geometry'
|
|
7
|
+
import {
|
|
8
|
+
normalizeVector,
|
|
9
|
+
crossProduct,
|
|
10
|
+
substractVector,
|
|
11
|
+
multiplyVector,
|
|
12
|
+
} from './vector'
|
|
13
|
+
import { Plane } from './objects/Plane'
|
|
14
|
+
|
|
15
|
+
export function getLongestEdgeData(outline) {
|
|
16
|
+
if (!outline || outline.length < 3) {
|
|
17
|
+
throw new Error('invalid outline', outline)
|
|
18
|
+
}
|
|
19
|
+
let longestEdgeLength = 0
|
|
20
|
+
let longestEdgeIndex = 0
|
|
21
|
+
let edge = [outline[0], outline[1]]
|
|
22
|
+
for (let index = 0; index < outline.length; index++) {
|
|
23
|
+
const p = outline[index]
|
|
24
|
+
const indexNext = (index + 1) % outline.length
|
|
25
|
+
const pNext = outline[indexNext]
|
|
26
|
+
const edgeLength = get3DDistanceBetweenPoints(p, pNext)
|
|
27
|
+
if (edgeLength > longestEdgeLength) {
|
|
28
|
+
longestEdgeLength = edgeLength
|
|
29
|
+
longestEdgeIndex = index
|
|
30
|
+
edge = [p, pNext]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
edge,
|
|
35
|
+
index: longestEdgeIndex,
|
|
36
|
+
edgeLength: longestEdgeLength,
|
|
37
|
+
edgeDirectionVector: normalizeVector(substractVector(edge[1], edge[0])),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getPlaneFromPolygon(polygon) {
|
|
42
|
+
const origin = pointProjectionOnPlane(
|
|
43
|
+
{ x: 0, y: 0, z: 0 },
|
|
44
|
+
polygon.normalVector,
|
|
45
|
+
polygon.outline[0]
|
|
46
|
+
)
|
|
47
|
+
const normalVector = polygon.normalVector
|
|
48
|
+
const { edgeDirectionVector } = getLongestEdgeData(polygon.outline)
|
|
49
|
+
let horizontalVector = {
|
|
50
|
+
x: edgeDirectionVector.x,
|
|
51
|
+
y: edgeDirectionVector.y,
|
|
52
|
+
z: edgeDirectionVector.z,
|
|
53
|
+
}
|
|
54
|
+
if (polygon.incline > 3) {
|
|
55
|
+
horizontalVector = {
|
|
56
|
+
x: Math.cos((polygon.direction * Math.PI) / 180),
|
|
57
|
+
y: -Math.sin((polygon.direction * Math.PI) / 180),
|
|
58
|
+
z: 0,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
let upVector = normalizeVector(
|
|
62
|
+
crossProduct(polygon.normalVector, horizontalVector)
|
|
63
|
+
)
|
|
64
|
+
const isOutlineClockwise = isClockWise(polygon.outline)
|
|
65
|
+
if (!isOutlineClockwise) {
|
|
66
|
+
horizontalVector = multiplyVector(-1, horizontalVector)
|
|
67
|
+
upVector = multiplyVector(-1, upVector)
|
|
68
|
+
}
|
|
69
|
+
return new Plane({
|
|
70
|
+
id: polygon.id,
|
|
71
|
+
origin,
|
|
72
|
+
normalVector,
|
|
73
|
+
horizontalVector,
|
|
74
|
+
upVector,
|
|
75
|
+
})
|
|
76
|
+
}
|
|
@@ -28,8 +28,8 @@ export function mergePolygons(polygonIdsToMerge, edges, layer, polygons) {
|
|
|
28
28
|
const insideEdge = edges.filter((e) => e.belongsTo.length == 2)
|
|
29
29
|
const insideEdgeToKeep = insideEdge.filter((e) => {
|
|
30
30
|
return (
|
|
31
|
-
!polygonIdsToMerge.includes(e.belongsTo[0].
|
|
32
|
-
!polygonIdsToMerge.includes(e.belongsTo[1].
|
|
31
|
+
!polygonIdsToMerge.includes(e.belongsTo[0].itemId) ||
|
|
32
|
+
!polygonIdsToMerge.includes(e.belongsTo[1].itemId)
|
|
33
33
|
)
|
|
34
34
|
})
|
|
35
35
|
const newEdges = [...outsideEdge, ...insideEdgeToKeep]
|
|
@@ -151,7 +151,7 @@ export function splitPolygonsV2(polygons) {
|
|
|
151
151
|
const edge2 = edges[intersection.edge_index_2]
|
|
152
152
|
|
|
153
153
|
for (let i = 0; i < edge1.belongsTo.length; i++) {
|
|
154
|
-
if (edge1.belongsTo[i].
|
|
154
|
+
if (edge1.belongsTo[i].itemId === candidate.id) {
|
|
155
155
|
if (!intersectedEdges[edge2.id]) {
|
|
156
156
|
intersectedEdges[edge2.id] = {
|
|
157
157
|
count: 0,
|
|
@@ -164,7 +164,7 @@ export function splitPolygonsV2(polygons) {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
for (let i = 0; i < edge2.belongsTo.length; i++) {
|
|
167
|
-
if (edge2.belongsTo[i].
|
|
167
|
+
if (edge2.belongsTo[i].itemId === candidate.id) {
|
|
168
168
|
if (!intersectedEdges[edge1.id]) {
|
|
169
169
|
intersectedEdges[edge1.id] = {
|
|
170
170
|
count: 0,
|
|
@@ -184,9 +184,9 @@ export function splitPolygonsV2(polygons) {
|
|
|
184
184
|
|
|
185
185
|
if (commmonEdgeId) {
|
|
186
186
|
const edge = intersectedEdges[commmonEdgeId].edge
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
)
|
|
187
|
+
const parentPolygons = edge.belongsTo
|
|
188
|
+
.filter((element) => element.item?.type === 'polygon')
|
|
189
|
+
.map((element) => element.item)
|
|
190
190
|
parentPolygons.sort(
|
|
191
191
|
(a, b) => calculateArea(b.outline) - calculateArea(a.outline)
|
|
192
192
|
)
|
|
@@ -392,7 +392,7 @@ export function getEdgeIntersections(edges, tolerance = FLOAT_PRECISION) {
|
|
|
392
392
|
let skipNextEdge = false
|
|
393
393
|
edge.belongsTo.forEach((edgeParent) => {
|
|
394
394
|
nextEdge.belongsTo.forEach((nextEdgeParent) => {
|
|
395
|
-
if (edgeParent.
|
|
395
|
+
if (edgeParent.itemId === nextEdgeParent.itemId) {
|
|
396
396
|
skipNextEdge = true
|
|
397
397
|
}
|
|
398
398
|
})
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { Line } from '../../objects/Line'
|
|
2
|
+
import { Point } from '../../objects/Point'
|
|
3
|
+
|
|
4
|
+
describe('Line.isTouchingLine', () => {
|
|
5
|
+
describe('when lines intersect at one point', () => {
|
|
6
|
+
test('should return true when lines intersect perpendicularly', () => {
|
|
7
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
8
|
+
const line2 = new Line(new Point(1, -1, 0), new Point(1, 1, 0))
|
|
9
|
+
|
|
10
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
11
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('should return true when lines intersect at an angle', () => {
|
|
15
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 2, 0))
|
|
16
|
+
const line2 = new Line(new Point(0, 2, 0), new Point(2, 0, 0))
|
|
17
|
+
|
|
18
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
19
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('should return true when lines intersect at endpoint', () => {
|
|
23
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
24
|
+
const line2 = new Line(new Point(2, 0, 0), new Point(2, 2, 0))
|
|
25
|
+
|
|
26
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
27
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('when lines are parallel but not collinear', () => {
|
|
32
|
+
test('should return false when lines are parallel and separated', () => {
|
|
33
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
34
|
+
const line2 = new Line(new Point(0, 1, 0), new Point(2, 1, 0))
|
|
35
|
+
|
|
36
|
+
expect(line1.isTouchingLine(line2)).toBe(false)
|
|
37
|
+
expect(line2.isTouchingLine(line1)).toBe(false)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('should return false when lines are parallel and separated vertically', () => {
|
|
41
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(0, 2, 0))
|
|
42
|
+
const line2 = new Line(new Point(1, 0, 0), new Point(1, 2, 0))
|
|
43
|
+
|
|
44
|
+
expect(line1.isTouchingLine(line2)).toBe(false)
|
|
45
|
+
expect(line2.isTouchingLine(line1)).toBe(false)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('should return false when lines are parallel and separated diagonally', () => {
|
|
49
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 2, 0))
|
|
50
|
+
const line2 = new Line(new Point(1, 0, 0), new Point(3, 2, 0))
|
|
51
|
+
|
|
52
|
+
expect(line1.isTouchingLine(line2)).toBe(false)
|
|
53
|
+
expect(line2.isTouchingLine(line1)).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('when lines are collinear', () => {
|
|
58
|
+
test('should return true when lines overlap completely', () => {
|
|
59
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
60
|
+
const line2 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
61
|
+
|
|
62
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
63
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('should return true when lines overlap partially', () => {
|
|
67
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(3, 0, 0))
|
|
68
|
+
const line2 = new Line(new Point(1, 0, 0), new Point(4, 0, 0))
|
|
69
|
+
|
|
70
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
71
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('should return true when one line is contained within the other', () => {
|
|
75
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(4, 0, 0))
|
|
76
|
+
const line2 = new Line(new Point(1, 0, 0), new Point(3, 0, 0))
|
|
77
|
+
|
|
78
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
79
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('should return true when lines touch at one endpoint', () => {
|
|
83
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
84
|
+
const line2 = new Line(new Point(2, 0, 0), new Point(4, 0, 0))
|
|
85
|
+
|
|
86
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
87
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('should return false when lines are collinear but do not overlap', () => {
|
|
91
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(1, 0, 0))
|
|
92
|
+
const line2 = new Line(new Point(2, 0, 0), new Point(3, 0, 0))
|
|
93
|
+
|
|
94
|
+
expect(line1.isTouchingLine(line2)).toBe(false)
|
|
95
|
+
expect(line2.isTouchingLine(line1)).toBe(false)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('should return true when lines are collinear and share one endpoint', () => {
|
|
99
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
100
|
+
const line2 = new Line(new Point(2, 0, 0), new Point(4, 0, 0))
|
|
101
|
+
|
|
102
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
103
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
describe('when lines touch at extremities', () => {
|
|
108
|
+
test('should return true when lines touch at first endpoint', () => {
|
|
109
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
110
|
+
const line2 = new Line(new Point(0, 0, 0), new Point(0, 2, 0))
|
|
111
|
+
|
|
112
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
113
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('should return true when lines touch at last endpoint', () => {
|
|
117
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
118
|
+
const line2 = new Line(new Point(2, -2, 0), new Point(2, 2, 0))
|
|
119
|
+
|
|
120
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
121
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('should return true when lines touch at middle point', () => {
|
|
125
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
126
|
+
const line2 = new Line(new Point(1, 0, 0), new Point(1, 2, 0))
|
|
127
|
+
|
|
128
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
129
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
describe('edge cases', () => {
|
|
134
|
+
test('should return true when lines are identical', () => {
|
|
135
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
136
|
+
const line2 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
137
|
+
|
|
138
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
139
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('should return true when lines are identical but reversed', () => {
|
|
143
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
144
|
+
const line2 = new Line(new Point(2, 0, 0), new Point(0, 0, 0))
|
|
145
|
+
|
|
146
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
147
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('should return false when lines are very close but not touching', () => {
|
|
151
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
152
|
+
const line2 = new Line(new Point(0, 0.001, 0), new Point(2, 0.001, 0))
|
|
153
|
+
|
|
154
|
+
expect(line1.isTouchingLine(line2)).toBe(false)
|
|
155
|
+
expect(line2.isTouchingLine(line1)).toBe(false)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('should return false when lines are skew (not parallel and not intersecting)', () => {
|
|
159
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
160
|
+
const line2 = new Line(new Point(0, 0, 1), new Point(2, 0, 1))
|
|
161
|
+
|
|
162
|
+
expect(line1.isTouchingLine(line2)).toBe(false)
|
|
163
|
+
expect(line2.isTouchingLine(line1)).toBe(false)
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
describe('aligned lines cases ', () => {
|
|
167
|
+
test('should return true line touching on one endpoint', () => {
|
|
168
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(10, 0, 0))
|
|
169
|
+
const line2 = new Line(new Point(10, 0, 0), new Point(20, 0, 0))
|
|
170
|
+
|
|
171
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
172
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
173
|
+
})
|
|
174
|
+
test('should return false line not touching', () => {
|
|
175
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(10, 0, 0))
|
|
176
|
+
const line2 = new Line(new Point(11, 0, 0), new Point(20, 0, 0))
|
|
177
|
+
|
|
178
|
+
expect(line1.isTouchingLine(line2)).toBe(false)
|
|
179
|
+
expect(line2.isTouchingLine(line1)).toBe(false)
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
describe('collinear edge cases with different orientations', () => {
|
|
183
|
+
test('should return true when collinear lines overlap with different directions', () => {
|
|
184
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(3, 0, 0))
|
|
185
|
+
const line2 = new Line(new Point(3, 0, 0), new Point(1, 0, 0))
|
|
186
|
+
|
|
187
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
188
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('should return true when collinear lines touch at endpoints', () => {
|
|
192
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 0, 0))
|
|
193
|
+
const line2 = new Line(new Point(2, 0, 0), new Point(4, 0, 0))
|
|
194
|
+
|
|
195
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
196
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
test('should return false when collinear lines are adjacent but not overlapping', () => {
|
|
200
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(1, 0, 0))
|
|
201
|
+
const line2 = new Line(new Point(1.1, 0, 0), new Point(2, 0, 0))
|
|
202
|
+
|
|
203
|
+
expect(line1.isTouchingLine(line2)).toBe(false)
|
|
204
|
+
expect(line2.isTouchingLine(line1)).toBe(false)
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
describe('diagonal lines', () => {
|
|
209
|
+
test('should return true when diagonal lines intersect', () => {
|
|
210
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 2, 0))
|
|
211
|
+
const line2 = new Line(new Point(0, 2, 0), new Point(2, 0, 0))
|
|
212
|
+
|
|
213
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
214
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test('should return true when diagonal collinear lines overlap', () => {
|
|
218
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(3, 3, 0))
|
|
219
|
+
const line2 = new Line(new Point(1, 1, 0), new Point(4, 4, 0))
|
|
220
|
+
|
|
221
|
+
expect(line1.isTouchingLine(line2)).toBe(true)
|
|
222
|
+
expect(line2.isTouchingLine(line1)).toBe(true)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test('should return false when diagonal parallel lines are separated', () => {
|
|
226
|
+
const line1 = new Line(new Point(0, 0, 0), new Point(2, 2, 0))
|
|
227
|
+
const line2 = new Line(new Point(0, 1, 0), new Point(2, 3, 0))
|
|
228
|
+
|
|
229
|
+
expect(line1.isTouchingLine(line2)).toBe(false)
|
|
230
|
+
expect(line2.isTouchingLine(line1)).toBe(false)
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Plane } from '../../objects/Plane'
|
|
2
|
+
import { Point } from '../../objects/Point'
|
|
3
|
+
|
|
4
|
+
describe('Plane.toRealityCoordinates & Plane.toPlaneCoordinates test', () => {
|
|
5
|
+
test('should return the same coordinates when converting to plane coordinates and back to reality coordinates', () => {
|
|
6
|
+
const plane = new Plane({
|
|
7
|
+
origin: { x: 10, y: 10, z: 10 },
|
|
8
|
+
normalVector: { x: 1, y: 1, z: 1 },
|
|
9
|
+
horizontalVector: { x: 1, y: -1, z: 0 },
|
|
10
|
+
upVector: { x: 1, y: 1, z: -2 },
|
|
11
|
+
})
|
|
12
|
+
const point = new Point(1, 2, 3)
|
|
13
|
+
const realityPoint = plane.toRealityCoordinates(point)
|
|
14
|
+
const planePoint = plane.toPlaneCoordinates(realityPoint)
|
|
15
|
+
expect(planePoint.x).toBeCloseTo(point.x)
|
|
16
|
+
expect(planePoint.y).toBeCloseTo(point.y)
|
|
17
|
+
expect(planePoint.z).toBeCloseTo(point.z)
|
|
18
|
+
})
|
|
19
|
+
test('should return correct coordinates 1', () => {
|
|
20
|
+
const plane = new Plane({
|
|
21
|
+
origin: { x: 10, y: 10, z: 10 },
|
|
22
|
+
normalVector: { x: 1, y: 1, z: 1 },
|
|
23
|
+
horizontalVector: { x: 1, y: -1, z: 0 },
|
|
24
|
+
upVector: { x: 1, y: 1, z: -2 },
|
|
25
|
+
})
|
|
26
|
+
const point = new Point(13, 11, 9)
|
|
27
|
+
const planePoint = plane.toPlaneCoordinates(point)
|
|
28
|
+
expect(planePoint.x).toBeCloseTo(1)
|
|
29
|
+
expect(planePoint.y).toBeCloseTo(1)
|
|
30
|
+
expect(planePoint.z).toBeCloseTo(1)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { toRealityRefFunction } from '../../index'
|
|
2
|
+
import { Plane } from '../../objects/Plane'
|
|
3
|
+
|
|
4
|
+
const RoofPlane = new Plane({
|
|
5
|
+
origin: { x: 0, y: 1, z: 0 },
|
|
6
|
+
normalVector: { x: 0, y: 0.71, z: 0.71 },
|
|
7
|
+
horizontalVector: { x: 1, y: 0, z: 0 },
|
|
8
|
+
upVector: { x: 0, y: -0.71, z: 0.71 },
|
|
9
|
+
})
|
|
10
|
+
describe('toRealityRefFunction', () => {
|
|
11
|
+
test('should convert canvas coordinates to reality coordinates with default plane', () => {
|
|
12
|
+
const canvasSize = { width: 800, height: 600 }
|
|
13
|
+
const mmPerPx = 1
|
|
14
|
+
const layoutOffset = { x: 0, y: 0 }
|
|
15
|
+
const toReality = toRealityRefFunction(canvasSize, mmPerPx, layoutOffset,RoofPlane)
|
|
16
|
+
|
|
17
|
+
const canvasPoint = { x: 1, y: -1.4142, z: 0 }
|
|
18
|
+
const realityPoint = toReality(canvasPoint)
|
|
19
|
+
expect(realityPoint.x).toBeCloseTo(1, 2)
|
|
20
|
+
expect(realityPoint.y).toBeCloseTo(0, 2)
|
|
21
|
+
expect(realityPoint.z).toBeCloseTo(1, 2)
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
|