@tetacom/ng-components 1.7.0 → 1.7.1

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.
@@ -4,9 +4,10 @@ import * as i0 from "@angular/core";
4
4
  export declare class Chart3dTooltipService {
5
5
  private _raycaster;
6
6
  private _mouse;
7
+ findClosestPointOnCurveLocal(point: THREE.Vector3, curve: THREE.CatmullRomCurve3): THREE.Vector3;
7
8
  constructor();
8
9
  findIntersection(event: MouseEvent, canvas: HTMLCanvasElement, camera: THREE.Camera, wellMeshes: WellMeshData[]): IntersectionResult | null;
9
- findClosestPointOnCurve(point: THREE.Vector3, curve: THREE.CatmullRomCurve3): THREE.Vector3;
10
+ private findClosestPointOnCurveToRay;
10
11
  private findClosestDataPoint;
11
12
  private calculateTVD;
12
13
  static ɵfac: i0.ɵɵFactoryDeclaration<Chart3dTooltipService, never>;
@@ -488,6 +488,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImpor
488
488
  }], ctorParameters: () => [] });
489
489
 
490
490
  class Chart3dTooltipService {
491
+ // Kept for marker/orientation logic in the component (not used for MD now)
492
+ findClosestPointOnCurveLocal(point, curve) {
493
+ let closestPoint = new THREE.Vector3();
494
+ let minDistance = Infinity;
495
+ const samples = 200;
496
+ for (let i = 0; i <= samples; i++) {
497
+ const t = i / samples;
498
+ const curvePoint = curve.getPoint(t);
499
+ const distance = point.distanceTo(curvePoint);
500
+ if (distance < minDistance) {
501
+ minDistance = distance;
502
+ closestPoint = curvePoint;
503
+ }
504
+ }
505
+ return closestPoint;
506
+ }
491
507
  constructor() {
492
508
  this._raycaster = new THREE.Raycaster();
493
509
  this._mouse = new THREE.Vector2();
@@ -505,19 +521,29 @@ class Chart3dTooltipService {
505
521
  if (intersects.length === 0) {
506
522
  return null;
507
523
  }
524
+ // Prefer the closest intersection by distance (Three usually returns sorted,
525
+ // but keeping it explicit avoids surprises across versions)
526
+ intersects.sort((a, b) => a.distance - b.distance);
508
527
  // 3. Find well data
509
528
  const intersect = intersects[0];
510
529
  const wellData = wellMeshes.find((_) => _.mesh === intersect.object);
511
530
  if (!wellData) {
512
531
  return null;
513
532
  }
514
- // 4. Calculate closest point on curve
515
- const closestPoint = this.findClosestPointOnCurve(intersect.point, wellData.curve);
516
- // 5. Find closest data point to get MD
533
+ // 4. Calculate closest point on the trajectory curve using the mouse ray.
534
+ // Using the curve parameter t makes the tooltip smooth even for very low-point trajectories (e.g. 3 points).
535
+ const hit = this.findClosestPointOnCurveToRay(this._raycaster.ray, wellData.curve, wellData.points);
536
+ const closestPoint = hit.point;
537
+ const intersectPoint = hit.hitPoint ?? intersect.point;
538
+ const intersectDistance = hit.hitDistance ?? intersect.distance;
539
+ // Override intersection with a stable point derived from ray→trajectory projection
540
+ intersect.point = intersectPoint;
541
+ intersect.distance = intersectDistance;
542
+ // 5. Find closest data point (useful for other fields)
517
543
  const closestDataPoint = this.findClosestDataPoint(closestPoint, wellData.points, wellData.scale);
518
- // 6. Calculate TVD and get MD from data point
544
+ // 6. Calculate TVD and MD
519
545
  const tvd = this.calculateTVD(closestPoint, wellData.scale.y);
520
- const md = closestDataPoint.md ?? 0;
546
+ const md = hit.md; // smoothly interpolated along curve parameter t
521
547
  return {
522
548
  wellData,
523
549
  intersectionPoint: intersect.point,
@@ -527,25 +553,81 @@ class Chart3dTooltipService {
527
553
  tvd,
528
554
  };
529
555
  }
530
- findClosestPointOnCurve(point, curve) {
531
- let closestPoint = new THREE.Vector3();
532
- let minDistance = Infinity;
533
- const samples = 100;
534
- for (let i = 0; i <= samples; i++) {
535
- const t = i / samples;
536
- const curvePoint = curve.getPoint(t);
537
- const distance = point.distanceTo(curvePoint);
538
- if (distance < minDistance) {
539
- minDistance = distance;
540
- closestPoint = curvePoint;
556
+ findClosestPointOnCurveToRay(ray, curve, orderedDataPoints) {
557
+ // Compute closest point on the rendered curve to the mouse ray.
558
+ // We sample t densely (fast enough) and then refine with a few iterations.
559
+ // MD is interpolated along the dataPoints md range using the same t.
560
+ if (!orderedDataPoints.length) {
561
+ return { point: new THREE.Vector3(), md: 0 };
562
+ }
563
+ if (orderedDataPoints.length === 1) {
564
+ // Degenerate curve
565
+ const p = curve.getPoint(0);
566
+ const s = Math.max(0, ray.direction.dot(new THREE.Vector3().subVectors(p, ray.origin)));
567
+ const hitPoint = ray.at(s, new THREE.Vector3());
568
+ return {
569
+ point: p,
570
+ md: orderedDataPoints[0].md ?? 0,
571
+ hitPoint,
572
+ hitDistance: s,
573
+ };
574
+ }
575
+ const mdMin = orderedDataPoints[0].md ?? 0;
576
+ const mdMax = orderedDataPoints[orderedDataPoints.length - 1].md ?? mdMin;
577
+ const d = ray.direction.clone().normalize();
578
+ // IMPORTANT: use getPointAt(u) (u = arc-length parameter), not getPoint(t).
579
+ // CatmullRomCurve3 "t" is not proportional to arc length and causes MD to change non-uniformly,
580
+ // especially noticeable when there are very few control points.
581
+ const distSqAt = (u) => {
582
+ const p = curve.getPointAt(u);
583
+ const s = Math.max(0, d.dot(new THREE.Vector3().subVectors(p, ray.origin)));
584
+ const r = ray.at(s, new THREE.Vector3());
585
+ return { distSq: r.distanceToSquared(p), p, s, r };
586
+ };
587
+ // 1) coarse scan over arc-length parameter u
588
+ const samples = 400;
589
+ let bestU = 0;
590
+ let best = distSqAt(0);
591
+ for (let i = 1; i <= samples; i++) {
592
+ const u = i / samples;
593
+ const cur = distSqAt(u);
594
+ if (cur.distSq < best.distSq) {
595
+ best = cur;
596
+ bestU = u;
597
+ }
598
+ }
599
+ // 2) refine around bestU
600
+ let left = Math.max(0, bestU - 1 / samples);
601
+ let right = Math.min(1, bestU + 1 / samples);
602
+ for (let iter = 0; iter < 10; iter++) {
603
+ const u1 = left + (right - left) / 3;
604
+ const u2 = right - (right - left) / 3;
605
+ const f1 = distSqAt(u1);
606
+ const f2 = distSqAt(u2);
607
+ if (f1.distSq < f2.distSq) {
608
+ right = u2;
609
+ best = f1;
610
+ bestU = u1;
611
+ }
612
+ else {
613
+ left = u1;
614
+ best = f2;
615
+ bestU = u2;
541
616
  }
542
617
  }
543
- return closestPoint;
618
+ // Final eval at bestU
619
+ const final = distSqAt(bestU);
620
+ const md = mdMin + (mdMax - mdMin) * bestU;
621
+ return {
622
+ point: final.p,
623
+ md,
624
+ hitPoint: final.r,
625
+ hitDistance: final.s,
626
+ };
544
627
  }
545
628
  findClosestDataPoint(curvePoint, dataPoints, scale) {
546
629
  let closestPoint = dataPoints[0];
547
630
  let minDistance = Infinity;
548
- let closestIndex = 0;
549
631
  for (let i = 0; i < dataPoints.length; i++) {
550
632
  const point = dataPoints[i];
551
633
  // Convert data point to 3D space using scales
@@ -554,36 +636,6 @@ class Chart3dTooltipService {
554
636
  if (distance < minDistance) {
555
637
  minDistance = distance;
556
638
  closestPoint = point;
557
- closestIndex = i;
558
- }
559
- }
560
- // If MD is not available in the closest point, try to interpolate
561
- if (closestPoint.md === undefined || closestPoint.md === null) {
562
- // Find first point with MD
563
- for (const point of dataPoints) {
564
- if (point.md !== undefined && point.md !== null) {
565
- return point;
566
- }
567
- }
568
- }
569
- // Try to interpolate MD between two closest points
570
- if (closestIndex > 0 && closestIndex < dataPoints.length - 1) {
571
- const prevPoint = dataPoints[closestIndex - 1];
572
- const nextPoint = dataPoints[closestIndex + 1];
573
- if (prevPoint.md !== undefined &&
574
- nextPoint.md !== undefined &&
575
- closestPoint.md === undefined) {
576
- // Linear interpolation
577
- const prevScaled = new THREE.Vector3(scale.x(prevPoint.x), scale.y(prevPoint.y), scale.z(prevPoint.z));
578
- const nextScaled = new THREE.Vector3(scale.x(nextPoint.x), scale.y(nextPoint.y), scale.z(nextPoint.z));
579
- const totalDist = prevScaled.distanceTo(nextScaled);
580
- const distFromPrev = curvePoint.distanceTo(prevScaled);
581
- const t = distFromPrev / totalDist;
582
- const interpolatedMD = prevPoint.md + (nextPoint.md - prevPoint.md) * t;
583
- return {
584
- ...closestPoint,
585
- md: interpolatedMD,
586
- };
587
639
  }
588
640
  }
589
641
  return closestPoint;
@@ -665,7 +717,8 @@ class Chart3dComponent {
665
717
  if (!data.points?.length) {
666
718
  return;
667
719
  }
668
- const points = data.points.map((_) => new THREE.Vector3(x(_.x), y(_.y), z(_.z)));
720
+ const orderedDataPoints = [...data.points].sort((a, b) => (a.md ?? 0) - (b.md ?? 0));
721
+ const points = orderedDataPoints.map((_) => new THREE.Vector3(x(_.x), y(_.y), z(_.z)));
669
722
  const color = d3.scaleOrdinal(d3.schemeTableau10);
670
723
  const material = new THREE.MeshBasicMaterial({
671
724
  color: data?.color ?? color(idx.toString()),
@@ -680,7 +733,7 @@ class Chart3dComponent {
680
733
  series: data,
681
734
  scale: { x, y, z },
682
735
  curve: curve,
683
- points: data.points,
736
+ points: orderedDataPoints,
684
737
  });
685
738
  });
686
739
  const circles = x.ticks(this.SIDE_SIZE / 10);
@@ -951,8 +1004,8 @@ class Chart3dComponent {
951
1004
  this._tooltipMarker.geometry.dispose();
952
1005
  this._tooltipMarker.material.dispose();
953
1006
  }
954
- // Find the closest point on the curve using service
955
- const pointOnCurve = this._tooltipService.findClosestPointOnCurve(intersectionPoint, curve);
1007
+ // Closest point on curve (local helper, keep marker purely visual)
1008
+ const pointOnCurve = this._tooltipService.findClosestPointOnCurveLocal(intersectionPoint, curve);
956
1009
  // Find parameter t for tangent calculation
957
1010
  let closestT = 0;
958
1011
  let minDistance = Infinity;