@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.
Files changed (32) hide show
  1. package/package.json +1 -1
  2. package/src/coords.js +44 -11
  3. package/src/geometry.js +47 -14
  4. package/src/geometryShortDistance.js +442 -0
  5. package/src/index.js +3 -0
  6. package/src/intersectionCheck.js +165 -0
  7. package/src/intersectionPolygon.js +24 -1
  8. package/src/matrix.js +15 -1
  9. package/src/objects/Circle.js +55 -5
  10. package/src/objects/Line.js +530 -264
  11. package/src/objects/Measurement.js +335 -0
  12. package/src/objects/Plane.js +176 -0
  13. package/src/objects/Point.js +16 -7
  14. package/src/objects/Polygon.js +130 -2
  15. package/src/objects/derivedState/nodesEdgesCreation.js +112 -35
  16. package/src/objects/derivedState/updateComputedGeometryPolygon.js +1 -0
  17. package/src/objects/graph/graphCreation.js +5 -5
  18. package/src/objects/hydrate.js +229 -0
  19. package/src/objects/index.js +2 -0
  20. package/src/plane.js +76 -0
  21. package/src/splitMergePolygons.js +8 -8
  22. package/src/tests/Line/isTouchingLine.spec.js +233 -0
  23. package/src/tests/Plane/CoordinateChange.spec.js +32 -0
  24. package/src/tests/coords/toRealityRefFunction.spec.js +25 -0
  25. package/src/tests/geometry/getAngleInDegFrom2DENUVector.spec.js +60 -0
  26. package/src/tests/geometry/projectionOnPlaneFollowingVector.spec.js +215 -0
  27. package/src/tests/geometryShortDistance/areObjectsIntersecting.spec.js +426 -0
  28. package/src/tests/geometryShortDistance/findClosestPoint.spec.js +53 -0
  29. package/src/tests/geometryShortDistance/getShortestSegmentCircleCircle.spec.js +126 -0
  30. package/src/tests/geometryShortDistance/getShortestSegmentLineCircle.spec.js +124 -0
  31. package/src/tests/geometryShortDistance/getShortestSegmentPointLine.spec.js +133 -0
  32. package/src/tests/geometryShortDistance/getShortestSegmentPointPoint.spec.js +115 -0
@@ -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
+ }
@@ -2,6 +2,8 @@ export * from './Point'
2
2
  export * from './Line'
3
3
  export * from './Polygon'
4
4
  export * from './Circle'
5
+ export * from './Measurement'
6
+ export * from './Plane'
5
7
  export * from './hydrate'
6
8
  export * from './Vector'
7
9
  export * from './derivedState'
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].polygonId) ||
32
- !polygonIdsToMerge.includes(e.belongsTo[1].polygonId)
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].polygonId === candidate.id) {
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].polygonId === candidate.id) {
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
- const parentPolygons = edge.belongsTo.map(
188
- (element) => element.polygon
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.polygonId === nextEdgeParent.polygonId) {
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
+