AutorÃa | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>./*** Drag-and-drop markers classes for dealing with shapes on the server side.** @package qtype_ddmarker* @copyright 2012 The Open University* @author Jamie Pratt <me@jamiep.org>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*//*** Base class to represent a shape.** @copyright 2012 The Open University* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/abstract class qtype_ddmarker_shape {/** @var bool Indicates if there is an error */protected $error = false;/** @var string The shape class prefix */protected static $classnameprefix = 'qtype_ddmarker_shape_';public function __construct($coordsstring) {}public function inside_width_height($widthheight) {foreach ($this->outlying_coords_to_test() as $coordsxy) {if ($coordsxy[0] < 0 || $coordsxy[0] > $widthheight[0] ||$coordsxy[1] < 0 || $coordsxy[1] > $widthheight[1]) {return false;}}return true;}abstract protected function outlying_coords_to_test();/*** Returns the center location of the shape.** @return array X and Y location*/abstract public function center_point();/*** Test if all passed parameters consist of only numbers.** @return bool True if only numbers*/protected function is_only_numbers() {$args = func_get_args();foreach ($args as $arg) {if (0 === preg_match('!^[0-9]+$!', $arg)) {return false;}}return true;}/*** Checks if the point is within the bounding box made by top left and bottom right** @param array $pointxy Array of the point (x, y)* @param array $xleftytop Top left point of bounding box* @param array $xrightybottom Bottom left point of bounding box* @return bool*/protected function is_point_in_bounding_box($pointxy, $xleftytop, $xrightybottom) {if ($pointxy[0] < $xleftytop[0]) {return false;} else if ($pointxy[0] > $xrightybottom[0]) {return false;} else if ($pointxy[1] < $xleftytop[1]) {return false;} else if ($pointxy[1] > $xrightybottom[1]) {return false;}return true;}/*** Gets any coordinate error** @return string|bool String of the error or false if there is no error*/public function get_coords_interpreter_error() {if ($this->error) {$a = new stdClass();$a->shape = self::human_readable_name(true);$a->coordsstring = self::human_readable_coords_format();return get_string('formerror_'.$this->error, 'qtype_ddmarker', $a);} else {return false;}}/*** Check if the location is within the shape.** @param array $xy $xy[0] is x, $xy[1] is y* @return boolean is point inside shape*/abstract public function is_point_in_shape($xy);/*** Returns the name of the shape.** @return string*/public static function name() {return substr(get_called_class(), strlen(self::$classnameprefix));}/*** Return a human readable name of the shape.** @param bool $lowercase True if it should be lowercase.* @return string*/public static function human_readable_name($lowercase = false) {$stringid = 'shape_'.self::name();if ($lowercase) {$stringid .= '_lowercase';}return get_string($stringid, 'qtype_ddmarker');}public static function human_readable_coords_format() {return get_string('shape_'.self::name().'_coords', 'qtype_ddmarker');}public static function shape_options() {$grepexpression = '!^'.preg_quote(self::$classnameprefix, '!').'!';$shapes = preg_grep($grepexpression, get_declared_classes());$shapearray = array();foreach ($shapes as $shape) {$shapearray[$shape::name()] = $shape::human_readable_name();}$shapearray['0'] = '';asort($shapearray);return $shapearray;}/*** Checks if the passed shape exists.** @param string $shape The shape name* @return bool*/public static function exists($shape) {return class_exists((self::$classnameprefix).$shape);}/*** Creates a new shape of the specified type.** @param string $shape The shape to create* @param string $coordsstring The string describing the coordinates* @return object*/public static function create($shape, $coordsstring) {$classname = (self::$classnameprefix).$shape;return new $classname($coordsstring);}}/*** Class to represent a rectangle.** @copyright 2012 The Open University* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class qtype_ddmarker_shape_rectangle extends qtype_ddmarker_shape {/** @var int Width of shape */protected $width;/** @var int Height of shape */protected $height;/** @var int Left location */protected $xleft;/** @var int Top location */protected $ytop;public function __construct($coordsstring) {$coordstring = preg_replace('!^\s*!', '', $coordsstring);$coordstring = preg_replace('!\s*$!', '', $coordsstring);$coordsstringparts = preg_split('!;!', $coordsstring);if (count($coordsstringparts) > 2) {$this->error = 'toomanysemicolons';} else if (count($coordsstringparts) < 2) {$this->error = 'nosemicolons';} else {$xy = explode(',', $coordsstringparts[0]);$widthheightparts = explode(',', $coordsstringparts[1]);if (count($xy) !== 2) {$this->error = 'unrecognisedxypart';} else if (count($widthheightparts) !== 2) {$this->error = 'unrecognisedwidthheightpart';} else {$this->width = trim($widthheightparts[0]);$this->height = trim($widthheightparts[1]);$this->xleft = trim($xy[0]);$this->ytop = trim($xy[1]);}if (!$this->is_only_numbers($this->width, $this->height, $this->ytop, $this->xleft)) {$this->error = 'onlyusewholepositivenumbers';}$this->width = (int) $this->width;$this->height = (int) $this->height;$this->xleft = (int) $this->xleft;$this->ytop = (int) $this->ytop;}}protected function outlying_coords_to_test() {return [[$this->xleft, $this->ytop], [$this->xleft + $this->width, $this->ytop + $this->height]];}public function is_point_in_shape($xy) {return $this->is_point_in_bounding_box($xy, array($this->xleft, $this->ytop),array($this->xleft + $this->width, $this->ytop + $this->height));}public function center_point() {return array($this->xleft + round($this->width / 2),$this->ytop + round($this->height / 2));}}/*** Class to represent a circle.** @copyright 2012 The Open University* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class qtype_ddmarker_shape_circle extends qtype_ddmarker_shape {/** @var int X center */protected $xcentre;/** @var int Y center */protected $ycentre;/** @var int Radius of circle */protected $radius;public function __construct($coordsstring) {$coordstring = preg_replace('!\s!', '', $coordsstring);$coordsstringparts = explode(';', $coordsstring);if (count($coordsstringparts) > 2) {$this->error = 'toomanysemicolons';} else if (count($coordsstringparts) < 2) {$this->error = 'nosemicolons';} else {$xy = explode(',', $coordsstringparts[0]);if (count($xy) !== 2) {$this->error = 'unrecognisedxypart';} else {$this->radius = trim($coordsstringparts[1]);$this->xcentre = trim($xy[0]);$this->ycentre = trim($xy[1]);}if (!$this->is_only_numbers($this->xcentre, $this->ycentre, $this->radius)) {$this->error = 'onlyusewholepositivenumbers';}$this->xcentre = (int) $this->xcentre;$this->ycentre = (int) $this->ycentre;$this->radius = (int) $this->radius;}}protected function outlying_coords_to_test() {return [[$this->xcentre - $this->radius, $this->ycentre - $this->radius],[$this->xcentre + $this->radius, $this->ycentre + $this->radius]];}public function is_point_in_shape($xy) {$distancefromcentre = sqrt(pow(($xy[0] - $this->xcentre), 2) + pow(($xy[1] - $this->ycentre), 2));return $distancefromcentre <= $this->radius;}public function center_point() {return array($this->xcentre, $this->ycentre);}}/*** Class to represent a polygon.** @copyright 2012 The Open University* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class qtype_ddmarker_shape_polygon extends qtype_ddmarker_shape {/*** @var array Arrary of xy coords where xy coords are also in a two element array [x,y].*/public $coords;/*** @var array min x and y coords in a two element array [x,y].*/protected $minxy;/*** @var array max x and y coords in a two element array [x,y].*/protected $maxxy;public function __construct($coordsstring) {$this->coords = array();$coordstring = preg_replace('!\s!', '', $coordsstring);$coordsstringparts = explode(';', $coordsstring);if (count($coordsstringparts) < 3) {$this->error = 'polygonmusthaveatleastthreepoints';} else {$lastxy = null;foreach ($coordsstringparts as $coordsstringpart) {$xy = explode(',', $coordsstringpart);if (count($xy) !== 2) {$this->error = 'unrecognisedxypart';}if (!$this->is_only_numbers(trim($xy[0]), trim($xy[1]))) {$this->error = 'onlyusewholepositivenumbers';}$xy[0] = (int) $xy[0];$xy[1] = (int) $xy[1];if ($lastxy !== null && $lastxy[0] == $xy[0] && $lastxy[1] == $xy[1]) {$this->error = 'repeatedpoint';}$this->coords[] = $xy;$lastxy = $xy;if (isset($this->minxy)) {$this->minxy[0] = min($this->minxy[0], $xy[0]);$this->minxy[1] = min($this->minxy[1], $xy[1]);} else {$this->minxy[0] = $xy[0];$this->minxy[1] = $xy[1];}if (isset($this->maxxy)) {$this->maxxy[0] = max($this->maxxy[0], $xy[0]);$this->maxxy[1] = max($this->maxxy[1], $xy[1]);} else {$this->maxxy[0] = $xy[0];$this->maxxy[1] = $xy[1];}}// Make sure polygon is not closed.if ($this->coords[count($this->coords) - 1][0] == $this->coords[0][0] &&$this->coords[count($this->coords) - 1][1] == $this->coords[0][1]) {unset($this->coords[count($this->coords) - 1]);}}}protected function outlying_coords_to_test() {return array($this->minxy, $this->maxxy);}public function is_point_in_shape($xy) {// This code is based on the winding number algorithm from// http://geomalgorithms.com/a03-_inclusion.html// which comes with the following copyright notice:// Copyright 2000 softSurfer, 2012 Dan Sunday// This code may be freely used, distributed and modified for any purpose// providing that this copyright notice is included with it.// SoftSurfer makes no warranty for this code, and cannot be held// liable for any real or imagined damage resulting from its use.// Users of this code must verify correctness for their application.$point = new qtype_ddmarker_point($xy[0], $xy[1]);$windingnumber = 0;foreach ($this->coords as $index => $coord) {$start = new qtype_ddmarker_point($this->coords[$index][0], $this->coords[$index][1]);if ($index < count($this->coords) - 1) {$endindex = $index + 1;} else {$endindex = 0;}$end = new qtype_ddmarker_point($this->coords[$endindex][0], $this->coords[$endindex][1]);if ($start->y <= $point->y) {if ($end->y >= $point->y) { // An upward crossing.$isleft = $this->is_left($start, $end, $point);if ($isleft == 0) {return true; // The point is on the line.} else if ($isleft > 0) {// A valid up intersect.$windingnumber += 1;}}} else {if ($end->y <= $point->y) { // A downward crossing.$isleft = $this->is_left($start, $end, $point);if ($isleft == 0) {return true; // The point is on the line.} else if ($this->is_left($start, $end, $point) < 0) {// A valid down intersect.$windingnumber -= 1;}}}}return $windingnumber != 0;}/*** Tests if a point is left / on / right of an infinite line.** @param qtype_ddmarker_point $start first of two points on the infinite line.* @param qtype_ddmarker_point $end second of two points on the infinite line.* @param qtype_ddmarker_point $point the oint to test.* @return number > 0 if the point is left of the line.* = 0 if the point is on the line.* < 0 if the point is right of the line.*/protected function is_left(qtype_ddmarker_point $start, qtype_ddmarker_point $end,qtype_ddmarker_point $point) {return ($end->x - $start->x) * ($point->y - $start->y)- ($point->x - $start->x) * ($end->y - $start->y);}public function center_point() {$center = array(round(($this->minxy[0] + $this->maxxy[0]) / 2),round(($this->minxy[1] + $this->maxxy[1]) / 2));if ($this->is_point_in_shape($center)) {return $center;} else {return null;}}}/*** Class to represent a point.** @copyright 2012 The Open University* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class qtype_ddmarker_point {/** @var int X location */public $x;/** @var int Y location */public $y;public function __construct($x, $y) {$this->x = $x;$this->y = $y;}/*** Return the distance between this point and another*/public function dist($other) {return sqrt(pow($this->x - $other->x, 2) + pow($this->y - $other->y, 2));}}