Products
Services
Contact Us

Scripts: Javascript :: HTML5 :: Library Article #33

Developer's Section

Creating a Custom Geometry and 3D Mesh using CanvasX / Three.js and the Canvas Element
By: Erobo Team Member

Hire a Developer for Related Work / Installation | $55 hr
Rating:  | Rate It:   
Average Votes: (1749)
Favorites:

This article teaches you the basics of creating custom geometries and objects inside the HTML5 Canvas Element.

Creating a custom object using the CanvasX / three.js libraries can be accomplished in a three step process. The initial procedure consists of creating the object's geometry, in which we define or trace the faces that we want. In the second step we create the actual faces and UVs by pushing these into the faces and uvs arrays of the geometry. Lastly, we create the actual Mesh by using our material of preference and adjust positioning.

See 3D House drawn on Canvas below:




You can also visit our Virtual 3ds account for a full 3d virtual world drawn on Canvas at: http://www.virtual3ds.com/erobo_software

Source Code of House Mesh:

 Code Snippet 1

<!--Declare Container-->
<div id="mycontainer"></div>

<!--Declare CanvasX or Three.js <= 75.0  or later libraries for Canvas-->
<script language="javascript" type="text/javascript"
        src="http://www.erobo.net/scripts/javascript/33/scripts/CanvasX.min.js"></script>

