├── .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.
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 callingT::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 | --------------------------------------------------------------------------------