├── .gitignore ├── .poggit.yml ├── CHANGELOG.md ├── LICENSE ├── README.md └── libgeom ├── src └── sofe │ └── libgeom │ ├── LazyStreamsShape.php │ ├── LibgeomMathUtils.php │ ├── Shape.php │ ├── SoftLevelStorage.php │ ├── UnsupportedOperationException.php │ ├── io │ ├── LibgeomBigEndianDataReader.php │ ├── LibgeomBigEndianDataWriter.php │ ├── LibgeomDataReader.php │ ├── LibgeomDataWriter.php │ ├── LibgeomLittleEndianDataReader.php │ └── LibgeomLittleEndianDataWriter.php │ └── shapes │ ├── CircularFrustumShape-draft-1.png │ ├── CircularFrustumShape.php │ ├── CuboidShape.php │ ├── EllipsoidShape.php │ ├── PointLineDistance-draft.png │ ├── PolygonFrustumShape-draft-1.png │ └── PolygonFrustumShape.php └── virion.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /.poggit.yml: -------------------------------------------------------------------------------- 1 | --- # https://poggit.pmmp.io/ci/BlockHorizons/libgeom/~ 2 | projects: 3 | libgeom: 4 | path: libgeom 5 | type: library 6 | model: virion 7 | libs: 8 | - src: SOF3/toomuchbuffer/toomuchbuffer 9 | version: "^1.0" 10 | ... 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | Note that changes backward-incompatibile for subclasses extending `Shape` will 9 | **not** lead to a bump in the major version. 10 | 11 | ## [Unreleased] (2.0.0) 12 | ### Added 13 | - `Shape::marginalDistance()` (formerly in `LazyStreamsShape`) 14 | - `Libgeom(Big|Little)EndianData(Writer|Reader)` for saving/loading shapes 15 | - Uses toomuchbuffer as library 16 | - `Shape::getChunksInvolved()` 17 | - `Shape::getMinX()` etc. 18 | - Already implemented in `LazyStreamsShape` 19 | - shape saving through `Shape::fromBinary()` and `Shape::toBinary()` 20 | - Shapes can now be constructed into an incomplete state, as detected by `isComplete()` 21 | - Added `Shape::getCenter()` 22 | - Added `Shape::getEstimatedSurfaceSize()` 23 | 24 | ### Changed 25 | - virion.yml limits the accepted API versions to `3.0.0-ALPHA6` and `3.0.0-ALPHA7` 26 | - `BlockStream` is replaced by [`Generator`](https://php.net/generator) with method signature changes 27 | - A major namespace refactor from `sofe\libgeom\shape` to `sofe\libgeom`. Shape implementations moved to `sofe\libgeom\shapes` (note the plural "shapes"). 28 | - `SoftLevelStorage` now only saves the level folder name, not the direct reference to the level itself 29 | - Added `$server` parameter to `SoftLevelStorage::isValid()` 30 | - The constructor argument order of some shapes 31 | - Shape objects are now thread-safe (by specification). 32 | - `getShallowStream()` was a typo, changed to `getHollowStream()`. 33 | 34 | ### Removed 35 | - Unused classes 36 | - `BlockStream`, `BatchBlockStream` and their subclasses 37 | - `EndOfBlockStreamException` 38 | 39 | ## 1.0.0 2017-07-20 40 | ### Removed 41 | - The whole `blockop` package. 42 | 43 | [Unreleased]: https://github.com/BlockHorizons/libgeom/compare/v1.0.0...HEAD 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libgeom 2 | Virion for geometric operations in PocketMine plugins 3 | 4 | ## v0 5 | In v0, there is a blockop package that is broken and only supports synchronous operation queuing 6 | 7 | ## v1 8 | In v1, the blockop package is removed. Only the shape package is available. 9 | -------------------------------------------------------------------------------- /libgeom/src/sofe/libgeom/LazyStreamsShape.php: -------------------------------------------------------------------------------- 1 | minX, $this->minY, $this->minZ, $this->maxX, $this->maxY, $this->maxZ); 31 | } 32 | 33 | public function getMinX() : int{ 34 | if(!isset($this->minX)){ 35 | $this->minX = (int) floor($this->lazyGetMinX()); 36 | } 37 | return $this->minX; 38 | } 39 | 40 | public function getMinY() : int{ 41 | if(!isset($this->minY)){ 42 | $this->minY = (int) floor($this->lazyGetMinY()); 43 | } 44 | return $this->minY; 45 | } 46 | 47 | public function getMinZ() : int{ 48 | if(!isset($this->minZ)){ 49 | $this->minZ = (int) floor($this->lazyGetMinZ()); 50 | } 51 | return $this->minZ; 52 | } 53 | 54 | public function getMaxX() : int{ 55 | if(!isset($this->maxX)){ 56 | $this->maxX = (int) ceil($this->lazyGetMaxX()); 57 | } 58 | return $this->maxX; 59 | } 60 | 61 | public function getMaxY() : int{ 62 | if(!isset($this->maxY)){ 63 | $this->maxY = (int) ceil($this->lazyGetMaxY()); 64 | } 65 | return $this->maxY; 66 | } 67 | 68 | public function getMaxZ() : int{ 69 | if(!isset($this->maxZ)){ 70 | $this->maxZ = (int) ceil($this->lazyGetMaxZ()); 71 | } 72 | return $this->maxZ; 73 | } 74 | 75 | protected abstract function lazyGetMinX() : float; 76 | 77 | protected abstract function lazyGetMinY() : float; 78 | 79 | protected abstract function lazyGetMinZ() : float; 80 | 81 | protected abstract function lazyGetMaxX() : float; 82 | 83 | protected abstract function lazyGetMaxY() : float; 84 | 85 | protected abstract function lazyGetMaxZ() : float; 86 | 87 | 88 | public function getSolidStream(Vector3 $vector) : \Generator{ 89 | for($vector->x = $this->getMinX(); $vector->x <= $this->getMaxX(); ++$vector->x){ 90 | for($vector->y = $this->getMinY(); $vector->y <= $this->getMaxY(); ++$vector->y){ 91 | for($vector->z = $this->getMinZ(); $vector->z <= $this->getMaxZ(); ++$vector->z){ 92 | if($this->isInside($vector)){ 93 | yield true; 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | public function getHollowStream(Vector3 $vector, float $padding, float $margin) : \Generator{ 101 | for($vector->x = $this->getMinX(); $vector->x <= $this->getMaxX(); ++$vector->x){ 102 | for($vector->y = $this->getMinY(); $vector->y <= $this->getMaxY(); ++$vector->y){ 103 | for($vector->z = $this->getMinZ(); $vector->z <= $this->getMaxZ(); ++$vector->z){ 104 | $dist = $this->marginalDistance($vector); 105 | if(-$padding <= $dist && $dist <= $margin - 1){ 106 | yield true; 107 | }else{ 108 | yield false; 109 | } 110 | } 111 | } 112 | } 113 | } 114 | 115 | public function getMaxHollowSize(float $padding, float $margin) : int{ 116 | if(!isset($this->maxHollowSize[$padding . ":" . $margin])){ 117 | $this->maxHollowSize[$padding . ":" . $margin] = $this->lazyGetMaxHollowSize($padding, $margin); 118 | } 119 | return $this->maxHollowSize[$padding . ":" . $margin]; 120 | } 121 | 122 | protected abstract function lazyGetMaxHollowSize(float $padding, float $margin) : int; 123 | 124 | public function getChunksInvolved() : array{ 125 | $chunks = []; 126 | for($X = $this->getMinX() >> 4; $X <= $this->getMaxX() >> 4; ++$X){ 127 | for($Z = $this->getMinZ() >> 4; $Z <= $this->getMaxZ() >> 4; ++$Z){ 128 | $chunks[] = Level::chunkHash($X, $Z); 129 | } 130 | } 131 | return $chunks; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /libgeom/src/sofe/libgeom/LibgeomMathUtils.php: -------------------------------------------------------------------------------- 1 | subtract($points[1]); 28 | $u = $points[2]->subtract($points[1])->cross($v01)->normalize(); 29 | for($i = 2, $iMax = count($points); $i <= $iMax; ++$i){ 30 | $cross = $points[$i - 1]->subtract($points[$i === count($points) ? 0 : $i])->cross($v01)->normalize(); 31 | if(!LibgeomMathUtils::areVectorsAlmostParallel($cross, $u)){ 32 | return false; 33 | } 34 | } 35 | return true; 36 | } 37 | 38 | public static function areVectorsAlmostParallel(Vector3 $v1, Vector3 $v2) : bool{ 39 | $rx = $v1->x / $v2->x; 40 | $ry = $v1->y / $v2->y; 41 | $rz = $v1->z / $v2->z; 42 | return LibgeomMathUtils::areFloatsAlmostEqual($rx, $ry) && LibgeomMathUtils::areFloatsAlmostEqual($rx, $rz); 43 | } 44 | 45 | public static function areFloatsAlmostEqual(float $f1, float $f2, float $epsilon = null) : bool{ 46 | if($f1 === $f2){ 47 | return false; 48 | } 49 | if(($f1 > 0) !== ($f2 > 0)){ 50 | return false; 51 | } 52 | if($epsilon === null){ 53 | $epsilon = max($f1, $f2) * 1e-15; // not accurate, but enough here 54 | } 55 | return $f1 - $f2 < $epsilon && $f2 - $f1 < $epsilon; 56 | } 57 | 58 | public static function evalFrustumVolume(float $A, float $a, float $hh) : float{ 59 | if($A < $a){ 60 | return self::evalFrustumVolume($a, $A, $hh); 61 | } 62 | $resultCount = LibgeomMathUtils::solveQuadratic(($a - $A) / $A, 2 * $a * $hh / $A, $a / $A * $hh * $hh, $h); // x2 should be negative 63 | assert($resultCount === 2); 64 | $H = $h + $hh; 65 | return $A / 3 * ($H - $h ** 3 / $H / $H); 66 | } 67 | 68 | /** 69 | * Solve the equation ax^2 + bx + c = 0 70 | * 71 | * @param float $a 72 | * @param float $b 73 | * @param float $c 74 | * @param float|null &$x1 the greater root of x 75 | * @param float|null &$x2 the smaller root of x 76 | * 77 | * @return int the number of distinct real roots 78 | */ 79 | public static function solveQuadratic(float $a, float $b, float $c, float &$x1 = null, float &$x2 = null) : int{ 80 | $d = $b * $b - 4 * $a * $c; 81 | if($d < 0){ 82 | return 0; 83 | } 84 | $x1 = (-$b + sqrt($d)) / 2 / $a; 85 | $x2 = (-$b - sqrt($d)) / 2 / $a; 86 | return $d > 0 ? 2 : 1; 87 | } 88 | 89 | public static function doesRayIntersectSegment(Vector3 $rayOrigin, Vector3 $rayDir, Vector3 $segStart, Vector3 $segEnd) : bool{ 90 | // doing operations assuming they're all on the same plane 91 | // hence, one of the three dimensions can be neglected, unless the plane is perpendicular to this axis 92 | // let $rayOrigin + t ($rayDir) = $segStart + u ($segEnd - $segStart) 93 | 94 | LibgeomMathUtils::solveAVBW($rayOrigin, $rayDir, $segStart, $segEnd->subtract($segStart), $t, $u); 95 | return $t > 0 and 0 <= $u and $u <= 1; 96 | } 97 | 98 | public static function doSegmentsIntersect(Vector3 $a, Vector3 $b, Vector3 $c, Vector3 $d) : bool{ 99 | LibgeomMathUtils::solveAVBW($a, $b->subtract($a), $c, $d->subtract($c), $t, $u); 100 | return 0 <= $t and $t <= 1 and 0 <= $u and $u <= 1; 101 | } 102 | 103 | /** 104 | * Solve the vector equation 105 | *
a + t v = b + u w
106 | 	 *
107 | 	 * @param Vector3    $a position vector
108 | 	 * @param Vector3    $v relative vector
109 | 	 * @param Vector3    $b position vector
110 | 	 * @param Vector3    $w relative vector
111 | 	 * @param float|null $t
112 | 	 * @param float|null $u
113 | 	 * @param int        $recursion
114 | 	 */
115 | 	protected static function solveAVBW(Vector3 $a, Vector3 $v, Vector3 $b, Vector3 $w, float &$t = null, float &$u = null, int $recursion = 0){
116 | 		$base = (float) ($v->x * $w->y - $v->y * $w->x);
117 | 		if($base === 0){
118 | 			if($recursion === 2){
119 | 				throw new \InvalidArgumentException("Arguments are not coplanar");
120 | 			}
121 | 			LibgeomMathUtils::solveAVBW(LibgeomMathUtils::shiftVector($a), LibgeomMathUtils::shiftVector($v), LibgeomMathUtils::shiftVector($b), LibgeomMathUtils::shiftVector($w), $t, $u, $recursion + 1);
122 | 		}
123 | 		$u = (($a->y - $b->y) * $v->x - ($a->x - $b->x) * $v->y) / $base;
124 | 		/** @noinspection CallableParameterUseCaseInTypeContextInspection */
125 | 		$t = ($b->x - $a->x + $u * $w->x) / $v->x;
126 | 	}
127 | 
128 | 	public static function shiftVector(Vector3 $vector) : Vector3{
129 | 		return new Vector3($vector->y, $vector->z, $vector->x);
130 | 	}
131 | 
132 | 	public static function getTriangleArea(Vector3 $a, Vector3 $b, Vector3 $c) : float{
133 | 		$A = $b->distance($c);
134 | 		$B = $c->distance($a);
135 | 		$C = $a->distance($b);
136 | 		$s = ($A + $B + $C) / 2;
137 | 		return sqrt($s * ($s - $A) * ($s - $B) * ($s - $C));
138 | 	}
139 | }
140 | 


