Глава 9  ◄     Содержание     ►   Глава 11

Three.js и геометрия.   Глава 10.   Подсолнечник (sunflower)

В данном разделе мы создадим модель огранки, которую можно назвать подсолнечник. Положим для этой огранки 128 граней рундиста. Вследствие этого на павильоне и короне мы можем расположить достаточно много четырехугольных граней определяющих ее внешний вид. Эти грани, если они раскрашены, имеют вид гармошки или, даже, подсолнечника. Полезно проследить последовательность и способы построения граней. Следует напомнить, что огранки алмазов должны обязательно быть (кроме специальных их типов) выпуклыми, и поэтому, так как данная модель имеет много граней на павильоне и короне, следует проявить особую внимательность при ее построении. С одной стороны желательно чтобы огранка была более рельефной. Но с другой стороны делать это надо осторожно, чтобы на ней не появились впадины, которые нарушат выпуклость. Поэтому нужно постоянно следить за азимутами и наклонами граней.

Есть еще одна особенность построения моделей многогранников. Если могогранник в основном образуют треугольные грани, то такие многогранники обычно более просто построить, чем те, которые имеют много четрехугольных (и более) граней. Это связано с тем, на первый взгляд очевидным фактом, что четырехугольная грань может лежать только в одной плоскости. В противном случае она будет сломана. Треугольник всегда проходит через три точки и обязательно лежит в одной плоскости, но для четырехугольника выбор четвертой точки иногда представляется большой проблемой - как связать две грани имеющие общую точку (или прямую) не сломав ни одну из этих граней. Пример - мы строим четырехугольную грань по трем точкам, а четвертая ее точка должна принадлежать еще одной грани лежащей в другой плоскости. Иногда, при неправильном построении, можно не сразу заметить, что первая грань оказалась сломанной. То, из каких многоугольников (или треугольников) и каким образом будет построен многогранник во многом (но не во всем) зависит от выбора набора параметров входящих в СДМ.


No      All      Cr-Gd-Pav

Нумерация вершин и обозначение граней модели

Схематичные изображения короны и павильона огранки, а также нумерация вершин показаны на рисунке 1 и 2. На рисунке 1 показана нумерация вершин короны при взгляде на нее сверху, а на рисунке 2 - нумерация вершин павильона при взгляде на него также сверху, но наблюдатель при этом находится внутри модели (или просто смотрит на модель сверху, но корона при этом является абсолютно прозрачной). Как уже говорилось при разработке модели бриллианта, при таком изображении граней короны и павильона они сопоставлены друг другу более естественным образом.

Следует только не забывать, что при таком изображении павильона обход его вершин (на рисунке 2) происходит по часовой стрелке. Обход вершин короны (на рисунке 1), как и положено, производится против часовой стрелки.

Структура данных модели (СДМ) огранки sunflower

Структура данных модели sunflower содержит следующие параметры:
var lw = 1.0; // отношение длины огранки к ее ширине
// Рундист
var r = 0.04; // толщина рундиста
var square_deviation = 0.00001; // квадратичность рундиста
// Корона
var hCrown = 0.2; // высота короны
var beta = 40*DEGREE; // угол наклона граней короны A0 - A15 
var t = 0.54; // ширина площадки
var hCrownDn = 0.055; // высота вершин 48, 49, 50, ... 63 короны
var hCrownMid = 0.06; // высота вершин 32, 33, 34, ... 47 короны
var hCrownUp = 0.115; // высота вершин 16, 17, 18, ... 31 короны
// Павильон
var hp = 0.58; // глубина павильона
var hPavFacet = 0.52; // задает глубину вершин 32, 33, ... 47 павильона
var ang_pav_main = 45*DEGREE; // угол наклона граней D0, D1, ... D15 павильона
var hPavUp = 0.12; // задает глубину вершин 0, 1, ... 15 павильона
var ang_pav_up = 56*DEGREE; // угол наклона граней F0, F1, ... F15 павильона

Построение рундиста огранки

