1const DISPLACEMENT_DENSITY = 5; 2 3const SPRING_SAMPLING_DENSITY = 251; 4 5const SPRING_SAMPLING_SUBDENSITY = 4; 6 7const CONTACT_TOLERANCE = 0.005; 8 9const INIT_ITERATION = 10; 10 11const SCALE_UP = 100; 12 13const SIDE_PARAM = { 14 thickness: 1.2, 15 armThickness: 1.5, 16 length: 13, 17 throw: 7, 18 angle: 20, 19 ring: 6 20}; 21 22const SPRING_PARAM = { 23 enable: true, 24 height: 28, 25 gap: 4, 26 thickness: 1.4, 27 width: 20, 28 count: 3 29}; 30 31const CASE_PARAM = { 32 sideSpringClearance: 1.0, 33 springWidth: 22, 34 wall: 4, 35 topBuffer: 2, 36 baseBuffer: 4.5, 37 hole: 2.0, 38 holeOffset: 7.0 39}; 40 41const SLIDER_PARAM = { 42 base: 4.0, 43 travel: 10.0, 44 preloadSide: 1.0, 45 preloadBase: 1.0, 46 stopper: 3, 47 fillet: 1.5, 48 tolerance: 0.1, 49 maxDeflection: 4.0 50}; 51 52class HapticProject { 53 constructor(_name, _sides, _caseP, _sliderP, _springP, _sideSpringP) { 54 this.name = _name; 55 56 this.updateDisplay = true; 57 this.updateGallery = false; 58 59 this.parameters = { 60 case: _caseP, 61 slider: _sliderP, 62 spring: _springP, 63 side: _sideSpringP 64 }; 65 66 this.sides = _sides; 67 68 this.fdData = []; 69 70 this.drawingBase = undefined; 71 this.drawingView = undefined; 72 this.drawingFab = undefined; 73 74 this.profile = { 75 left: new HapticProfile(this, "LEFT"), 76 right: 77 this.sides === "DOUBLE" ? new HapticProfile(this, "RIGHT") : undefined 78 }; 79 80 this.updateBase(); 81 82 this.updateView(0); 83 } 84 85 initFD() { 86 this.fdData = []; 87 for (var i in this.profile) { 88 if (this.profile[i]) this.profile[i].fdData = []; 89 } 90 91 var count = this.parameters.slider.travel * DISPLACEMENT_DENSITY; 92 for (var i = 0; i <= count; i++) { 93 this.fdData.push({ 94 d: i * (1 / DISPLACEMENT_DENSITY), 95 force: 0, 96 forceReverse: 0, 97 springForce: 0 98 }); 99 100 for (var j in this.profile) { 101 if (this.profile[j]) 102 this.profile[j].fdData.push({ 103 d: i * (1 / DISPLACEMENT_DENSITY), 104 angle: 0, 105 deflection: 0, 106 ratioForce: 0, 107 ratioFriction: 0, 108 ratioFrictionUp: 0, 109 offset: 0, 110 force: 0, 111 friction: 0 112 }); 113 } 114 } 115 } 116 117 updateFD(index, pos) { 118 if (index < this.fdData.length) { 119 var springCoeff = calcSpringGradient( 120 this.parameters.spring.width, 121 this.parameters.spring.thickness 122 ); // FORMULA FOR MAIN SPRING COEFF HERE 123 this.fdData[index].springForce = 124 (this.fdData[index].d + this.parameters.slider.preloadBase) * 125 springCoeff; 126 127 var tempForce = this.parameters.spring.enable 128 ? this.fdData[index].springForce 129 : 0; 130 131 var tempForceReverse = this.parameters.spring.enable 132 ? this.fdData[index].springForce 133 : 0; 134 135 for (var i in this.profile) { 136 if (this.profile[i]) { 137 if (this.profile[i].type !== "SYMMETRICAL") { 138 this.profile[i].updateProfile(pos); 139 this.profile[i].calcForce(index, CONTACT_TOLERANCE * SCALE_UP); 140 141 var sideCoeff = calcSideGradient(this.parameters.side.thickness); 142 143 var frictionCoeff = FRICTION_COEFFICIENT; 144 var sideForce = 145 sideCoeff * this.profile[i].fdData[index].deflection; 146 147 var avgArr = []; 148 for (var j = -1; j <= 1; j++) { 149 if (this.profile[i].fdData[index + j]) { 150 avgArr.push(this.profile[i].fdData[index + j]); 151 } 152 } 153 154 var ratioAvgForce = 155 avgArr.map(v => v.ratioForce).reduce((acc, v) => acc + v) / 156 avgArr.length; 157 var ratioAvgFriction = 158 avgArr.map(v => v.ratioFriction).reduce((acc, v) => acc + v) / 159 avgArr.length; 160 var ratioAvgFrictionUp = 161 avgArr.map(v => v.ratioFrictionUp).reduce((acc, v) => acc + v) / 162 avgArr.length; 163 164 this.profile[i].fdData[index].force = sideForce * ratioAvgForce; 165 166 this.profile[i].fdData[index].friction = 167 sideForce * ratioAvgFriction * frictionCoeff; 168 169 tempForce += this.profile[i].fdData[index].force; 170 tempForce += this.profile[i].fdData[index].friction; 171 172 tempForceReverse += this.profile[i].fdData[index].force; 173 tempForceReverse -= this.profile[i].fdData[index].friction; 174 175 var springFrictionRev = 176 this.fdData[index].springForce * 177 ratioAvgFrictionUp * 178 frictionCoeff; 179 180 // tempForceReverse -= this.parameters.spring.enable 181 // ? springFrictionRev 182 // : 0; 183 } 184 } 185 } 186 187 if (this.profile.left && this.profile.right) { 188 var follower; 189 var profile; 190 if (this.profile.left.type === "SYMMETRICAL") { 191 var follower = this.profile.left; 192 var profile = this.profile.right; 193 this.profile.left.fdData[index] = {...this.profile.right.fdData[index]}; 194 } else if (this.profile.right.type === "SYMMETRICAL") { 195 var follower = this.profile.right; 196 var profile = this.profile.left; 197 this.profile.right.fdData[index] = {...this.profile.left.fdData[index]}; 198 } 199 200 if (follower && profile) { 201 follower.fdData[index].force = profile.fdData[index].force; 202 follower.fdData[index].friction = profile.fdData[index].friction; 203 204 tempForce += follower.fdData[index].force; 205 tempForce += follower.fdData[index].friction; 206 207 tempForceReverse += follower.fdData[index].force; 208 tempForceReverse -= follower.fdData[index].friction; 209 210 var springFrictionRev = 211 this.fdData[index].springForce * ratioAvgFrictionUp * frictionCoeff; 212 213 // tempForceReverse -= this.parameters.spring.enable 214 // ? springFrictionRev 215 // : 0; 216 } 217 } 218 219 this.fdData[index].force = tempForce; 220 this.fdData[index].forceReverse = tempForceReverse; 221 this.updateGallery = true; 222 } 223 } 224 225 smoothFD() { 226 this.fdData.forEach((d, index) => { 227 var springCoeff = calcSpringGradient( 228 this.parameters.spring.width, 229 this.parameters.spring.thickness 230 ); // FORMULA FOR MAIN SPRING COEFF HERE 231 this.fdData[index].springForce = 232 (this.fdData[index].d + this.parameters.slider.preloadBase) * 233 springCoeff; 234 235 var tempForce = this.parameters.spring.enable 236 ? this.fdData[index].springForce 237 : 0; 238 239 var tempForceReverse = this.parameters.spring.enable 240 ? this.fdData[index].springForce 241 : 0; 242 243 for (var i in this.profile) { 244 if (this.profile[i]) { 245 var sideCoeff = calcSideGradient(this.parameters.side.thickness); 246 var frictionCoeff = FRICTION_COEFFICIENT; 247 var sideForce = sideCoeff * this.profile[i].fdData[index].deflection; 248 249 var avgArr = []; 250 for (var j = -1; j <= 1; j++) { 251 if (this.profile[i].fdData[index + j]) { 252 avgArr.push(this.profile[i].fdData[index + j]); 253 } 254 } 255 256 var ratioAvgForce = 257 avgArr.map(v => v.ratioForce).reduce((acc, v) => acc + v) / 258 avgArr.length; 259 var ratioAvgFriction = 260 avgArr.map(v => v.ratioFriction).reduce((acc, v) => acc + v) / 261 avgArr.length; 262 var ratioAvgFrictionUp = 263 avgArr.map(v => v.ratioFrictionUp).reduce((acc, v) => acc + v) / 264 avgArr.length; 265 266 this.profile[i].fdData[index].force = sideForce * ratioAvgForce; 267 268 this.profile[i].fdData[index].friction = 269 sideForce * ratioAvgFriction * frictionCoeff; 270 271 tempForce += this.profile[i].fdData[index].force; 272 tempForce += this.profile[i].fdData[index].friction; 273 274 tempForceReverse += this.profile[i].fdData[index].force; 275 tempForceReverse -= this.profile[i].fdData[index].friction; 276 277 var springFrictionRev = 278 this.fdData[index].springForce * ratioAvgFrictionUp * frictionCoeff; 279 // tempForceReverse -= this.parameters.spring.enable 280 // ? springFrictionRev 281 // : 0; 282 } 283 } 284 285 if (this.fdData[index].force !== tempForce) { 286 this.fdData[index].force = tempForce; 287 this.fdData[index].forceReverse = tempForceReverse; 288 this.updateGallery = true; 289 } 290 }); 291 } 292 293 updateBase() { 294 if (this.drawingBase) this.drawingBase.remove(); 295 switch (this.sides) { 296 case "SINGLE": 297 this.drawingBase = generateCaseSingle( 298 this.parameters.case, 299 this.parameters.slider, 300 this.parameters.side, 301 this.parameters.spring 302 ); 303 break; 304 case "DOUBLE": 305 this.drawingBase = generateCaseDouble( 306 this.parameters.case, 307 this.parameters.slider, 308 this.parameters.side, 309 this.parameters.side, 310 this.parameters.spring 311 ); 312 break; 313 } 314 315 for (var i in this.profile) { 316 if (this.profile[i]) this.profile[i].updateBase(); 317 } 318 319 if (!this.parameters.spring.enable) 320 this.drawingBase.children["SPRING"].remove(); 321 322 this.initFD(); 323 324 this.updateDisplay = true; 325 this.updateGallery = true; 326 } 327 328 updateView(i) { 329 if (this.drawingView) this.drawingView.remove(); 330 331 switch (this.sides) { 332 case "SINGLE": 333 this.drawingView = generateViewCaseSingle( 334 this.parameters.case, 335 this.parameters.slider, 336 this.parameters.side, 337 this.parameters.spring, 338 this.fdData.length > 0 ? this.profile.left.fdData[i].angle : 0, 339 this.fdData.length > 0 ? this.fdData[i].d : 0 340 ); 341 342 break; 343 case "DOUBLE": 344 this.drawingView = generateViewCaseDouble( 345 this.parameters.case, 346 this.parameters.slider, 347 this.parameters.side, 348 this.parameters.spring, 349 this.profile.left ? this.profile.left.fdData[i].angle : 0, 350 this.profile.right ? this.profile.right.fdData[i].angle : 0, 351 this.fdData.length > 0 ? this.fdData[i].d : 0 352 ); 353 break; 354 } 355 356 for (var s in this.profile) { 357 if (this.profile[s]) { 358 if (this.profile[s].active) { 359 var p = this.profile[s]; 360 if (p.base) p.base.strokeWidth = 0; 361 if (p.create) p.create.strokeWidth = 0; 362 if (p.active) p.active.strokeWidth = 0; 363 // p.updateProfile(i); 364 // p.setActiveProfile(); 365 366 if (this.profile[s].activeView) this.profile[s].activeView.remove(); 367 this.profile[s].activeView = this.profile[s].active.clone(); 368 this.profile[s].activeView.translate(0, this.fdData[i].d); 369 this.drawingView.addChild(this.profile[s].activeView); 370 this.profile[s].updateProfile(i); 371 } 372 } 373 } 374 375 this.drawingBase.strokeWidth = 0; 376 377 // for (var j in this.profile) { 378 // if (this.profile[j]) { 379 // var p = this.profile[j]; 380 // if (p.base) p.base.strokeWidth = 0; 381 // if (p.create) p.create.strokeWidth = 0; 382 // if (p.active) p.active.strokeWidth = 0; 383 // this.profile[j].updateProfile(i); 384 // } 385 // } 386 387 this.drawingView.strokeColor = "rgba(0, 0, 0, 1)"; 388 this.drawingView.strokeWidth = 1 / MIN_ZOOM; 389 this.drawingView.strokeCap = "round"; 390 this.drawingView.strokeJoin = "round"; 391 392 if (this.drawingFab) this.drawingFab.remove(); 393 394 this.drawingFab = this.drawingView.clone(); 395 this.drawingFab.children["CASE 1"].remove(); 396 this.drawingFab.children["SPRING 1"].remove(); 397 this.drawingFab.addChild(this.drawingBase.children["CASE"].clone()); 398 if (this.parameters.spring.enable) 399 this.drawingFab.addChild(this.drawingBase.children["SPRING"].clone()); 400 this.drawingFab.addChild(this.drawingBase.children["OUTER"].clone()); 401 this.drawingFab.addChild(this.drawingBase.children["MASK"].clone()); 402
403 var rect = this.drawingFab.bounds; 404 if (this.parameters.spring.enable) 405 this.drawingFab.children["SPRING 1"].translate(rect.width * 1.1, 0); 406 this.drawingFab.children["CASE 1"].translate(rect.width * 2.2, 0); 407 this.drawingFab.children["CIRCLE1 1"].translate(rect.width * 2.2, 0); 408 this.drawingFab.children["CIRCLE2 1"].translate(rect.width * 2.2, 0); 409 this.drawingFab.children["CIRCLE3 1"].translate(rect.width * 2.2, 0); 410 this.drawingFab.children["CIRCLE4 1"].translate(rect.width * 2.2, 0); 411 this.drawingFab.children["MASK 1"].translate(rect.width * 3.3, 0); 412 this.drawingFab.children["OUTER 1"].translate(rect.width * 4.4, 0); 413 var fabPos = new paper.Point(rect.x, rect.y); 414 this.drawingFab.translate(origin.subtract(fabPos)); 415 this.drawingFab.strokeWidth = 0; 416 417 if (!this.parameters.spring.enable) 418 this.drawingView.children["SPRING"].remove(); 419 420 this.updateDisplay = false; 421 } 422} 423 424class HapticProfile { 425 constructor(_parent, _side) { 426 this.parent = _parent; 427 this.side = _side; 428 this.base; 429 430 this.import; 431 this.create; 432 this.createView; 433 this.active; 434 this.activeView; 435 this.symmetrical; 436 437 this.type; 438 439 this.fdData = []; 440 } 441 442 updateBase() { 443 this.base = this.parent.drawingBase.children[this.side]; 444 var spine = this.base.firstSegment.point.getDistance( 445 this.base.lastSegment.point 446 ); 447 var arr = [ 448 this.import, 449 this.create, 450 this.createView, 451 this.active, 452 this.activeView 453 ]; 454 455 arr.forEach(p => { 456 if (p) { 457 var oldSpine = p.firstSegment.point.getDistance(p.lastSegment.point); 458 p.scale(1, spine / oldSpine, p.firstSegment.point); 459 p.translate( 460 this.base.firstSegment.point.subtract(p.firstSegment.point) 461 ); 462 } 463 }); 464 } 465 466 updateWidth(w) { 467 var rect = this.active.bounds; 468 var currW = 469 this.side === "LEFT" 470 ? this.active.firstSegment.point.x - rect.x 471 : rect.width - this.active.firstSegment.point.x + rect.x; 472 if (currW > 0) { 473 switch (this.type) { 474 case "IMPORT": 475 this.import.scale(w / currW, 1, this.import.firstSegment.point); 476 break; 477 case "CREATE": 478 this.create.scale(w / currW, 1, this.create.firstSegment.point); 479 break; 480 } 481 this.setActiveProfile(); 482 this.parent.updateGallery = true; 483 } 484 } 485 486 get profileWidth() { 487 if (this.active) { 488 var rect = this.active.bounds; 489 var currW = 490 this.side === "LEFT" 491 ? this.active.firstSegment.point.x - rect.x 492 : rect.width - this.active.firstSegment.point.x + rect.x; 493 return currW; 494 } 495 return undefined; 496 } 497 498 updateProfile(i) { 499 switch (this.type) { 500 case "CREATE": 501 var t = new paper.Point( 502 0, 503 this.fdData[i].d - 504 (this.createView.firstSegment.point.y - 505 this.base.firstSegment.point.y) 506 ); 507 this.createView.translate(t); 508 this.createView.strokeColor = "rgba(0, 0, 0, 1)"; 509 this.createView.strokeWidth = 0.1; 510 511 this.create.remove(); 512 this.create = new paper.Path([...this.createView.segments]); 513 this.create.translate( 514 t.x, 515 this.base.firstSegment.point.y - this.create.firstSegment.point.y 516 ); 517 this.create.selected = false; 518 this.create.strokeWidth = 0; 519 this.active = this.create; 520 break; 521 case "SYMMETRICAL": 522 this.setActiveProfile(); 523 if (this.createView) { 524 this.createView.selected = false; 525 this.createView.remove(); 526 } 527 break; 528 default: 529 if (this.createView) { 530 this.createView.selected = false; 531 this.createView.remove(); 532 } 533 break; 534 } 535 } 536 537 setImport(path) { 538 if (this.import) this.import.remove(); 539 this.import = path; 540 } 541 542 setActiveType(type) { 543 this.type = type; 544 this.setActiveProfile(); 545 } 546 547 setActiveProfile() { 548 switch (this.type) { 549 case "IMPORT": 550 this.active = this.import ? this.import : undefined; 551 break; 552 case "CREATE": 553 if (this.create) { 554 this.active = this.create; 555 if (this.createView) this.createView.remove(); 556 this.createView = this.create.clone(); 557 } else { 558 this.create = this.base.clone(); 559 this.createView = this.create.clone(); 560 this.active = this.create; 561 } 562 this.createView.selected = true; 563 break; 564 case "SYMMETRICAL": 565 var path = 566 this.parent.sides === "DOUBLE" 567 ? this.side === "LEFT" 568 ? this.parent.profile.right.active 569 : this.parent.profile.left.active 570 : undefined; 571 if (path) { 572 path = path.clone(); 573 path = path.translate( 574 this.base.firstSegment.point.subtract(path.firstSegment.point) 575 ); 576 path = path.scale(-1, 1, path.firstSegment.point); 577 } 578 this.active = path ? path : undefined; 579 break; 580 } 581 } 582 583 calcForce(i, tolerance) { 584 if (this.active) { 585 i = Math.floor(i); 586 if (i < this.fdData.length) { 587 var path = this.active.clone(); 588 if (this.side === "RIGHT") { 589 path = path.translate( 590 this.parent.profile.left.base.firstSegment.point.subtract( 591 path.firstSegment.point 592 ) 593 ); 594 path = path.scale(-1, 1, path.firstSegment.point); 595 } 596 597 var replacementSegments = [];
598 path.segments.forEach((s, i, arr) => { 599 if (i === 0 || i === arr.length - 1) { 600 replacementSegments.push(s); 601 } else if (i > 0 && i < arr.length - 1) { 602 var os = path.getOffsetOf(s.point); 603 var osNext = path.getOffsetOf(arr[i + 1].point); 604 var os1 = os - 1.5 / DISPLACEMENT_DENSITY; 605 var os2 = os + 1.5 / DISPLACEMENT_DENSITY; 606 if (os1 > 0 && os2 < osNext) { 607 var p1 = path.getPointAt(os1); 608 var p2 = path.getPointAt(os2); 609 var tan1 = path 610 .getTangentAt(os1) 611 .normalize(0.7 / DISPLACEMENT_DENSITY); 612 var tan2 = path 613 .getTangentAt(os2) 614 .normalize(0.7 / DISPLACEMENT_DENSITY); 615 tan2 = tan2.rotate(180); 616 617 var tempPath1 = path.clone(); 618 var tempPath2 = path.clone(); 619 tempPath1.divideAt(os1); 620 tempPath2.divideAt(os2); 621 622 var s1 = tempPath1.segments[i]; 623 var s2 = tempPath2.segments[i + 1]; 624 625 s1.handleOut = tan1; 626 s2.handleIn = tan2; 627 628 replacementSegments.push(s1); 629 replacementSegments.push(s2); 630 631 tempPath1.remove(); 632 tempPath2.remove(); 633 } else { 634 replacementSegments.push(s); 635 } 636 } 637 }); 638 639 path.segments = [...replacementSegments]; 640 641 var pathClosed = path.clone(); 642 pathClosed.lineBy(100, 0); 643 pathClosed.reverse(); 644 pathClosed.lineBy(100, 0); 645 pathClosed.reverse(); 646 pathClosed.closePath(); 647 648 path.translate(0, this.fdData[i].d); 649 650 pathClosed.translate(0, this.fdData[i].d); 651 652 var arm = this.parent.drawingBase.children["ARM"].clone(); 653 var pivot = arm.firstSegment.point; 654 655 arm.scale(SCALE_UP, pivot); 656 path.scale(SCALE_UP, pivot); 657 pathClosed.scale(SCALE_UP, pivot); 658 659 var tip = new paper.Path([ 660 arm.segments[2].point, 661 arm.segments[3].point, 662 arm.segments[4].point 663 ]); 664 665 var tipPtArr = []; 666 667 for (var j = 0; j <= SPRING_SAMPLING_DENSITY; j++) { 668 var offset = j * (tip.length / SPRING_SAMPLING_DENSITY); 669 tipPtArr.push(tip.getPointAt(offset)); 670 } 671 672 var contactPoint = this.calcContact( 673 i, 674 tolerance, 675 0, 676 pivot, 677 tipPtArr, 678 path, 679 pathClosed, 680 0, 681 this.parent.parameters.side.angle 682 ); 683 684 switch (contactPoint.type) { 685 case "contact": 686 if (contactPoint.springAngle !== this.fdData[i].angle) 687 this.parent.updateDisplay = true; 688 689 var armVectorInit = new paper.Point( 690 arm.lastSegment.point.subtract(arm.firstSegment.point) 691 ); 692 var armVector = armVectorInit.rotate( 693 contactPoint.springAngle, 694 new paper.Point(0, 0) 695 ); 696 var armVectorN = armVector.rotate(-90); 697 var angleBetween = armVectorN.getDirectedAngle(contactPoint.normal); 698 var profileRatio = Math.abs( 699 Math.cos((angleBetween * Math.PI) / 180) 700 ); 701 var yRatio = contactPoint.normal.y / contactPoint.normal.length; 702 var frictionRatio = Math.abs( 703 contactPoint.tangent.y / contactPoint.tangent.length 704 ); 705 706 this.fdData[i].angle = contactPoint.springAngle; 707 this.fdData[i].deflection = 708 Math.abs(armVector.x - armVectorInit.x) / SCALE_UP; 709 710 this.fdData[i].ratioForce = yRatio * profileRatio; 711 this.fdData[i].ratioFriction = frictionRatio * profileRatio; 712 713 var angleFriction = contactPoint.tangent.getDirectedAngle( 714 new paper.Point(0, -1) 715 ); 716 var frictionRatioUp = 717 angleFriction < 0 && Math.abs(angleFriction) > 0.1 718 ? -Math.cos((angleFriction / 180) * Math.PI) * 719 contactPoint.tangent.y 720 : 0; 721 this.fdData[i].ratioFrictionUp = frictionRatioUp; 722 723 break; 724 725 case "no contact": 726 this.fdData[i].angle = 0; 727 this.fdData[i].deflection = 0; 728 this.fdData[i].ratioForce = 0; 729 this.fdData[i].ratioFriction = 0; 730 this.fdData[i].ratioFrictionUp = 0; 731 break; 732 } 733 734 // path.scale(1 / SCALE_UP, pivot); 735 // if (i === 0) { 736 // path.selected = true; 737 // } 738 739 path.remove(); 740 pathClosed.remove(); 741 arm.remove(); 742 tip.remove(); 743 } 744 } 745 } 746 747 calcContact( 748 i, 749 tolerance, 750 count, 751 pivot, 752 tipPtArr, 753 path, 754 pathClosed, 755 angleStart, 756 angleEnd 757 ) { 758 var tipCullNull = tipPtArr.filter(p => p !== null); 759 var tipStart = tipCullNull.map(p => p.rotate(angleStart, pivot)); 760 var angleMiddle = (angleStart + angleEnd) / 2; 761 var tipMiddle = tipCullNull.map(p => p.rotate(angleMiddle, pivot)); 762 var tipEnd = tipCullNull.map(p => p.rotate(angleEnd, pivot)); 763 764 var iterate = false;
765 766 var containsStart = false; 767 var containsMiddle = false; 768 var containsEnd = false; 769 770 tipStart.forEach((p, k, arr) => { 771 if (k === arr.length - 1 || k % SPRING_SAMPLING_SUBDENSITY === 0) { 772 if (pathClosed.contains(p)) { 773 containsStart = true; 774 } 775 } 776 }); 777 778 tipMiddle.forEach((p, k, arr) => { 779 if (k === arr.length - 1 || k % SPRING_SAMPLING_SUBDENSITY === 0) { 780 if (pathClosed.contains(p)) { 781 containsMiddle = true; 782 } 783 } 784 }); 785 786 tipEnd.forEach((p, k, arr) => { 787 if (k === arr.length - 1 || k % SPRING_SAMPLING_SUBDENSITY === 0) { 788 if (pathClosed.contains(p)) { 789 containsEnd = true; 790 } 791 } 792 }); 793 794 if (containsStart && containsMiddle && containsEnd) { 795 return { offset: undefined, type: "exceed" }; 796 } else if (!containsStart && !containsMiddle && !containsEnd) { 797 return { offset: undefined, type: "no contact" }; 798 } else { 799 var aS, aE; 800 var tip; 801 if (containsStart && !containsMiddle) { 802 tip = tipMiddle.map(p => p); 803 aS = angleStart; 804 aE = angleMiddle; 805 } else if (containsMiddle && !containsEnd) { 806 tip = tipEnd.map(p => p); 807 aS = angleMiddle; 808 aE = angleEnd; 809 } 810 811 if (count > INIT_ITERATION) { 812 for (let p of tip) { 813 var pt = path.getNearestPoint(p); 814 if (pt.getDistance(p) < tolerance) { 815 var os = path.getOffsetOf(pt); 816 var nor = path.getNormalAt(os).normalize(); 817 var tan = path.getTangentAt(os).normalize(); 818 819 return { 820 point: pt, 821 offset: os, 822 normal: nor, 823 tangent: tan, 824 springAngle: aS, 825 type: "contact" 826 }; 827 } 828 } 829 830 iterate = true; 831 // var cpArr = tip.map(p => { 832 // var pt = path.getNearestPoint(p); 833 // return { point: pt, distance: pt.getDistance(p) }; 834 // }); 835 836 // cpArr.sort((a, b) => a.distance - b.distance); 837 838 // if (cpArr[0].distance < tolerance) { 839 // var cpt = cpArr[0].point; 840 // var os = path.getOffsetOf(cpt); 841 // var nor = path.getNormalAt(os).normalize(); 842 // var tan = path.getTangentAt(os).normalize(); 843 844 // return { 845 // point: cpt, 846 // offset: os, 847 // normal: nor, 848 // tangent: tan, 849 // springAngle: aS, 850 // type: "contact" 851 // }; 852 // } else iterate = true; 853 } else iterate = true; 854 } 855 856 if (iterate) { 857 count++; 858 return this.calcContact( 859 i, 860 tolerance, 861 count, 862 pivot, 863 tipPtArr, 864 path, 865 pathClosed, 866 aS, 867 aE 868 ); 869 } 870 } 871}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.