--------------------------------------------------------------------------------
/libgeom/src/sofe/libgeom/Shape.php:
--------------------------------------------------------------------------------
  1 | estimatedSize ?? ($this->estimatedSize = $this->estimateSize());
 46 | 	}
 47 | 
 48 | 	protected abstract function estimateSize() : int;
 49 | 
 50 | 	/**
 51 | 	 * Returns the estimated number of blocks from a hollow stream
 52 | 	 *
 53 | 	 * @param float $padding
 54 | 	 * @param float $margin
 55 | 	 *
 56 | 	 * @return int
 57 | 	 */
 58 | 	public abstract function getEstimatedSurfaceSize(float $padding, float $margin) : int;
 59 | 
 60 | 	/**
 61 | 	 * Returns a generator that yields all integer coordinates inside this shape.
 62 | 	 *
 63 | 	 * Pass a Vector3 (or its subclasses) object into this method. The properties of this Vector3 will be updated during
 64 | 	 * generation. When true is yielded, the Vector3 is currently inside this shape. When false is yielded, the Vector3 MAY OR
 65 | 	 * MAY NOT be inside this shape, but the generator pauses execution to allow the thread to sleep a while before continuing
 66 | 	 * the generation.
 67 | 	 *
 68 | 	 * A PluginTask can be implemented like this to process this shape for approximately 1 millisecond every tick:
 69 | 	 *
 70 | 	 * ```php
 71 | 	 * class Clazz extends PluginTask{
 72 | 	 *     private $temporalVector;
 73 | 	 *     private $generator;
 74 | 	 *
 75 | 	 *     public function __construct(Shape $shape){
 76 | 	 *         // delegation to parent constructor omitted
 77 | 	 *         $this->temporalVector = new Vector3;
 78 | 	 *         $this->generator = $shape->getSolidStream($this->temporalVector);
 79 | 	 *         $this->onRun(0);
 80 | 	 *     }
 81 | 	 *
 82 | 	 *     public function onRun(int $t){
 83 | 	 *         $start = microtime(true);
 84 | 	 *         while(microtime(true) - $start < 1.0e-3){
 85 | 	 *             $current = $this->generator->current();
 86 | 	 *             if($current === null){ // generator finished
 87 | 	 *                 return;
 88 | 	 *             }elseif($current === true){ // $this->temporalVector is currently valid for processing
 89 | 	 *                 process($this->temporalVector);
 90 | 	 *             } // else, $current must === false, and nothing should be done with $this->temporalVector (it's at undefined state)
 91 | 	 *             $current->next();
 92 | 	 *         }
 93 | 	 *         $this->getOwner()->scheduleDelayedTask($this, 1);
 94 | 	 *     }
 95 | 	 * }
 96 | 	 * ```
 97 | 	 *
 98 | 	 * @param Vector3 $vector
 99 | 	 *
100 | 	 * @return \Generator a generator that yields a boolean every time
101 | 	 */
102 | 	public abstract function getSolidStream(Vector3 $vector) : \Generator;
103 | 
104 | 	/**
105 | 	 * Returns an iterator that iterates through all blocks on the surface of this shape.
106 | 	 *
107 | 	 * @see Shape::getSolidStream() on how to handle the yield values of the generator.
108 | 	 *
109 | 	 * @param Vector3 $vector
110 | 	 * @param float   $padding The thickness of the "surface" inside the shape
111 | 	 * @param float   $margin  The thickness of the "surface" outside the shape
112 | 	 *
113 | 	 * @return \Generator a generator that yields a boolean every time
114 | 	 */
115 | 	public abstract function getHollowStream(Vector3 $vector, float $padding, float $margin) : \Generator;
116 | 
117 | 	/**
118 | 	 * Returns the vector's distance from the nearest point on the surface of the shape.
119 | 	 *
120 | 	 * The return value is negative if it is inside the vector, positive if outside the shape and zero if on the surface of the
121 | 	 * shape.
122 | 	 *
123 | 	 * @param Vector3 $vector
124 | 	 *
125 | 	 * @return float
126 | 	 */
127 | 	public abstract function marginalDistance(Vector3 $vector) : float;
128 | 
129 | 	/**
130 | 	 * Returns the center point of the shape.
131 | 	 *
132 | 	 *
133 | 	 *
134 | 	 * @return Vector3|null
135 | 	 */
136 | 	public function getCenter(){
137 | 		if(!$this->isComplete()){
138 | 			return null;
139 | 		}
140 | 		return $this->centerCache ?? ($this->centerCache = $this->lazyGetCenter());
141 | 	}
142 | 
143 | 	protected abstract function lazyGetCenter() : Vector3;
144 | 
145 | 	/**
146 | 	 * Returns an array of the Level::chunkHash()s of the chunks involved with this shape
147 | 	 *
148 | 	 * @return int[]
149 | 	 */
150 | 	public abstract function getChunksInvolved() : array;
151 | 
152 | 	/**
153 | 	 * By calling  T::fromBinary(), a new T instance should be created.
154 | 	 *
155 | 	 * This method must be overridden in all non-abstract subclasses.
156 | 	 *
157 | 	 * @param Server              $server
158 | 	 * @param LibgeomDataReader $stream
159 | 	 *
160 | 	 * @return Shape
161 | 	 * @throws \Exception
162 | 	 */
163 | 	public static function fromBinary(/** @noinspection PhpUnusedParameterInspection */
164 | 		Server $server, LibgeomDataReader $stream) : Shape{
165 | 		throw new \Exception("Unimplemented method " . static::class . "::fromBinary(\$binary)");
166 | 	}
167 | 
168 | 	public abstract function toBinary(LibgeomDataWriter $stream);
169 | 
170 | 	public abstract function getMinX() : int;
171 | 
172 | 	public abstract function getMinY() : int;
173 | 
174 | 	public abstract function getMinZ() : int;
175 | 
176 | 	public abstract function getMaxX() : int;
177 | 
178 | 	public abstract function getMaxY() : int;
179 | 
180 | 	public abstract function getMaxZ() : int;
181 | 
182 | 	public abstract function isComplete() : bool;
183 | 
184 | 	protected function onDimenChanged(){
185 | 		unset($this->estimatedSize, $this->centerCache);
186 | 	}
187 | }
188 | 


--------------------------------------------------------------------------------
/libgeom/src/sofe/libgeom/SoftLevelStorage.php:
--------------------------------------------------------------------------------
 1 | levelName === null){
29 | 			return null;
30 | 		}
31 | 		return $server->getLevelByName($this->levelName);
32 | 	}
33 | 
34 | 	public function getLevelName() : string{
35 | 		return $this->levelName;
36 | 	}
37 | 
38 | 	public function setLevel(Level $level){
39 | 		if($level !== null and $level->isClosed()){
40 | 			throw new \InvalidArgumentException("Cannot use unloaded level");
41 | 		}
42 | 		$this->levelName = $level->getFolderName();
43 | 		return $this;
44 | 	}
45 | 
46 | 	public function isValid(Server $server) : bool{
47 | 		return $server->getLevelByName($this->levelName) !== null;
48 | 	}
49 | }
50 | 


--------------------------------------------------------------------------------
/libgeom/src/sofe/libgeom/UnsupportedOperationException.php:
--------------------------------------------------------------------------------
 1 | stream->read($this->readVarInt(false));
35 | 	}
36 | 
37 | 	public function readBlockPosition(&$x, &$y, &$z, bool $signed = false){
38 | 		$x = $this->readVarInt();
39 | 		$y = $this->readVarInt($signed);
40 | 		$z = $this->readVarInt();
41 | 	}
42 | 
43 | 	/**
44 | 	 * @param float &$x
45 | 	 * @param float &$y
46 | 	 * @param float &$z
47 | 	 */
48 | 	public function readVector3f(&$x, &$y, &$z){
49 | 		$x = round($this->readFloat(), 4);
50 | 		$y = round($this->readFloat(), 4);
51 | 		$z = round($this->readFloat(), 4);
52 | 	}
53 | }
54 | 


--------------------------------------------------------------------------------
/libgeom/src/sofe/libgeom/io/LibgeomBigEndianDataWriter.php:
--------------------------------------------------------------------------------
 1 | writeVarInt(strlen($string), false);
35 | 		$this->stream->write($string);
36 | 	}
37 | 
38 | 	public function writeBlockPosition(int $x, int $y, int $z, bool $signed = false){
39 | 		$this->writeVarInt($x);
40 | 		$this->writeVarInt($y, $signed);
41 | 		$this->writeVarInt($z);
42 | 	}
43 | 
44 | 	public function writeVector3f(float $x, float $y, float $z){
45 | 		$this->writeFloat($x);
46 | 		$this->writeFloat($y);
47 | 		$this->writeFloat($z);
48 | 	}
49 | }
50 | 


--------------------------------------------------------------------------------
/libgeom/src/sofe/libgeom/io/LibgeomDataReader.php:
--------------------------------------------------------------------------------
 1 | stream->read($this->readVarInt(false));
35 | 	}
36 | 
37 | 	public function readBlockPosition(&$x, &$y, &$z, bool $signed = false){
38 | 		$x = $this->readVarInt();
39 | 		$y = $this->readVarInt($signed);
40 | 		$z = $this->readVarInt();
41 | 	}
42 | 
43 | 	/**
44 | 	 * @param float &$x
45 | 	 * @param float &$y
46 | 	 * @param float &$z
47 | 	 */
48 | 	public function readVector3f(&$x, &$y, &$z){
49 | 		$x = round($this->readFloat(), 4);
50 | 		$y = round($this->readFloat(),4);
51 | 		$z = round($this->readFloat(),4);
52 | 	}
53 | }
54 | 


--------------------------------------------------------------------------------
/libgeom/src/sofe/libgeom/io/LibgeomLittleEndianDataWriter.php:
--------------------------------------------------------------------------------
 1 | writeVarInt(strlen($string), false);
35 | 		$this->stream->write($string);
36 | 	}
37 | 
38 | 	public function writeBlockPosition(int $x, int $y, int $z, bool $signed = false){
39 | 		$this->writeVarInt($x);
40 | 		$this->writeVarInt($y, $signed);
41 | 		$this->writeVarInt($z);
42 | 	}
43 | 
44 | 	public function writeVector3f(float $x, float $y, float $z){
45 | 		$this->writeFloat($x);
46 | 		$this->writeFloat($y);
47 | 		$this->writeFloat($z);
48 | 	}
49 | }
50 | 


--------------------------------------------------------------------------------
/libgeom/src/sofe/libgeom/shapes/CircularFrustumShape-draft-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BlockHorizons/libgeom/55d6aa5fccb239eecd7c7ea50bd7f3bd3e359fb2/libgeom/src/sofe/libgeom/shapes/CircularFrustumShape-draft-1.png


--------------------------------------------------------------------------------
/libgeom/src/sofe/libgeom/shapes/CircularFrustumShape.php:
--------------------------------------------------------------------------------
  1 | skewed, oblique and elliptical.
 31 |  *
 32 |  * The axis of the shape refers to the line passing through the center of the base and the center of the top (for
 33 |  * cylinders and frustums), or the center of the base and the tip (for cones).
 34 |  * The normal of the shape refers to the line perpendicular to the plane of the base of the shape.
 35 |  *
 36 |  * 

Flexibility and limitations

37 | * 38 | * The shape can be oblique, i.e. the axis of the shape need not be parallel or perpendicular to the X/Y/Z axes. 39 | * 40 | * The shape can be skewed, i.e. the axis of the shape need not be perpendicular to the plane of the base. 41 | * 42 | * The shape can be elliptical, i.e. the base can be an ellipse (or a circle, which is a subset of ellipse). As for the 43 | * top, it can be a point (a circle with zero radius) or an ellipse (or a circle). If it is an ellipse, The minor 44 | * radius of the top must be parallel to either the major or minor radius of the bottom. The ratio of 45 | * minor and major radii of the top ellipse may or may not be equal or reciprocal to that of the bottom ellipse. 46 | * However, for frustums and cylinders, the planes of the base and the top must be parallel. 47 | * 48 | *

Definition

