This question is related to this question. The answer shows very nice way to extrude polygons that have holes (see the excellent live example). The main learning of the answer was that paths in three.js (r58) cannot have more than one moveTo command and it have to be in the start of the path, which means that path have to be broken by moveTos, so that moveTo start always a new path.
Extruding in three.js means that 2D paths are converted to 3D shapes using possible beveling. It is suitable for extruding texts to make 3D letters and words, but can be used also to extrude custom paths.
Now there arises two questions:
how is it possible to handle polygons that have multiple hole-polygons and multiple non-hole-polygons?
how is it possible to add a texture to generated shape as a whole?
I made an example of this as SVG in http://jsbin.com/oqomuj/1/edit:
The image is produced using this path:
<path d="
M57.11,271.77 L57.11,218.33 L41.99,218.63 L105.49,165.77 L138.41,193.18 L138.41,172.2 L152.53,172.2 L152.53,204.93 L168.99,218.63 L153.21,218.63 L153.21,271.77Z
M74.14,264.13 L105.49,264.13 L105.49,232.8 L74.14,232.8Z
M115.35,250.7 L135.96,250.7 L135.96,232.61 L115.35,232.61Z
M56.11,145.77 L56.11,92.33 L40.99,92.63 L104.49,39.77 L137.41,67.18 L137.41,46.2 L151.53,46.2 L151.53,78.93 L152.53,79.76 L155.55,77.23 L159.5,74.52 L168.65,69.81 L176.46,66.93 L188.04,64.16 L200.63,62.7 L213.65,62.7 L226.05,64.09 L234.83,66.06 L245.65,69.73 L252.87,73.27 L259.12,77.34 L262.63,80.33 L265.6,83.47 L268.01,86.76 L269.83,90.17 L271.08,93.68 L271.76,99.08 L271.04,104.64 L269.75,108.2 L267.87,111.63 L265.42,114.91 L262.44,118.01 L258.95,120.92 L255.02,123.63 L245.86,128.34 L238.06,131.22 L226.48,133.99 L213.88,135.44 L200.63,135.44 L188.04,133.99 L176.46,131.22 L168.65,128.34 L159.5,123.63 L155.55,120.92 L152.21,118.12 L152.21,145.77Z
M73.14,138.13 L104.49,138.13 L104.49,106.8 L73.14,106.8Z
M114.35,124.7 L134.96,124.7 L134.96,106.61 L114.35,106.61Z
M207.26,117.33 L210.57,117.26 L216.87,116.53 L222.66,115.15 L227.8,113.18 L233.11,110 L236.34,106.99 L238.51,103.64 L239.42,100.48 L239.42,97.67 L238.51,94.51 L236.34,91.16 L233.11,88.15 L227.8,84.97 L222.66,83 L216.87,81.62 L210.57,80.89 L203.94,80.89 L197.65,81.62 L191.86,83 L186.71,84.97 L181.41,88.15 L178.18,91.16 L176.01,94.51 L175.1,97.67 L175.1,100.48 L176.01,103.64 L178.18,106.99 L181.41,110 L186.71,113.18 L191.86,115.15 L197.65,116.53 L203.94,117.26Z
"></path>
and this path converted to individual arrays of vertices:
var lower_house_material = [{x:57.11,y:271.77},{x:57.11,y:218.33},{x:41.99,y:218.63},{x:105.49,y:165.77},{x:138.42,y:193.18},{x:138.42,y:172.2},{x:152.53,y:172.2},{x:152.53,y:204.93},{x:168.99,y:218.63},{x:153.21,y:218.63},{x:153.21,y:271.77}];
var lower_house_hole_1 = [{x:74.14,y:264.13},{x:105.49,y:264.13},{x:105.49,y:232.8},{x:74.14,y:232.8}];
var lower_house_hole_2 = [{x:115.35,y:250.7},{x:135.96,y:250.7},{x:135.96,y:232.61},{x:115.35,y:232.61}];
var upper_house_material = [{x:56.11,y:145.77},{x:56.11,y:92.33},{x:40.99,y:92.63},{x:104.49,y:39.77},{x:137.42,y:67.18},{x:137.42,y:46.2},{x:151.53,y:46.2},{x:151.53,y:78.93},{x:152.53,y:79.76},{x:155.55,y:77.23},{x:159.5,y:74.52},{x:168.65,y:69.81},{x:176.46,y:66.93},{x:188.04,y:64.16},{x:200.63,y:62.7},{x:213.65,y:62.7},{x:226.05,y:64.1},{x:234.83,y:66.06},{x:245.65,y:69.73},{x:252.87,y:73.27},{x:259.12,y:77.35},{x:262.63,y:80.33},{x:265.6,y:83.47},{x:268.01,y:86.76},{x:269.84,y:90.17},{x:271.08,y:93.68},{x:271.76,y:99.08},{x:271.04,y:104.64},{x:269.75,y:108.2},{x:267.87,y:111.63},{x:265.42,y:114.91},{x:262.44,y:118.01},{x:258.96,y:120.92},{x:255.02,y:123.63},{x:245.86,y:128.34},{x:238.06,y:131.22},{x:226.48,y:133.99},{x:213.88,y:135.45},{x:200.63,y:135.45},{x:188.04,y:133.99},{x:176.46,y:131.22},{x:168.65,y:128.34},{x:159.5,y:123.63},{x:155.55,y:120.92},{x:152.21,y:118.12},{x:152.21,y:145.77}];
var upper_house_hole_1 = [{x:73.14,y:138.13},{x:104.49,y:138.13},{x:104.49,y:106.8},{x:73.14,y:106.8}];
var upper_house_hole_2 = [{x:114.35,y:124.7},{x:134.96,y:124.7},{x:134.96,y:106.61},{x:114.35,y:106.61}];
var upper_house_hole_3 = [{x:207.26,y:117.33},{x:210.57,y:117.26},{x:216.87,y:116.53},{x:222.66,y:115.15},{x:227.8,y:113.18},{x:233.11,y:110},{x:236.34,y:106.99},{x:238.51,y:103.64},{x:239.42,y:100.48},{x:239.42,y:97.67},{x:238.51,y:94.51},{x:236.34,y:91.16},{x:233.11,y:88.15},{x:227.8,y:84.97},{x:222.66,y:83},{x:216.87,y:81.62},{x:210.57,y:80.89},{x:203.94,y:80.89},{x:197.65,y:81.62},{x:191.86,y:83},{x:186.71,y:84.97},{x:181.41,y:88.15},{x:178.18,y:91.16},{x:176.01,y:94.51},{x:175.1,y:97.67},{x:175.1,y:100.48},{x:176.01,y:103.64},{x:178.18,y:106.99},{x:181.41,y:110},{x:186.71,y:113.18},{x:191.86,y:115.15},{x:197.65,y:116.53},{x:203.94,y:117.26}];
The question is, how this like structure can be converted to 3D object in three.js so that it can be extruded using THREE.ExtrudeGeometry( shape, extrusionSettings ) and after that textured as a whole?
I can examine the path data to know what hole belongs to what polygon and handle all as separate shapes, but because I want to use one texture image across all the shapes, I think the preferred way is to handle all material-polygons as one shape, and hole-polygons as other shape and use something like:
var shape = [lower_house_material, upper_house_material];
shape.holes = [lower_house_hole_1, lower_house_hole_2, upper_house_hole_1, upper_house_hole_2, upper_house_hole_3];
var 3d_geometry = THREE.ExtrudeGeometry( shape, extrusionSettings );
So the 3d_geometry should be at the end one mesh to which I can append a texture this way:
var textureFront = new THREE.ImageUtils.loadTexture( 'textureFront.png');
var textureSide = new THREE.ImageUtils.loadTexture( 'textureSide.png');
var materialFront = new THREE.MeshBasicMaterial( { map: textureFront } );
var materialSide = new THREE.MeshBasicMaterial( { map: textureSide } );
var materialArray = [ materialFront, materialSide ];
var faceMaterial = new THREE.MeshFaceMaterial(materialArray);
var final_mesh = new THREE.Mesh(3d_geometry, faceMaterial );
And one of the textures could be something like this (256x256px):
And texture applied:
And because the mesh is extruded, there is also 3D thickness on the above, but you got the idea of texturing.
I know that y-coordinates have to be flipped but it is a trivial task and not the point of my question, but if three.js has ready-made function for clipping y, it would be helpful.
I have spent hours to examine the three.js source code, examples and documentation, but because the most frequent word there is "todo", it cannot help much. And I'm very newbie to three.js, I would think that this may be trivial task for some experienced three.js user.
UPDATE: And just to make sure, the hole polygons are always well-behaved, which means that hole polygons are always fully inside material-polygons and there are no duplicate vertices or self-intersections either in material-polygons or hole-polygons and all material-polygons have CW winding order and holes CCW.
UPDATE: Merging geometries was not a solution for texturing the whole extruded polygon set by one texture: http://jsfiddle.net/C5dga. The texture is repeated on all individual shapes, so merging geometries in this case has no real meaning. The solution could be possibly found on merging shapes before they are extruded, but not found solution for this yet.
You can merge geometries as in the following snippet, resulting in just a single mesh. From your prior questions, you already know how to texture a single geometry.
var geometry1 = new THREE.ExtrudeGeometry( shape1, extrusionSettings );
var geometry2 = new THREE.ExtrudeGeometry( shape2, extrusionSettings );
geometry1.merge( geometry2 );
. . .
var mesh = new THREE.Mesh( geometry1, material );
scene.add( mesh );
Fiddle: http://jsfiddle.net/pHn2B/88/
Fiddle: http://jsfiddle.net/C5dga/13/ (with texture)
EDIT: As an alternative to creating separate geometries and using the merge utility, you can create a single geometry using the following pattern, instead:
var geometry1 = new THREE.ExtrudeGeometry( [ shape1, shape2 ], extrusionSettings );
EDIT: updated to three.js r.70
I have a shape that consists of 4 polygons: 2 non-holes and 2 holes. This is only an example. In reality there can be a shape that consists of 50 polygons, of which 20 are non-holes and 30 are holes. In SVG path this like polygon can be represented easily by combining moveto:s and lineto:s. Every sub-polygon (hole or non-hole) in path string starts with moveto-command and ends with z (end) command and non-holes have winding order CW and holes CCW. Very easy and intuitive.
The shape in SVG is represented this way (http://jsbin.com/osoxev/1/edit):
<path d="M305.08,241.97 L306,251.51 L308.18,256.39 L311.72,259.09 L317.31,260.01 L324.71,259.01 L332.45,255.86 L335.57,257.53 L337.6,260.44 L336.94,262.33 L328.27,268.74 L317.89,273.41 L307.94,275.49 L296.26,275.23 L286.64,272.99 L279.78,269.31 L274.14,263.55 L271.65,260.21 L269.2,261.06 L254.83,268.51 L242.11,272.97 L227.59,275.23 L209.91,275.48 L197.47,273.63 L187.91,270.13 L180.48,265.09 L175.32,258.88 L172.2,251.44 L171.1,242.23 L172.24,233.63 L175.49,226.24 L181,219.54 L189.42,213.3 L201.36,207.73 L217.23,203.25 L238.28,200.1 L265.24,198.78 L269.37,198.47 L269.98,182.93 L268.74,171.32 L266.05,163.7 L261.58,157.72 L255.24,153.24 L247.06,150.32 L235.44,149.13 L224.71,150.05 L215.91,153 L210.23,156.86 L207.64,160.85 L207.19,165.28 L209.34,169.86 L212.01,174.15 L212.14,177.99 L209.8,181.78 L204.22,185.79 L197.62,187.68 L188.65,187.43 L182.41,185.39 L178.45,181.77 L176.2,176.9 L176.03,170.64 L178.2,164.13 L183.09,157.69 L191.04,151.36 L202.01,145.82 L216.09,141.57 L232.08,139.24 L250.07,139.18 L266.13,141.23 L279.05,145.06 L289.15,150.3 L295.91,156.19 L300.73,163.41 L303.85,172.47 L305.07,183.78 L305.07,241.97 L305.08,241.97Z
M243.99,64.95 L255.92,66.06 L266.21,69.28 L274.98,74.44 L280.64,80.19 L284.02,86.85 L285.26,94.52 L284.27,102.84 L281.24,109.66 L276.03,115.43 L267.89,120.46 L257.68,123.93 L245.79,125.33 L232.93,124.53 L222.21,121.74 L213.14,117.11 L207.36,111.92 L203.7,105.75 L201.94,98.18 L202.34,90.12 L204.86,83.4 L210.01,76.81 L217.49,71.33 L227.17,67.31 L238.35,65.2 L243.75,64.95 L243.99,64.95Z
M269.99,212.88 L269.48,208.76 L266.59,208.36 L245.76,210.86 L230.95,214.67 L220.9,219.34 L213.82,224.85 L209.69,230.71 L207.92,237.03 L208.4,244.49 L210.86,250.57 L215.2,255.08 L221.69,258.13 L230.57,259.43 L242.52,258.58 L255.27,255.23 L266.07,250.04 L269.34,247.02 L269.99,244.81 L269.99,212.88 L269.99,212.88Z
M243.63,73.34 L235.93,74.4 L230.07,77.36 L225.65,82.21 L223.05,88.57 L222.41,96.92 L223.94,104.53 L227.23,110.22 L231.99,114.29 L238.44,116.65 L246.81,116.94 L253.73,115.1 L258.87,111.5 L262.63,106.12 L264.64,98.93 L264.59,90.25 L262.47,83.41 L258.65,78.43 L253.37,75.08 L246.08,73.43 L243.68,73.34 L243.63,73.34Z"/>
When I try to follow the same logic in three.js, I run into problems. Below is an image of this:
The three.js doesn't seem to understand what moveto means. It should make "pen up" and draw nothing between previous point and point of moveto command. But the "pen doesnt go up" and the shape breaks.
The code portion is this (don't confuse of variable names, they are from other example):
// Create glyph shape (sorry the confusing name):
var starPoints2 = new THREE.Shape();
// Add first polygon
starPoints2.moveTo(307.94,275.49);
starPoints2.lineTo(296.26,275.23);
// .....
starPoints2.lineTo(286.64,272.99);
starPoints2.lineTo(307.94,275.49);
// Add second polygon
starPoints2.moveTo(245.79,125.33);
starPoints2.lineTo(232.93,124.53);
// .....
starPoints2.lineTo(257.68,123.93);
starPoints2.lineTo(245.79,125.33);
// Create path for holes
var smileyEye1Path = new THREE.Path();
// First hole
smileyEye1Path.moveTo(221.69,258.13);
smileyEye1Path.lineTo(215.2,255.08);
// .....
smileyEye1Path.lineTo(230.57,259.43);
smileyEye1Path.lineTo(221.69,258.13);
// Second hole
smileyEye1Path.moveTo(238.44,116.65);
smileyEye1Path.lineTo(231.99,114.29);
// .....
smileyEye1Path.lineTo(246.81,116.94);
smileyEye1Path.lineTo(238.44,116.65);
// Add holes to shape
var starShape = starPoints2;
starShape.holes.push( smileyEye1Path );
// Extrude after that. See the full code here:
// http://jsfiddle.net/pHn2B/33/
function(){}
http://jsfiddle.net/pHn2B/33/
What I'm doing wrong in my code or is there bug in three.js?
You can't have a moveTo in the middle of a shape definition. You have to have two separate shapes. You can do something like this:
var object = new THREE.Object3D();
var shape1 = new THREE.Shape();
var shape2 = new THREE.Shape();
var hole1 = new THREE.Path();
var hole2 = new THREE.Path();
shape1.holes.push( hole1 );
shape2.holes.push( hole2 );
. . .
object.add( mesh1 );
object.add( mesh2 );
scene.add( object );
Fiddle: http://jsfiddle.net/pHn2B/34/
three.js r.58
P.S. Friendly tip: In the future, it would be a good idea to make it easy for people to help you -- edit your variable names and remove unrelated code from your example.