From 0b90836890f2da2d973010be4b5d5b64f6a3d58d Mon Sep 17 00:00:00 2001 From: David Manthey Date: Tue, 29 Mar 2022 16:07:18 -0400 Subject: [PATCH 1/2] Add support for ellipse and circle annotations. --- CHANGELOG.md | 3 + girder_annotation/docs/annotations.rst | 85 ++++++++++--------- .../web_client/annotations/defaults/circle.js | 5 ++ .../annotations/defaults/ellipse.js | 7 ++ .../web_client/annotations/defaults/index.js | 4 + .../annotations/geojs/types/circle.js | 18 ++++ .../annotations/geojs/types/ellipse.js | 13 +++ .../annotations/geojs/types/index.js | 4 + .../web_client/annotations/geometry/circle.js | 23 +++++ .../annotations/geometry/ellipse.js | 29 +++++++ .../web_client/annotations/geometry/index.js | 4 + .../web_client/models/AnnotationModel.js | 10 +++ .../views/imageViewerWidget/geojs.js | 7 +- 13 files changed, 170 insertions(+), 42 deletions(-) create mode 100644 girder_annotation/girder_large_image_annotation/web_client/annotations/defaults/circle.js create mode 100644 girder_annotation/girder_large_image_annotation/web_client/annotations/defaults/ellipse.js create mode 100644 girder_annotation/girder_large_image_annotation/web_client/annotations/geojs/types/circle.js create mode 100644 girder_annotation/girder_large_image_annotation/web_client/annotations/geojs/types/ellipse.js create mode 100644 girder_annotation/girder_large_image_annotation/web_client/annotations/geometry/circle.js create mode 100644 girder_annotation/girder_large_image_annotation/web_client/annotations/geometry/ellipse.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 44589726c..995ba6bd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +### Features +- Add support for ellipse and circle annotations + ### Improvements - Improve parsing OME TIFF channel names ([806](../../pull/806)) - Improve handling when a file vanishes ([807](../../pull/807)) diff --git a/girder_annotation/docs/annotations.rst b/girder_annotation/docs/annotations.rst index ad9012603..e3852d958 100644 --- a/girder_annotation/docs/annotations.rst +++ b/girder_annotation/docs/annotations.rst @@ -48,35 +48,28 @@ it is not allowed. If element IDs are specified, they must be unique. "fontSize": 3.4, # Number. Optional "color": "#0000FF" # String. See note about colors. Optional }, - "lineColor": "#000000", # String. See note about colors. Optional - "lineWidth": 1, # Number >= 0. Optional "group": "group name", # String. Optional "user": {}, # User properties -- this can contain anything, # but should be kept small. Optional. } -Arrow -~~~~~ -Not currently rendered. +All Vector Shapes +~~~~~~~~~~~~~~~~~ + +These properties exist for all vector shapes (all but heatmaps, grid data, and image and pixelmap overlays). :: { - "type": "arrow", # Exact string. Required - # Optional general shape properties - "points": [ # Arrows ALWAYS have two points - [5,6,0], # Coordinate. Arrow head. Required - [-17,6,0] # Coordinate. Aroow tail. Required - ] + "lineColor": "#000000", # String. See note about colors. Optional + "lineWidth": 1, # Number >= 0. Optional } Circle ~~~~~~ -Not currently rendered. - :: { @@ -90,8 +83,6 @@ Not currently rendered. Ellipse ~~~~~~~ -Not currently rendered. - The width and height of an ellipse are the major and minor axes. :: @@ -155,30 +146,6 @@ Rectangle "fillColor": "rgba(0, 255, 0, 1)" # String. See note about colors. Optional } -Rectangle Grid -~~~~~~~~~~~~~~ - -Not currently rendered. - -A Rectangle Grid is a rectangle which contains regular subdivisions, -such as that used to show a regular scale grid overlay on an image. - -:: - - { - "type": "rectanglegrid", # Exact string. Required - # Optional general shape properties - "center": [10.3, -40.0, 0], # Coordinate. Required - "width": 5.3, # Number >= 0. Required - "height": 17.3, # Number >= 0. Required - "rotation": 0, # Number. Counterclockwise radians around normal. Required - "normal": [0, 0, 1.0], # Three numbers specifying normal. Default is positive Z. - # Optional - "widthSubdivisions": 3, # Integer > 0. Required - "heightSubdivisions": 4, # Integer > 0. Required - "fillColor": "rgba(0, 255, 0, 1)" # String. See note about colors. Optional - } - Heatmap ~~~~~~~ @@ -357,6 +324,46 @@ integers which correspond to indices in a ``categories`` array. ] } +Arrow +~~~~~ + +Not currently rendered. + +:: + + { + "type": "arrow", # Exact string. Required + # Optional general shape properties + "points": [ # Arrows ALWAYS have two points + [5,6,0], # Coordinate. Arrow head. Required + [-17,6,0] # Coordinate. Aroow tail. Required + ] + } + +Rectangle Grid +~~~~~~~~~~~~~~ + +Not currently rendered. + +A Rectangle Grid is a rectangle which contains regular subdivisions, +such as that used to show a regular scale grid overlay on an image. + +:: + + { + "type": "rectanglegrid", # Exact string. Required + # Optional general shape properties + "center": [10.3, -40.0, 0], # Coordinate. Required + "width": 5.3, # Number >= 0. Required + "height": 17.3, # Number >= 0. Required + "rotation": 0, # Number. Counterclockwise radians around normal. Required + "normal": [0, 0, 1.0], # Three numbers specifying normal. Default is positive Z. + # Optional + "widthSubdivisions": 3, # Integer > 0. Required + "heightSubdivisions": 4, # Integer > 0. Required + "fillColor": "rgba(0, 255, 0, 1)" # String. See note about colors. Optional + } + Component Values ---------------- diff --git a/girder_annotation/girder_large_image_annotation/web_client/annotations/defaults/circle.js b/girder_annotation/girder_large_image_annotation/web_client/annotations/defaults/circle.js new file mode 100644 index 000000000..177d76590 --- /dev/null +++ b/girder_annotation/girder_large_image_annotation/web_client/annotations/defaults/circle.js @@ -0,0 +1,5 @@ +export default { + fillColor: 'rgba(0,0,0,0)', + lineColor: 'rgb(0,0,0)', + lineWidth: 2 +}; diff --git a/girder_annotation/girder_large_image_annotation/web_client/annotations/defaults/ellipse.js b/girder_annotation/girder_large_image_annotation/web_client/annotations/defaults/ellipse.js new file mode 100644 index 000000000..7a63d58b8 --- /dev/null +++ b/girder_annotation/girder_large_image_annotation/web_client/annotations/defaults/ellipse.js @@ -0,0 +1,7 @@ +export default { + fillColor: 'rgba(0,0,0,0)', + lineColor: 'rgb(0,0,0)', + lineWidth: 2, + rotation: 0, + normal: [0, 0, 1] +}; diff --git a/girder_annotation/girder_large_image_annotation/web_client/annotations/defaults/index.js b/girder_annotation/girder_large_image_annotation/web_client/annotations/defaults/index.js index 9ee798f3b..d3f53b07e 100644 --- a/girder_annotation/girder_large_image_annotation/web_client/annotations/defaults/index.js +++ b/girder_annotation/girder_large_image_annotation/web_client/annotations/defaults/index.js @@ -1,9 +1,13 @@ import rectangle from './rectangle'; +import ellipse from './ellipse'; +import circle from './circle'; import polyline from './polyline'; import point from './point'; export { rectangle, + ellipse, + circle, polyline, point }; diff --git a/girder_annotation/girder_large_image_annotation/web_client/annotations/geojs/types/circle.js b/girder_annotation/girder_large_image_annotation/web_client/annotations/geojs/types/circle.js new file mode 100644 index 000000000..d2054fdb5 --- /dev/null +++ b/girder_annotation/girder_large_image_annotation/web_client/annotations/geojs/types/circle.js @@ -0,0 +1,18 @@ +import rectangle from './rectangle'; + +/** + * Convert a geojs circle annotation to the large_image + * annotation schema. + */ +function circle(annotation) { + const element = rectangle(annotation); + element.type = 'circle'; + element.radius = Math.max(element.width, element.height) / 2; + delete element.width; + delete element.height; + delete element.rotation; + delete element.normal; + return element; +} + +export default circle; diff --git a/girder_annotation/girder_large_image_annotation/web_client/annotations/geojs/types/ellipse.js b/girder_annotation/girder_large_image_annotation/web_client/annotations/geojs/types/ellipse.js new file mode 100644 index 000000000..c15f1a17b --- /dev/null +++ b/girder_annotation/girder_large_image_annotation/web_client/annotations/geojs/types/ellipse.js @@ -0,0 +1,13 @@ +import rectangle from './rectangle'; + +/** + * Convert a geojs ellipse annotation to the large_image + * annotation schema. + */ +function ellipse(annotation) { + const element = rectangle(annotation); + element.type = 'ellipse'; + return element; +} + +export default ellipse; diff --git a/girder_annotation/girder_large_image_annotation/web_client/annotations/geojs/types/index.js b/girder_annotation/girder_large_image_annotation/web_client/annotations/geojs/types/index.js index 835110c18..76d265d1c 100644 --- a/girder_annotation/girder_large_image_annotation/web_client/annotations/geojs/types/index.js +++ b/girder_annotation/girder_large_image_annotation/web_client/annotations/geojs/types/index.js @@ -1,11 +1,15 @@ import point from './point'; import rectangle from './rectangle'; +import ellipse from './ellipse'; +import circle from './circle'; import polygon from './polygon'; import line from './line'; export { point, rectangle, + ellipse, + circle, polygon, line }; diff --git a/girder_annotation/girder_large_image_annotation/web_client/annotations/geometry/circle.js b/girder_annotation/girder_large_image_annotation/web_client/annotations/geometry/circle.js new file mode 100644 index 000000000..d2233d431 --- /dev/null +++ b/girder_annotation/girder_large_image_annotation/web_client/annotations/geometry/circle.js @@ -0,0 +1,23 @@ +export default function circle(json) { + const center = json.center; + const x = center[0]; + const y = center[1]; + const radius = json.radius; + + const left = x - radius; + const right = x + radius; + const top = y - radius; + const bottom = y + radius; + + return { + type: 'Polygon', + coordinates: [[ + [left, top], + [right, top], + [right, bottom], + [left, bottom], + [left, top] + ]], + annotationType: 'circle' + }; +} diff --git a/girder_annotation/girder_large_image_annotation/web_client/annotations/geometry/ellipse.js b/girder_annotation/girder_large_image_annotation/web_client/annotations/geometry/ellipse.js new file mode 100644 index 000000000..d18993990 --- /dev/null +++ b/girder_annotation/girder_large_image_annotation/web_client/annotations/geometry/ellipse.js @@ -0,0 +1,29 @@ +import _ from 'underscore'; + +import rotate from '../rotate'; + +export default function ellipse(json) { + const center = json.center; + const x = center[0]; + const y = center[1]; + const height = json.height; + const width = json.width; + const rotation = json.rotation || 0; + + const left = x - width / 2; + const right = x + width / 2; + const top = y - height / 2; + const bottom = y + height / 2; + + return { + type: 'Polygon', + coordinates: [_.map([ + [left, top], + [right, top], + [right, bottom], + [left, bottom], + [left, top] + ], rotate(rotation, center))], + annotationType: 'ellipse' + }; +} diff --git a/girder_annotation/girder_large_image_annotation/web_client/annotations/geometry/index.js b/girder_annotation/girder_large_image_annotation/web_client/annotations/geometry/index.js index 9ee798f3b..d3f53b07e 100644 --- a/girder_annotation/girder_large_image_annotation/web_client/annotations/geometry/index.js +++ b/girder_annotation/girder_large_image_annotation/web_client/annotations/geometry/index.js @@ -1,9 +1,13 @@ import rectangle from './rectangle'; +import ellipse from './ellipse'; +import circle from './circle'; import polyline from './polyline'; import point from './point'; export { rectangle, + ellipse, + circle, polyline, point }; diff --git a/girder_annotation/girder_large_image_annotation/web_client/models/AnnotationModel.js b/girder_annotation/girder_large_image_annotation/web_client/models/AnnotationModel.js index eb0970f42..edfe10576 100644 --- a/girder_annotation/girder_large_image_annotation/web_client/models/AnnotationModel.js +++ b/girder_annotation/girder_large_image_annotation/web_client/models/AnnotationModel.js @@ -92,6 +92,16 @@ export default AccessControlledModel.extend({ strokeColor: {r: 153 / 255, g: 153 / 255, b: 153 / 255}, strokeWidth: 2 }, + ellipse: { + fillColor: {r: 176 / 255, g: 222 / 255, b: 92 / 255}, + strokeColor: {r: 153 / 255, g: 153 / 255, b: 153 / 255}, + strokeWidth: 2 + }, + circle: { + fillColor: {r: 176 / 255, g: 222 / 255, b: 92 / 255}, + strokeColor: {r: 153 / 255, g: 153 / 255, b: 153 / 255}, + strokeWidth: 2 + }, polyline: { strokeColor: {r: 1, g: 120 / 255, b: 0}, strokeOpacity: 0.5, diff --git a/girder_annotation/girder_large_image_annotation/web_client/views/imageViewerWidget/geojs.js b/girder_annotation/girder_large_image_annotation/web_client/views/imageViewerWidget/geojs.js index cba2175f9..d50817afa 100644 --- a/girder_annotation/girder_large_image_annotation/web_client/views/imageViewerWidget/geojs.js +++ b/girder_annotation/girder_large_image_annotation/web_client/views/imageViewerWidget/geojs.js @@ -41,13 +41,13 @@ var GeojsImageViewerWidgetExtension = function (viewer) { _postRender: function () { // the feature layer is for annotations that are loaded this.featureLayer = this.viewer.createLayer('feature', { - features: ['point', 'line', 'polygon'] + features: ['point', 'line', 'polygon', 'marker'] }); this.setGlobalAnnotationOpacity(this._globalAnnotationOpacity); this.setGlobalAnnotationFillOpacity(this._globalAnnotationFillOpacity); // the annotation layer is for annotations that are actively drawn this.annotationLayer = this.viewer.createLayer('annotation', { - annotations: ['point', 'line', 'rectangle', 'polygon'], + annotations: ['point', 'line', 'rectangle', 'ellipse', 'circle', 'polygon'], showLabels: false }); var geo = window.geo; // this makes the style checker happy @@ -220,7 +220,8 @@ var GeojsImageViewerWidgetExtension = function (viewer) { /** * Render an annotation model on the image. Currently, this is limited * to annotation types that can be (1) directly converted into geojson - * primitives, OR (2) be represented as heatmaps. + * primitives, (2) be represented as heatmaps, or (3) shown as image + * overlays. * * Internally, this generates a new feature layer for the annotation * that is referenced by the annotation id. All "elements" contained From 884372598a1f071ce9dd4ee2f1b2e4d4df3e9c10 Mon Sep 17 00:00:00 2001 From: David Manthey Date: Wed, 30 Mar 2022 14:39:29 -0400 Subject: [PATCH 2/2] Add tests. --- CHANGELOG.md | 3 +- .../web_client/package.json | 2 +- .../web_client_specs/annotationSpec.js | 38 +++++ .../web_client_specs/geojsAnnotationSpec.js | 39 +++++ .../web_client_specs/geojsSpec.js | 140 +++++++++++++++++- 5 files changed, 217 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 995ba6bd1..823d43d84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,12 @@ ## Unreleased ### Features -- Add support for ellipse and circle annotations +- Add support for ellipse and circle annotations ([811](../../pull/811)) ### Improvements - Improve parsing OME TIFF channel names ([806](../../pull/806)) - Improve handling when a file vanishes ([807](../../pull/807)) +- Add TileSourceXYZRangeError ([809](../../pull/809)) ## Version 1.12.0 diff --git a/girder/girder_large_image/web_client/package.json b/girder/girder_large_image/web_client/package.json index c72aec026..8e9a2fe7c 100644 --- a/girder/girder_large_image/web_client/package.json +++ b/girder/girder_large_image/web_client/package.json @@ -17,7 +17,7 @@ "dependencies": { "copy-webpack-plugin": "^4.5.2", "d3": "^3.5.16", - "geojs": "^1.0.1", + "geojs": "^1.8.0", "hammerjs": "^2.0.8", "js-yaml": "^3.14.0", "sinon": "^2.1.0", diff --git a/girder_annotation/test_annotation/web_client_specs/annotationSpec.js b/girder_annotation/test_annotation/web_client_specs/annotationSpec.js index c233aad1b..e17d60b9f 100644 --- a/girder_annotation/test_annotation/web_client_specs/annotationSpec.js +++ b/girder_annotation/test_annotation/web_client_specs/annotationSpec.js @@ -132,6 +132,44 @@ describe('Annotations', function () { expect(obj.coordinates).toEqual([1, 2]); }); + it('ellipse', function () { + var obj = largeImageAnnotation.annotations.geometry.ellipse({ + type: 'ellipse', + id: 'a', + center: [10, 20, 0], + width: 5, + height: 10, + rotation: 0 + }); + + expect(obj.type).toBe('Polygon'); + expect(obj.coordinates.length).toBe(1); + expect(obj.annotationType).toBe('ellipse'); + expectClose( + obj.coordinates[0], [ + [7.5, 15], [12.5, 15], [12.5, 25], [7.5, 25], [7.5, 15] + ] + ); + }); + + it('circle', function () { + var obj = largeImageAnnotation.annotations.geometry.circle({ + type: 'circle', + id: 'a', + center: [10, 20, 0], + radius: 5 + }); + + expect(obj.type).toBe('Polygon'); + expect(obj.coordinates.length).toBe(1); + expect(obj.annotationType).toBe('circle'); + expectClose( + obj.coordinates[0], [ + [5, 15], [15, 15], [15, 25], [5, 25], [5, 15] + ] + ); + }); + describe('heatmapColorTable', function () { var values = [0.508, 0.806, 0.311, 0.402, 0.535, 0.661, 0.866, 0.31, 0.241, 0.63, 0.555, 0.067, 0.668, 0.164, 0.512, 0.647, 0.501, 0.637, 0.498, 0.658, 0.332, 0.431, 0.053, 0.531]; var tests = [{ diff --git a/girder_annotation/test_annotation/web_client_specs/geojsAnnotationSpec.js b/girder_annotation/test_annotation/web_client_specs/geojsAnnotationSpec.js index 5682ebdbc..80896a9aa 100644 --- a/girder_annotation/test_annotation/web_client_specs/geojsAnnotationSpec.js +++ b/girder_annotation/test_annotation/web_client_specs/geojsAnnotationSpec.js @@ -219,5 +219,44 @@ describe('geojs-annotations', function () { lineWidth: lineWidth }); }); + + it('ellipse', function () { + type = 'ellipse'; + coordinates = [ + {x: 1, y: 2}, + {x: 1, y: 4}, + {x: 3, y: 4}, + {x: 3, y: 2} + ]; + expect(geojs.types.ellipse(annotation)).toEqual({ + type: 'ellipse', + center: [2, 3, 0], + width: 2, + height: 2, + rotation: 0, + fillColor: fillColor, + lineColor: lineColor, + lineWidth: lineWidth, + normal: [0, 0, 1] + }); + }); + + it('circle', function () { + type = 'circle'; + coordinates = [ + {x: 1, y: 2}, + {x: 1, y: 4}, + {x: 3, y: 4}, + {x: 3, y: 2} + ]; + expect(geojs.types.circle(annotation)).toEqual({ + type: 'circle', + center: [2, 3, 0], + radius: 1, + fillColor: fillColor, + lineColor: lineColor, + lineWidth: lineWidth + }); + }); }); }); diff --git a/girder_annotation/test_annotation/web_client_specs/geojsSpec.js b/girder_annotation/test_annotation/web_client_specs/geojsSpec.js index beb146cc3..bc5c691bb 100644 --- a/girder_annotation/test_annotation/web_client_specs/geojsSpec.js +++ b/girder_annotation/test_annotation/web_client_specs/geojsSpec.js @@ -452,7 +452,7 @@ $(function () { expect(elements.length).toBe(1); expect(elements[0].type).toBe('point'); closeTo(elements[0].center, [100, 200, 0]); - created = true; + created = true;; return null; }); var pt = viewer.viewer.gcsToDisplay({x: 100, y: 200}); @@ -483,7 +483,7 @@ $(function () { closeTo(elements[0].points[0], [100, 200, 0]); closeTo(elements[0].points[1], [200, 200, 0]); closeTo(elements[0].points[2], [200, 300, 0]); - created = true; + created = true;; return null; }); @@ -550,6 +550,140 @@ $(function () { }); }); + it('draw rectangle', function () { + var created; + + runs(function () { + viewer.startDrawMode('rectangle').then(function (elements) { + expect(elements.length).toBe(1); + closeTo(elements[0].center, [150, 300, 0]); + expect(elements[0].width).toBe(100); + expect(elements[0].height).toBe(200); + created = true;; + return null; + }); + + interactor.simulateEvent('mousemove', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 100, y: 200}) + }); + interactor.simulateEvent('mousedown', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 100, y: 200}) + }); + interactor.simulateEvent('mouseup', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 100, y: 200}) + }); + + interactor.simulateEvent('mousemove', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 200, y: 400}) + }); + interactor.simulateEvent('mousedown', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 200, y: 400}) + }); + interactor.simulateEvent('mouseup', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 200, y: 400}) + }); + }); + + waitsFor(function () { + return created; + }); + }); + + it('draw ellipse', function () { + var created; + + runs(function () { + viewer.startDrawMode('ellipse').then(function (elements) { + expect(elements.length).toBe(1); + closeTo(elements[0].center, [150, 300, 0]); + expect(elements[0].width).toBe(100); + expect(elements[0].height).toBe(200); + created = true;; + return null; + }); + + interactor.simulateEvent('mousemove', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 100, y: 200}) + }); + interactor.simulateEvent('mousedown', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 100, y: 200}) + }); + interactor.simulateEvent('mouseup', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 100, y: 200}) + }); + + interactor.simulateEvent('mousemove', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 200, y: 400}) + }); + interactor.simulateEvent('mousedown', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 200, y: 400}) + }); + interactor.simulateEvent('mouseup', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 200, y: 400}) + }); + }); + + waitsFor(function () { + return created; + }); + }); + + it('draw circle', function () { + var created; + + runs(function () { + viewer.startDrawMode('circle').then(function (elements) { + expect(elements.length).toBe(1); + closeTo(elements[0].center, [200, 200, 0]); + expect(elements[0].radius).toBe(100); + created = true;; + return null; + }); + + interactor.simulateEvent('mousemove', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 100, y: 100}) + }); + interactor.simulateEvent('mousedown', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 100, y: 100}) + }); + interactor.simulateEvent('mouseup', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 100, y: 100}) + }); + + interactor.simulateEvent('mousemove', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 200, y: 500}) + }); + interactor.simulateEvent('mousedown', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 200, y: 500}) + }); + interactor.simulateEvent('mouseup', { + button: 'left', + map: viewer.viewer.gcsToDisplay({x: 200, y: 500}) + }); + }); + + waitsFor(function () { + return created; + }); + }); + it('draw line', function () { var created; @@ -562,7 +696,7 @@ $(function () { closeTo(elements[0].points[0], [100, 200, 0]); closeTo(elements[0].points[1], [200, 200, 0]); closeTo(elements[0].points[2], [200, 300, 0]); - created = true; + created = true;; return null; });