1/* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32package com.jme3.math; 33 34import com.jme3.export.*; 35import com.jme3.util.TempVars; 36import java.io.IOException; 37 38/** 39 * <p>LineSegment represents a segment in the space. This is a portion of a Line 40 * that has a limited start and end points.</p> 41 * <p>A LineSegment is defined by an origin, a direction and an extent (or length). 42 * Direction should be a normalized vector. It is not internally normalized.</p> 43 * <p>This class provides methods to calculate distances between LineSegments, Rays and Vectors. 44 * It is also possible to retrieve both end points of the segment {@link LineSegment#getPositiveEnd(Vector3f)} 45 * and {@link LineSegment#getNegativeEnd(Vector3f)}. There are also methods to check whether 46 * a point is within the segment bounds.</p> 47 * 48 * @see Ray 49 * @author Mark Powell 50 * @author Joshua Slack 51 */ 52public class LineSegment implements Cloneable, Savable, java.io.Serializable { 53 54 static final long serialVersionUID = 1; 55 56 private Vector3f origin; 57 private Vector3f direction; 58 private float extent; 59 60 public LineSegment() { 61 origin = new Vector3f(); 62 direction = new Vector3f(); 63 } 64 65 public LineSegment(LineSegment ls) { 66 this.origin = new Vector3f(ls.getOrigin()); 67 this.direction = new Vector3f(ls.getDirection()); 68 this.extent = ls.getExtent(); 69 } 70 71 /** 72 * <p>Creates a new LineSegment with the given origin, direction and extent.</p> 73 * <p>Note that the origin is not one of the ends of the LineSegment, but its center.</p> 74 */ 75 public LineSegment(Vector3f origin, Vector3f direction, float extent) { 76 this.origin = origin; 77 this.direction = direction; 78 this.extent = extent; 79 } 80 81 /** 82 * <p>Creates a new LineSegment with a given origin and end. This constructor will calculate the 83 * center, the direction and the extent.</p> 84 */ 85 public LineSegment(Vector3f start, Vector3f end) { 86 this.origin = new Vector3f(0.5f * (start.x + end.x), 0.5f * (start.y + end.y), 0.5f * (start.z + end.z)); 87 this.direction = end.subtract(start); 88 this.extent = direction.length() * 0.5f; 89 direction.normalizeLocal(); 90 } 91 92 public void set(LineSegment ls) { 93 this.origin = new Vector3f(ls.getOrigin()); 94 this.direction = new Vector3f(ls.getDirection()); 95 this.extent = ls.getExtent(); 96 } 97 98 public float distance(Vector3f point) { 99 return FastMath.sqrt(distanceSquared(point)); 100 } 101 102 public float distance(LineSegment ls) { 103 return FastMath.sqrt(distanceSquared(ls)); 104 } 105 106 public float distance(Ray r) { 107 return FastMath.sqrt(distanceSquared(r)); 108 } 109 110 public float distanceSquared(Vector3f point) { 111 TempVars vars = TempVars.get(); 112 Vector3f compVec1 = vars.vect1; 113 114 point.subtract(origin, compVec1); 115 float segmentParameter = direction.dot(compVec1); 116 117 if (-extent < segmentParameter) { 118 if (segmentParameter < extent) { 119 origin.add(direction.mult(segmentParameter, compVec1), 120 compVec1); 121 } else { 122 origin.add(direction.mult(extent, compVec1), compVec1); 123 } 124 } else { 125 origin.subtract(direction.mult(extent, compVec1), compVec1); 126 } 127 128 compVec1.subtractLocal(point); 129 float len = compVec1.lengthSquared(); 130 vars.release(); 131 return len; 132 } 133 134 public float distanceSquared(LineSegment test) { 135 TempVars vars = TempVars.get(); 136 Vector3f compVec1 = vars.vect1; 137 138 origin.subtract(test.getOrigin(), compVec1); 139 float negativeDirectionDot = -(direction.dot(test.getDirection())); 140 float diffThisDot = compVec1.dot(direction); 141 float diffTestDot = -(compVec1.dot(test.getDirection())); 142 float lengthOfDiff = compVec1.lengthSquared(); 143 vars.release(); 144 float determinant = FastMath.abs(1.0f - negativeDirectionDot 145 * negativeDirectionDot); 146 float s0, s1, squareDistance, extentDeterminant0, extentDeterminant1, tempS0, tempS1; 147 148 if (determinant >= FastMath.FLT_EPSILON) { 149 // segments are not parallel 150 s0 = negativeDirectionDot * diffTestDot - diffThisDot; 151 s1 = negativeDirectionDot * diffThisDot - diffTestDot; 152 extentDeterminant0 = extent * determinant; 153 extentDeterminant1 = test.getExtent() * determinant; 154 155 if (s0 >= -extentDeterminant0) { 156 if (s0 <= extentDeterminant0) { 157 if (s1 >= -extentDeterminant1) { 158 if (s1 <= extentDeterminant1) // region 0 (interior) 159 { 160 // minimum at two interior points of 3D lines 161 float inverseDeterminant = ((float) 1.0) 162 / determinant; 163 s0 *= inverseDeterminant; 164 s1 *= inverseDeterminant; 165 squareDistance = s0 166 * (s0 + negativeDirectionDot * s1 + (2.0f) * diffThisDot) 167 + s1 168 * (negativeDirectionDot * s0 + s1 + (2.0f) * diffTestDot) 169 + lengthOfDiff; 170 } else // region 3 (side) 171 { 172 s1 = test.getExtent(); 173 tempS0 = -(negativeDirectionDot * s1 + diffThisDot); 174 if (tempS0 < -extent) { 175 s0 = -extent; 176 squareDistance = s0 * (s0 - (2.0f) * tempS0) 177 + s1 * (s1 + (2.0f) * diffTestDot) 178 + lengthOfDiff; 179 } else if (tempS0 <= extent) { 180 s0 = tempS0; 181 squareDistance = -s0 * s0 + s1 182 * (s1 + (2.0f) * diffTestDot) 183 + lengthOfDiff; 184 } else { 185 s0 = extent; 186 squareDistance = s0 * (s0 - (2.0f) * tempS0) 187 + s1 * (s1 + (2.0f) * diffTestDot) 188 + lengthOfDiff; 189 } 190 } 191 } else // region 7 (side) 192 { 193 s1 = -test.getExtent(); 194 tempS0 = -(negativeDirectionDot * s1 + diffThisDot); 195 if (tempS0 < -extent) { 196 s0 = -extent; 197 squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1 198 * (s1 + (2.0f) * diffTestDot) 199 + lengthOfDiff; 200 } else if (tempS0 <= extent) { 201 s0 = tempS0; 202 squareDistance = -s0 * s0 + s1 203 * (s1 + (2.0f) * diffTestDot) 204 + lengthOfDiff; 205 } else { 206 s0 = extent; 207 squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1 208 * (s1 + (2.0f) * diffTestDot) 209 + lengthOfDiff; 210 } 211 } 212 } else { 213 if (s1 >= -extentDeterminant1) { 214 if (s1 <= extentDeterminant1) // region 1 (side) 215 { 216 s0 = extent; 217 tempS1 = -(negativeDirectionDot * s0 + diffTestDot); 218 if (tempS1 < -test.getExtent()) { 219 s1 = -test.getExtent(); 220 squareDistance = s1 * (s1 - (2.0f) * tempS1) 221 + s0 * (s0 + (2.0f) * diffThisDot) 222 + lengthOfDiff; 223 } else if (tempS1 <= test.getExtent()) { 224 s1 = tempS1; 225 squareDistance = -s1 * s1 + s0 226 * (s0 + (2.0f) * diffThisDot) 227 + lengthOfDiff; 228 } else { 229 s1 = test.getExtent(); 230 squareDistance = s1 * (s1 - (2.0f) * tempS1) 231 + s0 * (s0 + (2.0f) * diffThisDot) 232 + lengthOfDiff; 233 } 234 } else // region 2 (corner) 235 { 236 s1 = test.getExtent(); 237 tempS0 = -(negativeDirectionDot * s1 + diffThisDot); 238 if (tempS0 < -extent) { 239 s0 = -extent; 240 squareDistance = s0 * (s0 - (2.0f) * tempS0) 241 + s1 * (s1 + (2.0f) * diffTestDot) 242 + lengthOfDiff; 243 } else if (tempS0 <= extent) { 244 s0 = tempS0; 245 squareDistance = -s0 * s0 + s1 246 * (s1 + (2.0f) * diffTestDot) 247 + lengthOfDiff; 248 } else { 249 s0 = extent; 250 tempS1 = -(negativeDirectionDot * s0 + diffTestDot); 251 if (tempS1 < -test.getExtent()) { 252 s1 = -test.getExtent(); 253 squareDistance = s1 254 * (s1 - (2.0f) * tempS1) + s0 255 * (s0 + (2.0f) * diffThisDot) 256 + lengthOfDiff; 257 } else if (tempS1 <= test.getExtent()) { 258 s1 = tempS1; 259 squareDistance = -s1 * s1 + s0 260 * (s0 + (2.0f) * diffThisDot) 261 + lengthOfDiff; 262 } else { 263 s1 = test.getExtent(); 264 squareDistance = s1 265 * (s1 - (2.0f) * tempS1) + s0 266 * (s0 + (2.0f) * diffThisDot) 267 + lengthOfDiff; 268 } 269 } 270 } 271 } else // region 8 (corner) 272 { 273 s1 = -test.getExtent(); 274 tempS0 = -(negativeDirectionDot * s1 + diffThisDot); 275 if (tempS0 < -extent) { 276 s0 = -extent; 277 squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1 278 * (s1 + (2.0f) * diffTestDot) 279 + lengthOfDiff; 280 } else if (tempS0 <= extent) { 281 s0 = tempS0; 282 squareDistance = -s0 * s0 + s1 283 * (s1 + (2.0f) * diffTestDot) 284 + lengthOfDiff; 285 } else { 286 s0 = extent; 287 tempS1 = -(negativeDirectionDot * s0 + diffTestDot); 288 if (tempS1 > test.getExtent()) { 289 s1 = test.getExtent(); 290 squareDistance = s1 * (s1 - (2.0f) * tempS1) 291 + s0 * (s0 + (2.0f) * diffThisDot) 292 + lengthOfDiff; 293 } else if (tempS1 >= -test.getExtent()) { 294 s1 = tempS1; 295 squareDistance = -s1 * s1 + s0 296 * (s0 + (2.0f) * diffThisDot) 297 + lengthOfDiff; 298 } else { 299 s1 = -test.getExtent(); 300 squareDistance = s1 * (s1 - (2.0f) * tempS1) 301 + s0 * (s0 + (2.0f) * diffThisDot) 302 + lengthOfDiff; 303 } 304 } 305 } 306 } 307 } else { 308 if (s1 >= -extentDeterminant1) { 309 if (s1 <= extentDeterminant1) // region 5 (side) 310 { 311 s0 = -extent; 312 tempS1 = -(negativeDirectionDot * s0 + diffTestDot); 313 if (tempS1 < -test.getExtent()) { 314 s1 = -test.getExtent(); 315 squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0 316 * (s0 + (2.0f) * diffThisDot) 317 + lengthOfDiff; 318 } else if (tempS1 <= test.getExtent()) { 319 s1 = tempS1; 320 squareDistance = -s1 * s1 + s0 321 * (s0 + (2.0f) * diffThisDot) 322 + lengthOfDiff; 323 } else { 324 s1 = test.getExtent(); 325 squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0 326 * (s0 + (2.0f) * diffThisDot) 327 + lengthOfDiff; 328 } 329 } else // region 4 (corner) 330 { 331 s1 = test.getExtent(); 332 tempS0 = -(negativeDirectionDot * s1 + diffThisDot); 333 if (tempS0 > extent) { 334 s0 = extent; 335 squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1 336 * (s1 + (2.0f) * diffTestDot) 337 + lengthOfDiff; 338 } else if (tempS0 >= -extent) { 339 s0 = tempS0; 340 squareDistance = -s0 * s0 + s1 341 * (s1 + (2.0f) * diffTestDot) 342 + lengthOfDiff; 343 } else { 344 s0 = -extent; 345 tempS1 = -(negativeDirectionDot * s0 + diffTestDot); 346 if (tempS1 < -test.getExtent()) { 347 s1 = -test.getExtent(); 348 squareDistance = s1 * (s1 - (2.0f) * tempS1) 349 + s0 * (s0 + (2.0f) * diffThisDot) 350 + lengthOfDiff; 351 } else if (tempS1 <= test.getExtent()) { 352 s1 = tempS1; 353 squareDistance = -s1 * s1 + s0 354 * (s0 + (2.0f) * diffThisDot) 355 + lengthOfDiff; 356 } else { 357 s1 = test.getExtent(); 358 squareDistance = s1 * (s1 - (2.0f) * tempS1) 359 + s0 * (s0 + (2.0f) * diffThisDot) 360 + lengthOfDiff; 361 } 362 } 363 } 364 } else // region 6 (corner) 365 { 366 s1 = -test.getExtent(); 367 tempS0 = -(negativeDirectionDot * s1 + diffThisDot); 368 if (tempS0 > extent) { 369 s0 = extent; 370 squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1 371 * (s1 + (2.0f) * diffTestDot) + lengthOfDiff; 372 } else if (tempS0 >= -extent) { 373 s0 = tempS0; 374 squareDistance = -s0 * s0 + s1 375 * (s1 + (2.0f) * diffTestDot) + lengthOfDiff; 376 } else { 377 s0 = -extent; 378 tempS1 = -(negativeDirectionDot * s0 + diffTestDot); 379 if (tempS1 < -test.getExtent()) { 380 s1 = -test.getExtent(); 381 squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0 382 * (s0 + (2.0f) * diffThisDot) 383 + lengthOfDiff; 384 } else if (tempS1 <= test.getExtent()) { 385 s1 = tempS1; 386 squareDistance = -s1 * s1 + s0 387 * (s0 + (2.0f) * diffThisDot) 388 + lengthOfDiff; 389 } else { 390 s1 = test.getExtent(); 391 squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0 392 * (s0 + (2.0f) * diffThisDot) 393 + lengthOfDiff; 394 } 395 } 396 } 397 } 398 } else { 399 // The segments are parallel. The average b0 term is designed to 400 // ensure symmetry of the function. That is, dist(seg0,seg1) and 401 // dist(seg1,seg0) should produce the same number.get 402 float extentSum = extent + test.getExtent(); 403 float sign = (negativeDirectionDot > 0.0f ? -1.0f : 1.0f); 404 float averageB0 = (0.5f) * (diffThisDot - sign * diffTestDot); 405 float lambda = -averageB0; 406 if (lambda < -extentSum) { 407 lambda = -extentSum; 408 } else if (lambda > extentSum) { 409 lambda = extentSum; 410 } 411 412 squareDistance = lambda * (lambda + (2.0f) * averageB0) 413 + lengthOfDiff; 414 } 415 416 return FastMath.abs(squareDistance); 417 } 418 419 public float distanceSquared(Ray r) { 420 Vector3f kDiff = r.getOrigin().subtract(origin); 421 float fA01 = -r.getDirection().dot(direction); 422 float fB0 = kDiff.dot(r.getDirection()); 423 float fB1 = -kDiff.dot(direction); 424 float fC = kDiff.lengthSquared(); 425 float fDet = FastMath.abs(1.0f - fA01 * fA01); 426 float fS0, fS1, fSqrDist, fExtDet; 427 428 if (fDet >= FastMath.FLT_EPSILON) { 429 // The ray and segment are not parallel. 430 fS0 = fA01 * fB1 - fB0; 431 fS1 = fA01 * fB0 - fB1; 432 fExtDet = extent * fDet; 433 434 if (fS0 >= (float) 0.0) { 435 if (fS1 >= -fExtDet) { 436 if (fS1 <= fExtDet) // region 0 437 { 438 // minimum at interior points of ray and segment 439 float fInvDet = ((float) 1.0) / fDet; 440 fS0 *= fInvDet; 441 fS1 *= fInvDet; 442 fSqrDist = fS0 443 * (fS0 + fA01 * fS1 + ((float) 2.0) * fB0) 444 + fS1 445 * (fA01 * fS0 + fS1 + ((float) 2.0) * fB1) + fC; 446 } else // region 1 447 { 448 fS1 = extent; 449 fS0 = -(fA01 * fS1 + fB0); 450 if (fS0 > (float) 0.0) { 451 fSqrDist = -fS0 * fS0 + fS1 452 * (fS1 + ((float) 2.0) * fB1) + fC; 453 } else { 454 fS0 = (float) 0.0; 455 fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC; 456 } 457 } 458 } else // region 5 459 { 460 fS1 = -extent; 461 fS0 = -(fA01 * fS1 + fB0); 462 if (fS0 > (float) 0.0) { 463 fSqrDist = -fS0 * fS0 + fS1 464 * (fS1 + ((float) 2.0) * fB1) + fC; 465 } else { 466 fS0 = (float) 0.0; 467 fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC; 468 } 469 } 470 } else { 471 if (fS1 <= -fExtDet) // region 4 472 { 473 fS0 = -(-fA01 * extent + fB0); 474 if (fS0 > (float) 0.0) { 475 fS1 = -extent; 476 fSqrDist = -fS0 * fS0 + fS1 477 * (fS1 + ((float) 2.0) * fB1) + fC; 478 } else { 479 fS0 = (float) 0.0; 480 fS1 = -fB1; 481 if (fS1 < -extent) { 482 fS1 = -extent; 483 } else if (fS1 > extent) { 484 fS1 = extent; 485 } 486 fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC; 487 } 488 } else if (fS1 <= fExtDet) // region 3 489 { 490 fS0 = (float) 0.0; 491 fS1 = -fB1; 492 if (fS1 < -extent) { 493 fS1 = -extent; 494 } else if (fS1 > extent) { 495 fS1 = extent; 496 } 497 fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC; 498 } else // region 2 499 { 500 fS0 = -(fA01 * extent + fB0); 501 if (fS0 > (float) 0.0) { 502 fS1 = extent; 503 fSqrDist = -fS0 * fS0 + fS1 504 * (fS1 + ((float) 2.0) * fB1) + fC; 505 } else { 506 fS0 = (float) 0.0; 507 fS1 = -fB1; 508 if (fS1 < -extent) { 509 fS1 = -extent; 510 } else if (fS1 > extent) { 511 fS1 = extent; 512 } 513 fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC; 514 } 515 } 516 } 517 } else { 518 // ray and segment are parallel 519 if (fA01 > (float) 0.0) { 520 // opposite direction vectors 521 fS1 = -extent; 522 } else { 523 // same direction vectors 524 fS1 = extent; 525 } 526 527 fS0 = -(fA01 * fS1 + fB0); 528 if (fS0 > (float) 0.0) { 529 fSqrDist = -fS0 * fS0 + fS1 * (fS1 + ((float) 2.0) * fB1) + fC; 530 } else { 531 fS0 = (float) 0.0; 532 fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC; 533 } 534 } 535 return FastMath.abs(fSqrDist); 536 } 537 538 public Vector3f getDirection() { 539 return direction; 540 } 541 542 public void setDirection(Vector3f direction) { 543 this.direction = direction; 544 } 545 546 public float getExtent() { 547 return extent; 548 } 549 550 public void setExtent(float extent) { 551 this.extent = extent; 552 } 553 554 public Vector3f getOrigin() { 555 return origin; 556 } 557 558 public void setOrigin(Vector3f origin) { 559 this.origin = origin; 560 } 561 562 // P+e*D 563 public Vector3f getPositiveEnd(Vector3f store) { 564 if (store == null) { 565 store = new Vector3f(); 566 } 567 return origin.add((direction.mult(extent, store)), store); 568 } 569 570 // P-e*D 571 public Vector3f getNegativeEnd(Vector3f store) { 572 if (store == null) { 573 store = new Vector3f(); 574 } 575 return origin.subtract((direction.mult(extent, store)), store); 576 } 577 578 public void write(JmeExporter e) throws IOException { 579 OutputCapsule capsule = e.getCapsule(this); 580 capsule.write(origin, "origin", Vector3f.ZERO); 581 capsule.write(direction, "direction", Vector3f.ZERO); 582 capsule.write(extent, "extent", 0); 583 } 584 585 public void read(JmeImporter e) throws IOException { 586 InputCapsule capsule = e.getCapsule(this); 587 origin = (Vector3f) capsule.readSavable("origin", Vector3f.ZERO.clone()); 588 direction = (Vector3f) capsule.readSavable("direction", Vector3f.ZERO.clone()); 589 extent = capsule.readFloat("extent", 0); 590 } 591 592 @Override 593 public LineSegment clone() { 594 try { 595 LineSegment segment = (LineSegment) super.clone(); 596 segment.direction = direction.clone(); 597 segment.origin = origin.clone(); 598 return segment; 599 } catch (CloneNotSupportedException e) { 600 throw new AssertionError(); 601 } 602 } 603 604 /** 605 * <p>Evaluates whether a given point is contained within the axis aligned bounding box 606 * that contains this LineSegment.</p><p>This function is float error aware.</p> 607 */ 608 public boolean isPointInsideBounds(Vector3f point) { 609 return isPointInsideBounds(point, Float.MIN_VALUE); 610 } 611 612 /** 613 * <p>Evaluates whether a given point is contained within the axis aligned bounding box 614 * that contains this LineSegment.</p><p>This function accepts an error parameter, which 615 * is added to the extent of the bounding box.</p> 616 */ 617 public boolean isPointInsideBounds(Vector3f point, float error) { 618 619 if (FastMath.abs(point.x - origin.x) > FastMath.abs(direction.x * extent) + error) { 620 return false; 621 } 622 if (FastMath.abs(point.y - origin.y) > FastMath.abs(direction.y * extent) + error) { 623 return false; 624 } 625 if (FastMath.abs(point.z - origin.z) > FastMath.abs(direction.z * extent) + error) { 626 return false; 627 } 628 629 return true; 630 } 631} 632