49 | * 50 | * A FrustomShape is basically defined by its base and top centers. 51 | * 52 | * Then the left-right radii and the front-back radii of the top and base define the sizes of the top and base, where 53 | * the base radii must be positive but the top radii may be either both positive or both zero (for cones). 54 | * 55 | * Additionally, the orientation of the base and top ellipses is defined by the normal, which is a relative unit vector. This determines what is "horizontal" (parallel to base plane) and what is "vertical" (perpendicular to base plane) for 56 | * the FrustomShape. In addition, two relative unit vectors, rightDir and frontDir, determine the left-right and 57 | * front-back directions of the FrustomShape. By the right-hand rule, normal cross rightDir = frontDir, where normal 58 | * points upwards. 59 | */ 60 | class CircularFrustumShape extends LazyStreamsShape{ 61 | /** @var Vector3 */ 62 | private $base; 63 | /** @var Vector3|null */ 64 | private $top; 65 | /** @var float|null */ 66 | private $baseRightRadius, $baseFrontRadius, $topRightRadius, $topFrontRadius; 67 | /** @var Vector3|null */ 68 | private $normal, $rightDir, $frontDir; 69 | 70 | /** 71 | * @param Level $level the level that the shape is located in. 72 | * @param Vector3 $base the position vector representing the midpoint of the base ellipse 73 | * @param Vector3 $top the position vector representing the midpoint of the top ellipse 74 | * @param Vector3 $normal the relative vector representing the orientation of line perpendicular to the plane of both ellipses, will be automatically normalized 75 | * @param Vector3 $baseRightCircum the position vector representing the intersection of the right radius and the circumference of the base ellipse 76 | * @param float $baseFrontRadius the front-back radius of the base ellipse, must be positive, may be smaller than |$baseRightCircum-$base| 77 | * @param float $topRightRadius the left-right radius of the top ellipse, must be non-negative 78 | * @param float $topFrontRadius the front-back radius of the top ellipse, may be smaller than $topRightRadius but parallel to $baseFrontRadius, must be non-negative, must be zero if and only if $topRightRadius is zero 79 | */ 80 | public function __construct(Level $level, Vector3 $base, Vector3 $top = null, Vector3 $normal = null, Vector3 $baseRightCircum = null, float $baseFrontRadius = null, float $topRightRadius = null, float $topFrontRadius = null){ 81 | $this->setLevel($level); 82 | assert($normal === null or abs($normal->dot($base->subtract($baseRightCircum))) < 1e-10, "baseRightCircum-base is not perpendicular to the normal"); 83 | $this->base = $base !== null ? $base->asVector3() : null; 84 | $this->top = $top !== null ? $base->asVector3() : null; 85 | if($normal !== null){ 86 | /** @noinspection PhpUndefinedMethodInspection */ 87 | $this->normal = $normal->normalize(); 88 | if($baseRightCircum !== null){ 89 | $baseRightRadiusLine = $baseRightCircum->subtract($this->base); 90 | $this->rightDir = $baseRightRadiusLine->normalize(); 91 | $this->baseRightRadius = $baseRightRadiusLine->length(); 92 | assert($this->baseRightRadius > 0); 93 | assert($this->baseFrontRadius > 0); 94 | if($topRightRadius !== null and $topFrontRadius !== null){ 95 | assert(($topRightRadius <=> 0) === ($topFrontRadius <=> 0) and $topRightRadius >= 0); 96 | } 97 | $this->frontDir = $this->normal->cross($this->rightDir); 98 | } 99 | } 100 | $this->baseFrontRadius = $baseFrontRadius; 101 | $this->topRightRadius = $topRightRadius; 102 | $this->topFrontRadius = $topFrontRadius; 103 | } 104 | 105 | 106 | public function getBase() : Vector3{ 107 | return $this->base; 108 | } 109 | 110 | public function setBase(Vector3 $base) : CircularFrustumShape{ 111 | $this->base = $base !== null ? $base->asVector3() : null; 112 | $this->onDimenChanged(); 113 | return $this; 114 | } 115 | 116 | public function getTop(){ 117 | return $this->top; 118 | } 119 | 120 | public function setTop(Vector3 $top = null) : CircularFrustumShape{ 121 | $this->top = $top !== null ? $top->asVector3() : null; 122 | $this->onDimenChanged(); 123 | return $this; 124 | } 125 | 126 | public function getNormal(){ 127 | return $this->normal; 128 | } 129 | 130 | public function getRightDir(){ 131 | return $this->rightDir; 132 | } 133 | 134 | public function getFrontDir(){ 135 | return $this->frontDir; 136 | } 137 | 138 | public function unsetDirections() : CircularFrustumShape{ 139 | unset($this->normal, $this->rightDir, $this->frontDir); 140 | $this->onDimenChanged(); 141 | return $this; 142 | } 143 | 144 | public function setNormal(Vector3 $normal) : CircularFrustumShape{ 145 | if($this->frontDir !== null and $this->rightDir !== null){ 146 | throw new \RuntimeException("Call unsetDirections() before calling setNormal()"); 147 | } 148 | $this->normal = $normal->normalize(); 149 | if(isset($this->frontDir)){ 150 | $this->rightDir = $this->frontDir->cross($this->normal); 151 | }elseif(isset($this->rightDir)){ 152 | $this->frontDir = $this->normal->cross($this->rightDir); 153 | } 154 | $this->onDimenChanged(); 155 | return $this; 156 | } 157 | 158 | public function setRightDir(Vector3 $rightDir) : CircularFrustumShape{ 159 | if($this->frontDir !== null and $this->normal !== null){ 160 | throw new \RuntimeException("Call unsetDirections() before calling setRightDir()"); 161 | } 162 | $this->rightDir = $rightDir; 163 | if(isset($this->normal)){ 164 | $this->frontDir = $this->normal->cross($this->rightDir); 165 | }elseif(isset($this->frontDir)){ 166 | $this->normal = $this->rightDir->cross($this->frontDir); 167 | } 168 | $this->onDimenChanged(); 169 | return $this; 170 | } 171 | 172 | public function setFrontDir(Vector3 $frontDir) : CircularFrustumShape{ 173 | if($this->rightDir !== null and $this->normal !== null){ 174 | throw new \RuntimeException("Call unsetDirections() before calling setRightDir()"); 175 | } 176 | $this->frontDir = $frontDir; 177 | 178 | if(isset($this->rightDir)){ 179 | $this->normal = $this->rightDir->cross($this->frontDir); 180 | }elseif(isset($this->normal)){ 181 | $this->rightDir = $this->frontDir->cross($this->normal); 182 | } 183 | $this->onDimenChanged(); 184 | return $this; 185 | } 186 | 187 | public function rotate(Vector3 $normal, Vector3 $rightDir) : CircularFrustumShape{ 188 | assert(abs($normal->dot($rightDir)) < 1e-10); 189 | $this->normal = $normal->normalize(); 190 | $this->rightDir = $rightDir->normalize(); 191 | $this->frontDir = $this->normal->cross($this->rightDir); 192 | $this->onDimenChanged(); 193 | return $this; 194 | } 195 | 196 | public function getBaseRightRadius(){ 197 | return $this->baseRightRadius; 198 | } 199 | 200 | public function setBaseRightRadius(float $radius = null) : CircularFrustumShape{ 201 | $this->baseRightRadius = $radius; 202 | $this->onDimenChanged(); 203 | return $this; 204 | } 205 | 206 | public function getBaseFrontRadius(){ 207 | return $this->baseFrontRadius; 208 | } 209 | 210 | public function setBaseFrontRadius(float $radius = null) : CircularFrustumShape{ 211 | $this->baseFrontRadius = $radius; 212 | $this->onDimenChanged(); 213 | return $this; 214 | } 215 | 216 | public function getTopRightRadius(){ 217 | return $this->topRightRadius; 218 | } 219 | 220 | public function setTopRightRadius(float $radius = null) : CircularFrustumShape{ 221 | $this->topRightRadius = $radius; 222 | $this->onDimenChanged(); 223 | return $this; 224 | } 225 | 226 | public function getTopFrontRadius(){ 227 | return $this->topFrontRadius; 228 | } 229 | 230 | public function setTopFrontRadius(float $radius = null) : CircularFrustumShape{ 231 | $this->topFrontRadius = $radius; 232 | $this->onDimenChanged(); 233 | return $this; 234 | } 235 | 236 | public function isComplete() : bool{ 237 | return isset($this->top, $this->normal, $this->baseRightRadius, $this->baseFrontRadius, $this->topRightRadius, $this->topFrontRadius); 238 | } 239 | 240 | public function isInside(Vector3 $vector) : bool{ 241 | assert($this->isComplete()); 242 | 243 | // See draft-1 244 | 245 | // take the planes of ellipses as horizontal, base plane as altitude = 0 and top plane as altitude = 1 246 | // lambda is equivalent to the altitude of the position to test 247 | // so if lambda is not in the closed range [0, 1], the position to test is definitely beyond the frustum 248 | 249 | $n = $this->normal; 250 | $b = $this->top; 251 | $a = $this->base; 252 | $p = $vector; 253 | $lambda = $n->dot($p->subtract($a)) / $n->dot($b->subtract($a)); 254 | if($lambda < 0 || $lambda > 1){ 255 | return false; // taking 256 | } 257 | $q = $a->add($b->subtract($a)->multiply($lambda)); 258 | $d = $q->subtract($p); 259 | 260 | assert(((float) $d->dot($n)) === 0.0, "Position-axis difference should be parallel to the ellipses"); 261 | 262 | $rightProjection = abs($d->dot($this->rightDir)); // if negative, the point is on the left of the axis, so flip it 263 | $frontProjection = abs($d->dot($this->frontDir)); // if negative the point is on the back of the axis, so flip it 264 | 265 | $rightRadius = $this->baseRightRadius + ($this->topRightRadius - $this->baseRightRadius) * $lambda; 266 | $frontRadius = $this->baseFrontRadius + ($this->topFrontRadius - $this->baseFrontRadius) * $lambda; 267 | 268 | return ($rightProjection / $rightRadius) ** 2 + ($frontProjection / $frontRadius) ** 2 <= 1; 269 | } 270 | 271 | public function marginalDistance(Vector3 $vector) : float{ 272 | assert($this->isComplete()); 273 | 274 | $n = $this->normal; 275 | $b = $this->top; 276 | $a = $this->base; 277 | $p = $vector; 278 | $lambda = $n->dot($p->subtract($a)) / $n->dot($b->subtract($a)); 279 | 280 | $topDistance = $lambda - 1; 281 | $baseDistance = 0 - $lambda; 282 | $vertDistance = abs($topDistance) < abs($baseDistance) ? $topDistance : $baseDistance; 283 | 284 | $q = $a->add($b->subtract($a)->multiply($lambda)); 285 | $D = $p->subtract($q); 286 | 287 | assert(((float) $D->dot($n)) === 0, "Position-axis difference should be parallel to the ellipses"); 288 | 289 | $rightRadius = $this->baseRightRadius + ($this->topRightRadius - $this->baseRightRadius) * $lambda; 290 | $frontRadius = $this->baseFrontRadius + ($this->topFrontRadius - $this->baseFrontRadius) * $lambda; 291 | 292 | $angle = acos($D->dot($this->rightDir) / $D->length()); 293 | $shouldRadius = $rightRadius * $frontRadius / sqrt(($rightRadius * sin($angle)) ** 2 + ($frontRadius * cos($angle)) ** 2); 294 | $horizDistance = $D->length() - $shouldRadius; 295 | // FIXME The shortest distance from the wall of the frustum rather than the horizontal distance should be used. Right now, if the angle between the horizontal and the wall of the frustum deviates a lot from 90 degrees, the wall may become too thin, or even leaving gaps in between. 296 | 297 | return abs($vertDistance) < abs($horizDistance) ? $vertDistance : $horizDistance; 298 | } 299 | 300 | protected function lazyGetCenter() : Vector3{ 301 | $pAxis = $this->top->subtract($this->base); 302 | // Weisstein, Eric W. "Conical Frustum." From MathWorld--A Wolfram Web Resource. http://mathworld.wolfram.com/ConicalFrustum.html 303 | // Formula for the height of the geometric centroid 304 | $w2 = $this->topRightRadius * $this->topFrontRadius; 305 | $x2 = $this->baseRightRadius * $this->baseFrontRadius; 306 | $wx = sqrt($w2 * $x2); 307 | $coef = ($w2 + $x2 * 3 + $wx * 2) / 4 / ($w2 + $x2 + $wx); 308 | return $this->base->add($pAxis->multiply($coef)); 309 | } 310 | 311 | protected function estimateSize() : int{ 312 | assert($this->isComplete()); 313 | 314 | // A(h) = area of layer h, where h at base = 0 and h at top = 1 315 | // = pi (baseRightRadius + h (topRightRadius - baseRightRadius)) (baseFrontRadius + h (topFrontRadius - baseFrontRadius)) 316 | // size = (top - base) dot normal * integrate of A(h) on dh from h = 0 to h = 1 317 | // = (top - base) dot normal * 318 | // baseRightRadius*baseFrontRadius + 1/2 (topRightRadius-baseRightRadius) baseFrontRadius 319 | // +1/2 (topFrontRadius-baseFrontRadius) baseRightRadius + 1/3 (topRightRadius-baseRightRadius) (topFrontRadius-baseFrontRadius) 320 | $height = $this->top->subtract($this->base)->dot($this->normal); // modulus(normal) == 1 321 | $a = $this->baseRightRadius; 322 | $b = $this->topRightRadius - $this->baseRightRadius; 323 | $c = $this->baseFrontRadius; 324 | $d = $this->topFrontRadius - $this->baseFrontRadius; 325 | $integrate = $a * $c + ($a * $d + $b * $c) / 2 + $b * $d / 3; 326 | return (int) round($height * M_PI * $integrate); 327 | } 328 | 329 | public function getEstimatedSurfaceSize(float $padding, float $margin) : int{ 330 | $size = 0; 331 | $height = $this->top->distance($this->base); 332 | for($i = -$padding; $i < $margin; ++$i){ 333 | $baseRadiusGeomMean = sqrt(($this->baseRightRadius - 1 + $i) * ($this->baseFrontRadius - 1 + $i)); 334 | $topRadiusGeomMean = sqrt(($this->topRightRadius - 1 + $i) * ($this->topFrontRadius - 1 + $i)); 335 | // Could I avoid the use of sqrt()? 336 | $size += M_PI * $topRadiusGeomMean * $topRadiusGeomMean; 337 | $size += M_PI * $baseRadiusGeomMean * $baseRadiusGeomMean; 338 | // Algorithm for slant area of frustum 339 | // By Weisstein, Eric W. "Conical Frustum." From MathWorld--A Wolfram Web Resource. Equation (3). 340 | $size += M_PI * ($topRadiusGeomMean + $baseRadiusGeomMean) * sqrt(($topRadiusGeomMean - $baseRadiusGeomMean) ** 2 + ($height + $i) ** 2); 341 | } 342 | return (int) round($size); 343 | } 344 | 345 | protected function lazyGetMinX() : float{ 346 | assert($this->isComplete()); 347 | $base = $this->evalObliqueRadius($this->rightDir->multiply($this->baseRightRadius)->x, $this->frontDir->multiply($this->baseFrontRadius)->x); 348 | $top = $this->evalObliqueRadius($this->rightDir->multiply($this->topRightRadius)->x, $this->frontDir->multiply($this->topFrontRadius)->x); 349 | return min($this->base->x - $base, $this->top->x - $top); 350 | } 351 | 352 | protected function lazyGetMaxX() : float{ 353 | assert($this->isComplete()); 354 | $base = $this->evalObliqueRadius($this->rightDir->multiply($this->baseRightRadius)->x, $this->frontDir->multiply($this->baseFrontRadius)->x); 355 | $top = $this->evalObliqueRadius($this->rightDir->multiply($this->topRightRadius)->x, $this->frontDir->multiply($this->topFrontRadius)->x); 356 | return max($this->base->x + $base, $this->top->x + $top); 357 | } 358 | 359 | protected function lazyGetMinY() : float{ 360 | assert($this->isComplete()); 361 | $base = $this->evalObliqueRadius($this->rightDir->multiply($this->baseRightRadius)->y, $this->frontDir->multiply($this->baseFrontRadius)->y); 362 | $top = $this->evalObliqueRadius($this->rightDir->multiply($this->topRightRadius)->y, $this->frontDir->multiply($this->topFrontRadius)->y); 363 | return min($this->base->y - $base, $this->top->y - $top); 364 | } 365 | 366 | protected function lazyGetMaxY() : float{ 367 | assert($this->isComplete()); 368 | $base = $this->evalObliqueRadius($this->rightDir->multiply($this->baseRightRadius)->y, $this->frontDir->multiply($this->baseFrontRadius)->y); 369 | $top = $this->evalObliqueRadius($this->rightDir->multiply($this->topRightRadius)->y, $this->frontDir->multiply($this->topFrontRadius)->y); 370 | return max($this->base->y + $base, $this->top->y + $top); 371 | } 372 | 373 | protected function lazyGetMinZ() : float{ 374 | assert($this->isComplete()); 375 | $base = $this->evalObliqueRadius($this->rightDir->multiply($this->baseRightRadius)->z, $this->frontDir->multiply($this->baseFrontRadius)->z); 376 | $top = $this->evalObliqueRadius($this->rightDir->multiply($this->topRightRadius)->z, $this->frontDir->multiply($this->topFrontRadius)->z); 377 | return min($this->base->z - $base, $this->top->z - $top); 378 | } 379 | 380 | protected function lazyGetMaxZ() : float{ 381 | assert($this->isComplete()); 382 | $base = $this->evalObliqueRadius($this->rightDir->multiply($this->baseRightRadius)->z, $this->frontDir->multiply($this->baseFrontRadius)->z); 383 | $top = $this->evalObliqueRadius($this->rightDir->multiply($this->topRightRadius)->z, $this->frontDir->multiply($this->topFrontRadius)->z); 384 | return max($this->base->z + $base, $this->top->z + $top); 385 | } 386 | 387 | private function evalObliqueRadius(float $a, float $b) : float{ 388 | // Nominal Animal (https://math.stackexchange.com/users/318422/nominal-animal), 389 | // Minimum and maximum points of ellipse in 3D, 390 | // URL (version: 2017-06-04): https://math.stackexchange.com/q/2309239 391 | 392 | return sqrt($a * $a + $b * $b); 393 | } 394 | 395 | protected function lazyGetMaxHollowSize(float $padding, float $margin) : int{ 396 | assert($this->isComplete()); 397 | 398 | $height = $this->top->subtract($this->base)->dot($this->normal); // modulus(normal) == 1 399 | $height += $margin * 2; 400 | $a = $this->baseRightRadius + $margin; 401 | $b = $this->topRightRadius - $this->baseRightRadius; 402 | $c = $this->baseFrontRadius + $margin; 403 | $d = $this->topFrontRadius - $this->baseFrontRadius; 404 | $integrate = $a * $c + ($a * $d + $b * $c) / 2 + $b * $d / 3; 405 | return (int) ceil($height * M_PI * $integrate * 1.3); 406 | } 407 | 408 | 409 | public static function fromBinary(/** @noinspection PhpUnusedParameterInspection */ 410 | Server $server, LibgeomDataReader $stream) : Shape{ 411 | $level = $server->getLevelByName($stream->readString()); 412 | $base = new Vector3(); 413 | $baseRightCircum = new Vector3(); 414 | $top = new Vector3(); 415 | $normal = new Vector3(); 416 | $stream->readVector3f($base->x, $base->y, $base->z); 417 | $stream->readVector3f($baseRightCircum->x, $baseRightCircum->y, $baseRightCircum->z); 418 | $baseFrontRadius = $stream->readFloat(); 419 | $stream->readVector3f($top->x, $top->y, $top->z); 420 | $topRightRadius = $stream->readFloat(); 421 | $topFrontRadius = $stream->readFloat(); 422 | $stream->readVector3f($normal->x, $normal->y, $normal->z); 423 | return new CircularFrustumShape($level, $base, $top, $normal, $baseRightCircum, $baseFrontRadius, $topRightRadius, $topFrontRadius); 424 | } 425 | 426 | public function toBinary(LibgeomDataWriter $stream){ 427 | $stream->writeString($this->getLevelName()); 428 | $stream->writeVector3f($this->base->x, $this->base->y, $this->base->z); 429 | $baseRightCircum = $this->base->add($this->rightDir->multiply($this->baseRightRadius)); 430 | $stream->writeVector3f($baseRightCircum->x, $baseRightCircum->y, $baseRightCircum->z); 431 | $stream->writeFloat($this->baseFrontRadius); 432 | $stream->writeVector3f($this->top->x, $this->top->y, $this->top->z); 433 | $stream->writeFloat($this->topRightRadius); 434 | $stream->writeFloat($this->topFrontRadius); 435 | $stream->writeVector3f($this->normal->x, $this->normal->y, $this->normal->z); 436 | } 437 | } 438 | -------------------------------------------------------------------------------- /libgeom/src/sofe/libgeom/shapes/CuboidShape.php: -------------------------------------------------------------------------------- 1 | from = $from === null ? null : $from->asVector3(); 36 | $this->to = $to === null ? null : $to->asVector3(); 37 | $this->onDimenChanged(); 38 | $this->setLevel($level); 39 | } 40 | 41 | protected function onDimenChanged(){ 42 | parent::onDimenChanged(); 43 | if(isset($this->from, $this->to)){ 44 | $this->from = $this->from->asVector3(); 45 | $this->to = $this->to->asVector3(); 46 | $this->min = new Vector3( 47 | min($this->from->x, $this->to->x), 48 | min($this->from->y, $this->to->y), 49 | min($this->from->z, $this->to->z)); 50 | $this->max = new Vector3( 51 | max($this->from->x, $this->to->x), 52 | max($this->from->y, $this->to->y), 53 | max($this->from->z, $this->to->z)); 54 | }else{ 55 | $this->min = null; 56 | $this->max = null; 57 | } 58 | } 59 | 60 | public function getMin(){ 61 | return $this->min; 62 | } 63 | 64 | public function getMax(){ 65 | return $this->max; 66 | } 67 | 68 | public function getFrom(){ 69 | return $this->from; 70 | } 71 | 72 | public function getTo(){ 73 | return $this->to; 74 | } 75 | 76 | public function setFrom(Vector3 $from = null) : CuboidShape{ 77 | $this->from = $from === null ? null : $from->asVector3(); 78 | $this->onDimenChanged(); 79 | return $this; 80 | } 81 | 82 | public function setTo(Vector3 $to = null) : CuboidShape{ 83 | $this->to = $to === null ? null : $to->asVector3(); 84 | $this->onDimenChanged(); 85 | return $this; 86 | } 87 | 88 | public function isComplete() : bool{ 89 | return isset($this->min, $this->max); 90 | } 91 | 92 | public function isInside(Vector3 $vector) : bool{ 93 | assert($this->isComplete()); 94 | return 95 | $this->min->x <= $vector->x and $vector->x <= $this->max->x and 96 | $this->min->y <= $vector->y and $vector->y <= $this->max->y and 97 | $this->min->z <= $vector->z and $vector->z <= $this->max->z; 98 | } 99 | 100 | protected function estimateSize() : int{ 101 | assert($this->isComplete()); 102 | $diff = $this->max->subtract($this->min); 103 | /** @noinspection UnnecessaryCastingInspection */ 104 | return (int) (($diff->x + 1) * ($diff->y + 1) * ($diff->z + 1)); 105 | } 106 | 107 | public function getEstimatedSurfaceSize(float $padding, float $margin) : int{ 108 | $width = $this->to->x - $this->from->x; 109 | $height = $this->to->y - $this->from->y; 110 | $depth = $this->to->z - $this->from->z; 111 | 112 | return (int) (($width + $margin * 2) * ($height + $margin * 2) * ($depth + $margin * 2) 113 | - ($width - $padding * 2) * ($height - $padding * 2) * ($depth - $padding * 2)); 114 | } 115 | 116 | public function marginalDistance(Vector3 $vector) : float{ 117 | assert($this->isComplete()); 118 | $diffs = [ 119 | $this->max->x - $vector->x, 120 | $vector->x - $this->min->x, 121 | $this->max->y - $vector->y, 122 | $vector->y - $this->min->y, 123 | $this->max->z - $vector->z, 124 | $vector->z - $this->min->z, 125 | ]; 126 | $min = min($diffs); 127 | if($min >= 0){ 128 | return -$min; 129 | } 130 | $m = new Vector3; 131 | $m->x = min($this->max->x, max($this->min->x, $vector->x)); 132 | $m->y = min($this->max->y, max($this->min->y, $vector->y)); 133 | $m->z = min($this->max->z, max($this->min->z, $vector->z)); 134 | return $vector->distance($m); 135 | } 136 | 137 | protected function lazyGetCenter() : Vector3{ 138 | return $this->min->add($this->max)->divide(2); 139 | } 140 | 141 | public function getChunksInvolved() : array{ 142 | assert($this->isComplete()); 143 | $chunks = []; 144 | for($X = $this->min->x >> 4; $X <= $this->max->x >> 4; ++$X){ 145 | for($Z = $this->min->z >> 4; $Z <= $this->max->z >> 4; ++$Z){ 146 | $chunks[] = Level::chunkHash($X, $Z); 147 | } 148 | } 149 | return $chunks; 150 | } 151 | 152 | public function getMinX() : int{ 153 | return $this->min->x; 154 | } 155 | 156 | public function getMinY() : int{ 157 | return $this->min->y; 158 | } 159 | 160 | public function getMinZ() : int{ 161 | return $this->min->z; 162 | } 163 | 164 | public function getMaxX() : int{ 165 | return $this->max->x; 166 | } 167 | 168 | public function getMaxY() : int{ 169 | return $this->max->y; 170 | } 171 | 172 | public function getMaxZ() : int{ 173 | return $this->max->z; 174 | } 175 | 176 | public function getSolidStream(Vector3 $vector) : \Generator{ 177 | for($vector->y = $this->min->y; $vector->y <= $this->max->y; ++$vector->y){ 178 | for($vector->x = $this->min->x; $vector->x <= $this->max->x; ++$vector->x){ 179 | for($vector->z = $this->min->z; $vector->z <= $this->max->z; ++$vector->z){ 180 | yield true; 181 | } 182 | } 183 | } 184 | } 185 | 186 | public function getHollowStream(Vector3 $vector, float $padding, float $margin) : \Generator{ 187 | for($l = 1 - (int) round($padding); $l <= (int) round($margin); ++$l){ 188 | $i = 0; 189 | for($vector->y = $this->min->y - $l; $i < 2; $vector->y = $this->max->y + $l, ++$i){ 190 | for($vector->x = $this->min->x - $l; $vector->x <= $this->max->x + $l; ++$vector->x){ 191 | for($vector->z = $this->min->z - $l; $vector->z <= $this->max->z + $l; ++$vector->z){ 192 | yield true; 193 | } 194 | } 195 | } 196 | for($vector->z = $this->min->z - $l; $i < 4; $vector->z = $this->max->z + $l, ++$i){ 197 | for($vector->y = $this->min->y - $l + 1; $vector->y <= $this->max->x + $l - 1; ++$vector->y){ 198 | for($vector->x = $this->min->x - $l; $vector->x <= $this->max->x + $l; ++$vector->x){ 199 | yield true; 200 | } 201 | } 202 | } 203 | for($vector->x = $this->min->x - $l; $i < 6; $vector->x = $this->max->x + $l, ++$i){ 204 | for($vector->y = $this->min->y - $l + 1; $vector->y <= $this->max->x + $l - 1; ++$vector->y){ 205 | for($vector->z = $this->min->z - $l + 1; $vector->z <= $this->max->z + $l - 1; ++$vector->z){ 206 | yield true; 207 | } 208 | } 209 | } 210 | } 211 | } 212 | 213 | public static function fromBinary(Server $server, LibgeomDataReader $stream) : Shape{ 214 | $from = new Vector3(); 215 | $to = new Vector3(); 216 | $stream->readBlockPosition($from->x, $from->y, $from->z); 217 | $stream->readBlockPosition($to->x, $to->y, $to->z); 218 | return new CuboidShape($server->getLevelByName($stream->readString()), $from, $to); 219 | } 220 | 221 | public function toBinary(LibgeomDataWriter $stream){ 222 | $stream->writeBlockPosition($this->from->x, $this->from->y, $this->from->z); 223 | $stream->writeBlockPosition($this->to->x, $this->to->y, $this->to->z); 224 | $stream->writeString($this->getLevelName()); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /libgeom/src/sofe/libgeom/shapes/EllipsoidShape.php: -------------------------------------------------------------------------------- 1 | setLevel($level); 36 | $this->center = $center !== null ? $center->asVector3() : null; 37 | if($xrad <= 0 or $yrad <= 0 or $zrad <= 0){ 38 | throw new \InvalidArgumentException("Radii of ellipsoid must be positive"); 39 | } 40 | $this->xrad = $xrad; 41 | $this->yrad = $yrad; 42 | $this->zrad = $zrad; 43 | } 44 | 45 | public function isInside(Vector3 $vector) : bool{ 46 | assert($this->isComplete()); 47 | $diff = $vector->subtract($this->center); 48 | return ($diff->x / $this->xrad) ** 2 + ($diff->y / $this->yrad) ** 2 + ($diff->z / $this->zrad) ** 2 <= 1; 49 | } 50 | 51 | public function marginalDistance(Vector3 $vector) : float{ 52 | assert($this->isComplete()); 53 | 54 | // Spherical equation of ellipsoid: 55 | // r^2 (cosθ sinϕ / a)² + (sinθ cosϕ / b)² + (cosθ / c)² = 1 56 | // Ref: Weisstein, Eric W. "Ellipsoid." From MathWorld--A Wolfram Web Resource. http://mathworld.wolfram.com/Ellipsoid.html 57 | // Hence, r = abc / √( (bc cosθ sinϕ)² + (ac sinθ cosϕ)² + (ab cosϕ)² ) 58 | 59 | $diff = $vector->subtract($this->center)->abs(); 60 | $spheric = clone $diff; 61 | $spheric->x /= $this->xrad; 62 | $spheric->y /= $this->yrad; 63 | $spheric->z /= $this->zrad; 64 | $spheric = $spheric->normalize(); 65 | $radial = $diff->divide($spheric->length()); 66 | // now $clone is the radial vector in the same direction as the diff vctor 67 | return $diff->length() - $radial->length(); 68 | } 69 | 70 | protected function estimateSize() : int{ 71 | assert($this->isComplete()); 72 | return (int) (4 / 3 * M_PI * $this->xrad * $this->yrad * $this->zrad); 73 | } 74 | 75 | public function getEstimatedSurfaceSize(float $padding, float $margin) : int{ 76 | return (int) round(M_PI * 4 / 3 * (($this->xrad + $margin) * ($this->yrad + $margin) * ($this->zrad + $margin) 77 | - ($this->xrad - $padding) * ($this->yrad - $padding) * ($this->zrad - $padding))); 78 | } 79 | 80 | public function getCenter(){ 81 | return $this->center; 82 | } 83 | 84 | protected function lazyGetCenter() : Vector3{ 85 | return $this->center; 86 | } 87 | 88 | public function setCenter(Vector3 $center = null) : EllipsoidShape{ 89 | if($center !== null){ 90 | $center = $center->asVector3(); 91 | } 92 | $this->center = $center; 93 | $this->onDimenChanged(); 94 | return $this; 95 | } 96 | 97 | public function getRadiusX(){ 98 | return $this->xrad; 99 | } 100 | 101 | public function getRadiusY(){ 102 | return $this->yrad; 103 | } 104 | 105 | public function getRadiusZ(){ 106 | return $this->zrad; 107 | } 108 | 109 | public function setRadiusX(float $xrad = null) : EllipsoidShape{ 110 | $this->xrad = $xrad; 111 | $this->onDimenChanged(); 112 | return $this; 113 | } 114 | 115 | public function setRadiusY(float $yrad = null) : EllipsoidShape{ 116 | $this->yrad = $yrad; 117 | $this->onDimenChanged(); 118 | return $this; 119 | } 120 | 121 | public function setRadiusZ(float $zrad = null) : EllipsoidShape{ 122 | $this->zrad = $zrad; 123 | $this->onDimenChanged(); 124 | return $this; 125 | } 126 | 127 | public function isComplete() : bool{ 128 | return isset($this->center, $this->xrad, $this->yrad, $this->zrad); 129 | } 130 | 131 | protected function lazyGetMinX() : float{ 132 | return $this->center->x - $this->xrad; 133 | } 134 | 135 | protected function lazyGetMinY() : float{ 136 | return $this->center->y - $this->yrad; 137 | } 138 | 139 | protected function lazyGetMinZ() : float{ 140 | return $this->center->z - $this->zrad; 141 | } 142 | 143 | protected function lazyGetMaxX() : float{ 144 | return $this->center->x + $this->xrad; 145 | } 146 | 147 | protected function lazyGetMaxY() : float{ 148 | return $this->center->y + $this->yrad; 149 | } 150 | 151 | protected function lazyGetMaxZ() : float{ 152 | return $this->center->z + $this->zrad; 153 | } 154 | 155 | protected function lazyGetMaxHollowSize(float $padding, float $margin) : int{ 156 | return (int) ceil(1.3 * 4 / 3 * M_PI * ( 157 | ($this->xrad + $margin) * ($this->yrad + $margin) * ($this->zrad + $margin) - 158 | ($this->xrad - $padding) * ($this->yrad - $padding) * ($this->zrad - $padding))); 159 | } 160 | 161 | 162 | public static function fromBinary(/** @noinspection PhpUnusedParameterInspection */ 163 | Server $server, LibgeomDataReader $stream) : Shape{ 164 | $level = $server->getLevelByName($stream->readString()); 165 | $center = new Vector3(); 166 | $stream->readBlockPosition($center->x, $center->y, $center->z); 167 | $stream->readVector3f($xrad, $yrad, $zrad); 168 | return new EllipsoidShape($level, $center, $xrad, $yrad, $zrad); 169 | } 170 | 171 | public function toBinary(LibgeomDataWriter $stream){ 172 | $stream->writeString($this->getLevelName()); 173 | $stream->writeVector3f($this->center->x, $this->center->y, $this->center->z); 174 | $stream->writeVector3f($this->xrad, $this->yrad, $this->zrad); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /libgeom/src/sofe/libgeom/shapes/PointLineDistance-draft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockHorizons/libgeom/55d6aa5fccb239eecd7c7ea50bd7f3bd3e359fb2/libgeom/src/sofe/libgeom/shapes/PointLineDistance-draft.png -------------------------------------------------------------------------------- /libgeom/src/sofe/libgeom/shapes/PolygonFrustumShape-draft-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlockHorizons/libgeom/55d6aa5fccb239eecd7c7ea50bd7f3bd3e359fb2/libgeom/src/sofe/libgeom/shapes/PolygonFrustumShape-draft-1.png -------------------------------------------------------------------------------- /libgeom/src/sofe/libgeom/shapes/PolygonFrustumShape.php: -------------------------------------------------------------------------------- 1 | baseAnchor = $baseAnchor->asVector3(); 68 | $this->topAnchor = $topAnchor->asVector3(); 69 | $this->basePolygon = []; 70 | $this->topPolygon = []; 71 | foreach($basePolygon as $basePoint){ 72 | $this->basePolygon[] = $basePoint->asVector3(); 73 | $this->topPolygon[] = $topAnchor->add($basePoint->subtract($baseAnchor)->multiply($topBaseRatio)); 74 | } 75 | $this->topBaseRatio = $topBaseRatio; 76 | assert(LibgeomMathUtils::areCoplanar($baseAnchor, ...$basePolygon), "Base anchor and base polygon must be coplanar"); 77 | $this->baseNormal = $basePolygon[2]->subtract($basePolygon[1])->cross($basePolygon[0]->subtract($basePolygon[1]))->normalize(); 78 | $this->isSelfIntersecting = $this->_isSelfIntersecting(); 79 | $this->setLevel($level); 80 | } 81 | 82 | private function _isSelfIntersecting() : bool{ 83 | $lines = []; 84 | for($i = 1, $iMax = count($this->basePolygon); $i <= $iMax; ++$i){ 85 | $lines[] = [$this->basePolygon[$i - 1], $this->basePolygon[$i === count($this->basePolygon) ? 0 : $i]]; 86 | } 87 | $linesCount = count($lines); 88 | for($i = 0; $i < $linesCount; ++$i){ 89 | for($j = $i + 1; $j < $linesCount; ++$j){ 90 | if(LibgeomMathUtils::doSegmentsIntersect($lines[$i][0], $lines[$i][1], $lines[$j][0], $lines[$j][1])){ 91 | return true; 92 | } 93 | } 94 | } 95 | return false; 96 | } 97 | 98 | public function isSelfIntersecting() : bool{ 99 | return $this->isSelfIntersecting; 100 | } 101 | 102 | public function isInside(Vector3 $point) : bool{ 103 | $lambda = $this->baseNormal->dot($point->subtract($this->baseAnchor)) / $this->baseNormal->dot($this->topAnchor->subtract($this->baseAnchor)); 104 | // $q = $this->baseAnchor->add($this->topAnchor->subtract($this->baseAnchor)->multiply($lambda)); 105 | $polygon = []; 106 | foreach($this->basePolygon as $i => $basePoint){ 107 | $polygon[] = $basePoint->add($this->topPolygon[$i]->subtract($basePoint)->multiply($lambda)); 108 | } 109 | $intersects = 0; 110 | $horiz = new Vector3(1, 0, 0); 111 | for($i = 1, $iMax = count($polygon); $i <= $iMax; ++$i){ 112 | if(LibgeomMathUtils::doesRayIntersectSegment($point, $horiz, $polygon[$i - 1], $polygon[$i === count($polygon) ? 0 : $i])){ 113 | $intersects ^= 1; 114 | } 115 | } 116 | return ($intersects & 1) !== 0; 117 | } 118 | 119 | public function marginalDistance(Vector3 $vector) : float{ 120 | assert(!$this->isSelfIntersecting()); 121 | $lambda = $this->baseNormal->dot($vector->subtract($this->baseAnchor)) / $this->baseNormal->dot($this->topAnchor->subtract($this->baseAnchor)); 122 | // $q = $this->baseAnchor->add($this->topAnchor->subtract($this->baseAnchor)->multiply($lambda)); 123 | /** @var Vector3[] $polygon */ 124 | $polygon = []; 125 | foreach($this->basePolygon as $i => $basePoint){ 126 | $polygon[] = $basePoint->add($this->topPolygon[$i]->subtract($basePoint)->multiply($lambda)); 127 | } 128 | $signum = $this->isInside($vector) ? 1 : -1; 129 | $minDistance = PHP_INT_MAX; 130 | foreach($polygon as $i => $point1){ 131 | $point2 = $polygon[$i + 1] ?? $polygon[0]; 132 | $ap = $vector->subtract($point1); 133 | $ab = $point2->subtract($point1); 134 | $distance = $ab->multiply($ap->dot($ab) / $ab->lengthSquared())->subtract($ap)->length(); 135 | if($minDistance > $distance){ 136 | $minDistance = $distance; 137 | } 138 | } 139 | return $minDistance * $signum; 140 | } 141 | 142 | protected function lazyGetCenter() : Vector3{ 143 | /** @var Vector3 $baseCenter */ 144 | $baseCenter = array_reduce($this->basePolygon, function(Vector3 $carry, Vector3 $item){ 145 | $carry->x += $item->x; 146 | $carry->y += $item->y; 147 | $carry->z += $item->z; 148 | return $carry; 149 | }, new Vector3); 150 | $topCenter = $this->topAnchor->add($baseCenter->subtract($this->baseAnchor)->multiply($this->topBaseRatio)); 151 | // Weisstein, Eric W. "Conical Frustum." From MathWorld--A Wolfram Web Resource. http://mathworld.wolfram.com/ConicalFrustum.html 152 | // Formula for the height of the geometric centroid 153 | $w2 = $this->topBaseRatio ** 2; 154 | $x2 = 1; 155 | $wx = $this->topBaseRatio; 156 | $coef = ($w2 + $x2 * 3 + $wx * 2) / 4 / ($w2 + $x2 + $wx); 157 | return $baseCenter->add($topCenter->subtract($baseCenter)->multiply($coef)); 158 | } 159 | 160 | protected function lazyGetMinX() : float{ 161 | return min(array_map(function(Vector3 $vector){ 162 | return $vector->x; 163 | }, $this->basePolygon, $this->topPolygon)); 164 | } 165 | 166 | protected function lazyGetMinY() : float{ 167 | return min(array_map(function(Vector3 $vector){ 168 | return $vector->y; 169 | }, $this->basePolygon, $this->topPolygon)); 170 | } 171 | 172 | protected function lazyGetMinZ() : float{ 173 | return min(array_map(function(Vector3 $vector){ 174 | return $vector->z; 175 | }, $this->basePolygon, $this->topPolygon)); 176 | } 177 | 178 | protected function lazyGetMaxX() : float{ 179 | return max(array_map(function(Vector3 $vector){ 180 | return $vector->x; 181 | }, $this->basePolygon, $this->topPolygon)); 182 | } 183 | 184 | protected function lazyGetMaxY() : float{ 185 | return max(array_map(function(Vector3 $vector){ 186 | return $vector->y; 187 | }, $this->basePolygon, $this->topPolygon)); 188 | } 189 | 190 | protected function lazyGetMaxZ() : float{ 191 | return max(array_map(function(Vector3 $vector){ 192 | return $vector->z; 193 | }, $this->basePolygon, $this->topPolygon)); 194 | } 195 | 196 | protected function lazyGetMaxHollowSize(float $padding, float $margin) : int{ 197 | $dx = $this->getMaxX() - $this->getMinX(); 198 | $dy = $this->getMaxY() - $this->getMinY(); 199 | $dz = $this->getMaxZ() - $this->getMinZ(); 200 | 201 | return (int) ceil($this->getEstimatedSize() * ($dx + $margin) * ($dy + $margin) * ($dz + $margin) / $dx / $dy / $dz); 202 | } 203 | 204 | public function isComplete() : bool{ 205 | return true; 206 | } 207 | 208 | /** @noinspection PhpInconsistentReturnPointsInspection 209 | * @param Vector3 $vector 210 | * @param float $padding 211 | * @param float $margin 212 | * 213 | * @return \Generator 214 | * 215 | * @throws UnsupportedOperationException 216 | */ 217 | public function getHollowStream(Vector3 $vector, float $padding, float $margin) : \Generator{ 218 | if($this->isSelfIntersecting){ 219 | throw new UnsupportedOperationException("Hollow self-intersecting frustums are not supported"); 220 | } 221 | return parent::getHollowStream($vector, $padding, $margin); 222 | } 223 | 224 | protected function estimateSize() : int{ 225 | return (int) ceil(LibgeomMathUtils::evalFrustumVolume($this->getBaseArea(), $this->getTopArea(), 226 | $this->topAnchor->subtract($this->baseAnchor)->dot($this->baseNormal))); 227 | } 228 | 229 | public function getEstimatedSurfaceSize(float $padding, float $margin) : int{ 230 | return (int) round( 231 | LibgeomMathUtils::evalFrustumVolume($this->getBaseArea() + $margin ** 2, $this->getTopArea() + $margin ** 2, 232 | $this->topAnchor->subtract($this->baseAnchor)->dot($this->baseNormal) + $margin * 2) 233 | - LibgeomMathUtils::evalFrustumVolume($this->getBaseArea() - $padding ** 2, $this->getTopArea() -$padding ** 2, 234 | $this->topAnchor->subtract($this->baseAnchor)->dot($this->baseNormal) - $padding * 2) 235 | ); // TODO improve implementation 236 | } 237 | 238 | public function getBaseArea() : float{ 239 | if(!isset($this->baseAreaCache)){ 240 | if($this->isSelfIntersecting()){ 241 | // TODO Implement 242 | }else{ 243 | $sum = 0.0; 244 | for($i = 2, $iMax = count($this->basePolygon); $i < $iMax; ++$i){ 245 | $sum += LibgeomMathUtils::getTriangleArea($this->basePolygon[0], $this->basePolygon[$i - 1], $this->basePolygon[$i]); 246 | } 247 | return $sum; 248 | } 249 | } 250 | return $this->baseAreaCache; 251 | } 252 | 253 | public function getTopArea() : float{ 254 | return $this->baseAreaCache * $this->topBaseRatio ** 2; 255 | } 256 | 257 | public function getBaseAnchor() : Vector3{ 258 | return $this->baseAnchor; 259 | } 260 | 261 | public function getTopAnchor() : Vector3{ 262 | return $this->topAnchor; 263 | } 264 | 265 | public function getBasePolygon() : array{ 266 | return $this->basePolygon; 267 | } 268 | 269 | public function getTopPolygon() : array{ 270 | return $this->topPolygon; 271 | } 272 | 273 | public function getBaseNormal() : Vector3{ 274 | return $this->baseNormal; 275 | } 276 | 277 | public function getTopBaseRatio() : float{ 278 | return $this->topBaseRatio; 279 | } 280 | 281 | 282 | public static function fromBinary(/** @noinspection PhpUnusedParameterInspection */ 283 | Server $server, LibgeomDataReader $stream) : Shape{ 284 | $level = $server->getLevelByName($stream->readString()); 285 | $baseAnchor = new Vector3(); 286 | $stream->readVector3f($baseAnchor->x, $baseAnchor->y, $baseAnchor->z); 287 | $size = $stream->readShort(); 288 | $basePolygon = []; 289 | for($i = 0; $i < $size; ++$i){ 290 | $basePolygon[] = $v = new Vector3(); 291 | $stream->readVector3f($v->x, $v->y, $v->z); 292 | } 293 | $topAnchor = new Vector3(); 294 | $stream->readVector3f($topAnchor->x, $topAnchor->y, $topAnchor->z); 295 | $topBaseRatio = $stream->readFloat(); 296 | return new PolygonFrustumShape($level, $baseAnchor, $basePolygon, $topAnchor, $topBaseRatio); 297 | } 298 | 299 | public function toBinary(LibgeomDataWriter $stream){ 300 | $stream->writeString($this->getLevelName()); 301 | $stream->writeVector3f($this->baseAnchor->x, $this->baseAnchor->y, $this->baseAnchor->z); 302 | $stream->writeShort(count($this->basePolygon)); 303 | foreach($this->basePolygon as $point){ 304 | $stream->writeVector3f($point->x, $point->y, $point->z); 305 | } 306 | $stream->writeVector3f($this->topAnchor->x, $this->topAnchor->y, $this->topAnchor->z); 307 | $stream->writeFloat($this->topBaseRatio); 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /libgeom/virion.yml: -------------------------------------------------------------------------------- 1 | name: libgeom 2 | version: 2.0.0 3 | api: [3.0.0-ALPHA6, 3.0.0-ALPHA7, 3.0.0-ALPHA8] 4 | antigen: sofe\libgeom 5 | author: SOFe 6 | --------------------------------------------------------------------------------