Рундист огранки подсолнечник расчитывается по тому же принципу, что и рундист бриллианта. Отличие заключается а количестве его граней. Если для бриллианта их 64, то для подсолнечника - 128. Расчет вершин рундиста происходит в функции InitGirdle находящейся в файле sunflower_verts.js. Она вызывается в самом начале работы функции VerticesCalculation в которой расчитываются координатывсех вершин модели. В результате своей работы InitGirdle формирует массив girdle из 256 вершин рундиста. Все верхние 128 вершин рундиста имеют одинаковое значение координаты Z равное значению r/2, а все нижние 128 вершин имеют значение координаты Z равное -r/2. В дальнейшем, после построения короны и павильона огранки, координаты вершин рундиста по оси OZ корректируются.

Построение короны

Первый этап расчета заключается в нахождении координат вершин площадки. Это делает функция Fill_Table_Sunburst:
var table = [16];
.................
function Fill_Table_Sunburst()
{
	var N = 16;
	var fi_0 = -90*DEGREE;
	var r1 = (t/2) * lw; // Полуось эллипса по оси X
	var r2 = -t/2;          // Полуось эллипса по оси Y

	var dx = square_deviation;
	if ( dx < -1 || dx >= 0.995 )
		return null;
	var p = 2 / ( 1 - dx );  // Степень суперэллипса

	var del_fi = 2 * Math.PI / N; // Шаг углового параметра
	var x, y, w, fi;

	var i;
	for (i = 0; i < N; i++)
	{
		fi = fi_0 + i*del_fi; // Значение углового параметра
		x = Math.cos(fi);
		y = Math.sin(fi);
		w = Math.pow (Math.abs (x), p) + Math.pow (Math.abs (y), p);
		w = 1 / Math.pow ( w, 1/p );
		var point = new Point3D ( r1 * w * x,   r2 * w * y, hCrown + r/2);
		table[i] = point;
	}
}

Как можно видеть из приведенного исходного текста функция Fill_Table_Sunburst почти полностью совпадает с функцией InitGirdle за тем исключением, что она рассчитывает всего 16 точек суперэллипса и затем помещает их в массив table в котором хранятся координаты вершин площадки. Вершины массива table затем копируются в первые 16 элементов массива crown.

Затем расчитываются плоскости в которых лежат грани A0, A1, A2, A3, A4 и горизонтальная плоскость pl_hor_up лежащая на высоте hCrownUp + r/2.
  Рассмотрим положение вершины 16. Она расположена в точке пересечения сразу пяти плоскостей C0-2, C1-1, A0, A1 и треугольной плоскости примыкающей одной своей стороной к площадке. Однако нас в данный момент это не интересует, так как из рассмотрения рисунка 1 и учета того факта, что грани короны расположены одинаковым образом упорядочено по окружности, можно придти к выводу, что для нахождения положения вершины 16 требуется определить точку пересечения всего трех плоскостей A0, A1 и pl_hor_up. При этом проблемы, про которую упоминалось в начале данной главы, в дальнейшем не возникнет. Вершины короны 16, 17, 18 и 19 определяются как точки пересечения соответствующих плоскостей:

	// upPoint_A - точка в которой пересекаются грани A0, A1, ... A15
	// координата Z этой точки задается углом beta и зависит от размера площадки t и высоты короны hCrown
	var upPoint_A = new Point3D(0, 0, r/2 + hCrown + (t/2) * Math.tan(beta));
	
	// азимуты плоскостей A0, A1, ... A15 задаются вершинами рундиста
	
	// плоскость в которой лежит грань A0
	var a0 = new Vector3D(upPoint_A[0] - table[0][0], upPoint_A[1] - table[0][1], upPoint_A[2] - table[0][2]);
	var b0 = new Vector3D(girdle[124][0] - girdle[4][0], girdle[124][1] - girdle[4][1], 0);
	// нормальный вектор плоскости A0
	var vec_A0 = a0.Cross(b0); // векторное произведение векторов a0 и b0
	vec_A0.Normer();
	var A0 = new Plane3D(); 
	A0.CreatePlaneNormalVectorPoint(vec_A0, table[0]);
	
	...................................................
	...................................................
	
	// плоскость в которой лежит грань A4
	var a4 = new Vector3D(upPoint_A[0] - table[4][0], upPoint_A[1] - table[4][1], upPoint_A[2] - table[4][2]);
	var b4 = new Vector3D(girdle[28][0] - girdle[28][0], girdle[36][1] - girdle[28][1], 0);
	// нормальный вектор плоскости A4
	var vec_A4 = a4.Cross(b4); // векторное произведение векторов a4 и b4
	vec_A4.Normer();
	var A4 = new Plane3D(); 
	A4.CreatePlaneNormalVectorPoint(vec_A4, table[4]);	
	
	// создание горизонтальной плоскости
	var pl_hor_up = new Plane3D();
	pl_hor_up.CreatePlaneNormalDistOXYZ(Z1, hCrownUp + r/2);
	
	// точки пересечения трех плоскостей определяют координаты вершин короны
	crown[16] = pl_hor_up.IntersectionThreePlanes(A0, A1);
	crown[17] = pl_hor_up.IntersectionThreePlanes(A1, A2);
	crown[18] = pl_hor_up.IntersectionThreePlanes(A2, A3);
	crown[19] = pl_hor_up.IntersectionThreePlanes(A3, A4);
	
	//  координаты оставшихся вершин короны 20, 21, .... 31 лежащих на уровне плоскости 
	// pl_hor_up определяются исходя из симметрии модели
	crown[20] = new Point3D(crown[19][0], -crown[19][1], crown[19][2]);
	crown[21] = new Point3D(crown[18][0], -crown[18][1], crown[18][2]);
	..................................................................
	..................................................................

Теперь найдем вершины короны 32, 33, ... 47. Все они лежат на одной и той же высоте на плоскости pl_hor_mid. Достаточно найти положение вершин 32, 33, 34, 35, 36, а остальные вершины этого ряда находятся исходя из симметрии модели. Проведем вертикальные плоскости через вершины рундиста 8, 16, 24, 32. Тогда искомые вершины расположены в точках пересечения трех соответствующих плоскостей.

	// две вспомогательные точки лежащие на оси Z
	var pt000 = new Point3D(0,0,0);
	var pt001 = new Point3D(0,0,1);
	
	// вертикальные плоскости проходящие через вершины рундиста и ось Z
	var pl_g0 = new Plane3D(); 
	pl_g0.CreatePlaneThreePoints(pt000, pt001, girdle[0]);	
	var pl_g8 = new Plane3D(); 
	pl_g8.CreatePlaneThreePoints(pt000, pt001, girdle[8]);
	var pl_g16 = new Plane3D(); 
	pl_g16.CreatePlaneThreePoints(pt000, pt001, girdle[16]);
	var pl_g24 = new Plane3D(); 
	pl_g24.CreatePlaneThreePoints(pt000, pt001, girdle[24]);	
	var pl_g32 = new Plane3D(); 
	pl_g32.CreatePlaneThreePoints(pt000, pt001, girdle[32]);

	// горизонтальная плоскость лежащая на высоте hCrownMid + r/2
	var pl_hor_mid = new Plane3D();
	pl_hor_mid.CreatePlaneNormalDistOXYZ(Z1, hCrownMid + r/2);

	// точки пересечения трех плоскостей определяют координаты вершин короны
	crown[32] = pl_hor_mid.IntersectionThreePlanes(pl_g0, A0);
	crown[33] = pl_hor_mid.IntersectionThreePlanes(pl_g8, A1);
	crown[34] = pl_hor_mid.IntersectionThreePlanes(pl_g16, A2);
	crown[35] = pl_hor_mid.IntersectionThreePlanes(pl_g24, A3);
	crown[36] = pl_hor_mid.IntersectionThreePlanes(pl_g32, A4);
	
	//  координаты оставшихся вершин короны 37, 38, .... 47 лежащих на уровне плоскости 
	// pl_hor_mid определяются исходя из симметрии модели	
	crown[37] = new Point3D(crown[35][0], -crown[35][1], crown[35][2]);
	crown[38] = new Point3D(crown[34][0], -crown[34][1], crown[34][2]);

Перейдем к нахождению вершин 48, 49, ... 63 лежащих на высоте hCrownDn + r/2 на плоскости pl_hor_dn. Для этого нам сначала надо расчитать плоскости на которых лежат четырехугольные грани C0-1, C0-2, C1-1, C1-2, ... Положение вершин определяется как точки пересечения трех соответствующих плоскостей.

	// плоскости на которых лежат грани C0_2,  C1_1, ... C4_1
	var C0_2 = new Plane3D(); 
	C0_2.CreatePlaneThreePoints(crown[16], crown[32], girdle[0]);
	var C1_1 = new Plane3D(); 
	C1_1.CreatePlaneThreePoints(crown[16], crown[33], girdle[8]);
	var C1_2 = new Plane3D(); 
	C1_2.CreatePlaneThreePoints(crown[17], crown[33], girdle[8]);
	var C2_1 = new Plane3D(); 
	C2_1.CreatePlaneThreePoints(crown[17], crown[34], girdle[16]);
	var C2_2 = new Plane3D(); 
	C2_2.CreatePlaneThreePoints(crown[18], crown[34], girdle[16]);
	var C3_1 = new Plane3D(); 
	C3_1.CreatePlaneThreePoints(crown[18], crown[35], girdle[24]);
	var C3_2 = new Plane3D(); 
	C3_2.CreatePlaneThreePoints(crown[19], crown[35], girdle[24]);
	var C4_1 = new Plane3D(); 
	C4_1.CreatePlaneThreePoints(crown[19], crown[36], girdle[32]);
	
	// Плоскость определяющая высоту треугольных граней примыкающих к рундисту
	var pl_hor_dn = new Plane3D();
	pl_hor_dn.CreatePlaneNormalDistOXYZ(Z1, hCrownDn + r/2);
	
	// точки пересечения трех плоскостей определяют координаты вершин короны
	crown[48] = pl_hor_dn.IntersectionThreePlanes(C0_2, C1_1);
	crown[49] = pl_hor_dn.IntersectionThreePlanes(C1_2, C2_1);
	crown[50] = pl_hor_dn.IntersectionThreePlanes(C2_2, C3_1);
	crown[51] = pl_hor_dn.IntersectionThreePlanes(C3_2, C4_1);
	
	//  координаты оставшихся вершин короны 52, 53, .... 63 лежащих на уровне плоскости 
	// pl_hor_dn определяются исходя из симметрии модели	
	crown[52] = new Point3D(crown[51][0], -crown[51][1], crown[51][2]);
	crown[53] = new Point3D(crown[50][0], -crown[50][1], crown[50][2]);
	...................................................................

Корону можно построить несколько иным путем. Рассмотрим несколько вариантов. С этой целью будем изменять выбор параметров для СДМ.
 Исключим параметр hCrownDn, но добавим в СДМ угол наклона граней B0 - B15. Также как и в предыдущем варианте найдем плоскость в которой лежит грань A0, затем вершины 16 и 32 короны и плоскость грани C0-2. Теперь, имея угол наклона грани B0, найдем плоскость в которой эта грань лежит. Вершину короны 48 в этом варианте построения определим как точку пересечения плоскостей в которых лежат грани B0, C0-2 и C1-1. Плоскость в которой лежит грань C1-1 определяется по тому же алгоритму, что и плоскость C0-2.

Рассмотрим еще одну параметризацию и еще один вариант построения короны. Для этого надо убрать из СДМ параметр hCrownMid, но вернуть в СДМ параметр hCrownDn (и также предполагаем, что остался угол наклона грани B0). В этом случае мы найдем вершину 32 как точку пересечения плоскостей в которых лежат грани A0, C0-1 и C0-2. Плоскость, в которой лежит грань C0-1, расчитывается в этом случае по двум вершинам короны 31, 63 и вершине рундиста 0, а плоскость, в которой лежит грань C0-2, расчитывается по двум вершинам короны 16, 48 и вершине рундиста 0.

Можно рассмотреть также случай, когда из СДМ исключен параметр hCrownUp. Вершина 16 короны будет найдена как точка пересечения плоскостей A0, A1 и C0-2. Плоскость, в которой лежит грань C0-2, расчитывается в этом случае по двум вершинам короны 32, 48 и вершине рундиста 0.

Можно также в СДМ задать параметр для наклона грани B, задать параметр hCrownDn, но исключить сразу два параметра - hCrownMid и hCrownUp. Координаты вершины 32 короны находится как точка пересечения плоскостей в которых лежат грани C0-1, C0-2 и A0. Координаты вершины короны 48 находится как точка пересечения плоскостей в которых лежат грани C0-2, C1-1 и B0. В этом случае плоскости в которых лежат грани A0 и B0 расчитывается как и раньше. Для расчета плоскостей C0-1, C0-2 и C1-1 потребуется вычислить (или лучше сказать - задать) азимуты этих граней. Внимательно рассмотрев рисунок 1, можно заметить, что азимут граней C0-1, C0-2 и C1-1 должен некоторым образомзависить от изгиба рундиста на участках соединяющих его вершины 124 - 0, 0 - 4 и 4 - 8. Поэтомудля вычисления (задания) азимута грани C0-1 можно использовать координаты вершин рундиста 124 и 0, для грани C0-2 - координаты вершин рундиста 0 и 4, ну, а для грани C1-1 - координаты вершин рундиста 4 и 8. Конкретную привязку азимутов (и даже регулировку их значений) этих граней к координатам рундиста можно осуществить разными способами, но на них сейчас мы останавливаться не будем. Также для граней C0-1, C0-2 и C1-1 мы знаем как определить положение вершин короны 63 и 48. Известны нам и координаты всех вершин рундиста. Поэтому у нас есть достаточно информации для построения плоскостей в которых лежат грани C0-1, C0-2 и C1-1.

Построение павильона огранки

Сначала создадим плоскости на которых лежат грани D0, D1, D2, D3 и D4. Точки пересечения этих плоскостей между собой и с горизонтальной плоскостью лежащей на уровне
      - hPavFacet * hp - r/2
дадут вершины павильона 32, 33, 34 и 35. Положение вершин павильона 36, 37, ..., 47, лежащих на этом же уровне, находятся исходя из симметрии модели огранки.

	// калетта огранки
	var kollet = new Point3D(0, 0, - hp - r/2);

	// плоскости на которых лежат грани D0 - D4
	//   Они определены положением калетты, углом наклона плоскостей 
	// D0 - D15 заданным параметром ang_pav_main и направлением определяемым 
	// прямыми проходящими через соответствующие пары вершин рундиста.
	var D0 = new Plane3D();
	D0.CreateInclinePlane(-ang_pav_main, girdle[252], girdle[132], kollet);
	var D1 = new Plane3D();
	D1.CreateInclinePlane(-ang_pav_main, girdle[132], girdle[140], kollet);
	var D2 = new Plane3D();
	D2.CreateInclinePlane(-ang_pav_main, girdle[140], girdle[148], kollet);
	var D3 = new Plane3D();
	D3.CreateInclinePlane(-ang_pav_main, girdle[148], girdle[156], kollet);
	var D4 = new Plane3D();
	D4.CreateInclinePlane(-ang_pav_main, girdle[156], girdle[164], kollet);

	// горизонтальная плоскость на уровне - hPavFacet * hp - r/2
	var pl_pav_facet = new Plane3D();
	pl_pav_facet.CreatePlaneNormalDistOXYZ(Z1, - hPavFacet * hp - r/2);

	// Находим вершины павильона рядом с калетой
	pavil[32] = pl_pav_facet.IntersectionThreePlanes(D0, D1);
	pavil[33] = pl_pav_facet.IntersectionThreePlanes(D1, D2);
	pavil[34] = pl_pav_facet.IntersectionThreePlanes(D2, D3);
	pavil[35] = pl_pav_facet.IntersectionThreePlanes(D3, D4);

	//  координаты вершин павильона 36, 37, .... 47 лежащих на уровне плоскости 
	// pl_pav_facet определяются исходя из симметрии модели	
	pavil[36] = new Point3D(pavil[35][0], -pavil[35][1], pavil[35][2]);
	pavil[37] = new Point3D(pavil[34][0], -pavil[34][1], pavil[34][2]);
	..................................................................

Для нахождения положения вершин павильона 0, 1, 2 и 3 расчитываются плоскости в которых лежат грани F0, F1, F2, F3 и горизонтальная плоскость лежащая на уровне
      - hPavUp - r/2