<script language="javascript" type="text/javascript">

  // get reference to the container
  var container = document.getElementById('mycontainer');

  // declare textures
  var texture = new THREE.Texture();
  texture.wrapS = THREE.RepeatWrapping;
  texture.wrapT = THREE.RepeatWrapping;
  texture.repeat.set(1, 1);
  var texture2 = new THREE.Texture();
  texture2.wrapS = THREE.RepeatWrapping;
  texture2.wrapT = THREE.RepeatWrapping;
  texture2.repeat.set(1, 1);

  // declare materials
  var material_1 = new THREE.MeshBasicMaterial(
  {
    map: texture,
    overdraw: true
  });

  var material_2 = new THREE.MeshBasicMaterial(
  {
    color: 0x000000,
    wireframe: true
  });

  var material_3 = new THREE.MeshBasicMaterial(
  {
    map: texture2,
    //color:0xcccccc,
    overdraw: true
  });

  var material_4 = new THREE.MeshBasicMaterial(
  {
    color: 0x000000,
    wireframe: true
  });

  //load materials
  var m1_loaded = false, m2_loaded = false;

  var textureCeiling = new Image();

  textureCeiling.onload = function () {

    texture.needsUpdate = true;
    material_1.map.image = this;
    m1_loaded = true;
    if (m1_loaded && m2_loaded) {
      start3dWorld();
    }
  };

  textureCeiling.src = 'http://www.erobo.net/scripts/javascript/' +
                       '33/scripts/house_roof.gif';

  var textureImgHouse = new Image();

  textureImgHouse.onload = function () {
    texture2.needsUpdate = true;
    material_3.map.image = this;
    m2_loaded = true;
    if (m1_loaded && m2_loaded) {
      start3dWorld();
    }
  };

  textureImgHouse.src = 'http://www.erobo.net/scripts/javascript/' +
                        '33/scripts/house_texture.gif';

  function start3dWorld() {

    // set the scene size
    var WIDTH = 464,
    HEIGHT = 380;

    // set some camera attributes
    var VIEW_ANGLE = 45,
    ASPECT = WIDTH / HEIGHT,
    NEAR = 0.1,
    FAR = 7000;

    // create a canvas renderer, camera
    // and a scene
    var camera = new THREE.PerspectiveCamera(VIEW_ANGLE,
                                ASPECT,
                                NEAR,
                                FAR);
    var scene = new THREE.Scene();

    // the camera starts at 0,0,0 so pull it back
    camera.position.z = 300;
    camera.position.y = 0;
    camera.position.x = 0;

    var vw_matrix = new THREE.Matrix4();

    //create terrain
    var geometry_terrain = new THREE.PlaneGeometry(1500, 1500, 30, 30);

    geometry_terrain.applyMatrix(vw_matrix);
    geometry_terrain.dynamic = true;
    geometry_terrain.computeFaceNormals();
    geometry_terrain.computeVertexNormals();
    geometry_terrain.computeTangents();
    geometry_terrain.computeCentroids();

    var plane2 = new THREE.Mesh(geometry_terrain,
    material_2);
    plane2.overdraw = true;
    plane2.position.set(0, -200, -750);
    plane2.rotation.x = -(Math.PI / 2);
    scene.add(plane2);

    //create sun
    //set up the sphere vars
    var radius = 50, segments = 10, rings = 10;

    var sphere2 = new THREE.Mesh(
    new THREE.SphereGeometry(radius, segments, rings),
    material_4);
    sphere2.position.set(-250, 220, -600);
    scene.add(sphere2);

    //create a house
    var house_geometry = new THREE.Geometry();

    //house front
    house_geometry.vertices.push(
          new THREE.Vector3(-10, 0, 0), new THREE.Vector3(-10, 10, 0),
          new THREE.Vector3(-3, 10, 0), new THREE.Vector3(-3, 0, 0));
    house_geometry.vertices.push(
          new THREE.Vector3(-3, 5, 0), new THREE.Vector3(-3, 10, 0),
          new THREE.Vector3(3, 10, 0), new THREE.Vector3(3, 5, 0));
    house_geometry.vertices.push(
          new THREE.Vector3(3, 0, 0), new THREE.Vector3(3, 10, 0),
          new THREE.Vector3(10, 10, 0), new THREE.Vector3(10, 0, 0));
    house_geometry.vertices.push(
          new THREE.Vector3(-10, 0, 0), new THREE.Vector3(-10, 0, -30),
          new THREE.Vector3(-10, 10, -30), new THREE.Vector3(-10, 10, 0));
    house_geometry.vertices.push(
          new THREE.Vector3(10, 0, 0), new THREE.Vector3(10, 0, -13),
          new THREE.Vector3(10, 10, -13), new THREE.Vector3(10, 10, 0));
    house_geometry.vertices.push(
          new THREE.Vector3(10, 0, -13), new THREE.Vector3(10, 3, -13),
          new THREE.Vector3(10, 3, -16), new THREE.Vector3(10, 0, -16));
    house_geometry.vertices.push(
          new THREE.Vector3(10, 6, -13), new THREE.Vector3(10, 10, -13),
          new THREE.Vector3(10, 10, -16), new THREE.Vector3(10, 6, -16));
    house_geometry.vertices.push(
          new THREE.Vector3(10, 0, -16), new THREE.Vector3(10, 10, -16),
          new THREE.Vector3(10, 10, -30), new THREE.Vector3(10, 0, -30));
    house_geometry.vertices.push(
          new THREE.Vector3(-10, 0, -30), new THREE.Vector3(-10, 10, -30),
          new THREE.Vector3(10, 10, -30), new THREE.Vector3(10, 0, -30));

    //create faces . Note: Face 4 is provided if using old versions of three.js
    house_geometry.faces.push(new THREE.Face3(0, 1, 2)); //Face 4(0, 1, 2, 3) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(0, 2, 3));
    house_geometry.faces.push(new THREE.Face3(3, 2, 1)); //Face 4(3, 2, 1, 0) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(3, 1, 0));
    house_geometry.faces.push(new THREE.Face3(4, 5, 6)); //Face 4(4, 5, 6, 7) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(4, 6, 7));
    house_geometry.faces.push(new THREE.Face3(7, 6, 5)); //Face 4(7, 6, 5, 4) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(7, 5, 4));
    house_geometry.faces.push(new THREE.Face3(8, 9, 10)); //Face 4(8, 9, 10, 11) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(8, 10, 11));
    house_geometry.faces.push(new THREE.Face3(11, 10, 9)); //Face 4(11, 10, 9, 8) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(11, 9, 8));
    house_geometry.faces.push(new THREE.Face3(12, 13, 14)); //Face 4(12, 13, 14, 15) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(12, 14, 15));
    house_geometry.faces.push(new THREE.Face3(15, 14, 13)); //Face 4(15, 14, 13, 12) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(15, 13, 12));
    house_geometry.faces.push(new THREE.Face3(16, 17, 18)); //Face 4(16, 17, 18, 19) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(16, 18, 19));
    house_geometry.faces.push(new THREE.Face3(19, 18, 17)); //Face 4(19, 18, 17, 16) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(19, 17, 16));
    house_geometry.faces.push(new THREE.Face3(20, 21, 22)); //Face 4(20, 21, 22, 23) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(20, 22, 23));
    house_geometry.faces.push(new THREE.Face3(23, 22, 21)); //Face 4(23, 22, 21, 20) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(23, 21, 20));
    house_geometry.faces.push(new THREE.Face3(24, 25, 26)); //Face 4(24, 25, 26, 27) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(24, 26, 27));
    house_geometry.faces.push(new THREE.Face3(27, 26, 25)); //Face 4(27, 26, 25, 24) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(27, 25, 24));
    house_geometry.faces.push(new THREE.Face3(28, 29, 30)); //Face 4(28, 29, 30, 31) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(28, 30, 31));
    house_geometry.faces.push(new THREE.Face3(31, 30, 29)); //Face 4(31, 30, 29, 28) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(31, 29, 28));
    house_geometry.faces.push(new THREE.Face3(32, 33, 34)); //Face 4(32, 33, 34, 35) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(32, 34, 35));
    house_geometry.faces.push(new THREE.Face3(35, 34, 33)); //Face 4(35, 34, 33, 32) Three.js < 60.0
    house_geometry.faces.push(new THREE.Face3(35, 33, 32));

    //create uvs
    var Uv1 = 0.8, Uv = 0.79;
    var Uv3 = 0.3, Uv2 = 0.29;
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    house_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);

    house_geometry.computeFaceNormals();
    house_geometry.computeCentroids();

    var house_object = new THREE.Mesh(house_geometry, material_3);

    var ceiling_geometry = new THREE.Geometry();

    //front hourse
    ceiling_geometry.vertices.push(
    new THREE.Vector3(-10, 10, 0), new THREE.Vector3(0, 20, 0),
    new THREE.Vector3(10, 10, 0));

    //side back house
    ceiling_geometry.vertices.push(
    new THREE.Vector3(-10, 10, -30), new THREE.Vector3(0, 20, -30),
    new THREE.Vector3(10, 10, -30));

    ceiling_geometry.faces.push(new THREE.Face3(0, 1, 2));
    ceiling_geometry.faces.push(new THREE.Face3(2, 1, 0));
    ceiling_geometry.faces.push(new THREE.Face3(3, 4, 5));
    ceiling_geometry.faces.push(new THREE.Face3(5, 4, 3));

    ceiling_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv2, Uv3), new THREE.Vector2(Uv2, Uv2),
        new THREE.Vector2(Uv3, Uv2)]);
    ceiling_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv3, Uv3),
        new THREE.Vector2(Uv2, Uv3)]);
    ceiling_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv, Uv),
        new THREE.Vector2(Uv1, Uv)]);
    ceiling_geometry.faceVertexUvs[0].push([
        new THREE.Vector2(Uv1, Uv), new THREE.Vector2(Uv1, Uv1),
        new THREE.Vector2(Uv, Uv1)]);

    ceiling_geometry.computeFaceNormals();
    ceiling_geometry.computeCentroids();

    //create house roof
    var house_ceiling = new THREE.Mesh(ceiling_geometry, material_3);

    var ceiling_geometry_2 = new THREE.Geometry();

    ceiling_geometry_2.vertices.push(
          new THREE.Vector3(-10, 10, 0), new THREE.Vector3(0, 20, 0),
          new THREE.Vector3(0, 20, -30), new THREE.Vector3(-10, 10, -30));
    ceiling_geometry_2.vertices.push(
          new THREE.Vector3(10, 10, 0), new THREE.Vector3(0, 20, 0),
          new THREE.Vector3(0, 20, -30), new THREE.Vector3(10, 10, -30));

    ceiling_geometry_2.faces.push(new THREE.Face3(0, 1, 2)); //Face 4(0, 1, 2, 3) Three.js < 60.0
    ceiling_geometry_2.faces.push(new THREE.Face3(0, 2, 3));
    ceiling_geometry_2.faces.push(new THREE.Face3(3, 2, 1)); //Face 4(3, 2, 1, 0) Three.js < 60.0
    ceiling_geometry_2.faces.push(new THREE.Face3(3, 1, 0));
    ceiling_geometry_2.faces.push(new THREE.Face3(4, 5, 6)); //Face 4(4, 5, 6, 7) Three.js < 60.0
    ceiling_geometry_2.faces.push(new THREE.Face3(4, 6, 7));
    ceiling_geometry_2.faces.push(new THREE.Face3(7, 6, 5)); //Face 4(7, 6, 5, 4) Three.js < 60.0
    ceiling_geometry_2.faces.push(new THREE.Face3(7, 5, 4));

    ceiling_geometry_2.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    ceiling_geometry_2.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    ceiling_geometry_2.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    ceiling_geometry_2.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    ceiling_geometry_2.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    ceiling_geometry_2.faceVertexUvs[0].push([
        new THREE.Vector2(Uv, Uv1), new THREE.Vector2(Uv1, Uv1), new THREE.Vector2(Uv1, Uv)]);
    ceiling_geometry_2.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);
    ceiling_geometry_2.faceVertexUvs[0].push([
        new THREE.Vector2(Uv3, Uv2), new THREE.Vector2(Uv2, Uv2), new THREE.Vector2(Uv2, Uv3)]);

    ceiling_geometry_2.computeFaceNormals();
    ceiling_geometry_2.computeCentroids();

    var house_ceiling_2 = new THREE.Mesh(ceiling_geometry_2, material_1);

    var house_3d_object = new THREE.Object3D();
    house_3d_object.add(house_object);
    house_3d_object.add(house_ceiling);
    house_3d_object.add(house_ceiling_2);
    house_3d_object.rotation.y = -0.8;
    house_3d_object.position.set(-10, -14, 250);
    scene.add(house_3d_object);

    scene.add(camera);

    var renderer = new THREE.CanvasRenderer();

    // start the renderer
    renderer.setSize(WIDTH, HEIGHT);

    renderer.setClearColor(0xffffff, 1);

    // attach the render-supplied DOM element
    container.appendChild(renderer.domElement);

    renderer.render(scene, camera);
  }

</script>



On the other hand, if you're looking for an application to automatically create your geometry / faces / uvs / meshes you can then use a 3D Drawing Application that can export to CanvasX or three.js with ease. Click here for a tutorial to create custom complex meshes and export them to CanvasX or three.js using a 3D App. You can also download 3D Vector Drawer to help you accomplish this task. And that's it for now. Enjoy!.

Downloads
CanvasX.min.js108.1 KB
house_roof.gif5.9 KB
house_texture.gif1.9 KB
virtual_world_sky.gif7.3 KB
 

See other Scripts in HTML5

 

Submit Your Scripts:

If you would like to have your Javascripts published in this section please fill out
the form below:
*Your Name or Username:
Home Town:
*Email:
*Description and Code:
*Enter Code shown
to the right:

[ Refresh Image ]