Search
Write a publication
Pull to refresh

Comments 9

PinnedPinned comments

На самом деле микро-чанки — не самый удачный пример, потому что «монетизировать» их микро-адреса ни я, ни Док не пытались и смысла не видели. Там ещё была куча идей, показывающих, как важно генерить максимум идей, чтобы было что отметать, а одна потом отомстила за всех.

В его случае это была «шестерёнка», в моём — «собачий логарифм». И да, мне отметать тоже пришлось мешок вариантов, включая извращения на тему манхэттенского расстояния, а уж касаемо текстурирования — тут кладбище вариантов может поспорить со средним кладбищем какодемонов после визита спейсмарина, причём текущий мне тоже не особо…

Хорошая статья. Девайсы Смотрел, к сожалению пока не хватает скилла на ремонт. Могу выслать.

Рейкастер я тоже писал, правда у меня стены были с текстурами и пришлось поломать голову с вычислением v-координаты для интерполятора. Интерполятор был на фикседпоинтах и сильно зависел от филлрейта (насколько утыкаемся носом в стенку), но на омапе 200мгц при 240х320 бегал.

Девайсы Смотрел

Ахаха, и не сказал %)

Чего там где и как? Расскажи хоть итоги осмотра :)

UPD: чёрт, не туда кликнул :( ответить хотел :(

На самом деле микро-чанки — не самый удачный пример, потому что «монетизировать» их микро-адреса ни я, ни Док не пытались и смысла не видели. Там ещё была куча идей, показывающих, как важно генерить максимум идей, чтобы было что отметать, а одна потом отомстила за всех.

В его случае это была «шестерёнка», в моём — «собачий логарифм». И да, мне отметать тоже пришлось мешок вариантов, включая извращения на тему манхэттенского расстояния, а уж касаемо текстурирования — тут кладбище вариантов может поспорить со средним кладбищем какодемонов после визита спейсмарина, причём текущий мне тоже не особо…

Следующая статья будет про текстурирование?

Пока я это дело приостановил, посмотрим, что Док сделает со своей частью — а там посмотрим, сколько и чего у нас осталось на «улучшайзинги». Текстуры пока так и остались на том варианте, который можно на «Итче» скачать и глянуть. Ну то есть на таком, примерно более-менее дающем зацепки для глаза насчёт расстояний и углов, но очень уж корявые эти зацепки…

Я сейчас ковыряю другие занимательные движки «на грани эзотерики», в том числе пресловутый MDA — возможно, надписи на стенах оттуда можно будет тоже сюда добавить в качестве вариантов текстурирования. Но там сильно больше столбцов, так что есть у меня опасения.

Так что следующая статья «на тему» будет, наверное, всё-таки про динозавров пека, хотя кое-какие наработки для этого движка там могут промелькнуть. А буквально следующая — это лог старых наработок про открытый телефон-звонилку, я её вот сейчас пытаюсь запостить :)

Думаю, как применить наработки 2.5D движка BoxEngine (тоже в своём роде самого-самого, см. также недавний комментарий) для текстурирования стен на Dendy, с учётом ограничений.
 

BoxEngine разрабатывался изначально так, чтобы выдать наилучшую картинку в 128x128 (2006-й год), так что сверхнизкое разрешение 32x30 само по себе не проблема.

Но вот палитра, 4 цвета на 4 тайла… Можно ли выбрать цвета так, чтобы не было вырвиглазно? Первый цвет это фон (серый), второй цвет это А, третий цвет это Б, четвёртый цвет это ближайший к (А+Б)/2.

 

Можно пока сделать одномерные текстуры. Это позволит не заморачиваться с вычислением текстурной координаты для каждого столбца, и памяти под текстуры тогда вволю (даже добавив в будущем поддержку двухмерных, есть смысл использовать и одномерные, где возможно).

 

Теперь, остаётся только научиться рисовать…

Текстурированный столбец

Код из BoxEngine (вариант на C, для редактора на компьютере):

__declspec (dllexport) int WINAPI DrawTexLineW(PIXEL *GenericPixels, int *WallTilesAddresses, int x0, int YStart, int tgl, int TileStart, int TileFinish, int adr, int xmin, int xmax, int TexPeriod, int TexFloater, int lig, int addi) {
	int xs,xc,t,b,addr,xcx,t2,x,k,t3,i,x2,tt1,tt2,xmx1,xmx2,xmn1,xmn2,LC;
	PIXEL c,c1,c2;	
	//addr=adr+(TileStart%TexPeriod);
	t=tgl>>(2+TexFloater);
	t2=(tgl>>(3+TexFloater))<<3;
	t3=t>>1; //t3a=t3+1;
	xs=YStart; x=(xs>>TexFloater)*ScreenWidth+x0;
	//tgl+=ScreenWidth<<TexFloater;
	LC=AO[0]<<24;
	switch (t) {
	case 1:
		for (k=TileStart;k<=TileFinish;k++) {
			if (x>xmax) break;
			//if (k%TexPeriod==0) addr=adr;
			addr=adr+WallTilesAddresses[k];
			LC=(AO[k]*lig+addi)&0xFF000000;
			xs+=tgl;
			xc=xs>>TexFloater;
			b=TexelPriorities8[xc-(x/ScreenWidth)];
			if ((b&3)==3) {
				GenericPixels[x+=ScreenWidth]=Textures0[addr]|LC;
				GenericPixels[x+=ScreenWidth]=Textures1[addr]|LC;
			} else GenericPixels[x+=ScreenWidth]=Textures01[addr]|LC;
			if ((b&12)==12) {
				GenericPixels[x+=ScreenWidth]=Textures2[addr]|LC;
				GenericPixels[x+=ScreenWidth]=Textures3[addr]|LC;
			} else GenericPixels[x+=ScreenWidth]=Textures23[addr]|LC;
			if ((b&48)==48) {
				GenericPixels[x+=ScreenWidth]=Textures4[addr]|LC;
				GenericPixels[x+=ScreenWidth]=Textures5[addr]|LC;
			} else GenericPixels[x+=ScreenWidth]=Textures45[addr]|LC;
			if ((b&192)==192) {
				GenericPixels[x+=ScreenWidth]=Textures6[addr]|LC;
				GenericPixels[x+=ScreenWidth]=Textures7[addr]|LC;
			} else GenericPixels[x+=ScreenWidth]=Textures67[addr]|LC;
			addr++;
		}
		break;
	case 2:
		for (k=TileStart;k<=TileFinish;k++) {
			if (x>xmax) break;
			//if (k%TexPeriod==0) addr=adr;
			addr=adr+WallTilesAddresses[k];
			LC=(AO[k]*lig+addi)&0xFF000000;
			xs+=tgl;
			xc=xs>>TexFloater;
			xcx=(xc-(x/ScreenWidth))&7;
			if (xcx==0&&xc-(x/ScreenWidth)>t2)
				b=TexelPriorities8[8];
			else
				b=TexelPriorities8[xcx];
			GenericPixels[x+=ScreenWidth]=Textures0[addr]|LC;
			if ((b&3)!=0) GenericPixels[x+=ScreenWidth]=Textures01[addr]|LC;
			GenericPixels[x+=ScreenWidth]=Textures1[addr]|LC;
			GenericPixels[x+=ScreenWidth]=Textures2[addr]|LC;
			if ((b&12)!=0) GenericPixels[x+=ScreenWidth]=Textures23[addr]|LC;
			GenericPixels[x+=ScreenWidth]=Textures3[addr]|LC;
			GenericPixels[x+=ScreenWidth]=Textures4[addr]|LC;
			if ((b&48)!=0) GenericPixels[x+=ScreenWidth]=Textures45[addr]|LC;
			GenericPixels[x+=ScreenWidth]=Textures5[addr]|LC;
			GenericPixels[x+=ScreenWidth]=Textures6[addr]|LC;
			if ((b&192)!=0) GenericPixels[x+=ScreenWidth]=Textures67[addr]|LC;
			GenericPixels[x+=ScreenWidth]=Textures7[addr]|LC;
			addr++;
		}
		break;
	case 3:
		for (k=TileStart;k<=TileFinish;k++) {
			if (x>xmax) break;
			//if (k%TexPeriod==0) addr=adr;
			addr=adr+WallTilesAddresses[k];
			LC=(AO[k]*lig+addi)&0xFF000000;
			xs+=tgl;
			xc=xs>>TexFloater;
			xcx=(xc-(x/ScreenWidth))&7;
			if (xcx==0&&xc-(x/ScreenWidth)>t2)
				b=TexelPriorities8[8];
			else
				b=TexelPriorities8[xcx];		
			if ((b&3)==3) {
				//GenericPixels[x+=ScreenWidth]=GenericPixels[x+=ScreenWidth]=Textures0[addr]|LC;
				//GenericPixels[x+=ScreenWidth]=GenericPixels[x+=ScreenWidth]=Textures1[addr]|LC;
				GenericPixels[x+=ScreenWidth]=c=Textures0[addr]|LC;
				GenericPixels[x+=ScreenWidth]=c;
				GenericPixels[x+=ScreenWidth]=c=Textures1[addr]|LC;
				GenericPixels[x+=ScreenWidth]=c;
			} else {
				GenericPixels[x+=ScreenWidth]=Textures0[addr]|LC;
				GenericPixels[x+=ScreenWidth]=Textures01[addr]|LC;
				GenericPixels[x+=ScreenWidth]=Textures1[addr]|LC;
			}
			if ((b&12)==12) {
				GenericPixels[x+=ScreenWidth]=c=Textures2[addr]|LC;
				GenericPixels[x+=ScreenWidth]=c;
				GenericPixels[x+=ScreenWidth]=c=Textures3[addr]|LC;
				GenericPixels[x+=ScreenWidth]=c;
			} else {
				GenericPixels[x+=ScreenWidth]=Textures2[addr]|LC;
				GenericPixels[x+=ScreenWidth]=Textures23[addr]|LC;
				GenericPixels[x+=ScreenWidth]=Textures3[addr]|LC;
			}
			if ((b&48)==48) {
				GenericPixels[x+=ScreenWidth]=c=Textures4[addr]|LC;
				GenericPixels[x+=ScreenWidth]=c;
				GenericPixels[x+=ScreenWidth]=c=Textures5[addr]|LC;
				GenericPixels[x+=ScreenWidth]=c;
			} else {
				GenericPixels[x+=ScreenWidth]=Textures4[addr]|LC;
				GenericPixels[x+=ScreenWidth]=Textures45[addr]|LC;
				GenericPixels[x+=ScreenWidth]=Textures5[addr]|LC;
			}
			if ((b&192)==192) {
				GenericPixels[x+=ScreenWidth]=c=Textures6[addr]|LC;
				GenericPixels[x+=ScreenWidth]=c;
				GenericPixels[x+=ScreenWidth]=c=Textures7[addr]|LC;
				GenericPixels[x+=ScreenWidth]=c;
			} else {
				GenericPixels[x+=ScreenWidth]=Textures6[addr]|LC;
				GenericPixels[x+=ScreenWidth]=Textures67[addr]|LC;
				GenericPixels[x+=ScreenWidth]=Textures7[addr]|LC;
			}
			addr++;
		}
		break;
	default:
		int tt1s,tt2s;
		tt1=t3+(t&1); tt1s=tt1*ScreenWidth;
		tt2=t3+1; tt2s=tt2*ScreenWidth;
		xmx1=xmax-(tt1+tt1)*ScreenWidth;
		xmx2=xmax-(tt2+t3)*ScreenWidth;
		xmn1=xmin+(tt1+tt1)*ScreenWidth;
		xmn2=xmin+(tt2+t3)*ScreenWidth;

		xs=YStart+tgl/*-(1<<TexFloater)*/; x=(xs>>TexFloater)*ScreenWidth+x0;
		//xs=YStart+tgl-TexFloater; x=xs>>TexFloater;
		k=TileStart;
		if (YStart<=((xmin/ScreenWidth)<<TexFloater)) {
			LC=(AO[k]*lig+addi)&0xFF000000;
			addr=adr+WallTilesAddresses[k++];
			xs-=tgl;
			xc=xs>>TexFloater;
			xcx=((x/ScreenWidth)-xc)&7;
			if (xcx==0&&(x/ScreenWidth)-xc>t2)
				b=TexelPriorities8k[8];
			else
				b=TexelPriorities8k[xcx];
			//1
			c1=Textures7[addr]|LC;	c2=Textures6[addr]|LC;
			if ((b&1)!=0) {
				if (x<xmn2) {c=Textures67[addr]|LC; goto fin_l2;}
				x2=x-tt2s;
				for (i=0;i<t3;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
				GenericPixels[x-=ScreenWidth]=Textures67[addr]|LC;
			} else {
				if (x<xmn1) goto fin_l1;
				x2=x-tt1s;
				for (i=0;i<tt1;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
			}
			x=x2;
			//2
			c1=Textures5[addr]|LC;	c2=Textures4[addr]|LC;
			if ((b&2)!=0) {
				if (x<xmn2) {c=Textures45[addr]|LC; goto fin_l2;}
				x2=x-tt2s;
				for (i=0;i<t3;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
				GenericPixels[x-=ScreenWidth]=Textures45[addr]|LC;
			} else {
				if (x<xmn1) goto fin_l1;
				x2=x-tt1s;
				for (i=0;i<tt1;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
			}
			x=x2;
			//3
			c1=Textures3[addr]|LC;	c2=Textures2[addr]|LC;
			if ((b&4)!=0) {
				if (x<xmn2) {c=Textures23[addr]|LC; goto fin_l2;}
				x2=x-tt2s;
				for (i=0;i<t3;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
				GenericPixels[x-=ScreenWidth]=Textures23[addr]|LC;
			} else {
				if (x<xmn1) goto fin_l1;
				x2=x-tt1s;
				for (i=0;i<tt1;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
			}
			x=x2;
			//4
			c1=Textures1[addr]|LC;	c2=Textures0[addr]|LC;
			if ((b&8)!=0) {
				if (x<xmn2) {c=Textures01[addr]|LC; goto fin_l2;}
				x2=x-tt2s;
				for (i=0;i<t3;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
				GenericPixels[x-=ScreenWidth]=Textures01[addr]|LC;
			} else {
				if (x<xmn1) goto fin_l1;
				x2=x-tt1s;
				for (i=0;i<tt1;i++) {GenericPixels[x-=ScreenWidth]=c1; GenericPixels[x2-=ScreenWidth]=c2;}
			}
			x=x2;
			//.
			addr++;
		};
		goto fin_l;

fin_l2:	for (i=0;i<t3;i++) {
			if (x<xmin) goto fin_l;
			GenericPixels[x-=ScreenWidth]=c1;
		}
		GenericPixels[x-=ScreenWidth]=c;
		for (i=0;i<t3;i++) {
			if (x<xmin) goto fin_l;
			GenericPixels[x-=ScreenWidth]=c2;
		}
		goto fin_l;

fin_l1:	for (i=0;i<tt1;i++) {
			if (x<xmin) goto fin_l;
			GenericPixels[x-=ScreenWidth]=c1;
		}
		for (i=0;i<tt1;i++) {
			if (x<xmin) goto fin_l;
			GenericPixels[x-=ScreenWidth]=c2;
		}

fin_l:;

		xs=YStart+tgl*(k-TileStart)-(1<<TexFloater); x=(xs>>TexFloater)*ScreenWidth+x0;
		for (;k<TileFinish;k++) {
			addr=adr+WallTilesAddresses[k];
			LC=(AO[k]*lig+addi)&0xFF000000;
			xs+=tgl;
			xc=xs>>TexFloater;
			xcx=(xc-(x/ScreenWidth))&7;
			if (xcx==0&&xc-(x/ScreenWidth)>t2)
				b=TexelPriorities8k[8];
			else
				b=TexelPriorities8k[xcx];
			//1
			c1=Textures0[addr]|LC;	c2=Textures1[addr]|LC;
			if ((b&1)!=0) {
				x2=x+tt2s;
				for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
				GenericPixels[x+=ScreenWidth]=Textures01[addr]|LC;
			} else {
				x2=x+tt1s;
				for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
			}
			x=x2;
			//2
			c1=Textures2[addr]|LC;	c2=Textures3[addr]|LC;
			if ((b&2)!=0) {
				x2=x+tt2s;
				for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
				GenericPixels[x+=ScreenWidth]=Textures23[addr]|LC;
			} else {
				x2=x+tt1s;
				for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
			}
			x=x2;
			//3
			c1=Textures4[addr]|LC;	c2=Textures5[addr]|LC;
			if ((b&4)!=0) {
				x2=x+tt2s;
				for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
				GenericPixels[x+=ScreenWidth]=Textures45[addr]|LC;
			} else {
				x2=x+tt1s;
				for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
			}
			x=x2;
			//4
			c1=Textures6[addr]|LC;	c2=Textures7[addr]|LC;
			if ((b&8)!=0) {
				x2=x+tt2s;
				for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
				GenericPixels[x+=ScreenWidth]=Textures67[addr]|LC;
			} else {
				x2=x+tt1s;
				for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
			}
			x=x2;
			//.
			addr++;
		};

		if (k==TileFinish) {
			addr=adr+WallTilesAddresses[k];
			LC=(AO[k]*lig+addi)&0xFF000000;
			xs+=tgl;
			xc=xs>>TexFloater;
			xcx=(xc-(x/ScreenWidth))&7;
			if (xcx==0&&xc-(x/ScreenWidth)>t2)
				b=TexelPriorities8k[8];
			else
				b=TexelPriorities8k[xcx];
			//1			
			c1=Textures0[addr]|LC;	c2=Textures1[addr]|LC;
			if ((b&1)!=0) {
				if (x>xmx2) {c=Textures01[addr]|LC; goto fin_r2;}
				x2=x+tt2s;
				for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
				GenericPixels[x+=ScreenWidth]=Textures01[addr]|LC;
			} else {
				if (x>xmx1) goto fin_r1;
				x2=x+tt1s;
				for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
			}
			x=x2;
			//2			
			c1=Textures2[addr]|LC;	c2=Textures3[addr]|LC;
			if ((b&2)!=0) {
				if (x>xmx2) {c=Textures23[addr]|LC; goto fin_r2;}
				x2=x+tt2s;
				for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
				GenericPixels[x+=ScreenWidth]=Textures23[addr]|LC;
			} else {
				if (x>xmx1) goto fin_r1;
				x2=x+tt1s;
				for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
			}
			x=x2;
			//3
			c1=Textures4[addr]|LC;	c2=Textures5[addr]|LC;
			if ((b&4)!=0) {
				if (x>xmx2) {c=Textures45[addr]|LC; goto fin_r2;}
				x2=x+tt2s;
				for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
				GenericPixels[x+=ScreenWidth]=Textures45[addr]|LC;
			} else {
				if (x>xmx1) goto fin_r1;
				x2=x+tt1s;
				for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
			}
			x=x2;
			//4
			c1=Textures6[addr]|LC;	c2=Textures7[addr]|LC;
			if ((b&8)!=0) {
				if (x>xmx2) {c=Textures67[addr]|LC; goto fin_r2;}
				x2=x+tt2s;
				for (i=0;i<t3;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
				GenericPixels[x+=ScreenWidth]=Textures67[addr]|LC;
			} else {
				if (x>xmx1) goto fin_r1;
				x2=x+tt1s;
				for (i=0;i<tt1;i++) {GenericPixels[x+=ScreenWidth]=c1; GenericPixels[x2+=ScreenWidth]=c2;}
			}
			x=x2;
			//.
			addr++;
		};

		goto fin_r;

fin_r2:	for (i=0;i<t3;i++) {
			if (x>xmax) goto fin_r;
			GenericPixels[x+=ScreenWidth]=c1;
		}
		GenericPixels[x+=ScreenWidth]=c;
		for (i=0;i<t3;i++) {
			if (x>xmax) goto fin_r;
			GenericPixels[x+=ScreenWidth]=c2;
		}
		goto fin_r;

fin_r1:	for (i=0;i<tt1;i++) {
			if (x>xmax) goto fin_r;
			GenericPixels[x+=ScreenWidth]=c1;
		}
		for (i=0;i<tt1;i++) {
			if (x>xmax) goto fin_r;
			GenericPixels[x+=ScreenWidth]=c2;
		}

fin_r:;

	}
	return 0;
}

Текстура разбита на группы, каждая из 8-ми подряд идущих текселей Texture0, Texture1, … Texture7. Также, в Texture01 предрасчитан цвет, средний от Texture0 и Texture1... Это позволяет создать эффект, которому трудно найти точный термин в компьютерной графике, но, например, шахматная доска на большом расстоянии может выглядеть однотонной, а не сверкать пиксельным калейдоскопом.

У Dendy ещё есть особенность, одна палитра на 2x2 тайла. Есть идеи, как с этим быть, и учтено ли это в gif'ке?

Не учтено, рисовал так, как будто эту проблему уже обошли. Идея есть, довольно банальная — раз уж особенности «взгляда под углом» не позволяют разгуляться с текстурированием (максимум — та рябь, которую я попробовал и выложил только на Итче), то нам потребуется максимум два цвета, а то и вообще один (как на гифке, одноцветная заливка). То есть чётные и нечётные тайлы просто делаем разными («залить цветом 3» и «залить цветом 4»), цвета 1 и 2 отдаём под пол и потолок (для скошенных верхнего и нижнего тайлов) и назначаем 3 и 4 в зависимости от того, какие нам попались стены. Получается плюс-минус независимый выбор цветов.

Тут два пути.

Первый это сделать идеальный Hovertank 3D. Но даже так, объединяя две палитры в одну, теряется разнообразие тайлов, получится меньше вариантов скошенностей.

Второй это с текстурированием, Wolf 3D. И вот для него то особенно пригодится идея...
 

Движок получается почти панорамным. Плавное вращение на месте в приоритете. Движения как получится. Хорошо.

Что, если контролировать позиции, где может находится камера? Просто не допускать ситуации, когда в пределах 6 градусов (2 тайла подряд) видны стены из разных палитр. В редакторе карт рассчитываем допустимые области для перемещения игрока. В недопустимые можно поставить непроходимые объекты.

Sign up to leave a comment.

Articles