Также расчитываются радиально расположенные вертикальные плоскости проходящие через калетту и вершины рундиста 132, 140, 148 и 156. После этого указанные вершины определяются как точки пересечения расчитанных плоскостей:

	// плоскости в которых лежат треугольные грани примыкающие к рундисту
	var F0 = new Plane3D();
	F0.CreateInclinePlane(-ang_pav_up, girdle[128], girdle[136], girdle[136]);
	var F1 = new Plane3D();
	F1.CreateInclinePlane(-ang_pav_up, girdle[136], girdle[144], girdle[144]);
	var F2 = new Plane3D();
	F2.CreateInclinePlane(-ang_pav_up, girdle[144], girdle[152], girdle[152]);
	var F3 = new Plane3D();
	F3.CreateInclinePlane(-ang_pav_up, girdle[152], girdle[160], girdle[160]);

	// горизонтальная плоскость
	var pl_pav_up = new Plane3D();
	pl_pav_up.CreatePlaneNormalDistOXYZ(Z1, - hPavUp - r/2);
	
	// вертикальные радиальные плоскости
	var pl_g132 = new Plane3D();
	pl_g132.CreatePlaneThreePoints(new Point3D(0,0,0), kollet, girdle[132]);
	var pl_g140 = new Plane3D();
	pl_g140.CreatePlaneThreePoints(new Point3D(0,0,0), kollet, girdle[140]);
	var pl_g148 = new Plane3D();
	pl_g148.CreatePlaneThreePoints(new Point3D(0,0,0), kollet, girdle[148]);
	var pl_g156 = new Plane3D();
	pl_g156.CreatePlaneThreePoints(new Point3D(0,0,0), kollet, girdle[156]);

	// вершины павильона 0, 1, 2 и 3
	pavil[0] = pl_pav_up.IntersectionThreePlanes(F0, pl_g132);
	pavil[1] = pl_pav_up.IntersectionThreePlanes(F1, pl_g140);
	pavil[2] = pl_pav_up.IntersectionThreePlanes(F2, pl_g148);
	pavil[3] = pl_pav_up.IntersectionThreePlanes(F3, pl_g156);

	// исходя из симметрии модели
	pavil[4]  = new Point3D(pavil[3][0], -pavil[3][1], pavil[3][2]);
	pavil[15] = new Point3D(-pavil[0][0], pavil[0][1], pavil[0][2]);

Положение вершины 16 короны найдем как точку пересечения плоскостей в которых лежат грани D0, E0-1 и E0-2. Плоскость в которой лежит грань D0 нам уже известна. Плоскости в которых лежат грани E0-1 и E0-2 мы построим по трем точкам используя координаты уже найденных вершин короны и рундиста. Вершины 17, 18, 19 и 20 найдем похожим способом.

	var E0_1 = new Plane3D();
	E0_1.CreatePlaneThreePoints(girdle[128], pavil[47], pavil[15]);
	var E0_2 = new Plane3D();
	E0_2.CreatePlaneThreePoints(girdle[128], pavil[32], pavil[0]);
	
	..............................................................
	..............................................................
	
	var E4_1 = new Plane3D();	
	E4_1.CreatePlaneThreePoints(girdle[160], pavil[35], pavil[3]);
	var E4_2 = new Plane3D();
	E4_2.CreatePlaneThreePoints(girdle[160], pavil[36], pavil[4]);

	pavil[16] = D0.IntersectionThreePlanes(E0_1, E0_2);
	pavil[17] = D1.IntersectionThreePlanes(E1_1, E1_2);
	pavil[18] = D2.IntersectionThreePlanes(E2_1, E2_2);
	pavil[19] = D3.IntersectionThreePlanes(E3_1, E3_2);
	pavil[20] = D4.IntersectionThreePlanes(E4_1, E4_2);
  Для павильона можно выбрать и другие способы параметризации - примерно таким же образом как это было сделано для короны.

Оставшиеся неопределенными вершины павильона находятся исходя из симметрии модели. Корректировка положения вершин рундиста со стороны как павильона, так и короны, производится таким же образом как это было сделано для модели бриллианта.

Краткое резюме по модели огранки подсолнечник

Огранка подсолнечник получилась довольно "гладкой" - для того чтобы она оставалась выпуклой нельзя слишком сильно измененять значения ее параметров.
  А что будет, если отменить проверку многогранника на выпуклость и если еще к этому добавить более широкий диапазон изменения значений параметров в dat.GUI ? Это можно увидеть из приведенных далее двух скриншотов экрана.


На первом скриншоте можно увидеть что-то похожее на солнце с лучами, а на втором - возможно это какая-то космическая станция или обитатель морских глубин ?
 Если раскрасить грани многогранника в подходящие цвета, то возможно эти изображения стали бы еще более интересными. Немного поэкспериментировав, можно получить совершенно фантастические 3D-модели практически без всяких усилий.

P.S. В главе, в которой рассматривается многогранник бриолет, отмена проверок позволяет получить одним нажатием клавиши "a" из огранки довольно устрашающую бомбу или, возможно, что-то напоминающее дирижабль. Для возврата к исходной огранке бриолет следует нажать клавишу "r".

   Глава 9  ◄     Содержание     ►   Глава 11