[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Drag-and-drop markers classes for dealing with shapes on the server side. 19 * 20 * @package qtype_ddmarker 21 * @copyright 2012 The Open University 22 * @author Jamie Pratt <me@jamiep.org> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 27 /** 28 * Base class to represent a shape. 29 * 30 * @copyright 2012 The Open University 31 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 32 */ 33 abstract class qtype_ddmarker_shape { 34 /** @var bool Indicates if there is an error */ 35 protected $error = false; 36 37 /** @var string The shape class prefix */ 38 protected static $classnameprefix = 'qtype_ddmarker_shape_'; 39 40 public function __construct($coordsstring) { 41 42 } 43 public function inside_width_height($widthheight) { 44 foreach ($this->outlying_coords_to_test() as $coordsxy) { 45 if ($coordsxy[0] > $widthheight[0] || $coordsxy[1] > $widthheight[1]) { 46 return false; 47 } 48 } 49 return true; 50 } 51 52 abstract protected function outlying_coords_to_test(); 53 54 /** 55 * Returns the center location of the shape. 56 * 57 * @return array X and Y location 58 */ 59 abstract public function center_point(); 60 61 /** 62 * Test if all passed parameters consist of only numbers. 63 * 64 * @return bool True if only numbers 65 */ 66 protected function is_only_numbers() { 67 $args = func_get_args(); 68 foreach ($args as $arg) { 69 if (0 === preg_match('!^[0-9]+$!', $arg)) { 70 return false; 71 } 72 } 73 return true; 74 } 75 76 /** 77 * Checks if the point is within the bounding box made by top left and bottom right 78 * 79 * @param array $pointxy Array of the point (x, y) 80 * @param array $xleftytop Top left point of bounding box 81 * @param array $xrightybottom Bottom left point of bounding box 82 * @return bool 83 */ 84 protected function is_point_in_bounding_box($pointxy, $xleftytop, $xrightybottom) { 85 if ($pointxy[0] < $xleftytop[0]) { 86 return false; 87 } else if ($pointxy[0] > $xrightybottom[0]) { 88 return false; 89 } else if ($pointxy[1] < $xleftytop[1]) { 90 return false; 91 } else if ($pointxy[1] > $xrightybottom[1]) { 92 return false; 93 } 94 return true; 95 } 96 97 /** 98 * Gets any coordinate error 99 * 100 * @return string|bool String of the error or false if there is no error 101 */ 102 public function get_coords_interpreter_error() { 103 if ($this->error) { 104 $a = new stdClass(); 105 $a->shape = self::human_readable_name(true); 106 $a->coordsstring = self::human_readable_coords_format(); 107 return get_string('formerror_'.$this->error, 'qtype_ddmarker', $a); 108 } else { 109 return false; 110 } 111 } 112 113 /** 114 * Check if the location is within the shape. 115 * 116 * @param array $xy $xy[0] is x, $xy[1] is y 117 * @return boolean is point inside shape 118 */ 119 abstract public function is_point_in_shape($xy); 120 121 /** 122 * Returns the name of the shape. 123 * 124 * @return string 125 */ 126 public static function name() { 127 return substr(get_called_class(), strlen(self::$classnameprefix)); 128 } 129 130 /** 131 * Return a human readable name of the shape. 132 * 133 * @param bool $lowercase True if it should be lowercase. 134 * @return string 135 */ 136 public static function human_readable_name($lowercase = false) { 137 $stringid = 'shape_'.self::name(); 138 if ($lowercase) { 139 $stringid .= '_lowercase'; 140 } 141 return get_string($stringid, 'qtype_ddmarker'); 142 } 143 144 public static function human_readable_coords_format() { 145 return get_string('shape_'.self::name().'_coords', 'qtype_ddmarker'); 146 } 147 148 149 public static function shape_options() { 150 $grepexpression = '!^'.preg_quote(self::$classnameprefix, '!').'!'; 151 $shapes = preg_grep($grepexpression, get_declared_classes()); 152 $shapearray = array(); 153 foreach ($shapes as $shape) { 154 $shapearray[$shape::name()] = $shape::human_readable_name(); 155 } 156 asort($shapearray); 157 return $shapearray; 158 } 159 160 /** 161 * Checks if the passed shape exists. 162 * 163 * @param string $shape The shape name 164 * @return bool 165 */ 166 public static function exists($shape) { 167 return class_exists((self::$classnameprefix).$shape); 168 } 169 170 /** 171 * Creates a new shape of the specified type. 172 * 173 * @param string $shape The shape to create 174 * @param string $coordsstring The string describing the coordinates 175 * @return object 176 */ 177 public static function create($shape, $coordsstring) { 178 $classname = (self::$classnameprefix).$shape; 179 return new $classname($coordsstring); 180 } 181 } 182 183 184 /** 185 * Class to represent a rectangle. 186 * 187 * @copyright 2012 The Open University 188 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 189 */ 190 class qtype_ddmarker_shape_rectangle extends qtype_ddmarker_shape { 191 /** @var int Width of shape */ 192 protected $width; 193 194 /** @var int Height of shape */ 195 protected $height; 196 197 /** @var int Left location */ 198 protected $xleft; 199 200 /** @var int Top location */ 201 protected $ytop; 202 203 public function __construct($coordsstring) { 204 $coordstring = preg_replace('!^\s*!', '', $coordsstring); 205 $coordstring = preg_replace('!\s*$!', '', $coordsstring); 206 $coordsstringparts = preg_split('!;!', $coordsstring); 207 208 if (count($coordsstringparts) > 2) { 209 $this->error = 'toomanysemicolons'; 210 211 } else if (count($coordsstringparts) < 2) { 212 $this->error = 'nosemicolons'; 213 214 } else { 215 $xy = explode(',', $coordsstringparts[0]); 216 $widthheightparts = explode(',', $coordsstringparts[1]); 217 if (count($xy) !== 2) { 218 $this->error = 'unrecognisedxypart'; 219 } else if (count($widthheightparts) !== 2) { 220 $this->error = 'unrecognisedwidthheightpart'; 221 } else { 222 $this->width = trim($widthheightparts[0]); 223 $this->height = trim($widthheightparts[1]); 224 $this->xleft = trim($xy[0]); 225 $this->ytop = trim($xy[1]); 226 } 227 if (!$this->is_only_numbers($this->width, $this->height, $this->ytop, $this->xleft)) { 228 $this->error = 'onlyusewholepositivenumbers'; 229 } 230 $this->width = (int) $this->width; 231 $this->height = (int) $this->height; 232 $this->xleft = (int) $this->xleft; 233 $this->ytop = (int) $this->ytop; 234 } 235 236 } 237 protected function outlying_coords_to_test() { 238 return array($this->xleft + $this->width, $this->ytop + $this->height); 239 } 240 public function is_point_in_shape($xy) { 241 return $this->is_point_in_bounding_box($xy, array($this->xleft, $this->ytop), 242 array($this->xleft + $this->width, $this->ytop + $this->height)); 243 } 244 public function center_point() { 245 return array($this->xleft + round($this->width / 2), 246 $this->ytop + round($this->height / 2)); 247 } 248 } 249 250 251 /** 252 * Class to represent a circle. 253 * 254 * @copyright 2012 The Open University 255 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 256 */ 257 class qtype_ddmarker_shape_circle extends qtype_ddmarker_shape { 258 /** @var int X center */ 259 protected $xcentre; 260 261 /** @var int Y center */ 262 protected $ycentre; 263 264 /** @var int Radius of circle */ 265 protected $radius; 266 267 public function __construct($coordsstring) { 268 $coordstring = preg_replace('!\s!', '', $coordsstring); 269 $coordsstringparts = explode(';', $coordsstring); 270 271 if (count($coordsstringparts) > 2) { 272 $this->error = 'toomanysemicolons'; 273 274 } else if (count($coordsstringparts) < 2) { 275 $this->error = 'nosemicolons'; 276 277 } else { 278 $xy = explode(',', $coordsstringparts[0]); 279 if (count($xy) !== 2) { 280 $this->error = 'unrecognisedxypart'; 281 } else { 282 $this->radius = trim($coordsstringparts[1]); 283 $this->xcentre = trim($xy[0]); 284 $this->ycentre = trim($xy[1]); 285 } 286 287 if (!$this->is_only_numbers($this->xcentre, $this->ycentre, $this->radius)) { 288 $this->error = 'onlyusewholepositivenumbers'; 289 } 290 291 $this->xcentre = (int) $this->xcentre; 292 $this->ycentre = (int) $this->ycentre; 293 $this->radius = (int) $this->radius; 294 } 295 } 296 297 protected function outlying_coords_to_test() { 298 return array($this->xcentre + $this->radius, $this->ycentre + $this->radius); 299 } 300 301 public function is_point_in_shape($xy) { 302 $distancefromcentre = sqrt(pow(($xy[0] - $this->xcentre), 2) + pow(($xy[1] - $this->ycentre), 2)); 303 return $distancefromcentre <= $this->radius; 304 } 305 306 public function center_point() { 307 return array($this->xcentre, $this->ycentre); 308 } 309 } 310 311 312 /** 313 * Class to represent a polygon. 314 * 315 * @copyright 2012 The Open University 316 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 317 */ 318 class qtype_ddmarker_shape_polygon extends qtype_ddmarker_shape { 319 /** 320 * @var array Arrary of xy coords where xy coords are also in a two element array [x,y]. 321 */ 322 public $coords; 323 /** 324 * @var array min x and y coords in a two element array [x,y]. 325 */ 326 protected $minxy; 327 /** 328 * @var array max x and y coords in a two element array [x,y]. 329 */ 330 protected $maxxy; 331 332 public function __construct($coordsstring) { 333 $this->coords = array(); 334 $coordstring = preg_replace('!\s!', '', $coordsstring); 335 $coordsstringparts = explode(';', $coordsstring); 336 if (count($coordsstringparts) < 3) { 337 $this->error = 'polygonmusthaveatleastthreepoints'; 338 } else { 339 $lastxy = null; 340 foreach ($coordsstringparts as $coordsstringpart) { 341 $xy = explode(',', $coordsstringpart); 342 if (count($xy) !== 2) { 343 $this->error = 'unrecognisedxypart'; 344 } 345 if (!$this->is_only_numbers(trim($xy[0]), trim($xy[1]))) { 346 $this->error = 'onlyusewholepositivenumbers'; 347 } 348 $xy[0] = (int) $xy[0]; 349 $xy[1] = (int) $xy[1]; 350 if ($lastxy !== null && $lastxy[0] == $xy[0] && $lastxy[1] == $xy[1]) { 351 $this->error = 'repeatedpoint'; 352 } 353 $this->coords[] = $xy; 354 $lastxy = $xy; 355 if (isset($this->minxy)) { 356 $this->minxy[0] = min($this->minxy[0], $xy[0]); 357 $this->minxy[1] = min($this->minxy[1], $xy[1]); 358 } else { 359 $this->minxy[0] = $xy[0]; 360 $this->minxy[1] = $xy[1]; 361 } 362 if (isset($this->maxxy)) { 363 $this->maxxy[0] = max($this->maxxy[0], $xy[0]); 364 $this->maxxy[1] = max($this->maxxy[1], $xy[1]); 365 } else { 366 $this->maxxy[0] = $xy[0]; 367 $this->maxxy[1] = $xy[1]; 368 } 369 } 370 // Make sure polygon is not closed. 371 if ($this->coords[count($this->coords) - 1][0] == $this->coords[0][0] && 372 $this->coords[count($this->coords) - 1][1] == $this->coords[0][1]) { 373 unset($this->coords[count($this->coords) - 1]); 374 } 375 } 376 } 377 378 protected function outlying_coords_to_test() { 379 return array($this->minxy, $this->maxxy); 380 } 381 382 public function is_point_in_shape($xy) { 383 // This code is based on the winding number algorithm from 384 // http://geomalgorithms.com/a03-_inclusion.html 385 // which comes with the following copyright notice: 386 387 // Copyright 2000 softSurfer, 2012 Dan Sunday 388 // This code may be freely used, distributed and modified for any purpose 389 // providing that this copyright notice is included with it. 390 // SoftSurfer makes no warranty for this code, and cannot be held 391 // liable for any real or imagined damage resulting from its use. 392 // Users of this code must verify correctness for their application. 393 394 $point = new qtype_ddmarker_point($xy[0], $xy[1]); 395 $windingnumber = 0; 396 foreach ($this->coords as $index => $coord) { 397 $start = new qtype_ddmarker_point($this->coords[$index][0], $this->coords[$index][1]); 398 if ($index < count($this->coords) - 1) { 399 $endindex = $index + 1; 400 } else { 401 $endindex = 0; 402 } 403 $end = new qtype_ddmarker_point($this->coords[$endindex][0], $this->coords[$endindex][1]); 404 405 if ($start->y <= $point->y) { 406 if ($end->y >= $point->y) { // An upward crossing. 407 $isleft = $this->is_left($start, $end, $point); 408 if ($isleft == 0) { 409 return true; // The point is on the line. 410 } else if ($isleft > 0) { 411 // A valid up intersect. 412 $windingnumber += 1; 413 } 414 } 415 } else { 416 if ($end->y <= $point->y) { // A downward crossing. 417 $isleft = $this->is_left($start, $end, $point); 418 if ($isleft == 0) { 419 return true; // The point is on the line. 420 } else if ($this->is_left($start, $end, $point) < 0) { 421 // A valid down intersect. 422 $windingnumber -= 1; 423 } 424 } 425 } 426 } 427 return $windingnumber != 0; 428 } 429 430 /** 431 * Tests if a point is left / on / right of an infinite line. 432 * 433 * @param qtype_ddmarker_point $start first of two points on the infinite line. 434 * @param qtype_ddmarker_point $end second of two points on the infinite line. 435 * @param qtype_ddmarker_point $point the oint to test. 436 * @return number > 0 if the point is left of the line. 437 * = 0 if the point is on the line. 438 * < 0 if the point is right of the line. 439 */ 440 protected function is_left(qtype_ddmarker_point $start, qtype_ddmarker_point $end, 441 qtype_ddmarker_point $point) { 442 return ($end->x - $start->x) * ($point->y - $start->y) 443 - ($point->x - $start->x) * ($end->y - $start->y); 444 } 445 446 public function center_point() { 447 $center = array(round(($this->minxy[0] + $this->maxxy[0]) / 2), 448 round(($this->minxy[1] + $this->maxxy[1]) / 2)); 449 if ($this->is_point_in_shape($center)) { 450 return $center; 451 } else { 452 return null; 453 } 454 } 455 } 456 457 458 /** 459 * Class to represent a point. 460 * 461 * @copyright 2012 The Open University 462 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 463 */ 464 class qtype_ddmarker_point { 465 /** @var int X location */ 466 public $x; 467 468 /** @var int Y location */ 469 public $y; 470 public function __construct($x, $y) { 471 $this->x = $x; 472 $this->y = $y; 473 } 474 475 /** 476 * Return the distance between this point and another 477 */ 478 public function dist($other) { 479 return sqrt(pow($this->x - $other->x, 2) + pow($this->y - $other->y, 2)); 480 } 481 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |