Repository: vue-gl/vue-gl
Pull request #739 (2019-11-29 13:39:07)

Add new VglShape Component and its base components
### Changes in this PR
* VglShape component represents THREE.Shape object.
* Add new curves namespace to VglNamespace provider.
* VglExtrudeGeometry to use shapes those specified in curves namespace by shapes.
* `names` prop accepting space-separated string array. `name` prop must not include any spaces.

### Things to be considered
* VglShape accepts a path prop. It accepts only vector2Array now, but should be parsed like SVG path element.
* ~~All name props accepting array should parsed space divided names like HTML class syntax.~~


Patch:
diff --git a/docs/examples/geometries/vgl-extrude-geometry.html b/docs/examples/geometries/vgl-extrude-geometry.html
new file mode 100644
index 0000000..a6bbc61
--- /dev/null
+++ b/docs/examples/geometries/vgl-extrude-geometry.html
@@ -0,0 +1,48 @@
+---
+---
+<div id="app">
+  <vgl-renderer antialias style="height: 100vh;">
+    <vgl-scene>
+      <vgl-shape name="shape" path="0 0, 10 10, -10 10, -20 -30, 30 -40"></vgl-shape>
+      <vgl-extrude-geometry name="extrude" shapes="shape" :depth="depth" :curve-segments="curveSegments" :steps="steps" :bevel-enabled="bevelEnabled" :bevel-thickness="bevelThickness" :bevel-size="bevelSize" :bevel-offset="bevelOffset" :bevel-segments="bevelSegments"></vgl-extrude-geometry>
+      <vgl-mesh-standard-material name="std"></vgl-mesh-standard-material>
+      <vgl-mesh geometry="extrude" material="std"></vgl-mesh>
+      <vgl-ambient-light color="#ffeecc"></vgl-ambient-light>
+      <vgl-directional-light position="0 1 2"></vgl-directional-light>
+    </vgl-scene>
+    <vgl-perspective-camera orbit-position="200 1 0.5" orbit-target="0 -30 50"></vgl-perspective-camera>
+  </vgl-renderer>
+
+  <aside class="control-panel">
+    <section>
+      <h3>Extrude</h3>
+      <label>Depth<input type="range" v-model="depth"></label>
+      <label>Curve segments<input type="range" v-model="curveSegments" max="20"></label>
+      <label>Steps<input type="range" v-model="steps" max="10"></label>
+    </section>
+    <section>
+      <h3>Bevel</h3>
+      <label>Enabled<input type="checkbox" v-model="bevelEnabled"></label>
+      <label>Thickness<input type="range" v-model="bevelThickness" step="0.1" max="10"></label>
+      <label>Size<input type="range" v-model="bevelSize" step="0.1" max="10"></label>
+      <label>Offset<input type="range" v-model="bevelOffset" step="0.1" min="-10" max="10"></label>
+      <label>Segments<input type="range" v-model="bevelSegments" max="10"></label>
+    </section>
+  </aside>
+</div>
+
+<script>
+  new Vue({
+    el: '#app',
+    data: {
+      depth: 50,
+      curveSegments: 5,
+      steps: 5,
+      bevelEnabled: false,
+      bevelThickness: 2,
+      bevelSize: 2,
+      bevelOffset: 0,
+      bevelSegments: 5,
+    },
+  });
+</script>
\ No newline at end of file
diff --git a/docs/property-types.md b/docs/property-types.md
index 4632b5c..bcd6c43 100644
--- a/docs/property-types.md
+++ b/docs/property-types.md
@@ -71,10 +71,7 @@ Following list shows all property types and parsing schemas.
   Parsed as an array of vector2s.
 - ***fog***
   Space-separated paramaters as color, near and far, corresponding [THREE.Fog](https://threejs.org/docs/#api/en/scenes/Fog)
+- ***name***
+  A string representing the component name. It must not contain any spaces.
 - ***names***
-  A string or an array of string.
-- ***shapes***
-  An array of vector2s or an array of
-  [THREE.Shape](https://threejs.org/docs/#api/en/extras/core/Shape),
-  corresponding to
-  [THREE.ExtrudeBufferGeometry](https://threejs.org/docs/#api/en/geometries/ExtrudeBufferGeometry)
+  Space separated strings or an array of string.
diff --git a/src/cameras/vgl-camera.js b/src/cameras/vgl-camera.js
index f08b6b2..b7df22e 100644
--- a/src/cameras/vgl-camera.js
+++ b/src/cameras/vgl-camera.js
@@ -1,7 +1,7 @@
 import { Camera, Vector3 } from 'three';
 import VglObject3d from '../core/vgl-object3d';
 import { parseVector3, parseSpherical } from '../parsers';
-import { vector3, spherical } from '../validators';
+import { vector3, spherical } from '../types';
 
 /**
  * This is abstract base component for cameras,
diff --git a/src/cameras/vgl-orthographic-camera.js b/src/cameras/vgl-orthographic-camera.js
index ad6ff19..09d8897 100644
--- a/src/cameras/vgl-orthographic-camera.js
+++ b/src/cameras/vgl-orthographic-camera.js
@@ -1,6 +1,6 @@
 import { OrthographicCamera } from 'three';
 import VglCamera from './vgl-camera';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * Camera that uses orthographic projection,
diff --git a/src/cameras/vgl-perspective-camera.js b/src/cameras/vgl-perspective-camera.js
index 163c490..9197c79 100644
--- a/src/cameras/vgl-perspective-camera.js
+++ b/src/cameras/vgl-perspective-camera.js
@@ -1,6 +1,6 @@
 import { PerspectiveCamera } from 'three';
 import VglCamera from './vgl-camera';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * Camera that uses perspective projection,
diff --git a/src/core/vgl-geometry.js b/src/core/vgl-geometry.js
index a6fe8bd..f7f2589 100644
--- a/src/core/vgl-geometry.js
+++ b/src/core/vgl-geometry.js
@@ -1,5 +1,6 @@
 import { BufferGeometry, BufferAttribute } from 'three';
-import { string, floatArray } from '../validators';
+import { name, floatArray } from '../types';
+import { nameValidator } from '../validators';
 import { parseArray } from '../parsers';
 
 /**
@@ -16,7 +17,7 @@ export default {
   },
   props: {
     /** Name of the component. */
-    name: string,
+    name: { type: name, validator: nameValidator },
     /** The x, y, and z coordinates of each vertex in this geometry. */
     positionAttribute: floatArray,
     /** The red, green, and blue channels of vertex color of each vertex in this geometry. */
@@ -67,9 +68,9 @@ export default {
       },
       immediate: true,
     },
-    name(name, oldName) {
+    name(newName, oldName) {
       if (oldName !== undefined) this.vglNamespace.geometries.delete(oldName, this.inst);
-      if (name !== undefined) this.vglNamespace.geometries.set(name, this.inst);
+      if (newName !== undefined) this.vglNamespace.geometries.set(newName, this.inst);
     },
     positionAttribute(positionAttribute) {
       const positionArray = parseArray(positionAttribute);
diff --git a/src/core/vgl-namespace.js b/src/core/vgl-namespace.js
index 0a08803..d12d5bd 100644
--- a/src/core/vgl-namespace.js
+++ b/src/core/vgl-namespace.js
@@ -10,6 +10,7 @@ import Namespace from './namespace';
  * - `vglNamespace.geometries`
  * - `vglNamespace.materials`
  * - `vglNamespace.textures`
+ * - `vglNamespace.curves`
  * - `vglNamespace.object3ds`
  *
  * Each namespace internally has a map of strings and objects, and has methods to access them.
@@ -40,7 +41,7 @@ export default {
   },
   provide() {
     const {
-      geometries, materials, textures, object3ds,
+      geometries, materials, textures, object3ds, curves,
     } = this.vglNamespace;
     return {
       vglNamespace: Object.create(this.vglNamespace, {
@@ -48,17 +49,19 @@ export default {
         materials: { value: materials ? materials.fork() : new Namespace() },
         textures: { value: textures ? textures.fork() : new Namespace() },
         object3ds: { value: object3ds ? object3ds.fork() : new Namespace() },
+        curves: { value: curves ? curves.fork() : new Namespace() },
       }),
     };
   },
   beforeDestroy() {
     const {
-      geometries, materials, textures, object3ds,
+      geometries, materials, textures, object3ds, curves,
     } = this.vglNamespace;
     if (geometries) geometries.destroy();
     if (materials) materials.destroy();
     if (textures) textures.destroy();
     if (object3ds) object3ds.destroy();
+    if (curves) curves.destroy();
   },
   render(h) {
     return this.$slots.default ? h('div', this.$slots.default) : undefined;
diff --git a/src/core/vgl-object3d.js b/src/core/vgl-object3d.js
index b681d70..d8141dd 100644
--- a/src/core/vgl-object3d.js
+++ b/src/core/vgl-object3d.js
@@ -6,8 +6,9 @@ import {
   euler,
   quaternion,
   boolean,
-  string,
-} from '../validators';
+  name,
+} from '../types';
+import { nameValidator } from '../validators';
 
 /**
  * This is the base mixin component for most object components in VueGL,
@@ -37,7 +38,7 @@ export default {
     /** Whether the material receives shadows. */
     receiveShadow: boolean,
     /** Optional name of the object. */
-    name: string,
+    name: { type: name, validator: nameValidator },
     /** Whether the object is visible. */
     hidden: boolean,
   },
@@ -93,15 +94,15 @@ export default {
     },
     parent(parent) { parent.add(this.inst); },
     name: [
-      function handler(name) { this.inst.name = name; },
+      function handler(newName) { this.inst.name = newName; },
       {
-        handler(name, oldName) {
+        handler(newName, oldName) {
           if (oldName !== undefined) {
             this.vglNamespace.object3ds.delete(oldName, this.inst);
-            if (name === undefined) this.vglObject3d.unlisten(this.emitAsObject3d);
+            if (newName === undefined) this.vglObject3d.unlisten(this.emitAsObject3d);
           }
-          if (name !== undefined) {
-            this.vglNamespace.object3ds.set(name, this.inst);
+          if (newName !== undefined) {
+            this.vglNamespace.object3ds.set(newName, this.inst);
             if (oldName === undefined) this.vglObject3d.listen(this.emitAsObject3d);
           }
         },
diff --git a/src/extras/vgl-curve-path.js b/src/extras/vgl-curve-path.js
index ff17d77..e07d938 100644
--- a/src/extras/vgl-curve-path.js
+++ b/src/extras/vgl-curve-path.js
@@ -1,9 +1,29 @@
 import { CurvePath } from 'three';
 import VglCurve from './vgl-curve';
+import { boolean } from '../types';
+
+/**
+ * An abstract base component extending VglCurve, Corresponding
+ * [THREE.CurvePath](https://threejs.org/docs/index.html#api/extras/core/CurvePath).
+ *
+ * Properties of [VglCurve](vgl-curve) are also available as mixin.
+ */
 
 export default {
   mixins: [VglCurve],
+  props: {
+    /** Whether or not to automatically close the path. */
+    autoClose: boolean,
+  },
   computed: {
+    /** The THREE.CurvePath instance. */
     inst: () => new CurvePath(),
   },
+  watch: {
+    inst: {
+      handler(inst) { Object.assign(inst, { autoClose: this.autoClose }); },
+      immediate: true,
+    },
+    autoClose(autoClose) { this.inst.autoClose = autoClose; },
+  },
 };
diff --git a/src/extras/vgl-curve.js b/src/extras/vgl-curve.js
index 7cc5347..e273ecf 100644
--- a/src/extras/vgl-curve.js
+++ b/src/extras/vgl-curve.js
@@ -1,12 +1,39 @@
 import { Curve } from 'three';
+import { name } from '../types';
+import { nameValidator } from '../validators';
 
+/**
+ * An abstract base component for representing a curve, corresponding
+ * [THREE.Curve](https://threejs.org/docs/index.html#api/extras/core/Curve).
+ */
 export default {
   inject: {
     vglNamespace: {
       default() { throw new Error('VueGL components must be wraped by VglNamespace component.'); },
     },
   },
+  props: {
+    /** Name of the component. */
+    name: { type: name, validator: nameValidator },
+  },
   computed: {
+    /** The THREE.Curve instance. */
     inst: () => new Curve(),
   },
+  beforeDestroy() {
+    if (this.name !== undefined) this.vglNamespace.curves.delete(this.name, this.inst);
+  },
+  watch: {
+    inst: {
+      handler(inst) { if (this.name !== undefined) this.vglNamespace.curves.set(this.name, inst); },
+      immediate: true,
+    },
+    name(newName, oldName) {
+      if (oldName !== undefined) this.vglNamespace.curves.delete(oldName, this.inst);
+      if (newName !== undefined) this.vglNamespace.curves.set(newName, this.inst);
+    },
+  },
+  render(h) {
+    return this.$slots.default ? h('div', this.$slots.default) : undefined;
+  },
 };
diff --git a/src/extras/vgl-path.js b/src/extras/vgl-path.js
index 3fe2442..c0cb10a 100644
--- a/src/extras/vgl-path.js
+++ b/src/extras/vgl-path.js
@@ -1,9 +1,27 @@
 import { Path } from 'three';
 import VglCurvePath from './vgl-curve-path';
+import { vector2Array } from '../types';
+import { parseVector2Array } from '../parsers';
+
+/**
+ * A 2D path representation, Corresponding
+ * [THREE.Path](https://threejs.org/docs/index.html#api/extras/core/Path).
+ *
+ * Properties of [VglCurvePath](vgl-curve-path) are also available as mixin.
+ */
 
 export default {
   mixins: [VglCurvePath],
+  props: {
+    /** The array of points as a LineCurve. */
+    path: vector2Array,
+  },
   computed: {
-    inst: () => new Path(),
+    /** The THREE.Path instance. */
+    inst() {
+      const path = new Path();
+      if (this.path !== undefined) path.setFromPoints(parseVector2Array(this.path));
+      return path;
+    },
   },
 };
diff --git a/src/extras/vgl-shape.js b/src/extras/vgl-shape.js
index b27db22..dae1f3e 100644
--- a/src/extras/vgl-shape.js
+++ b/src/extras/vgl-shape.js
@@ -1,9 +1,23 @@
 import { Shape } from 'three';
 import VglPath from './vgl-path';
+import { parseVector2Array } from '../parsers';
+
+/**
+ * Defines an arbitrary 2d shape plane using paths with optional holes. It can be used with
+ * VglExtrudeGeometry to get points, or to get triangulated faces. Corresponding
+ * [THREE.Shape](https://threejs.org/docs/index.html#api/extras/core/Shape).
+ *
+ * Properties of [VglPath](vgl-path) are also available as mixin.
+ */
 
 export default {
   mixins: [VglPath],
   computed: {
-    inst: () => new Shape(),
+    /** The THREE.Shape instance. */
+    inst() {
+      const shape = new Shape();
+      if (this.path !== undefined) shape.setFromPoints(parseVector2Array(this.path));
+      return shape;
+    },
   },
 };
diff --git a/src/geometries/vgl-box-geometry.js b/src/geometries/vgl-box-geometry.js
index 951895e..c8fd600 100644
--- a/src/geometries/vgl-box-geometry.js
+++ b/src/geometries/vgl-box-geometry.js
@@ -1,6 +1,6 @@
 import { BoxBufferGeometry } from 'three';
 import VglGeometry from '../core/vgl-geometry';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * This is the quadrilateral primitive geometry component,
diff --git a/src/geometries/vgl-circle-geometry.js b/src/geometries/vgl-circle-geometry.js
index 802474c..d3a101e 100644
--- a/src/geometries/vgl-circle-geometry.js
+++ b/src/geometries/vgl-circle-geometry.js
@@ -1,6 +1,6 @@
 import { CircleBufferGeometry } from 'three';
 import VglGeometry from '../core/vgl-geometry';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * This is a simple shape component of Euclidean geometry,
diff --git a/src/geometries/vgl-cone-geometry.js b/src/geometries/vgl-cone-geometry.js
index 1074bda..04976b6 100644
--- a/src/geometries/vgl-cone-geometry.js
+++ b/src/geometries/vgl-cone-geometry.js
@@ -1,6 +1,6 @@
 import { ConeBufferGeometry } from 'three';
 import VglCylinderGeometry from './vgl-cylinder-geometry';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * This is a component for generating cone geometries,
diff --git a/src/geometries/vgl-cylinder-geometry.js b/src/geometries/vgl-cylinder-geometry.js
index 9cbc6e7..b995f8d 100644
--- a/src/geometries/vgl-cylinder-geometry.js
+++ b/src/geometries/vgl-cylinder-geometry.js
@@ -1,6 +1,6 @@
 import { CylinderBufferGeometry } from 'three';
 import VglGeometry from '../core/vgl-geometry';
-import { number, boolean } from '../validators';
+import { number, boolean } from '../types';
 
 /**
  * This is a component for generating cylinder geometries,
diff --git a/src/geometries/vgl-dodecahedron-geometry.js b/src/geometries/vgl-dodecahedron-geometry.js
index 941c7ff..3a7b361 100644
--- a/src/geometries/vgl-dodecahedron-geometry.js
+++ b/src/geometries/vgl-dodecahedron-geometry.js
@@ -1,6 +1,6 @@
 import { DodecahedronBufferGeometry } from 'three';
 import VglGeometry from '../core/vgl-geometry';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * A component for generating a dodecahedron geometries,
diff --git a/src/geometries/vgl-extrude-geometry.js b/src/geometries/vgl-extrude-geometry.js
index 9af41f7..50ad0ee 100644
--- a/src/geometries/vgl-extrude-geometry.js
+++ b/src/geometries/vgl-extrude-geometry.js
@@ -1,7 +1,8 @@
 import { ExtrudeBufferGeometry } from 'three';
-import { boolean, number, shapes } from '../validators';
+import { boolean, number, names } from '../types';
 import VglGeometry from '../core/vgl-geometry';
-import { parseShape } from '../parsers';
+import { parseNames } from '../parsers';
+import { namesValidator } from '../validators';
 
 /**
  * A component for creating extruded geometry from a path shape,
@@ -13,13 +14,8 @@ import { parseShape } from '../parsers';
 export default {
   mixins: [VglGeometry],
   props: {
-    /** The Shape (or an Array of Shape)
-     * Accepts an array of points to internally create the shape.
-     * Points can either be String ("12 2"), Number ([12, 2]) or THREE.Vector2.
-     *
-     * For an array of shapes, please pass directly an Array of THREE.Shape
-     */
-    shapes,
+    /** The Shape names */
+    shapes: { type: names, validator: namesValidator },
     /** int. Number of points on the curves */
     curveSegments: number,
     /** int. Number of points used for subdividing segments
@@ -41,8 +37,11 @@ export default {
     /** THREE.Curve. A 3D spline path along which the shape should be extruded */
     extrudePath: Object,
     /**  Object that provides UV generator functions */
-    UVGenerator: Object,
+    uvGenerator: Object,
   },
+  data: () => ({
+    shapeNames: [],
+  }),
   computed: {
     options() {
       const {
@@ -55,7 +54,7 @@ export default {
         bevelOffset,
         bevelSegments,
         extrudePath,
-        UVGenerator,
+        uvGenerator,
       } = this;
       return {
         ...(curveSegments != null && { curveSegments: parseInt(curveSegments, 10) }),
@@ -67,14 +66,49 @@ export default {
         ...(bevelOffset != null && { bevelOffset: parseFloat(bevelOffset) }),
         ...(bevelSegments != null && { bevelSegments: parseInt(bevelSegments, 10) }),
         ...(extrudePath != null && { extrudePath }),
-        ...(UVGenerator != null && { UVGenerator }),
+        ...(uvGenerator != null && { UVGenerator: uvGenerator }),
       };
     },
     inst() {
-      return new ExtrudeBufferGeometry(
-        parseShape(this.shapes),
-        this.options,
-      );
+      const shapes = this.shapeNames.map(([name]) => this.vglNamespace.curves.get(name));
+      return new ExtrudeBufferGeometry(shapes, this.options);
+    },
+  },
+  methods: {
+    /** Update the array of Shape names and Shape UUIDs and emit an event. */
+    setShapeNames() {
+      const shapeNames = parseNames(this.shapes);
+      this.shapeNames = shapeNames
+        .map((shapeName) => [shapeName, this.vglNamespace.curves.get(shapeName).uuid]);
+      this.update();
+    },
+  },
+  beforeDestroy() {
+    if (this.shapes !== undefined) {
+      const shapeNames = parseNames(this.shapes);
+      shapeNames.forEach((shapeName) => {
+        this.vglNamespace.curves.unlisten(shapeName, this.setShapeUuids);
+      });
+    }
+  },
+  watch: {
+    shapes: {
+      handler(shapes, oldShapes) {
+        const oldNames = oldShapes === undefined ? [] : parseNames(oldShapes);
+        const newNames = shapes === undefined ? [] : parseNames(shapes);
+        oldNames.forEach((shapeName) => {
+          if (!newNames.includes(shapeName)) {
+            this.vglNamespace.curves.unlisten(shapeName, this.setShapeNames);
+          }
+        });
+        names.forEach((shapeName) => {
+          if (!oldNames.includes(shapeName)) {
+            this.vglNamespace.curves.listen(shapeName, this.setShapeNames);
+          }
+        });
+        if (shapes !== undefined) this.setShapeNames();
+      },
+      immediate: true,
     },
   },
 };
diff --git a/src/geometries/vgl-icosahedron-geometry.js b/src/geometries/vgl-icosahedron-geometry.js
index c8e5a84..f3aebd3 100644
--- a/src/geometries/vgl-icosahedron-geometry.js
+++ b/src/geometries/vgl-icosahedron-geometry.js
@@ -1,6 +1,6 @@
 import { IcosahedronBufferGeometry } from 'three';
 import VglGeometry from '../core/vgl-geometry';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * A component for generating a icosahedron geometries,
diff --git a/src/geometries/vgl-lathe-geometry.js b/src/geometries/vgl-lathe-geometry.js
index 002d29e..b341d1a 100644
--- a/src/geometries/vgl-lathe-geometry.js
+++ b/src/geometries/vgl-lathe-geometry.js
@@ -1,7 +1,7 @@
 import { LatheBufferGeometry } from 'three';
 import VglGeometry from '../core/vgl-geometry';
 import { parseVector2Array } from '../parsers';
-import { vector2Array, number } from '../validators';
+import { vector2Array, number } from '../types';
 
 /**
  * A component for creating extruded geometry from a path shape,
diff --git a/src/geometries/vgl-octahedron-geometry.js b/src/geometries/vgl-octahedron-geometry.js
index 1bc1ace..79f0a76 100644
--- a/src/geometries/vgl-octahedron-geometry.js
+++ b/src/geometries/vgl-octahedron-geometry.js
@@ -1,6 +1,6 @@
 import { OctahedronBufferGeometry } from 'three';
 import VglGeometry from '../core/vgl-geometry';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * A component for generating a octahedron geometries,
diff --git a/src/geometries/vgl-plane-geometry.js b/src/geometries/vgl-plane-geometry.js
index eaa0a1b..b25c05d 100644
--- a/src/geometries/vgl-plane-geometry.js
+++ b/src/geometries/vgl-plane-geometry.js
@@ -1,6 +1,6 @@
 import { PlaneBufferGeometry } from 'three';
 import VglGeometry from '../core/vgl-geometry';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * A component for generating plane geometries,
diff --git a/src/geometries/vgl-ring-geometry.js b/src/geometries/vgl-ring-geometry.js
index bc4f1f6..19fbef8 100644
--- a/src/geometries/vgl-ring-geometry.js
+++ b/src/geometries/vgl-ring-geometry.js
@@ -1,6 +1,6 @@
 import { RingBufferGeometry } from 'three';
 import VglGeometry from '../core/vgl-geometry';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * This is a simple shape component of Euclidean geometry,
diff --git a/src/geometries/vgl-sphere-geometry.js b/src/geometries/vgl-sphere-geometry.js
index 108bdf5..7a5695d 100644
--- a/src/geometries/vgl-sphere-geometry.js
+++ b/src/geometries/vgl-sphere-geometry.js
@@ -1,6 +1,6 @@
 import { SphereBufferGeometry } from 'three';
 import VglGeometry from '../core/vgl-geometry';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * This is a component for generating sphere geometries,
diff --git a/src/geometries/vgl-tetrahedron-geometry.js b/src/geometries/vgl-tetrahedron-geometry.js
index 2891bb8..5878d15 100644
--- a/src/geometries/vgl-tetrahedron-geometry.js
+++ b/src/geometries/vgl-tetrahedron-geometry.js
@@ -1,6 +1,6 @@
 import { TetrahedronBufferGeometry } from 'three';
 import VglGeometry from '../core/vgl-geometry';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * A component for generating a tetrahedron geometries,
diff --git a/src/geometries/vgl-text-geometry.js b/src/geometries/vgl-text-geometry.js
index 15e6ebf..ed1e67f 100644
--- a/src/geometries/vgl-text-geometry.js
+++ b/src/geometries/vgl-text-geometry.js
@@ -1,6 +1,6 @@
 import { TextBufferGeometry, BufferGeometry, FontLoader } from 'three';
 import VglExtrudeGeometry from './vgl-extrude-geometry';
-import { number, string, boolean } from '../validators';
+import { number, string, boolean } from '../types';
 
 const fonts = Object.create(null);
 
diff --git a/src/geometries/vgl-torus-geometry.js b/src/geometries/vgl-torus-geometry.js
index 4c52f13..5c0eeca 100644
--- a/src/geometries/vgl-torus-geometry.js
+++ b/src/geometries/vgl-torus-geometry.js
@@ -1,6 +1,6 @@
 import { TorusBufferGeometry } from 'three';
 import VglGeometry from '../core/vgl-geometry';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * A component for generating torus geometries,
diff --git a/src/geometries/vgl-torus-knot-geometry.js b/src/geometries/vgl-torus-knot-geometry.js
index 7acd498..bd9c32f 100644
--- a/src/geometries/vgl-torus-knot-geometry.js
+++ b/src/geometries/vgl-torus-knot-geometry.js
@@ -1,6 +1,6 @@
 import { TorusKnotBufferGeometry } from 'three';
 import VglGeometry from '../core/vgl-geometry';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * A component for generating torus knot geometries,
diff --git a/src/helpers/vgl-arrow-helper.js b/src/helpers/vgl-arrow-helper.js
index e566f15..fc49179 100644
--- a/src/helpers/vgl-arrow-helper.js
+++ b/src/helpers/vgl-arrow-helper.js
@@ -1,7 +1,7 @@
 import { ArrowHelper, Color, Vector3 } from 'three';
 import VglObject3d from '../core/vgl-object3d';
 import { parseVector3 } from '../parsers';
-import { number, string, vector3 } from '../validators';
+import { number, string, vector3 } from '../types';
 
 /**
  * An 3D arrow object for visualizing directions,
diff --git a/src/helpers/vgl-axes-helper.js b/src/helpers/vgl-axes-helper.js
index 69295b6..1d013ae 100644
--- a/src/helpers/vgl-axes-helper.js
+++ b/src/helpers/vgl-axes-helper.js
@@ -1,6 +1,6 @@
 import { AxesHelper } from 'three';
 import VglObject3d from '../core/vgl-object3d';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * An axis object to visualize the the 3 axes in a simple way,
diff --git a/src/helpers/vgl-box-helper.js b/src/helpers/vgl-box-helper.js
index c98784f..e113e62 100644
--- a/src/helpers/vgl-box-helper.js
+++ b/src/helpers/vgl-box-helper.js
@@ -1,6 +1,7 @@
 import { BoxHelper } from 'three';
 import VglObject3d from '../core/vgl-object3d';
-import { string } from '../validators';
+import { string, name } from '../types';
+import { nameValidator } from '../validators';
 
 /**
  * A helper component to show the world-axis-aligned bounding box around its parent,
@@ -15,7 +16,7 @@ export default {
     /** Size of the lines representing the axes. */
     color: { type: string, default: '#ff0' },
     /** Name of the object to show the world-axis-aligned boundingbox. */
-    object: string,
+    object: { type: name, validator: nameValidator },
   },
   computed: {
     /** The THREE.BoxHelper instance. */
@@ -37,12 +38,12 @@ export default {
       }
     },
     object: {
-      handler(name, oldName) {
+      handler(newName, oldName) {
         const { vglNamespace: { object3ds }, setFromObject } = this;
         if (oldName !== undefined) object3ds.unlisten(oldName, setFromObject);
-        if (name !== undefined) {
-          object3ds.listen(name, setFromObject);
-          setFromObject(this.vglNamespace.object3ds.get(name));
+        if (newName !== undefined) {
+          object3ds.listen(newName, setFromObject);
+          setFromObject(this.vglNamespace.object3ds.get(newName));
         }
       },
       immediate: true,
diff --git a/src/helpers/vgl-camera-helper.js b/src/helpers/vgl-camera-helper.js
index 6f320ee..c28fc3c 100644
--- a/src/helpers/vgl-camera-helper.js
+++ b/src/helpers/vgl-camera-helper.js
@@ -1,6 +1,7 @@
 import { CameraHelper, Object3D } from 'three';
 import VglObject3d from '../core/vgl-object3d';
-import { string } from '../validators';
+import { name } from '../types';
+import { nameValidator } from '../validators';
 
 /**
  * This helps with visualizing what a camera contains in its frustum,
@@ -14,7 +15,7 @@ export default {
   mixins: [VglObject3d],
   props: {
     /** Name of the camera to visualize. */
-    camera: { type: string, required: true },
+    camera: { type: name, required: true, validator: nameValidator },
   },
   data: () => ({
     /** If camera specified by the name exists or not. Do not set this data manually. */
@@ -51,10 +52,10 @@ export default {
   },
   watch: {
     camera: {
-      handler(name, oldName) {
+      handler(newName, oldName) {
         if (oldName !== undefined) this.vglNamespace.cameras.unlisten(oldName, this.update);
-        this.vglNamespace.cameras.listen(name, this.update);
-        this.exist = !!this.vglNamespace.cameras.get(name);
+        this.vglNamespace.cameras.listen(newName, this.update);
+        this.exist = !!this.vglNamespace.cameras.get(newName);
       },
       immediate: true,
     },
diff --git a/src/helpers/vgl-directional-light-helper.js b/src/helpers/vgl-directional-light-helper.js
index 4a3f929..594c14a 100644
--- a/src/helpers/vgl-directional-light-helper.js
+++ b/src/helpers/vgl-directional-light-helper.js
@@ -1,6 +1,6 @@
 import { DirectionalLightHelper, Object3D } from 'three';
 import VglObject3d from '../core/vgl-object3d';
-import { string, number } from '../validators';
+import { string, number } from '../types';
 
 /**
  * A helper component to assist with visualizing a DirectionalLight's effect on the scene,
diff --git a/src/helpers/vgl-grid-helper.js b/src/helpers/vgl-grid-helper.js
index 05dfd78..e654798 100644
--- a/src/helpers/vgl-grid-helper.js
+++ b/src/helpers/vgl-grid-helper.js
@@ -1,6 +1,6 @@
 import { GridHelper } from 'three';
 import VglObject3d from '../core/vgl-object3d';
-import { number, string } from '../validators';
+import { number, string } from '../types';
 
 /**
  * A component to define grids,
diff --git a/src/helpers/vgl-polar-grid-helper.js b/src/helpers/vgl-polar-grid-helper.js
index 646938b..0fed747 100644
--- a/src/helpers/vgl-polar-grid-helper.js
+++ b/src/helpers/vgl-polar-grid-helper.js
@@ -1,6 +1,6 @@
 import { PolarGridHelper } from 'three';
 import VglObject3d from '../core/vgl-object3d';
-import { number, string } from '../validators';
+import { number, string } from '../types';
 
 /**
  * A component to define polar grids,
diff --git a/src/helpers/vgl-rect-area-light-helper.js b/src/helpers/vgl-rect-area-light-helper.js
index dbeb50f..f0287be 100644
--- a/src/helpers/vgl-rect-area-light-helper.js
+++ b/src/helpers/vgl-rect-area-light-helper.js
@@ -1,6 +1,6 @@
 import { RectAreaLightHelper, Object3D } from 'three';
 import VglObject3d from '../core/vgl-object3d';
-import { string } from '../validators';
+import { string } from '../types';
 
 /**
  * Creates a visual aid for a RectAreaLight, corresponding [THREE.RectAreaLightHelper](https://threejs.org/docs/#api/en/helpers/RectAreaLightHelper).
diff --git a/src/helpers/vgl-spot-light-helper.js b/src/helpers/vgl-spot-light-helper.js
index 4893d82..b30012b 100644
--- a/src/helpers/vgl-spot-light-helper.js
+++ b/src/helpers/vgl-spot-light-helper.js
@@ -1,6 +1,6 @@
 import { SpotLightHelper, Object3D } from 'three';
 import VglObject3d from '../core/vgl-object3d';
-import { string } from '../validators';
+import { string } from '../types';
 
 /**
  * This component displays a cone shaped helper object for a SpotLight,
diff --git a/src/index.js b/src/index.js
index 876897e..81eefa6 100644
--- a/src/index.js
+++ b/src/index.js
@@ -61,6 +61,10 @@ import VglLatheGeometry from './geometries/vgl-lathe-geometry';
 import VglSpotLightHelper from './helpers/vgl-spot-light-helper';
 import VglHemisphereLight from './lights/vgl-hemisphere-light';
 import VglRectAreaLight from './lights/vgl-rect-area-light';
+import VglShape from './extras/vgl-shape';
+import VglPath from './extras/vgl-path';
+import VglCurvePath from './extras/vgl-curve-path';
+import VglCurve from './extras/vgl-curve';
 
 export {
   VglNamespace,
@@ -126,4 +130,8 @@ export {
   VglSpotLightHelper,
   VglHemisphereLight,
   VglRectAreaLight,
+  VglShape,
+  VglCurve,
+  VglCurvePath,
+  VglPath,
 };
diff --git a/src/lights/vgl-hemisphere-light.js b/src/lights/vgl-hemisphere-light.js
index bb74d86..160a9ea 100644
--- a/src/lights/vgl-hemisphere-light.js
+++ b/src/lights/vgl-hemisphere-light.js
@@ -1,6 +1,6 @@
 import { HemisphereLight } from 'three';
 import VglLight from './vgl-light';
-import { string } from '../validators';
+import { string } from '../types';
 
 /**
  * A light source positioned directly above the scene, with color fading from the sky color to the
diff --git a/src/lights/vgl-light.js b/src/lights/vgl-light.js
index 3a78a3b..8743311 100644
--- a/src/lights/vgl-light.js
+++ b/src/lights/vgl-light.js
@@ -1,6 +1,6 @@
 import { Light } from 'three';
 import VglObject3d from '../core/vgl-object3d';
-import { string, number } from '../validators';
+import { string, number } from '../types';
 
 /**
  * Abstract mixin component for lights,
diff --git a/src/lights/vgl-point-light.js b/src/lights/vgl-point-light.js
index ea3161a..87a7437 100644
--- a/src/lights/vgl-point-light.js
+++ b/src/lights/vgl-point-light.js
@@ -1,6 +1,6 @@
 import { PointLight } from 'three';
 import VglLight from './vgl-light';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * A light that gets emitted from a single point in all directions, corresponding [THREE.PointLight](https://threejs.org/docs/index.html#api/lights/PointLight). A common use case for this is to replicate the light emitted from a bare lightbulb. This light can cast shadows.
diff --git a/src/lights/vgl-rect-area-light.js b/src/lights/vgl-rect-area-light.js
index 7eac830..cae7d84 100644
--- a/src/lights/vgl-rect-area-light.js
+++ b/src/lights/vgl-rect-area-light.js
@@ -1,6 +1,6 @@
 import { RectAreaLight } from 'three';
 import VglLight from './vgl-light';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * RectAreaLight emits light uniformly across the face a rectangular plane. See
diff --git a/src/lights/vgl-spot-light.js b/src/lights/vgl-spot-light.js
index 2474903..4ac2963 100644
--- a/src/lights/vgl-spot-light.js
+++ b/src/lights/vgl-spot-light.js
@@ -1,7 +1,7 @@
 import { SpotLight } from 'three';
 import VglLight from './vgl-light';
 import { parseVector3 } from '../parsers';
-import { number, vector3 } from '../validators';
+import { number, vector3 } from '../types';
 
 /**
  * This light gets emitted from a single point in one direction, along a cone that increases in size the further from the light it gets. Corresponding [THREE.SpotLight](https://threejs.org/docs/index.html#api/lights/SpotLight). This light can cast shadows.
diff --git a/src/materials/vgl-line-basic-material.js b/src/materials/vgl-line-basic-material.js
index 40a368f..c1c3581 100644
--- a/src/materials/vgl-line-basic-material.js
+++ b/src/materials/vgl-line-basic-material.js
@@ -1,6 +1,6 @@
 import { LineBasicMaterial } from 'three';
 import VglMaterial from './vgl-material';
-import { string, number } from '../validators';
+import { string, number } from '../types';
 
 /**
  * A material for drawing wireframe-style geometries,
diff --git a/src/materials/vgl-line-dashed-material.js b/src/materials/vgl-line-dashed-material.js
index c9d9800..d509251 100644
--- a/src/materials/vgl-line-dashed-material.js
+++ b/src/materials/vgl-line-dashed-material.js
@@ -1,6 +1,6 @@
 import { LineDashedMaterial } from 'three';
 import VglMaterial from './vgl-material';
-import { string, number } from '../validators';
+import { string, number } from '../types';
 
 /**
  * A material for drawing wireframe-style geometries,
diff --git a/src/materials/vgl-material.js b/src/materials/vgl-material.js
index 011c01b..be1905d 100644
--- a/src/materials/vgl-material.js
+++ b/src/materials/vgl-material.js
@@ -7,7 +7,8 @@ import {
   BackSide,
   DoubleSide,
 } from 'three';
-import { string } from '../validators';
+import { string, name } from '../types';
+import { nameValidator } from '../validators';
 
 const vertexColors = {
   no: NoColors,
@@ -34,7 +35,7 @@ export default {
   },
   props: {
     /** Name of the material. */
-    name: string,
+    name: { type: name, validator: nameValidator },
     /** Defines which side of faces will be rendered. front, back or double. */
     side: { type: string, default: 'front' },
     /** Defines whether vertex coloring is used. Other options are 'vertex' and 'face'. */
@@ -64,9 +65,9 @@ export default {
       },
       immediate: true,
     },
-    name(name, oldName) {
+    name(newName, oldName) {
       if (oldName !== undefined) this.vglNamespace.materials.delete(oldName, this.inst);
-      if (name !== undefined) this.vglNamespace.materials.set(name, this.inst);
+      if (newName !== undefined) this.vglNamespace.materials.set(newName, this.inst);
     },
     side(side) {
       this.inst.side = sides[side];
diff --git a/src/materials/vgl-mesh-basic-material.js b/src/materials/vgl-mesh-basic-material.js
index fa91b8c..b628c73 100644
--- a/src/materials/vgl-mesh-basic-material.js
+++ b/src/materials/vgl-mesh-basic-material.js
@@ -1,6 +1,6 @@
 import { MeshBasicMaterial } from 'three';
 import { VglMaterialWithMap } from '../mixins';
-import { string } from '../validators';
+import { string } from '../types';
 
 /**
  * A material for drawing geometries in a simple shaded (flat or wireframe) way,
diff --git a/src/materials/vgl-mesh-depth-material.js b/src/materials/vgl-mesh-depth-material.js
index 458a365..baac882 100644
--- a/src/materials/vgl-mesh-depth-material.js
+++ b/src/materials/vgl-mesh-depth-material.js
@@ -1,6 +1,6 @@
 import { MeshDepthMaterial } from 'three';
 import { VglMaterialWithMap } from '../mixins';
-import { string, boolean } from '../validators';
+import { string, boolean } from '../types';
 
 /**
  * A material for drawing geometry by depth,
diff --git a/src/materials/vgl-mesh-lambert-material.js b/src/materials/vgl-mesh-lambert-material.js
index 74e5f46..02cf938 100644
--- a/src/materials/vgl-mesh-lambert-material.js
+++ b/src/materials/vgl-mesh-lambert-material.js
@@ -1,6 +1,6 @@
 import { MeshLambertMaterial } from 'three';
 import { VglMaterialWithMap } from '../mixins';
-import { string } from '../validators';
+import { string } from '../types';
 
 /**
  * A material for non-shiny surfaces, without specular highlights,
diff --git a/src/materials/vgl-mesh-normal-material.js b/src/materials/vgl-mesh-normal-material.js
index c97dd81..702216e 100644
--- a/src/materials/vgl-mesh-normal-material.js
+++ b/src/materials/vgl-mesh-normal-material.js
@@ -1,6 +1,6 @@
 import { MeshNormalMaterial } from 'three';
 import VglMaterial from './vgl-material';
-import { boolean } from '../validators';
+import { boolean } from '../types';
 
 /**
  * A material that maps the normal vectors to RGB colors,
diff --git a/src/materials/vgl-mesh-phong-material.js b/src/materials/vgl-mesh-phong-material.js
index 43c21e3..52aed89 100644
--- a/src/materials/vgl-mesh-phong-material.js
+++ b/src/materials/vgl-mesh-phong-material.js
@@ -1,6 +1,6 @@
 import { MeshPhongMaterial } from 'three';
 import { VglMaterialWithMap } from '../mixins';
-import { string, number } from '../validators';
+import { string, number } from '../types';
 
 /**
  * A material for shiny surfaces with specular highlights,
diff --git a/src/materials/vgl-mesh-physical-material.js b/src/materials/vgl-mesh-physical-material.js
index 652a157..72ee632 100644
--- a/src/materials/vgl-mesh-physical-material.js
+++ b/src/materials/vgl-mesh-physical-material.js
@@ -1,6 +1,6 @@
 import { MeshPhysicalMaterial } from 'three';
 import VglMeshStandardMaterial from './vgl-mesh-standard-material';
-import { number } from '../validators';
+import { number } from '../types';
 
 /**
  * An extension of the mesh standard material that allows for greater control over reflectivity,
diff --git a/src/materials/vgl-mesh-standard-material.js b/src/materials/vgl-mesh-standard-material.js
index 9befaa8..eb513d7 100644
--- a/src/materials/vgl-mesh-standard-material.js
+++ b/src/materials/vgl-mesh-standard-material.js
@@ -1,6 +1,6 @@
 import { MeshStandardMaterial } from 'three';
 import { VglMaterialWithMap } from '../mixins';
-import { string } from '../validators';
+import { string } from '../types';
 
 /**
  * A standard physically based material,
diff --git a/src/materials/vgl-points-material.js b/src/materials/vgl-points-material.js
index 36d7692..c11563b 100644
--- a/src/materials/vgl-points-material.js
+++ b/src/materials/vgl-points-material.js
@@ -1,6 +1,6 @@
 import { PointsMaterial } from 'three';
 import VglMaterial from './vgl-material';
-import { string, number, boolean } from '../validators';
+import { string, number, boolean } from '../types';
 
 /**
  * The default material used by [VglPoints](vgl-points),
diff --git a/src/materials/vgl-shader-material.js b/src/materials/vgl-shader-material.js
index 9c755f9..cb12522 100644
--- a/src/materials/vgl-shader-material.js
+++ b/src/materials/vgl-shader-material.js
@@ -1,6 +1,6 @@
 import { ShaderMaterial } from 'three';
 import { VglMaterialWithMap } from '../mixins';
-import { boolean, number, string } from '../validators';
+import { boolean, number, string } from '../types';
 
 /**
  * A material for drawing geometries with custom vertex or fragment shaders, corresponding to
diff --git a/src/materials/vgl-sprite-material.js b/src/materials/vgl-sprite-material.js
index a723f75..8c0546c 100644
--- a/src/materials/vgl-sprite-material.js
+++ b/src/materials/vgl-sprite-material.js
@@ -1,6 +1,7 @@
 import { SpriteMaterial } from 'three';
 import { VglMaterialWithMap } from '../mixins';
-import { string } from '../validators';
+import { string, name } from '../types';
+import { nameValidator } from '../validators';
 
 /**
  * A material for a use with a [VglSprite](vgl-sprite) component,
@@ -15,7 +16,7 @@ export default {
     /** CSS style color of the material. */
     color: { type: string, default: '#fff' },
     /** The texture map of the material. */
-    map: string,
+    map: { type: name, validator: nameValidator },
   },
   computed: {
     /** The THREE.SpriteMaterial instance. */
diff --git a/src/mixins.js b/src/mixins.js
index a00afe8..9224c82 100644
--- a/src/mixins.js
+++ b/src/mixins.js
@@ -1,43 +1,34 @@
 import VglMaterial from './materials/vgl-material';
 import VglObject3d from './core/vgl-object3d';
+import { parseNames } from './parsers';
 
 export const VglObject3dWithMatarial = {
   mixins: [VglObject3d],
   methods: {
-    setMaterial(material) {
+    setMaterial() {
       const { vglNamespace: { materials }, inst } = this;
-      if (Array.isArray(this.material)) {
-        inst.material = this.material.reduce(
-          (acc, current) => (materials.get(current) ? [...acc, materials.get(current)] : acc), [],
-        );
-      } else if (material) inst.material = material;
+      const parsedMaterials = parseNames(this.material).reduce(
+        (acc, current) => (materials.get(current) ? [...acc, materials.get(current)] : acc), [],
+      );
+      inst.material = parsedMaterials.length === 1 ? parsedMaterials[0] : parsedMaterials;
       this.vglObject3d.emit();
     },
   },
   watch: {
     inst() {
       if (this.material !== undefined) {
-        this.setMaterial(this.vglNamespace.materials.get(this.material));
+        this.setMaterial();
       }
     },
     material: {
       handler(material, oldMaterial) {
         const { vglNamespace: { materials }, setMaterial } = this;
         if (oldMaterial !== undefined) {
-          if (Array.isArray(oldMaterial)) {
-            oldMaterial.forEach((oldName) => materials.unlisten(oldName, setMaterial));
-          } else {
-            materials.unlisten(oldMaterial, setMaterial);
-          }
+          parseNames(oldMaterial).forEach((oldName) => materials.unlisten(oldName, setMaterial));
         }
         if (material !== undefined) {
-          if (Array.isArray(material)) {
-            material.forEach((name) => materials.listen(name, setMaterial));
-            setMaterial();
-          } else {
-            materials.listen(material, setMaterial);
-            setMaterial(materials.get(material));
-          }
+          parseNames(material).forEach((name) => materials.listen(name, setMaterial));
+          setMaterial();
         }
       },
       immediate: true,
@@ -45,11 +36,7 @@ export const VglObject3dWithMatarial = {
   },
   beforeDestroy() {
     const { vglNamespace: { materials }, material, setMaterial } = this;
-    if (Array.isArray(material)) {
-      material.forEach((name) => materials.unlisten(name, setMaterial));
-    } else {
-      materials.unlisten(material, setMaterial);
-    }
+    parseNames(material).forEach((name) => materials.unlisten(name, setMaterial));
   },
 };
 
diff --git a/src/objects/vgl-line.js b/src/objects/vgl-line.js
index 069a09f..198fc22 100644
--- a/src/objects/vgl-line.js
+++ b/src/objects/vgl-line.js
@@ -1,6 +1,7 @@
 import { Line } from 'three';
 import { VglObject3dWithMatarialAndGeometry } from '../mixins';
-import { string } from '../validators';
+import { name } from '../types';
+import { nameValidator } from '../validators';
 
 /**
  * A continuous line component,
@@ -13,9 +14,9 @@ export default {
   mixins: [VglObject3dWithMatarialAndGeometry],
   props: {
     /** Name of the geometry, representing the line segment(s). */
-    geometry: string,
+    geometry: { type: name, validator: nameValidator },
     /** Name of the material for the line. */
-    material: string,
+    material: { type: name, validator: nameValidator },
   },
   computed: {
     /** The THREE.Line instance. */
diff --git a/src/objects/vgl-mesh.js b/src/objects/vgl-mesh.js
index 8a30001..6c7cf68 100644
--- a/src/objects/vgl-mesh.js
+++ b/src/objects/vgl-mesh.js
@@ -1,6 +1,7 @@
 import { Mesh } from 'three';
 import { VglObject3dWithMatarialAndGeometry } from '../mixins';
-import { string, names } from '../validators';
+import { name, names } from '../types';
+import { nameValidator, namesValidator } from '../validators';
 
 /**
  * A component representing triangular polygon mesh based objects,
@@ -13,14 +14,14 @@ export default {
   mixins: [VglObject3dWithMatarialAndGeometry],
   props: {
     /** Name of the geometry, defining the object's structure. */
-    geometry: string,
+    geometry: { type: name, validator: nameValidator },
     /**
      * A Material name or an array of Material name, defining the object's appearance.
      *
      * A single material will apply the material to all object's faces meanwhile
      * an array of material will apply each material to the matching index object's face
      */
-    material: names,
+    material: { type: names, validator: namesValidator },
   },
   computed: {
     /** The THREE.Mesh instance. */
diff --git a/src/objects/vgl-points.js b/src/objects/vgl-points.js
index fd2998a..7191a90 100644
--- a/src/objects/vgl-points.js
+++ b/src/objects/vgl-points.js
@@ -1,6 +1,7 @@
 import { Points } from 'three';
 import { VglObject3dWithMatarialAndGeometry } from '../mixins';
-import { string } from '../validators';
+import { name } from '../types';
+import { nameValidator } from '../validators';
 
 /**
  * A component for displaying points,
@@ -13,9 +14,9 @@ export default {
   mixins: [VglObject3dWithMatarialAndGeometry],
   props: {
     /** Name of the geometry, defining the object's structure. */
-    geometry: string,
+    geometry: { type: name, validator: nameValidator },
     /** Name of the material, defining the object's appearance. */
-    material: string,
+    material: { type: name, validator: nameValidator },
   },
   computed: {
     /** The THREE.Points instance. */
diff --git a/src/objects/vgl-sprite.js b/src/objects/vgl-sprite.js
index b4dad92..36020f3 100644
--- a/src/objects/vgl-sprite.js
+++ b/src/objects/vgl-sprite.js
@@ -1,6 +1,7 @@
 import { Sprite } from 'three';
 import { VglObject3dWithMatarial } from '../mixins';
-import { string } from '../validators';
+import { name } from '../types';
+import { nameValidator } from '../validators';
 
 /**
  * A sprite component corresponding [THREE.Sprite](https://threejs.org/docs/index.html#api/objects/Sprite).
@@ -13,7 +14,7 @@ export default {
   mixins: [VglObject3dWithMatarial],
   props: {
     /** Name of the material, defining the object's appearance. */
-    material: string,
+    material: { type: name, validator: nameValidator },
   },
   computed: {
     /** The THREE.Sprite instance. */
diff --git a/src/parsers.js b/src/parsers.js
index efee9fb..f487572 100644
--- a/src/parsers.js
+++ b/src/parsers.js
@@ -6,7 +6,6 @@ import {
   Quaternion,
   Fog,
   Color,
-  Shape,
 } from 'three';
 
 /**
@@ -77,9 +76,8 @@ export function parseColor(str) {
 }
 
 /**
- * Return a parsed Shape
+ * Return a parsed name array
  */
-export function parseShape(str) {
-  if (!str) return new Shape();
-  return (str.type && str.type === 'Shape') ? str : new Shape(parseVector2Array(str));
+export function parseNames(str) {
+  return Array.isArray(str) ? str : str.split(' ');
 }
diff --git a/src/renderers/vgl-renderer.js b/src/renderers/vgl-renderer.js
index a14b1c6..5cb2a86 100644
--- a/src/renderers/vgl-renderer.js
+++ b/src/renderers/vgl-renderer.js
@@ -1,6 +1,7 @@
 import { WebGLRenderer } from 'three';
 import VglNamespace from '../core/vgl-namespace';
-import { boolean, string } from '../validators';
+import { boolean, string, name } from '../types';
+import { nameValidator } from '../validators';
 import { setCameraSize } from './utilities';
 
 /**
@@ -35,9 +36,9 @@ export default {
     /** Whether to use a logarithmic depth buffer. */
     logarithmicDepthBuffer: boolean,
     /** Name of the using camera. */
-    camera: string,
+    camera: { type: name, validator: nameValidator },
     /** Name of the target scene. */
-    scene: string,
+    scene: { type: name, validator: nameValidator },
     /** If set, use shadow maps in the scene. */
     shadowMapEnabled: boolean,
   },
diff --git a/src/scenes/vgl-scene.js b/src/scenes/vgl-scene.js
index 58ed264..b57326f 100644
--- a/src/scenes/vgl-scene.js
+++ b/src/scenes/vgl-scene.js
@@ -1,7 +1,7 @@
 import { Scene } from 'three';
 import VglObject3d from '../core/vgl-object3d';
 import { parseFog, parseColor } from '../parsers';
-import { string, fog } from '../validators';
+import { string, fog } from '../types';
 
 /**
  * This is where you place objects,
diff --git a/src/textures/vgl-texture.js b/src/textures/vgl-texture.js
index 9eeef55..d8d3aaa 100644
--- a/src/textures/vgl-texture.js
+++ b/src/textures/vgl-texture.js
@@ -49,9 +49,10 @@ import {
   RGBADepthPacking,
 } from 'three';
 import {
-  string, number, vector2, boolean,
-} from '../validators';
+  string, number, vector2, boolean, name,
+} from '../types';
 import { parseVector2 } from '../parsers';
+import { nameValidator } from '../validators';
 
 const mapping = {
   uv: UVMapping,
@@ -132,7 +133,7 @@ export default {
   props: {
     /** The path or URL to the file. This can also be a Data URI. */
     src: string,
-    name: string,
+    name: { type: name, validator: nameValidator },
     mapping: { type: string, default: 'uv' },
     wrapS: { type: string, default: 'clamp-to-edge' },
     wrapT: { type: string, default: 'clamp-to-edge' },
@@ -190,9 +191,9 @@ export default {
       },
       immediate: true,
     },
-    name(name, oldName) {
+    name(newName, oldName) {
       if (oldName !== undefined) this.vglNamespace.textures.delete(oldName, this.inst);
-      if (name !== undefined) this.vglNamespace.textures.set(name, this.inst);
+      if (newName !== undefined) this.vglNamespace.textures.set(newName, this.inst);
     },
     mapping(mode) {
       this.inst.mapping = mapping[mode];
diff --git a/src/types.js b/src/types.js
new file mode 100644
index 0000000..eb3dec2
--- /dev/null
+++ b/src/types.js
@@ -0,0 +1,22 @@
+import {
+  Vector3,
+  Spherical,
+  Euler,
+  Vector2,
+  Quaternion,
+  Fog,
+} from 'three';
+
+export const number = [String, Number];
+export const string = String;
+export const vector3 = [String, Vector3];
+export const spherical = [String, Spherical];
+export const boolean = Boolean;
+export const euler = [String, Euler];
+export const vector2 = [String, Vector2];
+export const quaternion = [String, Quaternion];
+export const floatArray = [String, Array];
+export const vector2Array = [String, Array];
+export const fog = [String, Fog];
+export const name = String;
+export const names = [String, Array];
diff --git a/src/validators.js b/src/validators.js
index 9657b4f..ea84bfd 100644
--- a/src/validators.js
+++ b/src/validators.js
@@ -1,22 +1,7 @@
-import {
-  Vector3,
-  Spherical,
-  Euler,
-  Vector2,
-  Quaternion,
-  Fog, Shape,
-} from 'three';
+export function nameValidator(value) {
+  return value.length > 0 && !value.includes(' ');
+}
 
-export const number = [String, Number];
-export const string = String;
-export const vector3 = [String, Vector3];
-export const spherical = [String, Spherical];
-export const boolean = Boolean;
-export const euler = [String, Euler];
-export const vector2 = [String, Vector2];
-export const quaternion = [String, Quaternion];
-export const floatArray = [String, Array];
-export const vector2Array = [String, Array];
-export const fog = [String, Fog];
-export const names = [String, Array];
-export const shapes = [Array, Shape];
+export function namesValidator(value) {
+  return Array.isArray(value) ? value.every((n) => nameValidator(n)) : value.length > 0;
+}


Test patch:
diff --git a/test/extras/vgl-shape.spec.js b/test/extras/vgl-shape.spec.js
new file mode 100644
index 0000000..eefedce
--- /dev/null
+++ b/test/extras/vgl-shape.spec.js
@@ -0,0 +1,59 @@
+import Vue from 'vue/dist/vue';
+import { Shape, Vector2 } from 'three';
+import { VglShape, VglNamespace, VglPath } from '../../src';
+
+describe('VglShape', () => {
+  let inject;
+  beforeEach(() => {
+    const { vglNamespace } = new Vue({ parent: new Vue(VglNamespace), inject: ['vglNamespace'] });
+    inject = { vglNamespace: { default: vglNamespace } };
+  });
+  test('the inst property should be an instance of the Shape', () => {
+    expect(new (Vue.extend(VglShape))({ inject }).inst)
+      .toBeInstanceOf(Shape);
+  });
+  test('the component should have common props with VglGeometry', () => {
+    const { $props } = new (Vue.extend(VglPath))({ inject });
+    expect(Object.keys(new (Vue.extend(VglShape))({ inject }).$props))
+      .toEqual(expect.arrayContaining(Object.keys($props)));
+  });
+  test('the options of the instance should be specified by props', () => {
+    const { inst } = new (Vue.extend(VglShape))({
+      inject,
+      propsData: {
+        path: '350 0, 350 1181, 0 1181, 0 79, 15 79, 15 0',
+      },
+    });
+    const expected = new Shape();
+    expected.setFromPoints([
+      new Vector2(350, 0),
+      new Vector2(350, 1181),
+      new Vector2(0, 1181),
+      new Vector2(0, 79),
+      new Vector2(15, 79),
+      new Vector2(15, 0),
+    ]);
+    delete inst.uuid;
+    delete expected.uuid;
+    expect(inst).toStrictEqual(expected);
+  });
+  test('the path prop as an array of Vector2', () => {
+    const path = [
+      new Vector2(350, 0),
+      new Vector2(350, 1181),
+      new Vector2(0, 1181),
+      new Vector2(0, 79),
+      new Vector2(15, 79),
+      new Vector2(15, 0),
+    ];
+    const { inst } = new (Vue.extend(VglShape))({
+      inject,
+      propsData: { path },
+    });
+    const expected = new Shape();
+    expected.setFromPoints(path);
+    delete inst.uuid;
+    delete expected.uuid;
+    expect(inst).toStrictEqual(expected);
+  });
+});
diff --git a/test/geometries/vgl-extrude-geometry.spec.js b/test/geometries/vgl-extrude-geometry.spec.js
index af98e7b..645ff2b 100644
--- a/test/geometries/vgl-extrude-geometry.spec.js
+++ b/test/geometries/vgl-extrude-geometry.spec.js
@@ -1,5 +1,5 @@
 import Vue from 'vue/dist/vue';
-import { ExtrudeBufferGeometry, Shape, Vector2 } from 'three';
+import { ExtrudeBufferGeometry, Shape } from 'three';
 import { VglExtrudeGeometry, VglGeometry, VglNamespace } from '../../src';
 
 describe('VglExtrudeGeometry', () => {
@@ -51,64 +51,29 @@ describe('VglExtrudeGeometry', () => {
     expect(bevelSegments).toStrictEqual(3);
   });
   test('the shape of the instance should be specified by props', () => {
-    const points = [[350, 0], [350, 1181], [0, 1181], [0, 79], [15, 79], [15, 0]];
-    const virtualShape = new Shape(points.map((e) => new Vector2(...e)));
+    const shape = new Shape();
+    inject.vglNamespace.default.curves.set('testShape', shape);
     const { inst } = new (Vue.extend(VglExtrudeGeometry))({
       inject,
       propsData: {
-        shapes: points,
+        shapes: 'testShape',
       },
     });
     const { shapes } = inst.parameters;
-    expect(shapes.type).toStrictEqual('Shape');
-    expect(virtualShape.currentPoint.equals(shapes.currentPoint)).toStrictEqual(true);
-    expect(JSON.stringify(shapes.extractPoints()))
-      .toStrictEqual(JSON.stringify(virtualShape.extractPoints()));
+    expect(shapes).toStrictEqual([shape]);
   });
-  test('the shapes props as an Array of string', () => {
-    const points = [[350, 0], [350, 1181], [0, 1181], [0, 79], [15, 79], [15, 0]];
-    const virtualShape = new Shape(points.map((e) => new Vector2(...e)));
+  test('the shapes props as an array of string', () => {
+    const shape1 = new Shape();
+    const shape2 = new Shape();
+    inject.vglNamespace.default.curves.set('shape1', shape1);
+    inject.vglNamespace.default.curves.set('shape2', shape2);
     const { inst } = new (Vue.extend(VglExtrudeGeometry))({
       inject,
       propsData: {
-        shapes: points.map((e) => e.join(' ')),
+        shapes: 'shape1 shape2',
       },
     });
     const { shapes } = inst.parameters;
-    expect(shapes.type).toStrictEqual('Shape');
-    expect(virtualShape.currentPoint.equals(shapes.currentPoint)).toStrictEqual(true);
-    expect(JSON.stringify(shapes.extractPoints()))
-      .toStrictEqual(JSON.stringify(virtualShape.extractPoints()));
-  });
-  test('the shapes props as an Array of Vector2', () => {
-    const points = [[350, 0], [350, 1181], [0, 1181], [0, 79], [15, 79], [15, 0]];
-    const virtualShape = new Shape(points.map((e) => new Vector2(...e)));
-    const { inst } = new (Vue.extend(VglExtrudeGeometry))({
-      inject,
-      propsData: {
-        shapes: points.map((e) => new Vector2(...e)),
-      },
-    });
-    const { shapes } = inst.parameters;
-    expect(shapes.type).toStrictEqual('Shape');
-    expect(virtualShape.currentPoint.equals(shapes.currentPoint)).toStrictEqual(true);
-    expect(JSON.stringify(shapes.extractPoints()))
-      .toStrictEqual(JSON.stringify(virtualShape.extractPoints()));
-  });
-  test('the shapes props as a THREE.Shape', () => {
-    const points = [[350, 0], [350, 1181], [0, 1181], [0, 79], [15, 79], [15, 0]];
-    const virtualShape = new Shape(points.map((e) => new Vector2(...e)));
-    const propShapes = virtualShape.clone();
-    const { inst } = new (Vue.extend(VglExtrudeGeometry))({
-      inject,
-      propsData: {
-        shapes: propShapes,
-      },
-    });
-    const { shapes } = inst.parameters;
-    expect(shapes.type).toStrictEqual('Shape');
-    expect(virtualShape.currentPoint.equals(shapes.currentPoint)).toStrictEqual(true);
-    expect(JSON.stringify(shapes.extractPoints()))
-      .toStrictEqual(JSON.stringify(virtualShape.extractPoints()));
+    expect(shapes).toStrictEqual([shape1, shape2]);
   });
 });
diff --git a/test/scenes/vgl-scene.spec.js b/test/scenes/vgl-scene.spec.js
index 313dd86..03f88b9 100644
--- a/test/scenes/vgl-scene.spec.js
+++ b/test/scenes/vgl-scene.spec.js
@@ -22,8 +22,8 @@ describe('VglScene', () => {
   });
   test('the propertie "backgroundTexture" of the instance should be specified by props', () => {
     const texture = new TextureLoader().load('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');
-    inject.vglNamespace.default.textures.set('test tx', texture);
-    const { inst } = new (Vue.extend(VglScene))({ inject, propsData: { backgroundTexture: 'test tx' } });
+    inject.vglNamespace.default.textures.set('testTx', texture);
+    const { inst } = new (Vue.extend(VglScene))({ inject, propsData: { backgroundTexture: 'testTx' } });
     expect(inst.background).toBe(texture);
   });
   test('the propertie "fog" of the instance should be specified by props', () => {
@@ -40,19 +40,19 @@ describe('VglScene', () => {
   });
   test('the properties "backgroundTexture" of the instance should change after props change', async () => {
     const texture = new TextureLoader().load('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
-    inject.vglNamespace.default.textures.set('test tx', texture);
+    inject.vglNamespace.default.textures.set('test-tx', texture);
     const vm = new (Vue.extend(VglScene))({ inject });
-    vm.backgroundTexture = 'test tx';
+    vm.backgroundTexture = 'test-tx';
     await vm.$nextTick();
     expect(vm.inst.background).toBe(texture);
   });
   test('the properties "backgroundTexture" of the instance should change after texture change', async () => {
     const texture1 = new TextureLoader().load('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');
     const texture2 = new TextureLoader().load('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
-    inject.vglNamespace.default.textures.set('test tx1', texture1);
-    inject.vglNamespace.default.textures.set('test tx2', texture2);
-    const vm = new (Vue.extend(VglScene))({ inject, propsData: { backgroundTexture: 'test tx1' } });
-    vm.backgroundTexture = 'test tx2';
+    inject.vglNamespace.default.textures.set('test-tx1', texture1);
+    inject.vglNamespace.default.textures.set('test-tx2', texture2);
+    const vm = new (Vue.extend(VglScene))({ inject, propsData: { backgroundTexture: 'test-tx1' } });
+    vm.backgroundTexture = 'test-tx2';
     await vm.$nextTick();
     expect(vm.inst.background).toBe(texture2);
   });
@@ -66,21 +66,21 @@ describe('VglScene', () => {
   });
   describe('the instance should be registered to scenes namespace', () => {
     test('after created', () => {
-      const vm = new (Vue.extend(VglScene))({ inject, propsData: { name: 'test name 1' } });
-      expect(inject.vglNamespace.default.scenes.get('test name 1')).toBe(vm.inst);
+      const vm = new (Vue.extend(VglScene))({ inject, propsData: { name: 'test_name1' } });
+      expect(inject.vglNamespace.default.scenes.get('test_name1')).toBe(vm.inst);
     });
     test('after the name prop changes', async () => {
-      const vm = new (Vue.extend(VglScene))({ inject, propsData: { name: 'test scene 1' } });
-      vm.name = 'test scene 2';
+      const vm = new (Vue.extend(VglScene))({ inject, propsData: { name: 'test_scene1' } });
+      vm.name = 'test_scene2';
       await Vue.nextTick();
-      expect(inject.vglNamespace.default.scenes.get('test scene 1')).toBeUndefined();
-      expect(inject.vglNamespace.default.scenes.get('test scene 2')).toBe(vm.inst);
+      expect(inject.vglNamespace.default.scenes.get('test_scene1')).toBeUndefined();
+      expect(inject.vglNamespace.default.scenes.get('test_scene2')).toBe(vm.inst);
     });
     test('after destroyed', async () => {
-      const vm = new (Vue.extend(VglScene))({ inject, propsData: { name: 'test name 1' } });
+      const vm = new (Vue.extend(VglScene))({ inject, propsData: { name: 'testName1' } });
       vm.$destroy();
       await Vue.nextTick();
-      expect(inject.vglNamespace.default.scenes.get('test name 1')).toBeUndefined();
+      expect(inject.vglNamespace.default.scenes.get('testName1')).toBeUndefined();
     });
   });
 });