Вы видите копию треда, сохраненную 25 ноября в 01:10.
Скачать тред: только с превью, с превью и прикрепленными файлами.
Второй вариант может долго скачиваться. Файлы будут только в живых или недавно утонувших тредах. Подробнее
Если вам полезен архив М.Двача, пожертвуйте на оплату сервера.
https://youtu.be/Mg6n3TBeQ3Y
Их много, они все одинаковые и их легко смотреть.
А в непопулярных местах я даже не знаю, что делать. Гораздо лучше находиться в толпе, в которой ты плывёшь как по течению, чем идти куда-то самому. Только вот сейчас толпа идёт куда-то совсем не туда, куда бы мне хотелось, поэтому я сижу и жду, пока это всё прекратится.
Ко всему прочему я чувствую, что я просто почти неосознанно мимикрирую под других людей, при этом сам я человеком не являюсь, во мне нет ничего своего, всё - попытка скопировать откуда-то. Но все эти попытки делать так, как делают другие, оканчиваются неудачами, потому что я бесполезен.
И вот не знаю, что делать, потому что при попытке загрузить винду через grub просто появляется чёрный экран, а потом опять появляется grub. Всё хочу попробовать починить, но не знаю, как гуглить эту проблему. Видимо, надо с установочного диска виндовс зайти в командную строку и там что-то сделать, чтобы всё вернуть, но я не знаю, что туда надо вводить.
480x600, 0:29
Дикдики выглядят забавно.
1280x720, 0:11
Я не знаю, что такое революция, я не хочу ничего совершать и не пытаюсь, я хочу навсегда заснуть и больше не просыпаться.
Я уже не помню, о чём был этот фильм. Я помню только, что там в конце гг купил какую-то женщину, и всё прекратилось. Не знаю, какие тут скрытые смыслы и что это отражает, да мне и неинтересно.
Мне сложно думать о желаниях других людей, когда у меня толком нет своих.
Обидно немного, что потерялись куча уровней и сет железной брони, а ещё, что я потерял слизь (хотел сделать поводок, чтобы притащить к себе поближе куриц, но, видимо, придётся использовать старые методы).
Не хочу говорить. Я слишком близко к сердцу воспринимаю мнение других людей о себе, постоянно всё преувеличиваю и не переношу критику в свою сторону. И ещё меня очень легко сбить с пути, потому что я не знаю сам точно, что хочу делать.
Если я скажу, что делаю, и кто-то наругает меня за то, что я занимаюсь какой-то ерундой и лучше бы делал что-то другое, или же скажет, что я слишком долго сижу на одном месте, хотя все легко воспринимают этот материал, а я слишком тупой, чтобы этим заниматься, или ещё что-то скажет в мой адрес такое, пусть даже не обидное, но мне всё равно будет плохо и неприятно. Это не обида на другого человека, но спусковой крючок, чтобы я начал думать о том, насколько я плох во всех отношениях.
Я просто не хочу давать лишней возможности сделать это.
Вот сейчас надо будет опять идти куда-то, делать какие-то телодвижения, чтобы мне поменяли паспорт, как будто бы мне он нужен, вообще ни разу им почти не воспользовался. А потом идти в аптеку, чтобы мне дали какие-то таблетки от аллергии, хотя я даже не уверен, что они мне помогут: когда я пришёл ко врачу он просто отмахнулся от меня и выписал какой-то спрей и вот эти таблетки, которые и так можно купить без рецепта.
Это всё отвлекает и изматывает, почему я должен этим заниматься. Почему мне так тяжело сконцентрироваться на чём-то.
Сделал немного странный домик и мостик, чтобы можно добраться до него. Внутри пока что ничего не сделал, думаю, там будет стол для зачарований. Пока что опять надоело играть, потом как-нибудь продолжу.
Сделал немного странно выглядящий амбар с застеклёнными стенами на первом этаже и гигантской треугольной крышей, который используется как двухэтажная теплица (представим, что второй этаж со стороны фронтонов можно закрыть, так что внутри будет тепло). И в связи с этим вроде как есть постоянный источник еды в виде тыквенных пирогов, которые делаются из яиц, сахара и, внезапно, тыкв, но, во-первых, надо поставить хопперы под загон с курицами, а у меня закончилось железо, чтобы их сделать, а, во-вторых, пока что курицы дают слишком мало яиц и тыкв тоже маловато.
Кстати, там ещё и арбузы есть, которые я вырастил из семян, которые нашёл в заброшенной шахте. А картофелину так и не нашёл. Мне кажется, жареный картофель - это лучшее, что есть из еды в игре: его очень много падает с каждого куста, и относительно этого он восстанавливает немало здоровья.
Надо, наверное, сделать подземный этаж амбара, в котором бы было куча тыквенных вот этих растений, чтобы не надо было выносить это всё на улицу. Я бы мог сделать многоуровневую уродливую земляную башню 9x9 блоков в поперечном сечении, где на каждом уровне бы выращивалось бы что-то, но это выглядело бы слишком ужасно, хоть и было бы более-менее эффективно в том числе в плане используемой площади.
Конечно, было бы, наверное, неплохо сделать автоматические фермы для тростника и тыкв, но мне как-то лень таким заниматься и я не знаю, как это можно оформить нормально. Как какой-нибудь большой особняк, который внутри был бы на деле несколькими настакованными друг на друга этажами из тыкв, тростника и каких-то механизмов, в которых я не разбираюсь.
Как минимум неплохо было бы сделать простенькую ферму куриц, но по моей памяти, когда я до этого в последний раз играл у меня даже это не получилось. Там суть была в том, что когда курицы вырастали, они упирались головой в лаву, разлитую сверху, поджаривались и дропали сразу приготовленное мясо. Но у меня они просто не хотели расти, пока я не убирал блок лавы сверху.
Короче, не знаю, может быть, потом как-нибудь попробую сделать что-нибудь автоматизированное, а сейчас попытаюсь обойтись тем, что есть.
Да, железо, кстати, закончилось полностью, поэтому пришлось спуститься в пещеры, где я благополучно помер со свежесобранным железным сетом, кучей железа и гле-то с шестью алмазами. Откуда-то сверху прямо на меня спрыгнул крипер и по ощущениям прямо в полёте сдетонировал. При этом у меня был почти фул сет железной брони (без ботинок) и полное здоровье. Назад возвращаться я даже не пытался: всё равно бы не нашёл путь до разбросанных айтемов.
В следующий раз придётся идти с каменными инструментами и на месте всё крафтить, как будто бы заново с самого начала буду играть.
Сделал немного странно выглядящий амбар с застеклёнными стенами на первом этаже и гигантской треугольной крышей, который используется как двухэтажная теплица (представим, что второй этаж со стороны фронтонов можно закрыть, так что внутри будет тепло). И в связи с этим вроде как есть постоянный источник еды в виде тыквенных пирогов, которые делаются из яиц, сахара и, внезапно, тыкв, но, во-первых, надо поставить хопперы под загон с курицами, а у меня закончилось железо, чтобы их сделать, а, во-вторых, пока что курицы дают слишком мало яиц и тыкв тоже маловато.
Кстати, там ещё и арбузы есть, которые я вырастил из семян, которые нашёл в заброшенной шахте. А картофелину так и не нашёл. Мне кажется, жареный картофель - это лучшее, что есть из еды в игре: его очень много падает с каждого куста, и относительно этого он восстанавливает немало здоровья.
Надо, наверное, сделать подземный этаж амбара, в котором бы было куча тыквенных вот этих растений, чтобы не надо было выносить это всё на улицу. Я бы мог сделать многоуровневую уродливую земляную башню 9x9 блоков в поперечном сечении, где на каждом уровне бы выращивалось бы что-то, но это выглядело бы слишком ужасно, хоть и было бы более-менее эффективно в том числе в плане используемой площади.
Конечно, было бы, наверное, неплохо сделать автоматические фермы для тростника и тыкв, но мне как-то лень таким заниматься и я не знаю, как это можно оформить нормально. Как какой-нибудь большой особняк, который внутри был бы на деле несколькими настакованными друг на друга этажами из тыкв, тростника и каких-то механизмов, в которых я не разбираюсь.
Как минимум неплохо было бы сделать простенькую ферму куриц, но по моей памяти, когда я до этого в последний раз играл у меня даже это не получилось. Там суть была в том, что когда курицы вырастали, они упирались головой в лаву, разлитую сверху, поджаривались и дропали сразу приготовленное мясо. Но у меня они просто не хотели расти, пока я не убирал блок лавы сверху.
Короче, не знаю, может быть, потом как-нибудь попробую сделать что-нибудь автоматизированное, а сейчас попытаюсь обойтись тем, что есть.
Да, железо, кстати, закончилось полностью, поэтому пришлось спуститься в пещеры, где я благополучно помер со свежесобранным железным сетом, кучей железа и гле-то с шестью алмазами. Откуда-то сверху прямо на меня спрыгнул крипер и по ощущениям прямо в полёте сдетонировал. При этом у меня был почти фул сет железной брони (без ботинок) и полное здоровье. Назад возвращаться я даже не пытался: всё равно бы не нашёл путь до разбросанных айтемов.
В следующий раз придётся идти с каменными инструментами и на месте всё крафтить, как будто бы заново с самого начала буду играть.
Да не то чтобы чем-то серьёзным, да я и не прогрессирую никак, так что какая разница.
А, ну хотя, оказывается, по крайней мере ферму тыкв и арбузов сделать довольно легко. Оказывается, если пушить тыкву или арбуз пистоном, то он ломается, так что можно либо сделать поток воды, в который будут попадать айтемы, либо снизу запустить вагонетку с хоппером. К тому же это будет довольно дёшево по ресурсам. Наверное, сделаю её потом и оформлю под какую-нибудь странную каменную постройку, а в амбаре оставлю только.
А для сахарного тростника я вспомнил, что есть zero tick фермы, суть которых, как я понимаю, в том, чтобы очень быстро дёргать блок, на котором растёт тростник, и тогда тростник будет расти значительно быстрее. Но это какое-то максимальное багоюзерство, так что я потом, может, сделаю обычную ферму. Она там, оказывается, такая же простая, как и для тыкв.
Всё же сделал ту ферму тыкв (вариант с вагонеткой с хоппером и одновременным срабатыванием всех пистонов). Вроде как работает, но довольно медленно (за минут 20 сгенерировала только 25 тыкв) и апдейтится она только, когда ты находишься относительно недалеко. (Ну, конечно, можно афк постоять и набрать побольше, но как-то лень таким заниматься. Может, надо сделать несколько таких ферм, но это долго и муторно) И очень дорого получилось из-за того, что надо проложить рельсы по всей площади снизу, даже с учётом того, что часть рельс я украл из заброшенной шахты. Но пока что никак её не оформлял. Думаю, просто засуну в дефолтный домик, только сделаю его Т-образным.
И заодно ещё засунул двух коров в маленькую коробку, чтобы их там размножать. В теории, когда их станет больше какого-то количества, они начнут сами погибать, так что сейчас надо расплодить их, и после этого можно будет дальше плодить, но уже собирать лут, в частности кожу, чтобы сделать книги и проапгрейдить энчантменты. И ещё засунул рельсы под загон с курицами, так что теперь у меня есть куча яиц, и, если бы было куча тыкв, то можно было делать кучу тыквенных пирогов, но тыкв как-то не очень много по итогу.
Вообще определённо надо зачаровать свои айтемы, хотя бы кирку на эффективность, но, во-первых, я так часто умираю, что не вижу особого смысла: всё равно потеряю, во-вторых, у меня пока что нет кожи, чтобы сделать книжные полки и улучшить зачарования (конечно, можно комбинировтаь зачарования: эффективность I + эффективность I = эффективность II, но для этого надо либо создавать кучу, к примеру, кирок, чтобы потом их зачаровывать и комбинировать на наковальне, а у меня опять закончилось железо, либо, зачаровывать книжки, которых у меня нет), а в-третьих, я не знаю, есть ли смысл зачаровывать железные инструменты: они же быстро ломаются, надо зачаровывать обязательно на анбрейкинг, а это минус левелы, а у меня левелов нет и я не знаю, откуда их взять.
После этого далеко ушёл от дома, хотел найти что-нибудь интересное (ледяной биом, чтобы потом забрать оттуда лёд / пустыню, чтобы фармить эндер глаза / джунгли для бамбука etc), нашёл только деревню и записал её координаты, чтобы потом вернуться и утащить оттуда жителей. Но потом меня взорвал крипер и я потерял все айтемы, в том числе картофель и морковь, которые я так долго искал, и ещё саплинги жирных тёмных деревьев и сколько-то кожи.
Так и не знаю, откуда брать левелы для зачарований. Есть вариант построить стандартную тёмную коробку в небе, из которой вниз будут падать спавнящиеся в ней мобы и почти убиваться, а я бы потом их добивал (если моб умирает сам, то опыт не дропается). И тогда можно было бы в том числе нафармить всяких предметов, но, мне кажется, я с этого мало получу опыта. Ещё можно бегать по аду (который у меня из-за пиратки неапдейтнутый и унылый) и собирать опыт с майнинга кварца, но это тоже как-то тяжеловато.
Всё же сделал ту ферму тыкв (вариант с вагонеткой с хоппером и одновременным срабатыванием всех пистонов). Вроде как работает, но довольно медленно (за минут 20 сгенерировала только 25 тыкв) и апдейтится она только, когда ты находишься относительно недалеко. (Ну, конечно, можно афк постоять и набрать побольше, но как-то лень таким заниматься. Может, надо сделать несколько таких ферм, но это долго и муторно) И очень дорого получилось из-за того, что надо проложить рельсы по всей площади снизу, даже с учётом того, что часть рельс я украл из заброшенной шахты. Но пока что никак её не оформлял. Думаю, просто засуну в дефолтный домик, только сделаю его Т-образным.
И заодно ещё засунул двух коров в маленькую коробку, чтобы их там размножать. В теории, когда их станет больше какого-то количества, они начнут сами погибать, так что сейчас надо расплодить их, и после этого можно будет дальше плодить, но уже собирать лут, в частности кожу, чтобы сделать книги и проапгрейдить энчантменты. И ещё засунул рельсы под загон с курицами, так что теперь у меня есть куча яиц, и, если бы было куча тыкв, то можно было делать кучу тыквенных пирогов, но тыкв как-то не очень много по итогу.
Вообще определённо надо зачаровать свои айтемы, хотя бы кирку на эффективность, но, во-первых, я так часто умираю, что не вижу особого смысла: всё равно потеряю, во-вторых, у меня пока что нет кожи, чтобы сделать книжные полки и улучшить зачарования (конечно, можно комбинировтаь зачарования: эффективность I + эффективность I = эффективность II, но для этого надо либо создавать кучу, к примеру, кирок, чтобы потом их зачаровывать и комбинировать на наковальне, а у меня опять закончилось железо, либо, зачаровывать книжки, которых у меня нет), а в-третьих, я не знаю, есть ли смысл зачаровывать железные инструменты: они же быстро ломаются, надо зачаровывать обязательно на анбрейкинг, а это минус левелы, а у меня левелов нет и я не знаю, откуда их взять.
После этого далеко ушёл от дома, хотел найти что-нибудь интересное (ледяной биом, чтобы потом забрать оттуда лёд / пустыню, чтобы фармить эндер глаза / джунгли для бамбука etc), нашёл только деревню и записал её координаты, чтобы потом вернуться и утащить оттуда жителей. Но потом меня взорвал крипер и я потерял все айтемы, в том числе картофель и морковь, которые я так долго искал, и ещё саплинги жирных тёмных деревьев и сколько-то кожи.
Так и не знаю, откуда брать левелы для зачарований. Есть вариант построить стандартную тёмную коробку в небе, из которой вниз будут падать спавнящиеся в ней мобы и почти убиваться, а я бы потом их добивал (если моб умирает сам, то опыт не дропается). И тогда можно было бы в том числе нафармить всяких предметов, но, мне кажется, я с этого мало получу опыта. Ещё можно бегать по аду (который у меня из-за пиратки неапдейтнутый и унылый) и собирать опыт с майнинга кварца, но это тоже как-то тяжеловато.
> И, потратив на это кучу камня, так и не придумал, как эту штуку можно использовать.
Поставь раздатчики и ловушки - возможно внутри будут спавниться мобы, которых ты таким образом убьешь.
Или сделай там склад оружия, припасов (стрелы, динамит).
>пока что нет кожи
Заведи ферму коров и размножай их. Советую прокачать меч на Добычу - будет падать больше кожи (и мяса).
>но потом меня взорвал крипер и я потерял все айтемы
Сделай эндер-сундук (2). Один поставь дома, другой таскай с собой и складывай туда весь ценный лут. Правда, придется таскать с собой кирку на Шёлковое Касание.
>откуда брать левелы для зачарований
1. Лутаешь кварц с ада
2. Торгуешься с жителями
3. Заводишь фермы животных и размножаешь
4. Спускаешься в пещеры и собираешь уголь, редстон, лазурит и алмазы.
5. Делаешь афк-рыбалку (норм способ, если уходишь куда-то)
6. Бродишь ночами или по пещерам и убиваешь монстров
>Есть вариант построить стандартную тёмную коробку в небе, из которой вниз будут падать спавнящиеся в ней мобы
Неплохой вариант, но следи чтобы под тобой внизу не было шахты. Вруби субтитры в настройках и смотри "Катится вагонетка". Если это выскакивает особенно в том месте, где ты хочешь построить коробку - не строй. Так как ниче не выйдет, все мобы будут спавниться в шахте. На себе проверено.
Оказывается, на столике картографа можно расширять карту только за один листик, поэтому я максимально увеличил масштаб карты и пошёл понемногу её открывать. Успешно реаибилитировался в плане ископаемых ресурсов: утащил довольно много железа и угля, апнул 30 лвл, но вот алмазов не нашёл. Нашёл относительно недалеко деревню, из которой утащил несколько книжек и картофель. Отметил её на карте, так что можно будет потом вернуться и выкрасть жителей (там она довольно удобно расположена около большого водоёма, из которого можно добраться до моего дома).
30 лвлов и 7 книжных полок хватило на то, чтобы сделать железную кирку на анбрейкинг 3 и эффективность 3, но я так её толком и не протестировал, потому что спалил её в лаве в аду, пока пытался найти там крепость.
А ещё, когда я только зашёл в портал, оказалось, что патруль то ли заспавнился там, то ли заспавнился снаружи, а потом попал в ад, но интересно было, что когда они, атаковав меня, случайно попали в зомби пигманов, то зомби начали агриться не только на них, но и на меня тоже.
>>341227
>Заведи ферму коров и размножай их
Да, я уже, но пока что там маловато коров, чтобы их можно было начать убивать.
>Сделай эндер-сундук
У меня какая-то беда с эндерменами: не хотят спавниться. В этом мире вообще видел только двух-трёх. В каком-то спидране майнкрафта я видел, как игрок сделал высокую башню посреди пустыни, а внизу сделал сетап, чтобы можно было удобно убивать эндерменов. И в самом верху он поставил спавн поинт. И ночью он забирался на башню, видимо, чтобы реролльнуть враждебных мобов внизу (как я понял, враждебные мобы спавнятся и рандомно деспавнятся снаружи сферы в 32 блока вокруг игрока и гарантированно деспавнятся снаружи сферы в 128 блоков), и, если находил среди них эндерменов, то агрил их, забирался в коробку внизу и убивал их. А потом топился в лаве, респавнился на вершине башни и так по кругу.
>5. Делаешь афк-рыбалку (норм способ, если уходишь куда-то)
Я так понял, там нужно ставить какой-то автокликер, не хочется и лень таким заниматься. А из остальных способов, видимо, самый эффективный - это добыча кварца.
Спасибо за советы.
Оказывается, на столике картографа можно расширять карту только за один листик, поэтому я максимально увеличил масштаб карты и пошёл понемногу её открывать. Успешно реаибилитировался в плане ископаемых ресурсов: утащил довольно много железа и угля, апнул 30 лвл, но вот алмазов не нашёл. Нашёл относительно недалеко деревню, из которой утащил несколько книжек и картофель. Отметил её на карте, так что можно будет потом вернуться и выкрасть жителей (там она довольно удобно расположена около большого водоёма, из которого можно добраться до моего дома).
30 лвлов и 7 книжных полок хватило на то, чтобы сделать железную кирку на анбрейкинг 3 и эффективность 3, но я так её толком и не протестировал, потому что спалил её в лаве в аду, пока пытался найти там крепость.
А ещё, когда я только зашёл в портал, оказалось, что патруль то ли заспавнился там, то ли заспавнился снаружи, а потом попал в ад, но интересно было, что когда они, атаковав меня, случайно попали в зомби пигманов, то зомби начали агриться не только на них, но и на меня тоже.
>>341227
>Заведи ферму коров и размножай их
Да, я уже, но пока что там маловато коров, чтобы их можно было начать убивать.
>Сделай эндер-сундук
У меня какая-то беда с эндерменами: не хотят спавниться. В этом мире вообще видел только двух-трёх. В каком-то спидране майнкрафта я видел, как игрок сделал высокую башню посреди пустыни, а внизу сделал сетап, чтобы можно было удобно убивать эндерменов. И в самом верху он поставил спавн поинт. И ночью он забирался на башню, видимо, чтобы реролльнуть враждебных мобов внизу (как я понял, враждебные мобы спавнятся и рандомно деспавнятся снаружи сферы в 32 блока вокруг игрока и гарантированно деспавнятся снаружи сферы в 128 блоков), и, если находил среди них эндерменов, то агрил их, забирался в коробку внизу и убивал их. А потом топился в лаве, респавнился на вершине башни и так по кругу.
>5. Делаешь афк-рыбалку (норм способ, если уходишь куда-то)
Я так понял, там нужно ставить какой-то автокликер, не хочется и лень таким заниматься. А из остальных способов, видимо, самый эффективный - это добыча кварца.
Спасибо за советы.
Наконец-то, дошли руки починить это: чтобы вернуться в прошлое тупое состояние, когда сначала выбираешь между виндой и грубом, а потом в грубе выбираешь линукс в грубе, достаточно было просто ввести в командную строку с загрузочного диска несколько команд через bootrec.exe, а потом с винды через какую-то сомнительную утилиту добавить вариант выбрать груб при загрузке.
Это заняло минут 5, но мне было лень искать загрузочный диск винды и подключать дисковод.
По-быстрому прошёл до великана в колодках, прикончил его и дефолтного военачальника, который идёт сразу же за великаном и отличается от предыдущего такого же, видимо, только тем, что может делать колющий выпад мечом, который контрится контратакой микири. Позвонил в демонический колокол в храме Сэмпо, вроде как теперь должно выпадать больше лута с противников, а я как раз помню, что мне постоянно не хватало денег в прошлый раз, когда я проходил игру.
К большому белому змею и дальше пока что не пошёл, вернулся в поместье Хирата и там пока что прошёл только до идола, к которому попадаешь сразу же после боя с охотником на шиноби. На самом деле, он пока что единственный вызвал трудности: очень часто промахивался с таймингами контратаки микири, но всё равно получилось убить его с первого раза.
Больше пока что, скорее всего, не буду сегодня в это играть.
Думаю, через минут 10 сяду заниматься.
а когда дают сдачу жать руку а потом говорить что я думал вы мне руку пожать хотели
Я боюсь, что поставлю и себя, и кассира в неловкое положение, если скажу что-нибудь не то. А ещё не знаю, нужно ли дожидаться, когда кассир распечатает чек и отдаст его тебе, если покупаешь что-то без сдачи. Он как бы обязан по крайней мере распечатать его, но я не знаю, может, можно уходить без него и это нормально.
И тут ещё проблема в том, что я не знаю точно, как округляются цены до рублей, так что иногда непонятно, дадут ли мне этот несчастный рубль на сдачу или нет. И тогда я обычно неловко жду, пока распечатается чек и уже узнаю постфактум, дадут ли мне сдачу. И, если не дают, то, выходит, со стороны это будет выглядеть так, будто я ждал, пока мне выдадут чек, а это же странно, да?
>>341997
Нет, ну, всё не настолько плохо. Момент, когда надо протянуть руку, чтобы тебе в неё насыпали монетки я распознаю нормально, с этим проблем нет.
Возможно.
>вызывает шок и потерю сознания
Нет, не вызывает, просто есть очень большая вероятность зафейлить это, на автомате могу сказать какую-нибудь стыдную глупость. Например, какое-то время назад у меня на кассе спросили, нужен ли мне пакет, на что я на автомате ответил "нет, спасибо", причём я неосознанно выделил "спасибо", как будто мне бы сделали большое одолжение, выдав пакет. И после этого кассирша ешё с какой-то странной интонацией ответила "пожалуйста". После этого я боюсь говорить лишнее.
И это такое только в продуктовых магазинах, в аптеках, например, у меня такого нет и я обычно здороваюсь, говорю спасибо и прочее, но это, скорее всего, из-за того, что там есть необходимость начать диалог самому и попросить тебе что-то дать в отличие от продуктового, где ты сам приносишь товары на кассу, так что, само собой, надо для начала поздороваться.
Короч, подходишь на кассу и говоришь "здравствуйте", а когда соберёшься уходить "спасибо, до свидания", эти четыре слова сложно заруинить. А если и заруинишь, то что? Кассиру явно не будет неловко, если запнёшься-заикнёшься.
>как округляются цены до рубля
Што? Цены вообще ведь не округляются. Если ты даёшь денег больше, чем надо - получаешь сдачу всегда. Чек можешь не ждать. А можешь ждать, мало ли для чего он тебе нужен. Это не странно, всё хорошо.
>эти четыре слова сложно заруинить
Ну да, наверное, ты прав.
>А если и заруинишь, то что? Кассиру явно не будет неловко, если запнёшься-заикнёшься.
Я понимаю, что в этом ничего такого, но на деле это последняя мысль, которая появляется в голове, первая - это то, что надо поскорее отсюда сбежать. Может, просто опыта мало, да и я, наверное, немного преувеличиваю.
>Цены вообще ведь не округляются.
В рф копейки вроде хоть ещё и не вышли из оборота, но их в сдаче уже точно не дают (монеток номиналом 10 копеек и меньше уже относительно давно не видел, да и пятидесятикопеечную монетку тоже давно не встречал). Вместо этого округляют конечную стоимость всех покупок до рублей, но вот в какую сторону и зависит ли это от магазина - не знаю. При этом цены товаров могут содержать копеечную часть, и она учитывается при суммировании общей стоимости в чеке (то есть за два товара каждый стоимостью 1 руб. 50 коп. ты заплатишь 3 рубля, а вот за один такой товар отдашь либо 1 рубль, либо 2 рубля, тут вот как раз я и не знаю).
>Чек можешь не ждать. А можешь ждать, мало ли для чего он тебе нужен. Это не странно, всё хорошо.
Понятно, спасибо.
да ты поехавший вообще не ходи в магазин там деньги грибут у тебя а продавщицам ничего вообще не говори будто ты и они тебя в последний раз видят и все
зашел к вам а тут вас все такое дорогое где же столько на жизнь зарабатывать?
когда же у вас будут нормальные скидки?
когда перестанут продавать маспо?
зачем столько магазинов нейм построили в округе?
когда кока кола и пеппси по 30 рублей будет?
зашел к вам в магазин вышел без денег!
когда бесплатно еду давать будут?
Почему нету скидок на нормальную еду? Почему так дорого все?
Еле-как, на самом деле, убил сумоиста-пьяницу, потратил на это слишком много попыток, возможно, отчасти из-за того, что позвонил в тот колокол. В основном фейлился из-за того, что попадал под его мощный одиночный удар ладонью, который легко предсказать, но я постоянно лез на рожон, так что не всегда удавалось вовремя отойти.
Так и не смог по-честному прикончить госпожу бабочку: в первый раз снёс ей почти всё здоровье во второй фазе, но случайно попался в захват, и она меня ваншотнула. А в последующие разы дела шли постепенно всё хуже и хуже. Я пытался терпеливо ждать её атак, когда она подлетает в воздух и тогда её можно сбить сюрикеном и нанести удар, но эти успешные атаки наносили так мало урона, что только на первую её фазу у меня уходило минут 5. А потом надо было проделать то же самое с её второй, почти идентичной, фазой. Пробовал использовать тот инструмент, который телепортирует тебя за спину, но у неё слишком мало предсказуемых медленных ударов, чтобы этот трюк гарантированно получился.
А по-другому я просто не знаю, как ей можно нанести урон, потому что она блокирует все атаки и слишком быстро восстанавливает концентрацию даже с половиной хп. Короче, в итоге мне надоело и я просто заспамил её уклонением-в-сторону-атакой-уклонением-в-сторону-атакой-... Не знаю, почему, но на ней это работает и она практически не может пошевелиться, так что за одну серию таких ударов можно снести ей половину хп.
Прошёл дальше мимо гигантского белого змея, убил самурая Гёбу, который оказался довольно простым, у него куча слабостей: во-первых, его конь боится петард, а, во-вторых, иногда открывается возможность зацепиться за него крюком, быстро сократить расстояние и нанести удар, от которого он с большой вероятностью станнится на некоторое время.
Пока что остановился на быке с привязанным к рогам горящим стогом сена.
Еле-как, на самом деле, убил сумоиста-пьяницу, потратил на это слишком много попыток, возможно, отчасти из-за того, что позвонил в тот колокол. В основном фейлился из-за того, что попадал под его мощный одиночный удар ладонью, который легко предсказать, но я постоянно лез на рожон, так что не всегда удавалось вовремя отойти.
Так и не смог по-честному прикончить госпожу бабочку: в первый раз снёс ей почти всё здоровье во второй фазе, но случайно попался в захват, и она меня ваншотнула. А в последующие разы дела шли постепенно всё хуже и хуже. Я пытался терпеливо ждать её атак, когда она подлетает в воздух и тогда её можно сбить сюрикеном и нанести удар, но эти успешные атаки наносили так мало урона, что только на первую её фазу у меня уходило минут 5. А потом надо было проделать то же самое с её второй, почти идентичной, фазой. Пробовал использовать тот инструмент, который телепортирует тебя за спину, но у неё слишком мало предсказуемых медленных ударов, чтобы этот трюк гарантированно получился.
А по-другому я просто не знаю, как ей можно нанести урон, потому что она блокирует все атаки и слишком быстро восстанавливает концентрацию даже с половиной хп. Короче, в итоге мне надоело и я просто заспамил её уклонением-в-сторону-атакой-уклонением-в-сторону-атакой-... Не знаю, почему, но на ней это работает и она практически не может пошевелиться, так что за одну серию таких ударов можно снести ей половину хп.
Прошёл дальше мимо гигантского белого змея, убил самурая Гёбу, который оказался довольно простым, у него куча слабостей: во-первых, его конь боится петард, а, во-вторых, иногда открывается возможность зацепиться за него крюком, быстро сократить расстояние и нанести удар, от которого он с большой вероятностью станнится на некоторое время.
Пока что остановился на быке с привязанным к рогам горящим стогом сена.
Точно будет плавная анимация исчезновения полной линии и падения блоков над ней, для движения фигурки вниз, наверное, да, тоде будет анимация. Для вращения - не знаю точно, потому что волл кики будут выглядеть странно: во время анимации фигурка будет проворачиваться и задевать блоки, лежащие рядом, или вызлезать за пределы игрового поля. Может быть, можно сделать анимацию того, как будто фигурка вытаскивается из игрового поля (как будто двигается на тебя, в общем, просто зумится), проворачивается и одновременно перемещается линейно к месту, куда она встанет, а потом обратно уменьшается. Не знаю, насколько хорошо это будет выглядеть, в голове пока что выглядит нормально.
Вернулся в начальную локацию, убил там какого-то совсем простого мини-босса воина с копьём (семеро копьеносцев Ашина что-то там, не помню, как его звали), он был какой-то совсем простой: если отразить его выпад контратакой микири, то он сразу же попытается сам контратаковать взмахом копья, и, если и этот удар отразить, то он теряет равновесие и падает, открываясь для серии ударов. Немного прошёлся по крышам уже внутри замка Ашина, но ещё не до конца там всё прошёл: умер от внезапно десантировавшего с воздушного змея ниндзи (хм, видимо, не склоняется, но мне всё равно).
А ещё сейчас одна анимация может прерываться другой: если последовательно быстро повернуть фигурку и сдвинуть её, то анимация поворота скипнется. Надо как-то переделать анимации так, чтобы можно было их совмещать? У меня сейчас анимация - это просто объект внутри сущности, к которой он принадлежит, и сущность отрисовывает анимацию вместо своего спрайта, когда это надо.
У меня в голове крутится тупая идея о том, что надо как-то приоритизировать анимации, по крайней мере разделить их на первичные, которые очень сильно влияют на спрайт, например, вот двигают его, и вторичные, которые можно было бы добавить к первичным, а то, как это будет делаться конкретно, будет решать первичная анимация.
Ну, то есть, допустим, есть idle анимация для какого-нибудь персонажа, и одновременно с этим надо показать, что жесть силён и наполнен энергией, так что к idle анимации добавляется анимация свечения. Но вообще непонятно, как это реализовывать.
Немного пофиксил управление, теперь всё работает вот так: есть KeyManager, к которому можно добавить листенеров, которые после каждого игрового тика уведомляются о том, какие кнопки были нажаты, отпущены и всё ещё держатся нажатыми, в течение последнего тика. А сам KeyManager обрабатывает нажатия как только, так сразу.
>>342591
>130 Кб
>>342695
>13613 Кб
>>342811
>2250 Кб
Я не знаю, в каком битрейте записывать.
Думаю, сейчас буду играть в секиро.
>Я не знаю, в каком битрейте записывать.
Бывает же quality-based (quantizer-based) режим, наиболее пригодный для кодирования в один проход, да и в два тоже, если размер результата не важен. С чистым bitrate-based кодируют строго в два, и даже тогда он, очевидно, ХУЖЕ, т. к. не руководствуется качеством как основным параметром.
>либо надо, чтобы по мере того, как поворачивается спрайт, постепенно заменять его на его же версию, но повёрнутую в другую сторону
Хороший вариант, можно ещё сделать гемы векторными и раскрашивать формулой: связать с каждой гранью воображаемые положение и нормаль (поворачиваемые вместе с гемом), придумать позицию источника света и освещать грань через max(0, dot(normal, vecToLight.normalized)) (+ можно блик). Как оптимизация, заранее отрисовать 5–25 версий этого гема, повёрнутых на разные углы, и выбирать ближайшую. Ну или просто сделать все версии вручную, если терпеливый.
Можно даже выбирать не просто ближайшую, а две соседних, из которых ближайшую нарисовать как есть, а вторую — поверх неё с блендингом, пропорционально удалённости от первой. Тогда даже медленный поворот будет выглядеть приемлемо, и будет достаточно меньшего количества версий. Например, если есть версии для A=0° и B=30°, то поворот на 10° изображается как A, поверх которой B с 33% непрозрачности. На самом деле я не учёл необходимость в плавном затухании старой версии. Можно как-то так: до mid=25° плавно проявляется B, так что в точке mid они оказываются наваленными одна на другую, и затем нижележащая A плавно затухает от mid до 30°.
>Ну, то есть, допустим, есть idle анимация для какого-нибудь персонажа, и одновременно с этим надо показать, что жесть силён и наполнен энергией, так что к idle анимации добавляется анимация свечения. Но вообще непонятно, как это реализовывать.
У объекта есть анимируемые параметры (позиция-поворот, ЦВЕТ и ИНТЕНСИВНОСТЬ СВЕЧЕНИЯ и тому подобное). Активных анимаций может быть несколько, с каждой может быть связан вес (или, для начала, все полагаются равноправными). Анимация сообщает, какие параметры хочет установить, в какое значение, и, возможно, с каким специальным весом/приоритетом. Финальное значение параметра формируется их взвешенной суммой. КЛЁВО Я ПРИДУМАЛ, А
Например, если у объекта активны состояния
СУМАСШЕДШИЙ КЛОУН: глобальный поворот = ...плывёт в воздухе и периодически переворачивается вниз головой...
БРОСОК ПИРОГА: поворот руки = ...замах для броска...
СМЕХ: рот = ...открыт..., звук = ...смех...
То мы получаем перевёрнутого смеющегося клоуна, замахнувшегося для броска. Это был неудачный пример, потому что ни одного параметра не пришлось смешивать. Ну, допустим, он только что чесал голову этой же рукой и его ещё не отпустило, но вес анимации уже затухает:
БРОСОК ПИРОГА: вес = 1.0, поворот руки = ...
ЧЕСАНИЕ ГОЛОВЫ: вес = 0.14, поворот руки = ...
Финальный поворот руки = (1.0 × поворот для пирога + 0.14 × поворот для головы) / (1.0 + 0.14).
Так нельзя на самом деле можно взвешенно сложить N>2 поворотов, но в крайнем случае смешивание нескольких значений всегда можно выразить через смешивание двух. Например, смешаем четыре значения — A с весом 0.5, B с весом 0.75, C с весом 0.9 и D с весом 0.3:
temp = A
temp = mix(B, temp, 0.5 / (0.5 + 0.75))
temp = mix(C, temp, (0.5 + 0.75) / (0.5 + 0.75 + 0.9))
result = mix(D, temp, (0.5 + 0.75 + 0.9) / (0.5 + 0.75 + 0.9 + 0.3))
Где mix(A, B, x) выполняет интерполяцию между двумя значениями A и B с параметром x∈[0.0; 1.0], в частности, возвращает A при x=0 и B при x=1. Мог ошибиться в конкретной общей формуле, но суть понятна. Я даже использовал такой подход в своей игре VISIBLE FIGHTERS (>>303974 →) для генерации взвешенно-случайного значения за один проход по весам!
Можно легко пофиксить вылезание фигурки за пределы игрового поля в ходе анимации, отслеживая, в каких точках оказались вершины её выпуклой оболочки. Если одна из них вылезла за пределы поля на V, сдвигаем всю поворачиваемую фигурку на ‒V. С залезанием на другие фигурки так просто не получится, правда. Я вчера думал о том, что можно аналогичным образом отследить, в какие квадратики она залезла, отодвинуть (деформировать) их и деформировать остальные квадратики в некотором радиусе от них с уменьшающейся силой, но это может странно/страшно выглядеть: мол, сегодня квадратики теснятся, чтобы вместить невмещавшуюся фигурку, завтра попытаются что-то сказать, а послезавтра вылезут наружу.
>Я не знаю, в каком битрейте записывать.
Бывает же quality-based (quantizer-based) режим, наиболее пригодный для кодирования в один проход, да и в два тоже, если размер результата не важен. С чистым bitrate-based кодируют строго в два, и даже тогда он, очевидно, ХУЖЕ, т. к. не руководствуется качеством как основным параметром.
>либо надо, чтобы по мере того, как поворачивается спрайт, постепенно заменять его на его же версию, но повёрнутую в другую сторону
Хороший вариант, можно ещё сделать гемы векторными и раскрашивать формулой: связать с каждой гранью воображаемые положение и нормаль (поворачиваемые вместе с гемом), придумать позицию источника света и освещать грань через max(0, dot(normal, vecToLight.normalized)) (+ можно блик). Как оптимизация, заранее отрисовать 5–25 версий этого гема, повёрнутых на разные углы, и выбирать ближайшую. Ну или просто сделать все версии вручную, если терпеливый.
Можно даже выбирать не просто ближайшую, а две соседних, из которых ближайшую нарисовать как есть, а вторую — поверх неё с блендингом, пропорционально удалённости от первой. Тогда даже медленный поворот будет выглядеть приемлемо, и будет достаточно меньшего количества версий. Например, если есть версии для A=0° и B=30°, то поворот на 10° изображается как A, поверх которой B с 33% непрозрачности. На самом деле я не учёл необходимость в плавном затухании старой версии. Можно как-то так: до mid=25° плавно проявляется B, так что в точке mid они оказываются наваленными одна на другую, и затем нижележащая A плавно затухает от mid до 30°.
>Ну, то есть, допустим, есть idle анимация для какого-нибудь персонажа, и одновременно с этим надо показать, что жесть силён и наполнен энергией, так что к idle анимации добавляется анимация свечения. Но вообще непонятно, как это реализовывать.
У объекта есть анимируемые параметры (позиция-поворот, ЦВЕТ и ИНТЕНСИВНОСТЬ СВЕЧЕНИЯ и тому подобное). Активных анимаций может быть несколько, с каждой может быть связан вес (или, для начала, все полагаются равноправными). Анимация сообщает, какие параметры хочет установить, в какое значение, и, возможно, с каким специальным весом/приоритетом. Финальное значение параметра формируется их взвешенной суммой. КЛЁВО Я ПРИДУМАЛ, А
Например, если у объекта активны состояния
СУМАСШЕДШИЙ КЛОУН: глобальный поворот = ...плывёт в воздухе и периодически переворачивается вниз головой...
БРОСОК ПИРОГА: поворот руки = ...замах для броска...
СМЕХ: рот = ...открыт..., звук = ...смех...
То мы получаем перевёрнутого смеющегося клоуна, замахнувшегося для броска. Это был неудачный пример, потому что ни одного параметра не пришлось смешивать. Ну, допустим, он только что чесал голову этой же рукой и его ещё не отпустило, но вес анимации уже затухает:
БРОСОК ПИРОГА: вес = 1.0, поворот руки = ...
ЧЕСАНИЕ ГОЛОВЫ: вес = 0.14, поворот руки = ...
Финальный поворот руки = (1.0 × поворот для пирога + 0.14 × поворот для головы) / (1.0 + 0.14).
Так нельзя на самом деле можно взвешенно сложить N>2 поворотов, но в крайнем случае смешивание нескольких значений всегда можно выразить через смешивание двух. Например, смешаем четыре значения — A с весом 0.5, B с весом 0.75, C с весом 0.9 и D с весом 0.3:
temp = A
temp = mix(B, temp, 0.5 / (0.5 + 0.75))
temp = mix(C, temp, (0.5 + 0.75) / (0.5 + 0.75 + 0.9))
result = mix(D, temp, (0.5 + 0.75 + 0.9) / (0.5 + 0.75 + 0.9 + 0.3))
Где mix(A, B, x) выполняет интерполяцию между двумя значениями A и B с параметром x∈[0.0; 1.0], в частности, возвращает A при x=0 и B при x=1. Мог ошибиться в конкретной общей формуле, но суть понятна. Я даже использовал такой подход в своей игре VISIBLE FIGHTERS (>>303974 →) для генерации взвешенно-случайного значения за один проход по весам!
Можно легко пофиксить вылезание фигурки за пределы игрового поля в ходе анимации, отслеживая, в каких точках оказались вершины её выпуклой оболочки. Если одна из них вылезла за пределы поля на V, сдвигаем всю поворачиваемую фигурку на ‒V. С залезанием на другие фигурки так просто не получится, правда. Я вчера думал о том, что можно аналогичным образом отследить, в какие квадратики она залезла, отодвинуть (деформировать) их и деформировать остальные квадратики в некотором радиусе от них с уменьшающейся силой, но это может странно/страшно выглядеть: мол, сегодня квадратики теснятся, чтобы вместить невмещавшуюся фигурку, завтра попытаются что-то сказать, а послезавтра вылезут наружу.
>Бывает же quality-based (quantizer-based) режим
Да, действительно, я не заметил, что такое есть в OBS, сначала показалось, что там во всех режимах надо выставлять битрейт.
>можно ещё сделать гемы векторными
Да, наверное, сделаю.
>Можно легко пофиксить вылезание фигурки за пределы игрового поля в ходе анимации, отслеживая, в каких точках оказались вершины её выпуклой оболочки.
Да, наверное, тоже можно сделать. А с тем, чтобы ещё и блоки расступались перед ней, тут я как-то даже не знаю, как это может выглядеть, например, в случае как на пикриле (пунктиром показано положение фигурки после поворота).
>У объекта есть анимируемые параметры
>Анимация сообщает, какие параметры хочет установить, в какое значение, и, возможно, с каким специальным весом/приоритетом. Финальное значение параметра формируется их взвешенной суммой.
Хм, да, теперь понятнее, спасибо, попробую переделать потом, чтобы у меня в самом объекте были параметры вроде вращения, движения и прочих штук.
Вернулся до конца допройти начальную локацию, а именно обыскать колодец, в котором гг держали в самом начале игры, а он и не пытался сбежать, потому что был сломлен духом. Около него нашёл какого-то немного поехавшего самурая, который вроде как слышал игру на каком-то музыкальном инструменте, но никак не мог понять, откуда издаётся звук. Потом его можно будет либо заманить в ловушку, либо вроде как попросить помочь тому вору-торговцу собирать всякие ценные вещи с трупов на полях битв. Я в первом своём прохождении по глупости случайно заманил его в ловушку взамен на какой-то бесполезный айтем в качестве награды, так обидно и жалко стало, в этот раз не буду так делать.
А на дне колодца рыскал ещё один самурай в фиолетовом, такой же, какой был в Хирате, только теперь он был обозначен как мечник одинокой тени. С ним можно даже начать диалог, в ходе которого он говорит, что ищет упавшего духом шиноби, который сидел по слухам в этом колодце, но, как только заподазривает, что это я и есть, то сразу нападает. Вроде как он ещё должен был что-то говорить про моё бессмертие, но у меня он почему-то больше ничего не сказал.
А из колодца путь в подземелья, в которых я нашёл очень тупого босса, которого не смог прикончить, потому что у него очень высокая сопротивляемость к физическим атакам. При этом сам он простой: он тупо кастует кучу шаров, которые витают в воздухе и, если врезаться, то наносят урон и заполняют полоску статус эффекта "страх", который меня тупо убивает, когда полностью заполняется. А из мили атак у него только полтора взмаха посохом. И главная проблема с ним - это то, что у меня не хватает хп и терпения аккуратно его бить мечом, снося крупицы здоровья. Один раз попробовал заиспользовать божественные конфети, которые позволяют наносить нормальный урон всякой нежити вроде этого босса, надеясь на лёгкую победу, но в итоге только потерял одну из двух коробок с конфети, которые у меня были. После этого рейджквитнул и не стал дальше играть. Наверное, надо пока что скипнуть этого босса до тех пор, пока у меня не появится какой-то особый клинок, который позволяет наносить нормальный урон нежити, но я уже не помню с первого прохождения, когда он даётся и долго ли ещё ждать.
Вернулся до конца допройти начальную локацию, а именно обыскать колодец, в котором гг держали в самом начале игры, а он и не пытался сбежать, потому что был сломлен духом. Около него нашёл какого-то немного поехавшего самурая, который вроде как слышал игру на каком-то музыкальном инструменте, но никак не мог понять, откуда издаётся звук. Потом его можно будет либо заманить в ловушку, либо вроде как попросить помочь тому вору-торговцу собирать всякие ценные вещи с трупов на полях битв. Я в первом своём прохождении по глупости случайно заманил его в ловушку взамен на какой-то бесполезный айтем в качестве награды, так обидно и жалко стало, в этот раз не буду так делать.
А на дне колодца рыскал ещё один самурай в фиолетовом, такой же, какой был в Хирате, только теперь он был обозначен как мечник одинокой тени. С ним можно даже начать диалог, в ходе которого он говорит, что ищет упавшего духом шиноби, который сидел по слухам в этом колодце, но, как только заподазривает, что это я и есть, то сразу нападает. Вроде как он ещё должен был что-то говорить про моё бессмертие, но у меня он почему-то больше ничего не сказал.
А из колодца путь в подземелья, в которых я нашёл очень тупого босса, которого не смог прикончить, потому что у него очень высокая сопротивляемость к физическим атакам. При этом сам он простой: он тупо кастует кучу шаров, которые витают в воздухе и, если врезаться, то наносят урон и заполняют полоску статус эффекта "страх", который меня тупо убивает, когда полностью заполняется. А из мили атак у него только полтора взмаха посохом. И главная проблема с ним - это то, что у меня не хватает хп и терпения аккуратно его бить мечом, снося крупицы здоровья. Один раз попробовал заиспользовать божественные конфети, которые позволяют наносить нормальный урон всякой нежити вроде этого босса, надеясь на лёгкую победу, но в итоге только потерял одну из двух коробок с конфети, которые у меня были. После этого рейджквитнул и не стал дальше играть. Наверное, надо пока что скипнуть этого босса до тех пор, пока у меня не появится какой-то особый клинок, который позволяет наносить нормальный урон нежити, но я уже не помню с первого прохождения, когда он даётся и долго ли ещё ждать.
>А из колодца путь в подземелья
Через колодец
Если
Если пройти через колодце
По дну колодца, но не совсем под дну, там надо по стене забраться
Надо спуститься в колодец, а потом подняться, но не из колодца, а дальше пройти
Это и не колодец-то по сути, а ров какой-то, но там тоже вода есть
Да
Но сейчас проблема в том, что если очень быстро нажать поворот-в-одну-сторону-поворот-в-другую, то возвращение в исходное положение, поворот всего на пару градусов, будет длиться ровно столько же, сколько полный поворот. И это довольно тупо, но я не знаю, что с этим делать. Вернее, знаю, надо просто задавать длительность анимации пропорционально тому, насколько далеки финальные значения параметров от тех, которые есть сейчас, но не знаю, насколько это будет правильным решением и как определять, достаточно ли текущее положение близко к целевому, чтобы можно было сократить длительность анимации, и как сильно вообще сокращать её длительность.
И ещё не знаю, стоит ли делать наложение анимаций друг на друга с помощью взвешенной суммы параметров. В качестве веса анимации можно было бы взять то, насколько она свежая, так что новые анимации бы перебивали старые, но при этом всё было бы чуть более плавно и повороты туда-обратно выглядели бы более плавно.
Хм, думаю в момент второго нажатия, нужно посчитать дельту между углом изначального положения (до первого нажатия) и "текущим" углом во время второго нажатия. Далее нужно пропорционально перевести радианы во время.
Допустим анимация поворота в общем длится 300 ms, до первого нажатия нажатия угол 1.57, во время второго нажатия 1.2, тогда дельта будет 0.37. Конвертируем ее во время, если полный поворот 1.57 радиан, тогда время будет равно 300 * 0.37 / 1.57. Значит анимация после второго нажатия будет -0.37 радиан за ~70 ms
Ой, это хорошая идея, спасибо, потом сделаю.
←
>>343683
>стоит ли делать наложение анимаций друг на друга с помощью взвешенной суммы параметров
А-а-а, нет, конечно, я же пошутил((9.
Я пошутил, что пошутил, но твой случай можно сравнить не со смеющимся клоуном, светящимся от внутренней силы, который может прийти в 5 не(сильно)конфликтующих состояний одновременно и всё это нужно внятно отобразить, а с двумя взаимоисключающими анимациями смены цвета свечения, когда с появлением новой предыдущую можно, и даже нужно, просто прибить.
Если анимация анимирует поворот, и объявляется более актуальная анимация для поворота той же фигурки, то всё, что нужно сделать — прервать первую, а вторую начать со значения незаконченного поворота.
>но не знаю, насколько это будет правильным решением и как определять, достаточно ли текущее положение близко к целевому, чтобы можно было сократить длительность анимации, и как сильно вообще сокращать её длительность
Во-первых, ты серьёзно? Это школьная программа.
При равномерном движении время = путь/скорость. Да ладно.
При равноускоренном S=v₀t+at²/2, и из известных начальной скорости v₀, ускорения a и пути S время находится решением квадратного уравнения относительно t. Вроде как, тьфу-тьфу, не твой случай.
Во-вторых, анимация так-то не обязана иметь явное время, её можно реализовать и как
animation = { cur_angle, target_angle }
function update(dt)
{
step = 5.0 * dt;
diff = angle_diff(target_angle, cur_angle);
if abs(diff) > step then
cur_angle = normalize_angle(cur_angle + step * sign(diff))
else
cur_angle = target_angle
animation_finished()
}
Где:
function angle_diff(a, b) { return normalize_angle(a - b) }
function normalize_angle(a) { return fmod(a + pi, twopi) - pi }
function fmod(x, y) { return x - y*floor(x/y); }
Иными словами, angle_diff — разность со знаком между углами по кратчайшей дуге, normalize_angle — приведение угла к [-π; π), а fmod — неотрицательный (т. е. принятый в математике; предполагается y > 0) вещественный остаток от деления.
То есть, в случае равномерного движения, текущий угол приближается к целевому каждый кадр на vel×dt, и процесс завершается, когда цель достигнута. Я не говорю, что это хорошая идея, потому что такой подход, наверное, проще сломать какими-нибудь крайними случаями, чем «сейчас выполняется плавное изменение параметра от значения A до значения B за время T, прошло t, поэтому мгновенное значение параметра — mix(A, B, t/T)», и потому что ты всё равно можешь захотеть заранее знать время анимации, чтобы, к примеру, не допустить слишком долгой.
←
>>343683
>стоит ли делать наложение анимаций друг на друга с помощью взвешенной суммы параметров
А-а-а, нет, конечно, я же пошутил((9.
Я пошутил, что пошутил, но твой случай можно сравнить не со смеющимся клоуном, светящимся от внутренней силы, который может прийти в 5 не(сильно)конфликтующих состояний одновременно и всё это нужно внятно отобразить, а с двумя взаимоисключающими анимациями смены цвета свечения, когда с появлением новой предыдущую можно, и даже нужно, просто прибить.
Если анимация анимирует поворот, и объявляется более актуальная анимация для поворота той же фигурки, то всё, что нужно сделать — прервать первую, а вторую начать со значения незаконченного поворота.
>но не знаю, насколько это будет правильным решением и как определять, достаточно ли текущее положение близко к целевому, чтобы можно было сократить длительность анимации, и как сильно вообще сокращать её длительность
Во-первых, ты серьёзно? Это школьная программа.
При равномерном движении время = путь/скорость. Да ладно.
При равноускоренном S=v₀t+at²/2, и из известных начальной скорости v₀, ускорения a и пути S время находится решением квадратного уравнения относительно t. Вроде как, тьфу-тьфу, не твой случай.
Во-вторых, анимация так-то не обязана иметь явное время, её можно реализовать и как
animation = { cur_angle, target_angle }
function update(dt)
{
step = 5.0 * dt;
diff = angle_diff(target_angle, cur_angle);
if abs(diff) > step then
cur_angle = normalize_angle(cur_angle + step * sign(diff))
else
cur_angle = target_angle
animation_finished()
}
Где:
function angle_diff(a, b) { return normalize_angle(a - b) }
function normalize_angle(a) { return fmod(a + pi, twopi) - pi }
function fmod(x, y) { return x - y*floor(x/y); }
Иными словами, angle_diff — разность со знаком между углами по кратчайшей дуге, normalize_angle — приведение угла к [-π; π), а fmod — неотрицательный (т. е. принятый в математике; предполагается y > 0) вещественный остаток от деления.
То есть, в случае равномерного движения, текущий угол приближается к целевому каждый кадр на vel×dt, и процесс завершается, когда цель достигнута. Я не говорю, что это хорошая идея, потому что такой подход, наверное, проще сломать какими-нибудь крайними случаями, чем «сейчас выполняется плавное изменение параметра от значения A до значения B за время T, прошло t, поэтому мгновенное значение параметра — mix(A, B, t/T)», и потому что ты всё равно можешь захотеть заранее знать время анимации, чтобы, к примеру, не допустить слишком долгой.
>gif
krasivo
>с двумя взаимоисключающими анимациями
>предыдущую можно, и даже нужно, просто прибить
Ну, может быть.
>Во-первых, ты серьёзно?
Да. Что поделать.
>текущий угол приближается к целевому каждый кадр на vel×dt, и процесс завершается, когда цель достигнута
Да, так можно было сделать, но для анимаций движения активной фигурки я всё же оставлю вариант с явно заданным временем, чтобы было проще скорректировать длительность анимаций по мере того, как ускоряется игра. Чтобы длительность анимаций была по крайней мере не больше времени в течение которого фигурка висит в воздухе на одном уровне.
А для анимации падения блоков над удалёнными рядами можно и лучше будет, наверное, сделать твой вариант с заданной скоростью, и сделать, чтобы они падали с ускорением.
А за эти модные формулы спасибо, я их заберу себе, а то у меня очень по-тупому реализованы эти вещи.
Это всё довольно тупо и странно, но немного перекликается с тем, что самурай Тэнгу говорил про стиль боя Ишшины Ашина (который эти самураи используют), что самое главное правило в нём - это побеждать врагов, а самый эффективный способ это делать - наносить точечные фатальные удары, что и делает вот тот мини-босс. Так что ето ни в коем случае не очередной мусорный мини-босс, которого впихнули туда, чтобы мне было, откуда достать бусинку для апгрейда здоровья. Нет-нет, конечно, нет.
Победил Геничиро, но он воскрес и сбежал, потому что предварительно наглотался неких вод молодости, которые частично лишили его человеческого облика, чтобы это ни значило на практике, единственное - у него торс выглядит немного то ли подгнившим, то ли обожжённым из-за молний, которые он кастует. Кстати, так и не понял тайминги того, как отражать эти молнии. В теории надо подпрыгнуть в воздух чуть позже, чем он, принять удар молнией на себя, пока находишься в воздухе, а потом перенаправить молнии в него, когда он будет уже на земле, а ты ещё в воздухе. Но как-то у меня слишком непостоянно получалось это делать, только из-за этого пришлось несколько раз перепроходить его долгую первую фазу. А так он относительно простой, только одну атака стрелами его немного непонятно, как избегать. Он как-то внезапно иногда подпрыгивает и осыпает тебя градом стрел, находясь сам в воздухе, и в большинстве случаев он ловил меня на этом.
Как оказалось, Геничиро хотел связать себя с Куро узами бессмертия, но сам Куро не хотел этого делать, потому что драконье поветрье + он находит это бесчеловечным. А сам Геничиро хотел себе бессмертие вроде как, чтобы защищать Ашину. Эмма служит Ишшину Ашина, и это он приказал ей помочь со спасением Куро, потому как не хотел использовать силу наследия дракона для ведения войны. И ещё оказалось, что протез сделал отец Эммы для резчика, а руку резчику отрубил Ишшин, по словам резчика, для его же блага. И Куро теперь хочет разрубить узы бессмертия, а для этого нужен специальный клинок и какие-то ещё штуки, которые, видимо, теперь надо будет искать до конца игры.
А демон колокола действительно неплохо помогает, по крайней мере недостатка в ресурсах для апгрейда инструментов протеза у меня нет. Другое дело - это, что апгрейды какие-то немного бесполезные.
Это всё довольно тупо и странно, но немного перекликается с тем, что самурай Тэнгу говорил про стиль боя Ишшины Ашина (который эти самураи используют), что самое главное правило в нём - это побеждать врагов, а самый эффективный способ это делать - наносить точечные фатальные удары, что и делает вот тот мини-босс. Так что ето ни в коем случае не очередной мусорный мини-босс, которого впихнули туда, чтобы мне было, откуда достать бусинку для апгрейда здоровья. Нет-нет, конечно, нет.
Победил Геничиро, но он воскрес и сбежал, потому что предварительно наглотался неких вод молодости, которые частично лишили его человеческого облика, чтобы это ни значило на практике, единственное - у него торс выглядит немного то ли подгнившим, то ли обожжённым из-за молний, которые он кастует. Кстати, так и не понял тайминги того, как отражать эти молнии. В теории надо подпрыгнуть в воздух чуть позже, чем он, принять удар молнией на себя, пока находишься в воздухе, а потом перенаправить молнии в него, когда он будет уже на земле, а ты ещё в воздухе. Но как-то у меня слишком непостоянно получалось это делать, только из-за этого пришлось несколько раз перепроходить его долгую первую фазу. А так он относительно простой, только одну атака стрелами его немного непонятно, как избегать. Он как-то внезапно иногда подпрыгивает и осыпает тебя градом стрел, находясь сам в воздухе, и в большинстве случаев он ловил меня на этом.
Как оказалось, Геничиро хотел связать себя с Куро узами бессмертия, но сам Куро не хотел этого делать, потому что драконье поветрье + он находит это бесчеловечным. А сам Геничиро хотел себе бессмертие вроде как, чтобы защищать Ашину. Эмма служит Ишшину Ашина, и это он приказал ей помочь со спасением Куро, потому как не хотел использовать силу наследия дракона для ведения войны. И ещё оказалось, что протез сделал отец Эммы для резчика, а руку резчику отрубил Ишшин, по словам резчика, для его же блага. И Куро теперь хочет разрубить узы бессмертия, а для этого нужен специальный клинок и какие-то ещё штуки, которые, видимо, теперь надо будет искать до конца игры.
А демон колокола действительно неплохо помогает, по крайней мере недостатка в ресурсах для апгрейда инструментов протеза у меня нет. Другое дело - это, что апгрейды какие-то немного бесполезные.
В квесте Котаро отдал белую вертушку, чтобы он вернулся приглядывать за душами умерших в результате экспериментов детей в чертоги морока, а того потерянного самурая я всё же довёл до призрака водяной, где он и испустил дух. При этом его даже не эта водяная убила, он просто постепенно угасал по мере того, как вместе со мной продвигался ближе к деревне Мибу, и, когда я убил водяную, он что-то пробормотал про то, что рад знать, что водяная ждала именно его и теперь он сможет навсегда остаться с ней, хотя водяная вообще не упоминала имя этого самурая, а всё причитала о том, что все скрывают от неё какого-то лорда Сакудзу, а, когда я убил её, она поблагодарила меня, но тот самурай, видимо, совсем уже был в бреду и, невольно подслушав наш диалог, принял всё на свой счёт.
Но надо было лучше, наверное, отдать самурая лекарю, который, судя по записке, которая лежит недалеко от него, он Доджун, ученик лекаря Досаку, занимавшегося изучением вод молодости. И там дальше по квесту ему нужно будет достать красные глаза карпа из деревни Мибу, с помощью которых он и себя, и приведённого мною самурая, усилил бы водами молодости, но при этом бы сошёл с ума и вместе с ним напал бы на меня потом в подземельях. Хотя, возможно, у него было не всё в порядке с головой и до этого, потому что, если его подслушать, пока он стоит в камере, он говорит сам с собой, меняя интонации, представляя, что разговаривает со своим обезумевшим сенсеем Досаку, который собирается поставить опыты на своём ученике. Но я всё это на ютубе уже посмотрел, потому что у меня такое будет уже только разве что на нг+.
Изучил забавную технику, которая позволяет обратить врага на свою сторону. Обычно это не очень полезно, только разве что, если законтролить оверпаверед врага-толстяка, который будет потом сметать всё на своём пути. Но также это полезно в некоторых сюжетных местах, например, в храме семпо можно так взять в контроль моба, который поможет подержать воздушного змея, с помощью которого можно было бы перебраться через пропасть. Или в опустившейся долине, в храме белого змея, нужно законтролить мартышку, чтобы та отвлекла внимание змея, а я мог бы пробраться дальше.
Ещё, убив, монахиню, которая охраняла вход в пещеру с ароматным камнем (который оказался кристаллом, который "рос внутри тех, кто на протяжении долгого времени пил из первоисточника", но я так и не понял пока что, что подразумевается под первоисточником тут, наверное, где-то дальше по сюжету будет), получил возможность плавать под водой, что открыло сразу кучу возможностей. Вообще, это такая крутая идея - постепенно давать игроку дополнительные способности, с помощью которых он мог бы потом заново для себя открыть уже пройденные локации. Правда, тут никаких особо новых секретных мест я не открыл, только выбил кучу чешуек драгоценного карпа и нашёл двух новых безголовых: один сидел под водой, а второй сидел в гроте, в который можно было попасть только проплыв под водой. Подводный пока что единственный из безголовых, которого я смог прикончить, но только потому, что он значительно слабее других. Остальных, видимо, надо очень точно парировать: при отражении атак статус-эффект страх не накладывается, и при этом у безголового заполняется полоска концентрации, которая у него восстанавливается обратно довольно медленно. Вроде как улучшенным зонтом можно блокировать и наложение страха, но у меня его нет ещё.
А того мини-босса около бездонной дыры, ведущей в туманную деревню и в деревню Мибу, оказалось очень легко убить, если изучить технику смертельного удара в прыжке. При этом надо всё равно использовать конфети, но я нашёл 5 штук в деревне дальше, так что к тому моменту у меня их было довольно много. Как только он подлетает в воздух, есть возможность прямо в воздухе нанести ему смертельный удар (мм, в английской версии он deathblow, звучит получше, но непонятно, как перевести одним словом на русский) без необходимости сносить всю полоску концентрации. Вроде то же самое можно делать с петухами, но они отталкивают тебя лапами от себя, как только пытаешься подпрыгнуть. Самые сложные противники в игре, с ними в прямом бою вообще лучше не связываться.
Ещё убил одного из двух больших белых змеев, которых пока что встретил. С него выпал кусок каких-то его внутренностей. А из храма с другим змеем можно утащить засушенный вариант этих внутренностей. Пока что не знаю, зачем они нужны, их пока что только упоминала старуха из храма Сэмпо, называвшая их змеиными фруктами.
Короче, я пока что остановился перед обезьяной-стражем из трейлера в опустившейся долине, которая воскресает после того, как ей отрубить голову. И у меня есть варианты либо её траить, либо одного из трёх безголовых, которых я нашёл. И ещё само вот это ущелье, на дне которого сидит босс, я тоже не до конца обошёл.
Кстати, я долго не понимал, кто такие эти торговцы подношениями, но, судя по всему, это просто воры, которые снимают ценные вещи с трупов. Это можно узнать, поговорив с торговцем информацией, который упоминает, что раньше тоже торговал подношениями, потом перестал, но привычка обирать трупы осталась.
В квесте Котаро отдал белую вертушку, чтобы он вернулся приглядывать за душами умерших в результате экспериментов детей в чертоги морока, а того потерянного самурая я всё же довёл до призрака водяной, где он и испустил дух. При этом его даже не эта водяная убила, он просто постепенно угасал по мере того, как вместе со мной продвигался ближе к деревне Мибу, и, когда я убил водяную, он что-то пробормотал про то, что рад знать, что водяная ждала именно его и теперь он сможет навсегда остаться с ней, хотя водяная вообще не упоминала имя этого самурая, а всё причитала о том, что все скрывают от неё какого-то лорда Сакудзу, а, когда я убил её, она поблагодарила меня, но тот самурай, видимо, совсем уже был в бреду и, невольно подслушав наш диалог, принял всё на свой счёт.
Но надо было лучше, наверное, отдать самурая лекарю, который, судя по записке, которая лежит недалеко от него, он Доджун, ученик лекаря Досаку, занимавшегося изучением вод молодости. И там дальше по квесту ему нужно будет достать красные глаза карпа из деревни Мибу, с помощью которых он и себя, и приведённого мною самурая, усилил бы водами молодости, но при этом бы сошёл с ума и вместе с ним напал бы на меня потом в подземельях. Хотя, возможно, у него было не всё в порядке с головой и до этого, потому что, если его подслушать, пока он стоит в камере, он говорит сам с собой, меняя интонации, представляя, что разговаривает со своим обезумевшим сенсеем Досаку, который собирается поставить опыты на своём ученике. Но я всё это на ютубе уже посмотрел, потому что у меня такое будет уже только разве что на нг+.
Изучил забавную технику, которая позволяет обратить врага на свою сторону. Обычно это не очень полезно, только разве что, если законтролить оверпаверед врага-толстяка, который будет потом сметать всё на своём пути. Но также это полезно в некоторых сюжетных местах, например, в храме семпо можно так взять в контроль моба, который поможет подержать воздушного змея, с помощью которого можно было бы перебраться через пропасть. Или в опустившейся долине, в храме белого змея, нужно законтролить мартышку, чтобы та отвлекла внимание змея, а я мог бы пробраться дальше.
Ещё, убив, монахиню, которая охраняла вход в пещеру с ароматным камнем (который оказался кристаллом, который "рос внутри тех, кто на протяжении долгого времени пил из первоисточника", но я так и не понял пока что, что подразумевается под первоисточником тут, наверное, где-то дальше по сюжету будет), получил возможность плавать под водой, что открыло сразу кучу возможностей. Вообще, это такая крутая идея - постепенно давать игроку дополнительные способности, с помощью которых он мог бы потом заново для себя открыть уже пройденные локации. Правда, тут никаких особо новых секретных мест я не открыл, только выбил кучу чешуек драгоценного карпа и нашёл двух новых безголовых: один сидел под водой, а второй сидел в гроте, в который можно было попасть только проплыв под водой. Подводный пока что единственный из безголовых, которого я смог прикончить, но только потому, что он значительно слабее других. Остальных, видимо, надо очень точно парировать: при отражении атак статус-эффект страх не накладывается, и при этом у безголового заполняется полоска концентрации, которая у него восстанавливается обратно довольно медленно. Вроде как улучшенным зонтом можно блокировать и наложение страха, но у меня его нет ещё.
А того мини-босса около бездонной дыры, ведущей в туманную деревню и в деревню Мибу, оказалось очень легко убить, если изучить технику смертельного удара в прыжке. При этом надо всё равно использовать конфети, но я нашёл 5 штук в деревне дальше, так что к тому моменту у меня их было довольно много. Как только он подлетает в воздух, есть возможность прямо в воздухе нанести ему смертельный удар (мм, в английской версии он deathblow, звучит получше, но непонятно, как перевести одним словом на русский) без необходимости сносить всю полоску концентрации. Вроде то же самое можно делать с петухами, но они отталкивают тебя лапами от себя, как только пытаешься подпрыгнуть. Самые сложные противники в игре, с ними в прямом бою вообще лучше не связываться.
Ещё убил одного из двух больших белых змеев, которых пока что встретил. С него выпал кусок каких-то его внутренностей. А из храма с другим змеем можно утащить засушенный вариант этих внутренностей. Пока что не знаю, зачем они нужны, их пока что только упоминала старуха из храма Сэмпо, называвшая их змеиными фруктами.
Короче, я пока что остановился перед обезьяной-стражем из трейлера в опустившейся долине, которая воскресает после того, как ей отрубить голову. И у меня есть варианты либо её траить, либо одного из трёх безголовых, которых я нашёл. И ещё само вот это ущелье, на дне которого сидит босс, я тоже не до конца обошёл.
Кстати, я долго не понимал, кто такие эти торговцы подношениями, но, судя по всему, это просто воры, которые снимают ценные вещи с трупов. Это можно узнать, поговорив с торговцем информацией, который упоминает, что раньше тоже торговал подношениями, потом перестал, но привычка обирать трупы осталась.
теперь я запомню, что там два т
можно было бы отпустить демона колокола и тогда, возможно, всё стало бы чуть проще, но я принципиально не хочу его уже отпускать, потому что всю игру, считай, с ним бегал, а ещё, да, можно свистеть и станнить его, использовать конфетти для чуть большего урона, но у меня просто нет желания фармить деньги на все эти микро-упрощения
я дошёл до момента, когда куро сбежал по потайному ходу из замка во время его осады, и я вроде как убил всех боссов и мини-боссов до этого момента, кроме вот демона ненависти, а дальше всё, уже точно не буду в это играть, потому что это какое-то издевательство из раза в раз пробовать его пройти, причём с каждым разом получается всё хуже и хуже
конечно, немного интересно, что там в конце, что это за чёрный клинок, который упоминается в записке ишшина, да и в целом, на самом деле, игра крутая, особенно в плане подачи сюжета, но я не хочу больше это трогать
Вместо того, чтобы заниматься чем-то нормальным, я упаковываю иксы и игреки в один объект, чтобы можно было писать строки мусорного кода, которые не умещаются даже в монитор, вот вроде такого:
>DoubleVector rotationPivot = coordinates.add(shapeType.getRotationPivot().add(0.5, 0.5).times(blockWidth));
почему к getRotationPivot надо прибавлять мистические половинки - секрет
на самом деле, нет, это надо будет потом исправить, просто у меня эта же штука вызывается в другом месте, где прибавлять эти 0.5 не надо
Ну, как бы, в теории это должно уменьшить вдвое вероятность ошибки в местах, где вычисляются координаты, но в то же время, так что это вроде не так бесполезно.
Напоминает мой код вращения камеры на кватернионах, в итоге сделала через перевод сферических координат.
Позорный, но простой способ — вбить оболочку вручную, дескать, слишком много чести для 4 клеточек искать её общим способом. Только впоследствии придётся убивать всех, кто узнает, что ты так сделал.
class Shape
{
IntVec2[] cells;
Vec2[] convexHull;
}
// → x
// ↓
// y
//
// ######
// ######
// ###
// ###
// ###
// ###
G: Shape =
{
cells: {(0, 0), (0, 1), (0, 2), (1, 0)}
convexHull: {(0.0, 0.0), (0.0, 3.0), (1.0, 3.0), (2.0, 1.0), (2.0, 0.0)}
}
update()
{
for (point in fallingItem.shape.convexHull)
{
screenPoint = fallingThing.screenTransform.apply(point)
screenBoundA = min(screenBoundA, screenPoint);
screenBoundB = max(screenBoundB, screenPoint);
}
// fix = max(Vec2.Zero, -screenBoundA) - max(Vec2.Zero, screenBoundB - screenFieldSize)
if (screenBoundA.x < 0) fix.x -= screenBoundA.x;
if (screenBoundA.y < 0) fix.y -= screenBoundA.y;
if (screenBoundB.x > screenFieldSize.x) fix.x -= screenBoundB.x - screenFieldSize.x;
if (screenBoundB.y > screenFieldSize.y) fix.y -= screenBoundB.y - screenFieldSize.y;
fallingThing.screenPos += fix;
}
Либо заметим, что у реалистичных фигурок вершины выпуклой оболочки — это узлы сетки, принадлежащие единственной ячейке. Это всё ещё проще общего способа, но и свидетелей убивать уже не придётся.
Позорный, но простой способ — вбить оболочку вручную, дескать, слишком много чести для 4 клеточек искать её общим способом. Только впоследствии придётся убивать всех, кто узнает, что ты так сделал.
class Shape
{
IntVec2[] cells;
Vec2[] convexHull;
}
// → x
// ↓
// y
//
// ######
// ######
// ###
// ###
// ###
// ###
G: Shape =
{
cells: {(0, 0), (0, 1), (0, 2), (1, 0)}
convexHull: {(0.0, 0.0), (0.0, 3.0), (1.0, 3.0), (2.0, 1.0), (2.0, 0.0)}
}
update()
{
for (point in fallingItem.shape.convexHull)
{
screenPoint = fallingThing.screenTransform.apply(point)
screenBoundA = min(screenBoundA, screenPoint);
screenBoundB = max(screenBoundB, screenPoint);
}
// fix = max(Vec2.Zero, -screenBoundA) - max(Vec2.Zero, screenBoundB - screenFieldSize)
if (screenBoundA.x < 0) fix.x -= screenBoundA.x;
if (screenBoundA.y < 0) fix.y -= screenBoundA.y;
if (screenBoundB.x > screenFieldSize.x) fix.x -= screenBoundB.x - screenFieldSize.x;
if (screenBoundB.y > screenFieldSize.y) fix.y -= screenBoundB.y - screenFieldSize.y;
fallingThing.screenPos += fix;
}
Либо заметим, что у реалистичных фигурок вершины выпуклой оболочки — это узлы сетки, принадлежащие единственной ячейке. Это всё ещё проще общего способа, но и свидетелей убивать уже не придётся.
На самом деле, я думаю, это не столько позорно, сколько error-prone, чувствительно к изменениям и затратно по силам.
>Либо заметим, что у реалистичных фигурок вершины выпуклой оболочки — это узлы сетки, принадлежащие единственной ячейке.
Если честно, ничего не понял. Просто брать вершины каждого блока в фигурке и проверять, не вышел ли он за границы?
В общем, проблема с graham scan в ошибках округления. Может быть такое, что алгоритм увидит три вершины на одной прямой и переберёт их в неправильном порядке, потому что ему покажется, что он не лежат на одной прямой. Не знаю, как этого избегать, вообще я запутался.
>Если честно, ничего не понял
Пикрелейтед.
>Может быть такое, что алгоритм увидит три вершины на одной прямой и переберёт их в неправильном порядке, потому что ему покажется, что он не лежат на одной прямой.
Теперь я не понял. Ты ищешь выпуклую оболочку уже повёрнутой фигурки, что ли? Этого не нужно делать, т. к. выпуклая оболочка повёрнутой фигурки идентична повёрнутой выпуклой оболочке неповёрнутой фигурки, а вершины неповёрнутой фигурки лежат в узлах целочисленной сетки, где погрешностей нет в принципе.
У тебя ведь наверняка есть локальная система координат фигурки, где Т-тетрамино состоит из ячеек, скажем, (0, 0), (1, 0), (2, 0) и (1, 1), которые, пройдя ряд преобразований, становятся экранными координатами спрайтов. Выпуклую оболочку можно однократно вычислить в этой локальной системе координат, сохранить с остальными метаданными фигурки и впоследствии применить к её вершинам те же преобразования, чтобы узнать, куда она там залезла на экране.
>Пикрелейтед.
А, вот так, понятно, при кольно.
>Ты ищешь выпуклую оболочку уже повёрнутой фигурки, что ли?
Да. Ну да, можно вычислять её заранее и поворачивать потом, как-то не подумал об этом. Спасибо.
То есть в том числе сейчас, если нажимаешь на кнопку вращения в какую-то определённую сторону, то фигурка гарантированно будет вращаться в эту сторону. Из-за этого прерывание вот этого быстрого вращения выглядит довольно резко, но, я думаю, так будет лучше.
Есть альтернативный вариант на втором видео, в котором фигурка, если её вращаешь, не разворачивается мгновенно, а немного замедляется и докручивается до нужного положения. Но тут проблема в том, что если чуть-чуть её раскрутить, то докручиваться она будет быстро. И починить это простыми средствами у меня как-то не получилось, а делать полноценное сложение анимаций я тут не хочу.
Так что, наверное, оставлю первый вариант, потому что он более отзывчивый. Да и вообще это заметно только, если сильно замедлить все анимации, так что можно вообще было с самого начала ничего не поправлять.
Выпуклые оболочки вычисляются только единожды для каждой фигурки, гемы, освещённые под разными углами, генерируются только один раз и потом между этими контрольными точками делается переход с изменением прозрачности. Осталось только починить ту ерунду с прибавлением (0.5, 0.5) и, наверное, оно будет выглядеть достаточно сносно для меня.
Не имею возможности ответить положительно, потому как не смотрел аниме с ними.
И делать это с эффектом перестановки блоков. Там же одинаковое количество блоков во всех фигурках. Думаю, это нетрудно будет сделать потом a star поиском.
Единственная идея - переделать классы для типов фигурок так, чтобы они возвращали не boolean[][] массивы в качестве своих представлений, а int[][], где блоки были бы проиндексированы.
Поэтому у меня сразу после начала поворота меняются цвета, а не после того, как фигурка закончила вращаться. Меня просто смутило это, когда пересмотрел внимательнее.
И, я сейчас понял, что если реализовывать эту идею с индексированием, то придётся запоминать положение, из которого вращение начиналось, чтобы раскрасить правильно блоки. И это всё в голове складывается в какое-то совсем уродливое решение с кучей неочевидных вещей. Так что, возможно, всё же вариант с тем, чтобы воспринимать фигурку как несколько склееных блоков, лучше.
Демон ненависти оказался всё же не таким сложным, и после него мне оставалось только пройти Геничиро, из которого почему-то вылез молодой Ишшин после того, как тот порезал себя чёрным клинком бессмертных. И я так и не понял, как это и почему. Ишшин относительно простой в первой фазе, потому что часто открывается для контр-атак и его вот этот крест ашина тоже легко отражается. А во второй и третьей фазах он внезапно достаёт копьё и начинает им махать, почти не останавливаясь, не давая тебе времени даже на полечиться. Ещё и с мушкета палит. Но в принципе тоже можно пройти.
На нг+ вряд ли буду играть, хотя хотел посмотреть на концовку которая будет, если остаться верным кодексу шиноби. Пришлось бы следовать указаниям поехавшего старика, желающего заполучить бессмертие. Но вряд ли буду проходить нг+, я попробовал пройти сейчас по первым локациям, и как будто бы у меня и не было никаких улучшений силы: врагам как будто утроили здоровье и шкалу концентрации.
Родители опять выгоняют на улицу в магазин, зачем-то нужен творог, причём продаётся он на развес, так что придётся подходить и просить дать тебе его. За что мне вообще такое.
Модные формулы, part 2
Введём Rotation2D и Transform2D (или у тебя такой уже есть, или в Java такой уже есть, но тогда чё за хуйня).
class Rotation2D
{
float cosa, sina;
static Rotation2D fromAngle(angle) { cosa = cos(angle); sina = sin(angle); }
float toAngle() { return atan2(sina, cosa); }
Vec2 apply(Vec2 v)
{
return Vec2.of(cosa*v.x - sina*v.y, sina*v.x + cosa*v.y);
}
Rotation2D combine(Rotation2D b)
{
return
{
cosa = this.cosa*b.cosa - this.sina*b.sina,
sina = this.sina*b.cosa + this.cosa*b.sina
}
}
}
class Transform2D
{
Vec2 trans;
Rotation2D rot;
Vec2 apply(Vec2 v)
{
return trans + rot.apply(v);
}
Transform2D combine(Transform2D b)
{
return
{
trans = this.combine(b.trans),
rot = this.rot.combine(b.rot)
}
}
}
Это (почти) прямой эквивалент хранения трёхмерного преобразования как Vec3 trans + Quaternion rot. Кватернион поворота вокруг оси V на угол α — гиперкомплексное число {Imᵢⱼₖ=V sin α/2, Re=cos α/2}; а наш Rotation2D — обычное комплексное {Im=sin α, Re=cos α}.
Объекты в сцене могут быть собраны в иерархию: например, фигурка — дитя корня сцены, а части фигурки — дети фигурки. У узла есть локальное преобразование — localTransform: например, у фигурки это её смещение в рамках поля и поворот, а у частей фигурки — их локальные смещения в фигурке. Когда все localTransform расставлены, для каждого объекта, начиная с корня, мы можем вычислить globalTransform = parent.globalTransform.combine(this.localTransform), это и будет его видимым преобразованием. Удобно взять за рабочую единицу длины размер 1 блока, а на экран всё преобразовывать в последнюю очередь (blockPixelPos = block.globalTransform.trans / SCREEN_SIZE_IN_BLOCKS*SCREEN_SIZE_IN_PIXELS).
В терминах Transform поворот вокруг точки P можно выразить как комбинацию смещения на -P, поворота, и смещения снова на P.
Модные формулы, part 2
Введём Rotation2D и Transform2D (или у тебя такой уже есть, или в Java такой уже есть, но тогда чё за хуйня).
class Rotation2D
{
float cosa, sina;
static Rotation2D fromAngle(angle) { cosa = cos(angle); sina = sin(angle); }
float toAngle() { return atan2(sina, cosa); }
Vec2 apply(Vec2 v)
{
return Vec2.of(cosa*v.x - sina*v.y, sina*v.x + cosa*v.y);
}
Rotation2D combine(Rotation2D b)
{
return
{
cosa = this.cosa*b.cosa - this.sina*b.sina,
sina = this.sina*b.cosa + this.cosa*b.sina
}
}
}
class Transform2D
{
Vec2 trans;
Rotation2D rot;
Vec2 apply(Vec2 v)
{
return trans + rot.apply(v);
}
Transform2D combine(Transform2D b)
{
return
{
trans = this.combine(b.trans),
rot = this.rot.combine(b.rot)
}
}
}
Это (почти) прямой эквивалент хранения трёхмерного преобразования как Vec3 trans + Quaternion rot. Кватернион поворота вокруг оси V на угол α — гиперкомплексное число {Imᵢⱼₖ=V sin α/2, Re=cos α/2}; а наш Rotation2D — обычное комплексное {Im=sin α, Re=cos α}.
Объекты в сцене могут быть собраны в иерархию: например, фигурка — дитя корня сцены, а части фигурки — дети фигурки. У узла есть локальное преобразование — localTransform: например, у фигурки это её смещение в рамках поля и поворот, а у частей фигурки — их локальные смещения в фигурке. Когда все localTransform расставлены, для каждого объекта, начиная с корня, мы можем вычислить globalTransform = parent.globalTransform.combine(this.localTransform), это и будет его видимым преобразованием. Удобно взять за рабочую единицу длины размер 1 блока, а на экран всё преобразовывать в последнюю очередь (blockPixelPos = block.globalTransform.trans / SCREEN_SIZE_IN_BLOCKS*SCREEN_SIZE_IN_PIXELS).
В терминах Transform поворот вокруг точки P можно выразить как комбинацию смещения на -P, поворота, и смещения снова на P.
У меня раньше фигурка была одной цельной сущностью, в которой нельзя было выделить отдельные блоки, но из-за этого повороты неодноцветых фигурок получались неправильными >>346095, поэтому хотел сделать так, чтобы фигурка представляла из себя несколько сущностей-блоков, но, как только я начал это всё переделывать, полезло куча багов.
И ещё я хотел сделать так, чтобы поворот применялся только непосредственно при анимации поворота, а в остальные моменты времени спрайты отрисовывались бы без поворота, на этом меня сильно заклинило.
>>346617
>Модные формулы
Нет, эти формулы немодные, там же просто матрицы поворота. Но за идею с комбинированием преобразований
>у фигурки это её смещение в рамках поля и поворот, а у частей фигурки — их локальные смещения в фигурке
спасибо, может, переделаю у себя так.
>/ SCREEN_SIZE_IN_BLOCKS*SCREEN_SIZE_IN_PIXELS
Не очень хочу делать, чтобы оно ресайзилось как угодно.
Неплохая идея, я тоже часто зацикливаюсь на деталях, могу долго рефакторить и думать как правильно сделать отношения между разными абстрактными сущностями, хотя это и не добавляет никакого функционала. Почему отдельные кубики разных цветов? Или это тетрис с элементом три в ряд?
Да, думал сделать потом помимо обычного тетриса режим N-в-ряд с упрощёнными фигурками (из 2-3 блоков). Или, может, как в доктор марио. А в обычном тетрисе, я думаю, фигурки будут раскрашены равномерно.
>У меня, из типа фигурки можно достать её boolean[][] представление для четырёх положений вращений
И это тупо, потому что можно просто сделать интерфейс взаимодействия с этими массивами, а не выдавать их пользователю напрямую (и их же ещё копировать приходится, чтобы пользователь не смог их изменить). К тому же в таком случае необязательно будет генерировать четыре массива, достаточно будет одного одномерного массива, для которого будут по-разному генерироваться индексы в зависимости от заданного положения вращения при переводе (x, y) в индекс массива. То есть для initial положения это будет i = x frameWidth + y, для right положения i = 12 - 4x + y, и так же для оставшихся двух положений (пикрилейтед). Эту идею я нагло украл, но у себя ещё не реализовал.
А ещё у меня очень тупо сделана проверка коллизий, потому что у меня фигурка на поле не знает ничего о других объектах на игровом поле, так что проверка на то, можно ли вставить фигурку в какое-то место, делается внутри объекта-игрового поля, а сама фигурка безусловно двигается куда угодно. А для того, чтобы поправить это, достаточно просто передать фигурке ссылку на поле, на котором она находится, но мне почему-то показалось, что это странная идея.
И это я тоже не поправил, потому что я хотел по-быстрому перейти от своей корявой реализации, где координаты для всех сущностей указывались относительно начала координат экрана* + можно было сделать сдвиг при рендере сущности на экран, к нормальной реализации с комбинирующимися трансформациями, но у меня не получилось сделать это быстро, и я даже не пришёл к компилирующемуся варианту.
Тупые звёздочки, постоянно забываю. Но текст не важен, важны только картинки.
>достаточно будет одного одномерного массива, для которого будут по-разному генерироваться индексы
Нет, так сделать не получится, у меня же оси вращения не всегда совпадают с центром коробки.
>проверка на то, можно ли вставить фигурку в какое-то место, делается внутри объекта-игрового поля, а сама фигурка безусловно двигается куда угодно
Я пытался поменять, но получалось только хуже и хуже. И я не знаю, как нормально реализовать wall-kick'и, если фигурка не может становиться в неправильные места, потому что очень часто волл кик состоит из смещения в неправильную позицию + поворота. И это можно было бы обойти, если добавить вид движения сдвиг-поворот. Но не знаю, выглядит как-то тупо. Или нет, я не знаю, всё просто выглядит очень уродливо.
И вот этот дополнительный сдвиг, который удерживает фигурку внутри рамки тоже выглядит тупо. Можно было бы реализовать общий случай, в котором бы брались выпуклая оболочка родительской сущности и дочерней сущности и второе сдвигалось бы так, чтобы оно умещалось в первое. И для этого вроде надо только проверить, есть ли точки, которые вылезают наружу и сделать сдвиг в направлении ближайшей точки из родительской выпуклой оболочки.
И тогда всё будет работать корректно даже, если игровое поле будет наклонено, но мне лень это делать и я не вижу смысла.
Почему-то опять посреди дня начинает беспричинно клонить в сон, не знаю, как с этим бороться.
Всё же поиграл ещё немного и снова потерял очередной сет железной брони, немного заполненную карту и зачарованные кирку и меч. И опять помер от взрыва крипера, когда меня отбросило в яму с ним от чьего-то удара. Наверное, имеет смысл накопить левел, зачаровать алмазную кирку на эффективность, безопасно найти алмазы (не бегая по пещерам, а прокапывая коридоры на 11 уровне где-нибудь рядом с домом), сделать алмазную броню и зачаровать её на протекшн (а лучше, наверное, на бласт протекшн, но я не знаю, на каких уровнях появляется это зачарование). Может, тогда меня не будут ван-шотать криперы.
Всё же самый лучший способ набивания уровней - это фарм мобов по ночам. Возможно, стоит делать это в пустынях, потому что по ощущениям там спавнится гораздо больше враждебных мобов (ну, и по логике тоже: в пустынях, кроме зайцев, мирных мобов, так что, наверное, это оставляет больше возможностей для спавна агрессивных мобов; хотя всё равно не знаю, действительно ли там лучше спавн мобов настолько, чтобы мне имело смысл идти до ближайшией пустыни через всю карту), и ещё там хорошая область видимости, меньше шансов, что к тебе подкрадётся крипер.
Кстати, наконец-то выпала морковь с зомби, до этого не мог найти её нигде. Правда, толку от неё не очень много. Можно, конечно, использовать в качестве еды золотую морковь, которая вроде как только немногим хуже говядины, но почти один золотой слиток на каждую морковку - это как-то дороговато.
А в остальном я вообще никак не продвинулся, ну, разве что только теперь чуть больше ресурсов лежит в сундуках, всё же удалось пару раз пройтись по пещерам и не огрести от криперов, и сделал ещё большое поле с пшеницей около реки. Вроде как это не самый лучший вариант выращивания всяких штук в майнкрафте, потому что всякие штуки лучше растут, если их высаживать в линии, но я в любом случае планирую использовать эту пшеницу только для размножения коров, и там её так много высажено, что мне только одного урожая с этого поля хватит надолго.
А ещё нашёл карту сокровищ, ну, и сам сундук тоже. Там ничего интересного не было в целом, только вот эта круглая синяя штука, с помощью которой вроде как делается штука, которая позволяет дышать под водой, но у меня нет ракушек для этого, да и необходимости в этом тоже пока что нет.
Всё же поиграл ещё немного и снова потерял очередной сет железной брони, немного заполненную карту и зачарованные кирку и меч. И опять помер от взрыва крипера, когда меня отбросило в яму с ним от чьего-то удара. Наверное, имеет смысл накопить левел, зачаровать алмазную кирку на эффективность, безопасно найти алмазы (не бегая по пещерам, а прокапывая коридоры на 11 уровне где-нибудь рядом с домом), сделать алмазную броню и зачаровать её на протекшн (а лучше, наверное, на бласт протекшн, но я не знаю, на каких уровнях появляется это зачарование). Может, тогда меня не будут ван-шотать криперы.
Всё же самый лучший способ набивания уровней - это фарм мобов по ночам. Возможно, стоит делать это в пустынях, потому что по ощущениям там спавнится гораздо больше враждебных мобов (ну, и по логике тоже: в пустынях, кроме зайцев, мирных мобов, так что, наверное, это оставляет больше возможностей для спавна агрессивных мобов; хотя всё равно не знаю, действительно ли там лучше спавн мобов настолько, чтобы мне имело смысл идти до ближайшией пустыни через всю карту), и ещё там хорошая область видимости, меньше шансов, что к тебе подкрадётся крипер.
Кстати, наконец-то выпала морковь с зомби, до этого не мог найти её нигде. Правда, толку от неё не очень много. Можно, конечно, использовать в качестве еды золотую морковь, которая вроде как только немногим хуже говядины, но почти один золотой слиток на каждую морковку - это как-то дороговато.
А в остальном я вообще никак не продвинулся, ну, разве что только теперь чуть больше ресурсов лежит в сундуках, всё же удалось пару раз пройтись по пещерам и не огрести от криперов, и сделал ещё большое поле с пшеницей около реки. Вроде как это не самый лучший вариант выращивания всяких штук в майнкрафте, потому что всякие штуки лучше растут, если их высаживать в линии, но я в любом случае планирую использовать эту пшеницу только для размножения коров, и там её так много высажено, что мне только одного урожая с этого поля хватит надолго.
А ещё нашёл карту сокровищ, ну, и сам сундук тоже. Там ничего интересного не было в целом, только вот эта круглая синяя штука, с помощью которой вроде как делается штука, которая позволяет дышать под водой, но у меня нет ракушек для этого, да и необходимости в этом тоже пока что нет.
Почти около дома под землёй случайно наткнулся на спавнер скелетонов. Раскопал его и сделал лифт который их поднимает скелетов вверх, а потом сбрасывает вниз, чтобы потом можно было убивать их за один хит (если они без брони). Кстати, не знал, что теперь соул сэнд под водой создаёт водоворот, который выталкивает тебя наверх, при кольно. А блок магмы наоборот, затягивает вниз, так что можно сделать водяной лифт, который просто меняет местами эти блоки. Опыта с этой гриндилки не то чтобы много выпадает, но вполне можно накопить 30 уровень и выше. Стрел и костей выпадает очень-очень много. У меня были опасения по поводу того, будет ли оно вообще работать, потому как рядом со спавнером было какое-то аномальное количество подземных ущелий. На всякий случай осветил ближайшие пещеры, не знаю, сыграло ли это в итоге какую-либо роль.
Ещё нашёл тростник в затонувшем корабле (джунгли я ещё не находил, так что это полезно), и удочку на мендинг, и больше на ней никаких зачарований не было, так что толку от этого мендинга вообще никакого.
Хотел перенести автоматическую ферму тыкв и арбузов вниз под землю (оказывается, тот дизайн, который у меня был, был актуален в какой-то из старых версий, где был баг с тем, что тыква не хотела плодоносить, если над ней есть блок, так что нельзя было поставить обзёрвер над ней, а в новых версиях такого нет, так что можно лепить обзёрверы и пистоны над ростками тыквы и расширять поле с посадками как угодно, только не забывать об освещении и воде), к спавнеру скелетов, и заодно впихнуть туда авто-ферму жареной курицы.
Но перед этим решил впихнуть туда же массив из печек, которые бы работали одновременно и между ними равномерно бы распределялись топливо и те штуки, которые жарятся. И я просто бездумно взял дизайн мамбо джамбо https://youtu.be/yyyhxRztamE, который сделал эту штуку полностью на хопперах: сверху идёт труба из хопперов, которые подключены горизонтально последовательно друг к другу, и из последнего торчит иневертированный компаратор, который лочит ещё одну линию хопперов под первой линией, которые уже направлены в печки. И суть в том, что предметы затекают по верхней трубе, равномерно заполняют её, активируют компаратор последнего хоппера, всё сбрасывается в нижние хопперы, которые после этого опять лочатся (не пускают в себя айтемы из верхних), пока новые N айтемов не заполнят верхнюю трубу.
И это красивая идея, но проблема в том, что у этой штуки какие-то кривые тайминги и иногда в некоторые печки не попадают предметы. И хопперы засоряются. И гораздо проще, дешевле и лучше было бы просто сделать вагонетку сверху, которая бы останавливалась у сундука, заполнялась, а потом бы ездила по хопперам и равномерно распределяла айтемы.
Почти около дома под землёй случайно наткнулся на спавнер скелетонов. Раскопал его и сделал лифт который их поднимает скелетов вверх, а потом сбрасывает вниз, чтобы потом можно было убивать их за один хит (если они без брони). Кстати, не знал, что теперь соул сэнд под водой создаёт водоворот, который выталкивает тебя наверх, при кольно. А блок магмы наоборот, затягивает вниз, так что можно сделать водяной лифт, который просто меняет местами эти блоки. Опыта с этой гриндилки не то чтобы много выпадает, но вполне можно накопить 30 уровень и выше. Стрел и костей выпадает очень-очень много. У меня были опасения по поводу того, будет ли оно вообще работать, потому как рядом со спавнером было какое-то аномальное количество подземных ущелий. На всякий случай осветил ближайшие пещеры, не знаю, сыграло ли это в итоге какую-либо роль.
Ещё нашёл тростник в затонувшем корабле (джунгли я ещё не находил, так что это полезно), и удочку на мендинг, и больше на ней никаких зачарований не было, так что толку от этого мендинга вообще никакого.
Хотел перенести автоматическую ферму тыкв и арбузов вниз под землю (оказывается, тот дизайн, который у меня был, был актуален в какой-то из старых версий, где был баг с тем, что тыква не хотела плодоносить, если над ней есть блок, так что нельзя было поставить обзёрвер над ней, а в новых версиях такого нет, так что можно лепить обзёрверы и пистоны над ростками тыквы и расширять поле с посадками как угодно, только не забывать об освещении и воде), к спавнеру скелетов, и заодно впихнуть туда авто-ферму жареной курицы.
Но перед этим решил впихнуть туда же массив из печек, которые бы работали одновременно и между ними равномерно бы распределялись топливо и те штуки, которые жарятся. И я просто бездумно взял дизайн мамбо джамбо https://youtu.be/yyyhxRztamE, который сделал эту штуку полностью на хопперах: сверху идёт труба из хопперов, которые подключены горизонтально последовательно друг к другу, и из последнего торчит иневертированный компаратор, который лочит ещё одну линию хопперов под первой линией, которые уже направлены в печки. И суть в том, что предметы затекают по верхней трубе, равномерно заполняют её, активируют компаратор последнего хоппера, всё сбрасывается в нижние хопперы, которые после этого опять лочатся (не пускают в себя айтемы из верхних), пока новые N айтемов не заполнят верхнюю трубу.
И это красивая идея, но проблема в том, что у этой штуки какие-то кривые тайминги и иногда в некоторые печки не попадают предметы. И хопперы засоряются. И гораздо проще, дешевле и лучше было бы просто сделать вагонетку сверху, которая бы останавливалась у сундука, заполнялась, а потом бы ездила по хопперам и равномерно распределяла айтемы.
Вернее нет, по-другому можно было бы сделать: анимация сдвига на вектор, которые бы легко комбинировались, но не знаю, насколько правильно такое делать, накапливающаяся ошибка округления, всё такое. Хмм, ну, или сделать комбинирование двух анимаций движения, чтобы в итоге получилась одна жирная анимация. Но как их комбинировать, если каждая из них представляет из себя start_point, end_point и duration - непонятно. Даже при одинаковых стартовых точках непонятно. А ещё я ведь хотел, чтобы анимации падения фигурки на одну клетку вниз и сдвига вправо/влево отличались.
Наверное, правильным решением будет сделать один жуткий класс, который бы включал в себя и движение по горизонтали, и по вертикали, и для обоих хранил бы ещё длительность. Задавать длительность движения по какой-то из осей как -1, если объект не двигается по этой оси. И комбинировать эти анимации, чтобы более свежая переписывала старую, но не в местах, где у свежей стоит -1 в длительности. Чего-то тоже как-то тупо. Получается, надо ещё и для движения по каждой оси ещё и то, сколько оно длится на данный момент. Лучше всё же отдельно по иксу и по игреку сделать.
Довольно полезная интересная штука, я её впихнул себе в авто-плавильню. Как обычно всё выглядит максимально уродилво: я не утруждал себя особо выбором блоков, к тому же так совпало, что прямо над печками были эти пещеры. Вроде как должно работать, по крайней мере работает, если не загружать до отказа. А загружать до отказа мне нечем.
https://youtu.be/7FieCkACm5Y
Вот это тоже отличная штука, оказывается, нестакающиеся предметы дают более сильный сигнал, так что можно их отделять от стакающихся предметов. Пока я возился с печками, оказалось, что ферма скелетов работает слишком хорошо, и через некоторое время у меня всё вокруг было замусорено бронёй и луками, так что это супер-полезно. А после сортировки весь мусор можно сбросить в лаву дроппером с помощью какой-то такой хитрой штуки https://youtu.be/E6fvNU8LRkc?t=82
И опять всё очень жутко выглядит, и я уже не буду пытаться это исправить. Отчасти это из-за того, что прямо за стеной у меня уже выкопанная полость со спавнером скелетов.
И ещё у меня заспавнились эти костяные штуки. Во время грозы молния ударила в скелета на такой лошади и на месте неё заспавнились сразу четыре всадника. Но выжило в итоге только половина коней, потому что скелеты сразу начали стрелять друг в друга. На всякий случай нейм тэгнул их. Вроде как они умеют бегать под водой, что выгодно отличает их от обычных коней. Но у них какая-то мелкая полоска здоровья. А хилятся они зельями мгновенного урона.
И ещё я долго не понимал, почему ничего не работает, потому что забыл отделить анимацию волл кика от анимации движений, которые делает игрок.
Ну, это я поправлю, я думаю.
Странно, я до этого не слышал этой песенки у него, хорошая.
Баг выше довольно легко исправился, я уже и не помню, что там конкретно было не так. Так что теперь оно по крайней мере играбельно.
Засунул упавшие блоки в красно-чёрмный tree map из координат блоков в сами блоки-сущности (до этого они просто в списке лежали), где они лежат упорядоченными сначала по игреку, а потом по иксу. Не знаю, насколько это тупое решение. После падения очередной фигурки проверяю только те ряды блоков, которые потенциально могли заполниться (те четыре, которые занимает фигурка). Убираю их, прохожусь по всем блокам выше удалённых линий и опускаю их. А потом переназначаю tree map: удаляю старые координаты, добавляю новые.
Анимации падения блоков вниз пока что нет, так что падающие блоки без спрайтов отрисовываются.
Написал в итоге кучу мусора, потому что хотел поскорее получить что-то работающее. Чуть позже придётся вычищать.
Неплохо вышло
Я вообще не понимаю, как течёт время, оно как будто бы идёт рандомными прыжками в два-три часа вперёд. Между ними всё в порядке, но это в порядке длится не то чтобы сильно долго. Так умру и не сам замечу ведь. Вообще по ощущениям я посреди одного большого ничего, и, если даже я попытаюсь сделать шаг в какую-то сторону, то это ни на что не повлияет, и я просто окажусь в той же точке, что и был.
В теории надо просто, действительно, делать то, что должно и особо не задумываться о том, какие могут быть последствия и вообще к чему это всё приведёт, но это полная противоположность тому, что вроде как следует делать? вот это всё постановка и достижение маленьких целей. Короче, at the end of the day всё это абсолютно какой-то мусор.
Добавил метод getGlobalTransform(Entity), который может для некоторой дочерней сущности выдать какой-то особый глобал трансформ (вернее дефолтный глобал трансформ с дополнительным преобразованием). Если дочерняя сущность нуждается в каком-то дополнительном преобразовании, то она должна переопределить метод needsAdditionalTransform(), чтобы он там когда-нибудь в своей жизни возвращал true. И тогда, если у родительской сущности есть это особое дополнительное преобразование для этой сущности, то оно применяется.
Убрал анимации из методов для перемещения фигурки, вынес добавление анимаций в отдельные методы, которые надо вызывать сразу после выполнения перемещения. Фигурка запоминает последнее своё движение/вращение и анимирует его при вызове соответствующего метода, то есть не надо передавать параметры анимации. Сейчас понял, что это тупо, потом уберу эту ерунду с запоминанием последнего движения.
И я в итоге умудрился всё сломать, и теперь у меня полезло куча странных багов, и самое неприятное, что я не могу толком их повторять, поэтому непонятно, были ли они и до этого, или это только сейчас всё сломалось.
Вроде что-то поправил, вроде стало получше выглядеть, а вроде и не изменилось ничего. Вообще по сути никак не продвинулся, только сделал так, как изначально стоило сделать, и вроде как не наплодил ошибок особо. И поэтому даже показать нечего, потому что всё выглядит ровно так же, как и было до этого.
>Что-то такое
Ээ, то есть я хотел сказать, что добавил анимацию пропадания блоков.
https://youtu.be/JnpQEwxO67w
А с игровым полем связаны состояние, когда фигурка падает, состояние, когда исчезают заполненные линии, и состояние, когда блоки над зачищенными линиями падают вниз.
Всё это выглядит очень так себе, и вроде как корректным решением будет реализовать у себя что-то вроде этого:
https://youtu.be/NYFeFUxU6DI
Но я так и не придумал нормально, как это всё наложить на свою штуку. (Но определённо надо избавиться от лишнего состояния для падающей фигурки, и засунуть их все в список состояний игрового поля.) И, если воспринимать этот animation graph как несколько связанных между собой состояний, между которыми можно перемещаться по определённым правилам, то не совсем понятно, как реализовать анимации, которые могут выполняться одновременно.
Я не знаю как это реализовать по-другому, кроме как запихать все анимации, которые могут выполняться одновременно, в один супервайзер объект (который я тут отметил как blend space, но это, наверное, не совсем то, по крайней мере потому, что у меня сумма анимаций невзвешенная), который бы их все одновременно вызывал, и который бы представлял из себя отдельной состояние. Но это означает в общем случае, что либо эти супервайзер объекты будут максимально раздуты, чтобы уместить в себе все анимации, которые в теории могут сочетаться друг с другом. Либо, что будут супервайзер объекты с повторяющимися анимациями.
И ещё я не могу придумать, как адекватно сделать триггер перехода между состояниями. Это должно быть какое-то условие, определённое внутри самого состояния, или переход должен триггерить кто-то снаружи? Если это кто-то снаружи, то как он может знать, в какое состояние дальше можно перейти.
А с игровым полем связаны состояние, когда фигурка падает, состояние, когда исчезают заполненные линии, и состояние, когда блоки над зачищенными линиями падают вниз.
Всё это выглядит очень так себе, и вроде как корректным решением будет реализовать у себя что-то вроде этого:
https://youtu.be/NYFeFUxU6DI
Но я так и не придумал нормально, как это всё наложить на свою штуку. (Но определённо надо избавиться от лишнего состояния для падающей фигурки, и засунуть их все в список состояний игрового поля.) И, если воспринимать этот animation graph как несколько связанных между собой состояний, между которыми можно перемещаться по определённым правилам, то не совсем понятно, как реализовать анимации, которые могут выполняться одновременно.
Я не знаю как это реализовать по-другому, кроме как запихать все анимации, которые могут выполняться одновременно, в один супервайзер объект (который я тут отметил как blend space, но это, наверное, не совсем то, по крайней мере потому, что у меня сумма анимаций невзвешенная), который бы их все одновременно вызывал, и который бы представлял из себя отдельной состояние. Но это означает в общем случае, что либо эти супервайзер объекты будут максимально раздуты, чтобы уместить в себе все анимации, которые в теории могут сочетаться друг с другом. Либо, что будут супервайзер объекты с повторяющимися анимациями.
И ещё я не могу придумать, как адекватно сделать триггер перехода между состояниями. Это должно быть какое-то условие, определённое внутри самого состояния, или переход должен триггерить кто-то снаружи? Если это кто-то снаружи, то как он может знать, в какое состояние дальше можно перейти.
Спасибо.
Яя примерно про то, что было тут >>349467 на картике. Раньше у меня анимации хранились в самих сущностях, триггерились снаружи методами, максимально абстрагированными от внутренней реализации сущностей (то есть достаточно было вызвать метод поворота для фигурки, чтобы она начала поворачиваться). Но такой подход плох тем, что у тебя нет никакого дополнительного контроля над анимациями (Если честно, я уже забыл, какой у меня был ход мыслей, и какой дополнительный контроль над анимациями я хотел получить. Ну, как минимум я хотел их прерывать, а ещё понимать, в какой момент они завершаются.)
Короче, в итоге я засунул все анимации вместе с анимируемыми объектами в один такой animation manager, единственное преимущество которого, на самом деле, это то, что он у меня позволяет подписываться на события завершения анимаций, так что я этим, во-первых, избавился от необходимости в активном ожидании того, пока закончится какая-то анимация, чтобы что-то произошло, а, во-вторых, это позволило засунуть всю игровую логику в один метод changeState(newState), который переходит к новому состоянию, попутно выполняя какие-то действия, и он как правило вызывается вот как раз после завершения всяких анимаций. Скорее всего, это тупо, потому что у меня теперь все тайминги зависят от таймингов анимаций. Но, по крайней мере, теперь я смогу значительно проще реализовать некоторые штуки, которые хотел добавить.
Я не знаю, почему это всё далось мне с таким трудом, я кучу раз много всего переписывал заново, отчасти потому что толком сам не понимал, чего я в итоге хотел. В частности я хотел сделать очень общую версию animation менеджера, который бы справлялся с какими угодно объектами и с любыми анимациями и можно было бы как угодно к нему лепить коллбеки, но в итоге отказался от этой затеи. В целом я не знаю, это было как-то совсем плохо и ужасно, как будто бы я из одной кучи мусора собрал другую, но немного другой формы, и это всё ещё накладывалось на то, что я постоянно не высыпался в последние дни из-за того, что пытался наладить режим сна и просыпался раньше, ложась при этом одинаково поздно. Короче, я просто не знаю, я потерялся тут.
И всё ещё осталась единственная вещь, которую у меня не получилось реализовать в новом сеттинге: это тупые повороты. Временно сделал так, чтобы нельзя было прервать один поворот другим, на практике такое ощущается просто ужасно. Проблема в том, что раньше у меня был прямой доступ к текущей анимации поворота во время добавления следующей, так что я мог вытащить конечный угол предыдущего поворота, его направление и делать любую странную математику совмещая эти два поворота.
Сейчас же у меня все анимации хранятся в одном списке в animation менеджере в слишком абстрактном виде, который позволяет только выполнять анимации, и, хоть я и могу выделить в этом списке именно анимацию поворота, мне придётся её кастить, чтобы получить доступ к параметрам анимации поворота, а так мне не особо хочется делать, хоть это и сработало бы, да. Я попробовал что-то сделать без этого, но у меня ничего не вышло. Когда игрок нажимает на кнопку поворота у меня есть только:
- текущий визуальный угол поворота фигурки
- текущий логический поворот фигурки (то есть то, как она повёрнута на игровом поле в терминах клеточек, на 0/90/180/270 градусов)
- то, какой надо сделать логический поворот (против часовой стрелки или по часовой)
- знание о том, есть ли у фигурки уже какая-то анимация поворота
И я не знаю, что мне с этим делать. До этого у меня было так, что последовательные повороты в одном направлении раскручивали фигурку чуть быстрее, а поворот в противоположном направлении мгновенно начинал её вращать в новом направлении. Но сейчас у меня нет знания о том, в какую сторону сейчас вертится фигурка.
Яя примерно про то, что было тут >>349467 на картике. Раньше у меня анимации хранились в самих сущностях, триггерились снаружи методами, максимально абстрагированными от внутренней реализации сущностей (то есть достаточно было вызвать метод поворота для фигурки, чтобы она начала поворачиваться). Но такой подход плох тем, что у тебя нет никакого дополнительного контроля над анимациями (Если честно, я уже забыл, какой у меня был ход мыслей, и какой дополнительный контроль над анимациями я хотел получить. Ну, как минимум я хотел их прерывать, а ещё понимать, в какой момент они завершаются.)
Короче, в итоге я засунул все анимации вместе с анимируемыми объектами в один такой animation manager, единственное преимущество которого, на самом деле, это то, что он у меня позволяет подписываться на события завершения анимаций, так что я этим, во-первых, избавился от необходимости в активном ожидании того, пока закончится какая-то анимация, чтобы что-то произошло, а, во-вторых, это позволило засунуть всю игровую логику в один метод changeState(newState), который переходит к новому состоянию, попутно выполняя какие-то действия, и он как правило вызывается вот как раз после завершения всяких анимаций. Скорее всего, это тупо, потому что у меня теперь все тайминги зависят от таймингов анимаций. Но, по крайней мере, теперь я смогу значительно проще реализовать некоторые штуки, которые хотел добавить.
Я не знаю, почему это всё далось мне с таким трудом, я кучу раз много всего переписывал заново, отчасти потому что толком сам не понимал, чего я в итоге хотел. В частности я хотел сделать очень общую версию animation менеджера, который бы справлялся с какими угодно объектами и с любыми анимациями и можно было бы как угодно к нему лепить коллбеки, но в итоге отказался от этой затеи. В целом я не знаю, это было как-то совсем плохо и ужасно, как будто бы я из одной кучи мусора собрал другую, но немного другой формы, и это всё ещё накладывалось на то, что я постоянно не высыпался в последние дни из-за того, что пытался наладить режим сна и просыпался раньше, ложась при этом одинаково поздно. Короче, я просто не знаю, я потерялся тут.
И всё ещё осталась единственная вещь, которую у меня не получилось реализовать в новом сеттинге: это тупые повороты. Временно сделал так, чтобы нельзя было прервать один поворот другим, на практике такое ощущается просто ужасно. Проблема в том, что раньше у меня был прямой доступ к текущей анимации поворота во время добавления следующей, так что я мог вытащить конечный угол предыдущего поворота, его направление и делать любую странную математику совмещая эти два поворота.
Сейчас же у меня все анимации хранятся в одном списке в animation менеджере в слишком абстрактном виде, который позволяет только выполнять анимации, и, хоть я и могу выделить в этом списке именно анимацию поворота, мне придётся её кастить, чтобы получить доступ к параметрам анимации поворота, а так мне не особо хочется делать, хоть это и сработало бы, да. Я попробовал что-то сделать без этого, но у меня ничего не вышло. Когда игрок нажимает на кнопку поворота у меня есть только:
- текущий визуальный угол поворота фигурки
- текущий логический поворот фигурки (то есть то, как она повёрнута на игровом поле в терминах клеточек, на 0/90/180/270 градусов)
- то, какой надо сделать логический поворот (против часовой стрелки или по часовой)
- знание о том, есть ли у фигурки уже какая-то анимация поворота
И я не знаю, что мне с этим делать. До этого у меня было так, что последовательные повороты в одном направлении раскручивали фигурку чуть быстрее, а поворот в противоположном направлении мгновенно начинал её вращать в новом направлении. Но сейчас у меня нет знания о том, в какую сторону сейчас вертится фигурка.
Классно. Я вроде читал у @oldnewthing, что верное средство создать у бета-тестеров впечатление о масштабности внесённых в операционную систему изменений — изменить дефолтные обои рабочего стола.
Хм, может, было бы удобно иметь в анимации метод вроде conflicts(Animation other) или tryCombine(Animation other), с которыми она могла бы самостоятельно изъявить желание заменить собой другую анимацию, возможно, учтя параметры заменяемой: например, ускоренное движение со специальным параметром initialSpeed = DONT_REALLY_CARE может взять текущую скорость объекта за начальную, а поворот — в зависимости от того, выполняется он в ту же сторону или в другую, продолжить кручение объекта по денормализованному углу или развернуться в другую сторону по кратчайшему. Эти методы внутри себя всё равно потребуют приведений вниз (но эй, чем это хуже Object.equals), зато это позволило бы хранить анимации в списке Animation, с коллбэками завершения в их полях — мотив распечатать Animation, лишить его абстрактности. Представь себя в ♂animation gym♂ на месте анимации-новичка, когда тебя подводят по очереди к остальным и спрашивают: «слился бы с ним?». Возможно, коллбэку понадобится дополнительный параметр, указывающий причину завершения анимации — естественное завершение, замена другой и т. д., от которой может зависеть логика.
Мне кажется, в Java коллбэкам не нужны пользовательские аргументы, как у тебя String, т. к. можно на месте объявить анонимный класс, реализующий CallbackHandler, который запомнит в своих полях, или зачастую вовсе как замыкания, всё, что ему нужно, с правильными типами без поехавших приведений (привидений): https://ideone.com/MLsMsb.
>иметь в анимации метод вроде conflicts(Animation other) или tryCombine(Animation other)
Даа, была такая идея ещё до того, когда я впихнул свой animation gym. Наверное, да, это лучшее решение, которое тут может быть.
>анимации в списке Animation, с коллбэками завершения в их полях
Проблема с тем, чтобы триггерить коллбэки прямо по завершению анимации в том, что коллбэк может изменять список анимаций, пока мы всё ещё итерируем по списку, занимаясь апдейтом анимаций. Сейчас у меня очень тупой обход этой проблемы: коллбэки хранятся внутри самого animation gym'а отдельно от анимаций и вызываются только после апдейта всех анимаций.
Единственное более-менее нормальное решение, о котором я тут могу думать - это сделать дополнительную буферную очередь, в которую новые анимации будут временно попадать, пока мы итерируем и апдейтим анимации. И понадобится ещё одна очередь для прерванных анимаций.
Может быть, можно сделать специальный wrapper объект вроде PostponedAction, которые бы складывались в очередь. Как бы, это вроде выглядит не очень сложно, но какое-то количество единиц времени назад, когда я занимался этим, я предпочёл забить на это. Может, сделаю потом.
>Возможно, коллбэку понадобится дополнительный параметр, указывающий причину завершения анимации — естественное завершение, замена другой и т. д., от которой может зависеть логика.
Выглядит как хорошая идея, но пока что не могу даже намеренно придумать ей применение у себя. Может, потом возникнет необходимость в таком.
>мотив распечатать Animation, лишить его абстрактности
Яя, как-то не думал о таком, но вообще, да, это имеет смысл, и это подтверждает истинность правила тридцать четыре: если какая-то девочка ещё не была раздета, то есть хорошая мотивация сделать это.
>можно на месте объявить анонимный класс, реализующий CallbackHandler, который запомнит в своих полях, или зачастую вовсе как замыкания, всё, что ему нужно
О, да, совершенно не подумал об этом, хорошая идея.
Спасибо за со веты.
В ютубовских рекомендациях появилась возможность отсортировать выдачу по популярным среди твоей истории просмотра тегам, так что теперь у меня есть возможность в любой момент открыть большую стену с вокалоидными песенками (ну, или с видео по манкрафту, которые почему-то помечены тегом Mods). И там ещё есть с десяток тегов в списке, но они иррелевантны из-за того, что с ними у меня в истории просмотра связаны по одному-два видео, так что они плохо держатся на одном месте и быстро сменяют друг друга. В какой-то момент у меня появился тег 3D Modeling из-за того, что я посмотрел видео с топ 10 самых тяжёлых 3д моделей в яндере симуляторе.
Кстати, я абсолютно пропустил всё, что связано с яндере девом, и вообще забыл думать о существовании его игры примерно с того момента, когда она взорвалась популярностью сколько-то лет назад, но внезапно наткнулся на видео с бокалом молока и со спидранами по бану в его дискорде, и после этого я пересмотрел примерно всё, что есть на ютубе, связанное с ним, осталось только обсмотреться видео с, собственно, его личного канала с апдейтами игры, потому что мне они нравятся. И тут забавно примерно всё: и его поведение, и, возможно, даже в большей степени, реакция других людей на то, что ууу, какой он плохой, жесть перверт лоликонщик неблагодарная ленивая тварь, которая не умеет кодить и ворует ассеты, и вся ситуация в целом, из которой в итоге единственным победителем выходит, конечно же, он. Жалко немного, что я опоздал, всё уже закончилось, и идёт на спад, в том числе и его earnings per month на патреоне.
Кстати, интересный вопрос, который остаётся для меня открытым, это то, действительно ли использование else-if сильно менее эффективно, чем switch? Просто, ето, конечно, весело и забавно потешаться над его стенами из else-if'ов, но вряд ли это главная причина лагов в игре даже, если он сравнивает строки, когда мог бы сделать enum'ы. Просто забавно, как очень много кто на полном серьёзе объясняет плохую производительность вот этим вот, хотя проблема наверняка в чём-то другом, но просто гораздо легче сказать
>If you just used switch case statments the framerate wouldn't drop
>Those else if blocks. I counted like 90 else ifs in the code visible in the video
>your code is not optimized! Switch cases are simpler.
>...There's a function called "switch", ya know?
сойти за умного, потешить свою самооценку. Как бы, они не обёртывают это в шутку, они действительно высказывают своё мнение, указывают на его неправоту. И это то, из чего состоят примерно все видео/комментарии всяких рандомов по сабжу: уф, какой же он лох, я-то определённо лучше него, кто угодно может сделать лучшую игру, чем он. Вот это https://youtu.be/eXWsXmVybns простто золото, мне кажется, невозможно быть более самодовольным, высокомерным, уверенным в себе, ето меня очень сильно позабавило.
К слову
https://youtu.be/dlrjDvS7wxo
>GetComponent / AddCompoent are all expensive opertaions as mentioned. Unless components have been swapped to other instance ( which also isn't the best practice ) There should be a field that holds a Component. Putting them in Update() is literally saying " I'm stupid. "
>accessing a transform like 20 times in Update is bad for performance, because there is a transform hierarchy (you have to go from the local transform of the root object all the way down to the local transform of the object you are accessing) and the longer the hierarchy is, the more it costs to access or set the transform values, such as rotation and position. A better solution: access transform.position and transform.rotation one time and store those values into variables. then operate on the variables and set it back to the transform.position and transform.rotation at the end.
выглядит как что-то значительно более конструктивное и походящее на правду.
В ютубовских рекомендациях появилась возможность отсортировать выдачу по популярным среди твоей истории просмотра тегам, так что теперь у меня есть возможность в любой момент открыть большую стену с вокалоидными песенками (ну, или с видео по манкрафту, которые почему-то помечены тегом Mods). И там ещё есть с десяток тегов в списке, но они иррелевантны из-за того, что с ними у меня в истории просмотра связаны по одному-два видео, так что они плохо держатся на одном месте и быстро сменяют друг друга. В какой-то момент у меня появился тег 3D Modeling из-за того, что я посмотрел видео с топ 10 самых тяжёлых 3д моделей в яндере симуляторе.
Кстати, я абсолютно пропустил всё, что связано с яндере девом, и вообще забыл думать о существовании его игры примерно с того момента, когда она взорвалась популярностью сколько-то лет назад, но внезапно наткнулся на видео с бокалом молока и со спидранами по бану в его дискорде, и после этого я пересмотрел примерно всё, что есть на ютубе, связанное с ним, осталось только обсмотреться видео с, собственно, его личного канала с апдейтами игры, потому что мне они нравятся. И тут забавно примерно всё: и его поведение, и, возможно, даже в большей степени, реакция других людей на то, что ууу, какой он плохой, жесть перверт лоликонщик неблагодарная ленивая тварь, которая не умеет кодить и ворует ассеты, и вся ситуация в целом, из которой в итоге единственным победителем выходит, конечно же, он. Жалко немного, что я опоздал, всё уже закончилось, и идёт на спад, в том числе и его earnings per month на патреоне.
Кстати, интересный вопрос, который остаётся для меня открытым, это то, действительно ли использование else-if сильно менее эффективно, чем switch? Просто, ето, конечно, весело и забавно потешаться над его стенами из else-if'ов, но вряд ли это главная причина лагов в игре даже, если он сравнивает строки, когда мог бы сделать enum'ы. Просто забавно, как очень много кто на полном серьёзе объясняет плохую производительность вот этим вот, хотя проблема наверняка в чём-то другом, но просто гораздо легче сказать
>If you just used switch case statments the framerate wouldn't drop
>Those else if blocks. I counted like 90 else ifs in the code visible in the video
>your code is not optimized! Switch cases are simpler.
>...There's a function called "switch", ya know?
сойти за умного, потешить свою самооценку. Как бы, они не обёртывают это в шутку, они действительно высказывают своё мнение, указывают на его неправоту. И это то, из чего состоят примерно все видео/комментарии всяких рандомов по сабжу: уф, какой же он лох, я-то определённо лучше него, кто угодно может сделать лучшую игру, чем он. Вот это https://youtu.be/eXWsXmVybns простто золото, мне кажется, невозможно быть более самодовольным, высокомерным, уверенным в себе, ето меня очень сильно позабавило.
К слову
https://youtu.be/dlrjDvS7wxo
>GetComponent / AddCompoent are all expensive opertaions as mentioned. Unless components have been swapped to other instance ( which also isn't the best practice ) There should be a field that holds a Component. Putting them in Update() is literally saying " I'm stupid. "
>accessing a transform like 20 times in Update is bad for performance, because there is a transform hierarchy (you have to go from the local transform of the root object all the way down to the local transform of the object you are accessing) and the longer the hierarchy is, the more it costs to access or set the transform values, such as rotation and position. A better solution: access transform.position and transform.rotation one time and store those values into variables. then operate on the variables and set it back to the transform.position and transform.rotation at the end.
выглядит как что-то значительно более конструктивное и походящее на правду.
>Просто забавно, как очень много кто на полном серьёзе объясняет плохую производительность вот этим вот
Искать настоящую проблему — это работа, всё равно что просить критиков твоего рисунка нарисовать лучше, зная, что они заведомо не будут этого делать. Тем не менее, такие простые вещи выдают нуба без фундаментальной подготовки, который тем более срежется на чём-то сложнее и в целом. В лучшем случае этот нуб искренне не видит своей некомпетентности, но несоразмерное продвижение в интернете позволяет предположить худший вариант — нуб осознанно является подпрыгивающим кабанчиком с позицией «fake it until you make it», что легко может злить людей, пытающихся вкладывать душу в предметную область. Или у тебя от Щенка не было агрессии какой-то и зубы не скрипели?
Давеча прочитал на ёбаном.ит статью про мошенника-инфоцыгана с ником Winderton. Он, не зная программирования, стриг деньги и уважение с нешкольников за «менторинг», ведение стримов и создание видосиков по программированию, где он либо льёт воду, либо перепечатывает чужой код (вероятно, с другого монитора) и при малейшем отступлении от темы несёт такую дичь, что уши вянут. Можешь даже про свою Java посмотреть, только под стол не укатись: https://www.youtube.com/watch?v=8kmXmGvc9SA. YandereDev не очевидный мошенник, но тоже ворует материалы, стрижёт деньги со школьников и имеет проблемы с базовой компетентностью в занятии, о котором заявляет — так ли они отличаются?
Фундаментально же все три приведённых примера — паразиты, в здоровом сообществе таких необходимо уничтожать, и что одним хейтерство, то другим иммунная защита.
switch позволяет специфичные оптимизации. Цепочка else-if эквивалентна ему, если операция получения и сравнения значений не имеют побочных эффектов, но компиляторы тупые и ввиду этого «если» легко могут перестраховаться и не применить к ней switch-специфичные оптимизации. Например, в Free Pascal есть девяносто восемь оптимизаций switch (впрочем, общепринятых): https://wiki.freepascal.org/Case_Compiler_Optimization, и я даже чуть-чуть прикладывал к этому руку: https://bugs.freepascal.org/view.php?id=31536.
>you have to go from the local transform of the root object all the way down to the local transform of the object you are accessing
Вообще-то с 99% вероятностью там что-то вроде
private Transform local, global;
private bool globalDirty;
public Transform Local {
get { return local; }
set { if (value != local) { local = value; globalDirty = true; makeChildsDirtyToo(); } }
}
public Transform Global {
get {
if (globalDirty) {
global = parent.Global * local;
globalDirty = false;
}
return global;
}
}
Всё равно 0 раз вызвать ‘get’ всегда будет быстрее, чем 1000 раз вызвать ‘uberFastGet’, я лишь хочу сказать, что эта претензия одного уровня оправданности с претензией к цепочке if-else.
>Просто забавно, как очень много кто на полном серьёзе объясняет плохую производительность вот этим вот
Искать настоящую проблему — это работа, всё равно что просить критиков твоего рисунка нарисовать лучше, зная, что они заведомо не будут этого делать. Тем не менее, такие простые вещи выдают нуба без фундаментальной подготовки, который тем более срежется на чём-то сложнее и в целом. В лучшем случае этот нуб искренне не видит своей некомпетентности, но несоразмерное продвижение в интернете позволяет предположить худший вариант — нуб осознанно является подпрыгивающим кабанчиком с позицией «fake it until you make it», что легко может злить людей, пытающихся вкладывать душу в предметную область. Или у тебя от Щенка не было агрессии какой-то и зубы не скрипели?
Давеча прочитал на ёбаном.ит статью про мошенника-инфоцыгана с ником Winderton. Он, не зная программирования, стриг деньги и уважение с нешкольников за «менторинг», ведение стримов и создание видосиков по программированию, где он либо льёт воду, либо перепечатывает чужой код (вероятно, с другого монитора) и при малейшем отступлении от темы несёт такую дичь, что уши вянут. Можешь даже про свою Java посмотреть, только под стол не укатись: https://www.youtube.com/watch?v=8kmXmGvc9SA. YandereDev не очевидный мошенник, но тоже ворует материалы, стрижёт деньги со школьников и имеет проблемы с базовой компетентностью в занятии, о котором заявляет — так ли они отличаются?
Фундаментально же все три приведённых примера — паразиты, в здоровом сообществе таких необходимо уничтожать, и что одним хейтерство, то другим иммунная защита.
switch позволяет специфичные оптимизации. Цепочка else-if эквивалентна ему, если операция получения и сравнения значений не имеют побочных эффектов, но компиляторы тупые и ввиду этого «если» легко могут перестраховаться и не применить к ней switch-специфичные оптимизации. Например, в Free Pascal есть девяносто восемь оптимизаций switch (впрочем, общепринятых): https://wiki.freepascal.org/Case_Compiler_Optimization, и я даже чуть-чуть прикладывал к этому руку: https://bugs.freepascal.org/view.php?id=31536.
>you have to go from the local transform of the root object all the way down to the local transform of the object you are accessing
Вообще-то с 99% вероятностью там что-то вроде
private Transform local, global;
private bool globalDirty;
public Transform Local {
get { return local; }
set { if (value != local) { local = value; globalDirty = true; makeChildsDirtyToo(); } }
}
public Transform Global {
get {
if (globalDirty) {
global = parent.Global * local;
globalDirty = false;
}
return global;
}
}
Всё равно 0 раз вызвать ‘get’ всегда будет быстрее, чем 1000 раз вызвать ‘uberFastGet’, я лишь хочу сказать, что эта претензия одного уровня оправданности с претензией к цепочке if-else.
>Или у тебя от Щенка не было агрессии какой-то и зубы не скрипели?
Было такое. Из-за этого в основном пришлось скрыть её тред в какой-то момент для сохранения своего здоровья. Впрочем, эти чувства имеют несколько иную природу я завистливое чмо с насекомыми в голове То же самое можно ведь сказать про тех, кто от балды называет причину лагов в игре: многие из них точно в такой же манере некомпетентны и, возможно, имеют ещё меньшие навыки в программировании, чтобы высказывать претензии на этот счёт. Представь, что ты бы играл в какой-нибудь DCSS, а я бы подсел рядом и каждый твой фейл комментировал словами вроде "уф, ну, неудивительно, что ты всё заруинил, надо было всего лишь сделать X", при том, что сам я в игре ничего не смыслю и никогда в неё не играл. Я вижу примерно так всю диванную аналитику, связанную с кодом яндере дева, в которой есть хотя бы малая толика серьёзности.
>YandereDev не очевидный мошенник, но тоже ворует материалы, стрижёт деньги со школьников и имеет проблемы с базовой компетентностью в занятии
Да, всё так, я не пытался его оправдать. И хейт, и лулзы абсолютно заслуженные.
>Цепочка else-if эквивалентна ему, если операция получения и сравнения значений не имеют побочных эффектов
Мм, да, как-то не подумал об этом, кстати.
> Например, в Free Pascal есть девяносто восемь оптимизаций switch (впрочем, общепринятых)
О, по нятно, прикольно.
>Вообще-то с 99% вероятностью там что-то вроде
Дааа, я тоже думал о том, что как-то странно то, что он объясняет дороговизну операции тем, что там просто надо по иерархии трансформов вниз спускаться.
Лесные ягоды, кактусы, бамбук и водоросли не стал пока что высаживать, мне пока что хватит этого. Та автоматическая ферма тыкв, что у меня была, сломалась из-за того, что я случайно сломал блок и вода смыла рельсы под тыквами, и мне было слишком лень всё восстанавливать. Я думаю, её надо восстановить, но под землёй, рядом с фермой скелетов, и в чуть больших масштабах. И ещё я хотел туда же, под землю, запихнуть автоматическую ферму курятины и полуавтоматическую ферму коров, но это всё чуть потом.
Пока что, я думаю, можно начать идти в сторону того, чтобы утащить из деревни нескольких жителей, сделать ферму жителей, и дальше начать с ними торговать (собственно, за этим я и делал фермы, которые упоминал выше) и, возможно, сделать ферму железа.
Кстати, вот этот массив из печек работает просто отлично: складываешь всё в сундук, и оно само довольно быстро переплавляется. На броню так и не вышло выбить нормальные энчантменты, на лук тоже никак не выходит выбить бесконечность (хотя в этом и нет сильной необходимости, стрел у меня всегда достаточно, но одного стака стрел обычно не хватает, а два уже занимают слишком много место в инвентаре), но хотя бы есть power 5, огонь и анбрейкинг (и punch 1, который больше мешает). И на меч тоже постоянно выпадают мусорные зачарования.
Алсо нашёл ещё одну заброшенную шахту, в которой, в отличие от всех других, что мне попадались, был спавнер ядовитых пауков. Можно сделать ферму ниток. Достаточно будет поставить магма-блок и водой направить пауков к нему.
Я не понимаю, почему кабанчики злят задротов. Это же два разных варианта занятости.
>анимации в списке Animation, с коллбэками завершения в их полях
>Может быть, можно сделать специальный wrapper объект вроде PostponedAction, которые бы складывались в очередь.
Сделал так. Вообще опять переделал animation gym, теперь меня он более-менее устраивает, до этого там был какой-то ужас написан, боюсь вспоминать. Теперь вот так:
https://youtu.be/ksW7SuH6IAs
- Animation<AnimatedObject> - абстрактная иммутабельная штука, которую можно применить к объекту заданного типа с помощью perform(object, currentDuration, interpolation).
- AnimationWrapper<AnimatedObject> (я не придумал более нормального названия) - это wrapper для анимируемого объекта и анимации с ним связанной. Содержит в себе текущую длительность, которая апдейтится вызовом tick(). По завершению делает коллбэки с указанием причины (просто закончилась, закончилась путём применения силы, была прервана, была прервана другой анимацией).
- AnimatedObject<T, K extends Enum<K>>, где T - тип объекта, который анимируется, K - перечисление возможных видов анимаций. Хранит в себе объект и все незаконченные AnimationWrapper'ы. Можно добавлять новые анимации, прерывать старые, подписываться на уведомления о завершении анимаций etc. Сюда запихнул ту штуку с PostponedAction, чтобы не появлялось concurrent modification exception, когда коллбэки добавляют новые анимации или делают что-то со старыми.
- AnimationGym - сборище всех анимаций. Пока что остановился на том, что храню анимации для каждого вида сущностей (пока что это только фигурка и упавшие блоки) в хеш таблицах из AnimatedObject'ов. И пока что для каждого вида сущности есть отдельный набор методов для добавления/прерывания/доставания анимаций (ну, там есть для каждого действия генерик реализация, и конкретные методы просто вызывают её). И не знаю, как сделать так, чтобы избавиться от отдельного набора методов взаимодействия с анимациями для каждой сущности. Ну, вернее, наверное, знаю, но ещё не пробовал переделывать, потому что думал, что придумаю что-нибудь получше, что бы не включало в себя странные касты.
Внешне ничего не изменилось я надеюсь на это, я мог наплодить багов
>анимации в списке Animation, с коллбэками завершения в их полях
>Может быть, можно сделать специальный wrapper объект вроде PostponedAction, которые бы складывались в очередь.
Сделал так. Вообще опять переделал animation gym, теперь меня он более-менее устраивает, до этого там был какой-то ужас написан, боюсь вспоминать. Теперь вот так:
https://youtu.be/ksW7SuH6IAs
- Animation<AnimatedObject> - абстрактная иммутабельная штука, которую можно применить к объекту заданного типа с помощью perform(object, currentDuration, interpolation).
- AnimationWrapper<AnimatedObject> (я не придумал более нормального названия) - это wrapper для анимируемого объекта и анимации с ним связанной. Содержит в себе текущую длительность, которая апдейтится вызовом tick(). По завершению делает коллбэки с указанием причины (просто закончилась, закончилась путём применения силы, была прервана, была прервана другой анимацией).
- AnimatedObject<T, K extends Enum<K>>, где T - тип объекта, который анимируется, K - перечисление возможных видов анимаций. Хранит в себе объект и все незаконченные AnimationWrapper'ы. Можно добавлять новые анимации, прерывать старые, подписываться на уведомления о завершении анимаций etc. Сюда запихнул ту штуку с PostponedAction, чтобы не появлялось concurrent modification exception, когда коллбэки добавляют новые анимации или делают что-то со старыми.
- AnimationGym - сборище всех анимаций. Пока что остановился на том, что храню анимации для каждого вида сущностей (пока что это только фигурка и упавшие блоки) в хеш таблицах из AnimatedObject'ов. И пока что для каждого вида сущности есть отдельный набор методов для добавления/прерывания/доставания анимаций (ну, там есть для каждого действия генерик реализация, и конкретные методы просто вызывают её). И не знаю, как сделать так, чтобы избавиться от отдельного набора методов взаимодействия с анимациями для каждой сущности. Ну, вернее, наверное, знаю, но ещё не пробовал переделывать, потому что думал, что придумаю что-нибудь получше, что бы не включало в себя странные касты.
Внешне ничего не изменилось я надеюсь на это, я мог наплодить багов
Думаю, его злит(злит ли) не кабанизм, а неприемлемое поведение и то, что они загрезняют комьюнити своим присутствием.
Бля, заткнитесь оба нахуй. Не джуны, а разновидность кабанчиков, которая пользуется несовершенством общества, чтобы ходить по головам, имея реальные знания хлебушка. Будучи деструктивным для группы, это поведение поощряется индивидуальным отбором (отсюда быдло очень уважает изворотливость и умение наёбывать), поэтому требует пресечения извне. Я не верю, что это могло быть неочевидным из контекста, скорее кто-то не дочитал до третьего абзаца, а сразу стриггернулся на слово «Щенок» в первом. Ты >>351197, наверное, не читал обе истории, чтобы сложить 2 и 2:
— Как она использовала пизду вместо резюме.
>«а можно я завтра приеду и начну уже что-то делать, вы мне так понравились?» — «л... ладно, приезжай».
— Как она обшмонала и выгнала кого-то из какой-то шараги за то, что он пронёс телефон. На моей памяти люди и даже сельди входят в положение.
>Ахует, ваша пизда тупая выгнала моего корефана с экзамена и теперь он скорее всего не поступит в Казанскую Школу 21. Спасибо мразь. (...) Пиздец, я две недели готовился, ночи не спал, а пизда просто на поезде прокатилась.
Так это капитализм, кто дороже себя продал при всех своих потроха: резюме, вагина, навыки, то победил на рыночке. В чем проблема, ты не можешь в продажу себя?
Если ей платят, значит она или приносит пользу бизнесу или сосет кому-то в системе рынка
Ты серьезно приводишь историю, где она выгнала какого-то лоха, который пронес мобильный телефон у себя в анусе, хотя это запрещено, как пример ее...чего?
https://youtu.be/zrdSQrxRKgw
https://youtu.be/ZysJs-P0kNE
Ии жесть это был ужасный опыт. Начиная с того, что по аду невозможно передвигаться, и заканчивая тем, что постоянно где-то рядом издают жуткие звуки вот эти большие белые летающие осьминожки (я даже не уверен, что наличие звуков сигнализирует о том, что рядом есть хотя бы один гаст, такое ощущение, что рядом с тобой всегда крутится по крайней мере один, но его никогда не видно).
В этот раз не стал повторять предыдущие ошибки, и оставил алмазный эквипмент дома. Собрал пару блейз палок, в сундуках нашёл жесть шесть алмазов и два седла, нашёл бородавки, заскриншотил координаты и поскорее сбежал оттуда.
Кстати, я не упоминал, но строить портал в ад на открытом месте, где ночью могут спавниться враждебные мобы - это худшая идея. Как оказалось, они могут телепортироваться через него в ад, и там оставаться сколько угодно времени, так что когда я определённое время назад зашёл в ад, меня встретила орда монстров, включая пару криперов, которые почти успели взорваться мне прямо в лицо, как только я телепортировался в ад. Так что надо будет потом запихнуть портал в ад в какое-то закрытое от мобов место.
https://youtu.be/Q2o4v8iuNvI
Играл в это ещё немного: хотел воспользоваться выбитым магма кремом, чтобы сделать зелье огнестойкости и выбить немного блейз палок. И я почувствовал себя достаточно уверенным, чтобы пойти в ад в своей модной броне и со всем модным эквипментом. Всё поначалу шло хорошо, блейзы не наносили мне абсолютно никакого урона (только в мили с очень маленьким радиусом, но они к игроку сами не лезут, так что они выглядели супер pathetic). И потом мне захотелось побегать и поискать ещё магма кубов, чтобы потом сделать ещё больше зелий огнестойкости, но внезапно упал в лаву. И действие зелья как раз закончилось примерно в этот момент. На самом деле, не так жалко эквипмент, сколько те блейз палки, которые я собирал, а ещё внезапно выпавшую голову визер скелета.
Короче, я так понял, в аду без зелья огнестойкости и без ботинок на смягчение падения вообще нечего делать: один неаккуратный шаг и ты летишь в пропасть. В лучшем случае на дне ничего не будет, в худшем - ты падаешь в лаву и все вещи сгорают.
https://youtu.be/LNhsp5Pv1e4
Думал, может, опять начать бегать, но, видимо, пока что всё ещё слишком холодно, чтобы даже просто находиться на улице. Да и я не уверен, что сейчас и полминуты пробегу без остановки, потому что вот даже сейчас, когда вынужденно пришлось пройтись всего лишь километров 5, очень сильно устал и замёрз.
Состояния игры теперь добавляются в очередь из переходов из одного состояния в другое, и при этом у них чётко определено, из какого в какое состояние можно переходить. Это избавило меня от некоторых ошибок.
И ещё добавил комбинации анимаций. Вернее они не комбинируются, а скорее, когда добавляется новая анимация поверх старой, то у старой есть шанс повлиять на новую. Комбинируются не только повороты, но и движения фигурки вниз с разной скоростью (до этого я вручную отменял предыдущую анимацию движения вниз и заменял на ускоренную, и это было не очень хорошим решением, теперь это неявно делается на уровне generic wrapper'а для объекта и его анимаций).
И ещё поправил какое-то количество багов разной сложности. НО остался один, который возникает стабильно, и я толком не могу его повторить: почему-то иногда даже, если кнопка ускорения движения вниз отпущена, фигурка спавнится уже ускоренной, и я никак не могу повлиять на её скорость (вот на видео пятая упавшая фигурка (синяя палка) заспавнилась ускоренной). Понятно, что проблемы с управлением, но пока что я так и не понял, что не так.
И на видео я абсолютно не смог из себя ничего изобразить, тотальное унижение пока что сломанным рандомом и багами.
>тотальное унижение пока что сломанным рандомом и багами
Я посчитал, что то, что я не умею играть, настолько тут не играет роли, что скорее упомянул среди причин плохой игры некие баги, которых на видео тут одна штука видна, и проявился он от силы два раза, ускорив мне падение фигурки. И в оба раза я удачно поставил фигурку.
делал это через немного кривоватое приложение glyphtracer от какого-то рандома https://launchpad.net/glyphtracer, которое векторизировало изображение (тут надо отметить, что если оно плохо векторизируется, то имеет смысл увеличить разрешение исходного изображения, а если выдаёю ошибку о переполнении какого-то списка, то придётся создать несколько фонтов и потом смерджить их), и потом немного фиксил в fontforge
написание собаки (которая at sign), амперсанда и знака вопроса честно украл со своей клавиатуры, потому что понравилось, как они на ней нарисованы
Одного из жителей пришлось прирезать, потому что тот никак не хотел менять свою профессию на фермера. Причём я так и не понял, когда он успел найти себе профессию, пока я его тащил до дома. Видимо, в какой-то момент я протащил его рядом со стоун-каттером. Но даже, когда я убрал все стоун-каттеры в округе, то он всё равно не поменял профессию.
Пришлось притащить нового жителя. Не знаю, как он умудрился выжить при перевозке.
И у меня почему-то пока что так и не получилось запихнуть двух жителей вот в коробку рядом с жителем-фермером https://youtu.be/VTseLpa9Iyc. Они у меня почему-то на утро постоянно сбегают из неё. Может, я как-то кровати неправильно поставил, не знаюю.
https://youtu.be/qtuX4cHk-vE
Текст статичный пока что.
https://youtu.be/Ngj9bXui9EQ
>>351960
>почему-то иногда даже, если кнопка ускорения движения вниз отпущена, фигурка спавнится уже ускоренной, и я никак не могу повлиять на её скорость
И починил сложение анимаций поворотов: до этого у меня там было что-то совсем странное и не особо работающее.
https://youtu.be/0RU_05zpETo
>починил сложение анимаций поворотов
А, нет, не починил, всё равно не работает так, как я хотел.
пока что работает не очень корректно
Можно превращать фигурку в «призрака», если в ходе поворота она пересекает другие, хотя я уже даже не уверен, что это сколь-нибудь лучше того, что есть.
Не читай:
На гифке заметно, особенно на крайнем нижне-левом блоке вращаемой фигурки, что интерполяция между двумя положениями в стиле «интерполяция смещения + интерполяция поворота» ведёт себя не совсем естественно: блок словно смещается левее идеального промежуточного положения.
Пивот этой фигурки находится в центре углового блока — видно, как в ходе анимации он описывает прямую. Если бы он находился в центре нижне-левого блока, гифка выглядела бы правильно — но только потому, что тогда смещение в обоих положениях было бы одинаковым, т. е. фактически не интерполировалось. Та же проблема вылезла бы в других конфигурациях.
По слухам, «красивый» вариант с интерполяцией смещения по дуге получается автоматически, если трансформация выражена двойным кватернионом вместо пары смещение+поворот. Но у меня до сих пор не дошли руки разобраться, что это вообще такое, поэтому тс-с, я ничего не говорил.
Аа, ты вот такое имел в виду. Понятно.
Черновой вариант этого. Иногда срабатывает, а иногда - нет. Точно не срабатывает, когда фигурка дополнительно сдвигается, чтобы она умещалась внутри рамок поля, но это легко поправить: я просто почему-то использовал локальные трансформы вместо глобальных (дополнительный сдвиг у меня в глобальном трансформе делается). Почему в остальных случаях не срабатывает, пока что не знаю (хотя, может, это просто из-за того, что я только смотрю, пересекает ли фигурка упавшие блоки на 50% поворота, и как раз на 50% она ничего не пересекает).
Пока что всё работает тупым перебором пересечений отрезков фигурки со всеми отрезками всех упавших блоков, но точно стоит ограничиться только теми, которые лежат рядом, а, возможно, ещё лучше хранить где-нибудь вот ету поверхность верхнюю из упавших блоков (я не знаю, как объяснить, ну, вот это то, что сверху) и проверять пересечения только с её отрезками.
Мышка окончательно погибла: делает дабл клик, когда не надо, если удерживать нажатие на клавишу, то оно не удерживается и прерывается.
Да, тоже была такая идея. Но тут не особо принципиально, как оно рисует: мне просто нужна была вот эта рисовательная штука для тестирования другой штуки, которую буду делать потом.
Неплохо вышло
Я породил монстра...
>>354236
Для того, чтобы не перебирать все отрезки, можно использовать ГИПЕРПРОСТРАНСТВЕННЫЕ ДЕРЕВЬЯ: рекурсивно разбивать сцену на половинки, описывать вокруг них объёмы и затем проверять первым делом пересечения с этими объёмами (https://2ch.hk/dr/src/17085/14929361443541.gif (М)).
Наивная реализация может выглядеть так: https://ideone.com/or6C3W (см. картинку). Дерево перестраивается вручную, вызовом .rebuild().
Я делал автоматическое перестраивание узла при накоплении порогового объёма искажений: https://2ch.hk/dr/src/17085/14929361443350.gif (М) (искажённость отражается красной полоской вверху узла), но это
— вдвое сложнее
— вводит стрёмную эвристику
— как-то омежно, сравни
>П-привет, я-я добавлю в тебя объект мм... ты могло бы перестроиться, кхм, пук, ну или если не хочешь, то ладно...
с альфачёвским
>Сосочка-деревце, почему медленное, сейчас будешь быстрое. Давай не стесняйся, тридцать микросекундочек на перестроиться и жду для выполнения запросов. MIN_LEAF_NODE_SIZE — тебе.
А сейчас посмотрел свежим взглядом и начал подозревать, что нужно было всего лишь модифицировать для этой цели любое самобалансирующееся дерево: хранить в каждом узле описанный объём поддерева, который при поворотах/добавлениях/удалениях перерассчитывается как this.bb = AABB.bound(left.bb, right.bb) (и при необходимости это пропагейтится вверх, как, впрочем, и сами повороты) и ВСЁ, а все эти автоматические или ручные перестраивания были принципиально неверной идеей, всё равно что вместо TreeMap постоянно АВТОМАТИЧЕСКИ ИЛИ ВРУЧНУЮ пересортировывать массив.
За десять секунд ничего не понял, но при кольно. Я чуть позже почитаю, когда с тем, что сейчас делаю, расправлюсь, я не умею в мультитаскинг.
>>354855
Не знаю.
Is this your tool for planning military operations and establishing zones of influence? So pity to see your pacifist belief system being left long in the past.
Понятно, прикольно. Ненавижу этот тупой и-дэ-е-ван из-за того, что он беспричинно делает перенос строк, зачем это вообще нужно, это же не вим какое-то, где это ещё можно понять.
>public double get(int cid)
Сначала подумал, что как-то не очень выглядит, но потом передумал.
>Пересекаются ли отрезки a0–b0 и a1–b1.
>https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect
Не знаю, что не так с этим сайтом, но я не могу отделаться от того, что непроизвольно мой внутренний голос приобретает индийский акцент, когда читаю текст. Кстати, а не будет ли немного лучше вытащить случай с тем, что конец одного отрезка принадлежит продолжению другого из общего случая? Ну, то есть просто заменить "o1 != o2 && o3 != o4" на "o1 o2 < 0 && o3 o4 < 0"? Чтобы обработать этот случай не кросс-продуктом, а просто сравнениями координат.
>public static AABB bound(int count, Function<Integer, AABB> ithBB)
>Vec2.Vec2(x, y)
>Node без конструктора с ручной расстановкой значений полей
страшилка на ночь
Ну а так да, по нятно, как оно там делает разбиения и описывает объёмы, прикольно. Ну да, с самобалансирующимися деревьями было бы прикольнее, да.
>public static AABB bound(int count, Function<Integer, AABB> ithBB)
>страшилка на ночь
Да ты охуел! Я специально не убирал этот код, несмотря на то, что он не используется, потому что это же шедевр, я гений нахуй.
Более идиоматично сделать это итератором, но они вроде как не умеют rewind, а что если алгоритму понадобится больше 1 прохода или вообще случайный доступ, м, м-мм??? Например, так будет с поиском минимальной сферы, описанной вокруг N других.
— Тебе приятно будет при интуитивном сходстве видеть, как AABB.bound работает с итератором, а Sphere.bound — с функцией int->Sphere?
— Или ты предпочитаешь складывать всё во временные списки, хотя, уж не знаю, плодит это мусор в куче или умная Java всё красиво разруливает и выделяет на стеке, но ведь это банально даже не удобнее?
Бонус: https://ideone.com/8iKwBz.
Переделал на IntFunction, чтобы избавиться от боксинга; я не знал, что она существует, и при этом не хотел вводить свой интерфейс. Или ты с самого начала это и имел в виду?.. (T︹T)
Остальное — осознанные страшилки, но:
Node — внутренний класс, его не предполагается выдавать пользователю. Может, ещё через геттеры и сеттеры с ним работать?
К тому же возможна следующая оптимизация. Вспомним, что делает .rebuild():
root.collapse();
root.divide();
То есть мы сплющиваем дерево, передавая объекты корню и выбрасывая промежуточные узлы, а затем переразбиваем, выделяя новые. Но можно было изначально не выбрасывать их, а сложить в связный список — даже не нужно вводить специальное поле под next, можно переиспользовать left. Затем в ходе divide мы, пока возможно, не new'аем узлы, а достаём из этого списка и используем снова. Всё ещё хочешь присваивать поля в конструкторе вместо того, чтобы переиспользовать присваивания без различия между new'нутым и достанным из жопы Node'ами? Должен ли этот трудяга, выполняющий всю чёрную работу, этот перемазанный в угле шахтёр, заступать на смену в галстуке?
>Vec2.Vec2(x, y)
Я не знаю, не возникнет ли конфликтов, когда в области видимости есть и тип, и метод, названные одинаково, но если нет, то это позволит import static'нуть этот метод и создавать объект просто как Vec2(), без уродского new и без второго идентификатора, который нужно держать в голове. new для иммутабельных объектов — это ересь. Вдруг я захочу оптимизировать потребление памяти и ввести кэш, который будет возвращать существующий вектор вместо создания нового, или аналог того, что сделано для Integer с ‒127..127. Метод с именем Vec2.make или Vec2.of нельзя import static'нуть. Можно сделать Vec2.vec2, но тогда нужно будет, как частный случай второго идентификатора, постоянно вспоминать, почему в одном месте большие буквы, а в другом — маленькие: Vec2 myVec = vec2(1, 1.5).
>Чтобы обработать этот случай не кросс-продуктом, а просто сравнениями координат.
Но как вы распознаете этот случай без кросс-морепродуктов — иными словами, как вы собрались узнать истинность выражения o1*o2 < 0 && o3*o4 < 0, не имея на руках o1, o2, o3, o4? Можно воспользоваться читерскими свойствами умножений и переместить проверки на == 0 после вычисления каждого кросса:
o1 = orientation(...)
if (o1 == 0 && ...) ...
o2 = orientation(...)
if (o2 == 0 && ...) ...
Но, во-первых, чтобы воспользоваться этой «оптимизацией», придётся не только постоянно искать пересечения отрезков с 3 коллинеарными точками (часто таким занимаетесь, с повёрнутыми-то фигурками?), но ещё и учитывать порядок проверок, чтобы передавать точки, которые наиболее вероятно лежат на одной прямой, в нужных местах (не много пользы будет от шортката после вычисления o3-o4, верно?), во-вторых, это с высокой вероятностью окажется вовсе контрпродуктивным: если вы вычислили значение и тут же его используете, вы рискуете заплатить больше, чем обычно, за латентность операций, которые в ходе вычисления были задействованы. Например, если латентность умножения — 5 тактов, а его «reciprocal throughput» (число тактов, через которое процессор начнёт исполнять следующую независимую инструкцию) — 2, то если результат умножения понадобится вотпрямщас, вы рискуете прождать лишние 3 такта (или все 33, если исходные числа ожидались из памяти, или сами были результатом какого-нибудь там деления), в которые процессор мог бы исполнять следующую инструкцию — словом, начать вычислять o2, пока o1 ещё в процессе, а не ждать окончания вычисления o1, чтобы решить, шорткатиться на ту несчастную ветку или нет.
>мой внутренний голос приобретает индийский акцент, когда читаю текст
...И тут я впервые увидел вашу закоммиченную реализацию segmentsIntersect. Как у вас только язык поднимается. Снимайте штаны. ;P
>Сначала подумал, что как-то не очень выглядит, но потом передумал.
Это нужно только для выбора оси разбиения, но в оригинале у меня вектор реализован, наоборот, как
data: array[0 .. 1] of float;
property x: float read data[0] write data[0];
property y: float read data[1] write data[1];
и get(cid) является естественным способом доступа к компонентам.
Про самобалансирующиеся деревья я сказал не подумав и это может в итоге оказаться абсолютно несостоятельной идеей, как минимум неочевидно, какой порядок ввести в пространстве:
— Пусть размер блока — 1 метр строительный тетрис. Округлим координаты до метров или дециметров, сплющим этой вот нумерацией пар Кантора и упорядочим соответственно полученным числам. Но тем самым объекты будут упорядочены «кругами» (ромбами), и описанные объёмы на всех уровнях, кроме, может, парочки нижних, окажутся описанными вокруг объёма, сравнимого с объёмом всей сцены, и т. о. бесполезными.
— Пусть сцена гарантированно находится в квадрате 100м×100м и нам нужно сравнить 2 точки, A и B. Разделим сцену вертикально, на левую и правую половинки. Если точки находятся в разных половинках, то та, что в правой — больше. Иначе возьмём половинку, в которой они находятся, и разделим горизонтально, на верхнюю и нижнюю. Если точки в разных, то та, что в верхней — больше. Иначе возьмём половинку (уже четвертинку всего) и разделим вертикально... и так далее. Так результат должен быть лучше: мы вроде как лексикографически упорядочили точки по их пути в квадродереве, поэтому близкие будут обычно близко, но при этом, в отличие от ванильного квадродерева, наша иерархия делит детей не по половинкам (четвертинкам) родительского объёма, а как угодно. Но мне пока лень это проверять, и это может оказаться-таки не лучше этого самого квадродерева.
P. S.
>страшилка на ночь
>04:33:13
>public static AABB bound(int count, Function<Integer, AABB> ithBB)
>страшилка на ночь
Да ты охуел! Я специально не убирал этот код, несмотря на то, что он не используется, потому что это же шедевр, я гений нахуй.
Более идиоматично сделать это итератором, но они вроде как не умеют rewind, а что если алгоритму понадобится больше 1 прохода или вообще случайный доступ, м, м-мм??? Например, так будет с поиском минимальной сферы, описанной вокруг N других.
— Тебе приятно будет при интуитивном сходстве видеть, как AABB.bound работает с итератором, а Sphere.bound — с функцией int->Sphere?
— Или ты предпочитаешь складывать всё во временные списки, хотя, уж не знаю, плодит это мусор в куче или умная Java всё красиво разруливает и выделяет на стеке, но ведь это банально даже не удобнее?
Бонус: https://ideone.com/8iKwBz.
Переделал на IntFunction, чтобы избавиться от боксинга; я не знал, что она существует, и при этом не хотел вводить свой интерфейс. Или ты с самого начала это и имел в виду?.. (T︹T)
Остальное — осознанные страшилки, но:
Node — внутренний класс, его не предполагается выдавать пользователю. Может, ещё через геттеры и сеттеры с ним работать?
К тому же возможна следующая оптимизация. Вспомним, что делает .rebuild():
root.collapse();
root.divide();
То есть мы сплющиваем дерево, передавая объекты корню и выбрасывая промежуточные узлы, а затем переразбиваем, выделяя новые. Но можно было изначально не выбрасывать их, а сложить в связный список — даже не нужно вводить специальное поле под next, можно переиспользовать left. Затем в ходе divide мы, пока возможно, не new'аем узлы, а достаём из этого списка и используем снова. Всё ещё хочешь присваивать поля в конструкторе вместо того, чтобы переиспользовать присваивания без различия между new'нутым и достанным из жопы Node'ами? Должен ли этот трудяга, выполняющий всю чёрную работу, этот перемазанный в угле шахтёр, заступать на смену в галстуке?
>Vec2.Vec2(x, y)
Я не знаю, не возникнет ли конфликтов, когда в области видимости есть и тип, и метод, названные одинаково, но если нет, то это позволит import static'нуть этот метод и создавать объект просто как Vec2(), без уродского new и без второго идентификатора, который нужно держать в голове. new для иммутабельных объектов — это ересь. Вдруг я захочу оптимизировать потребление памяти и ввести кэш, который будет возвращать существующий вектор вместо создания нового, или аналог того, что сделано для Integer с ‒127..127. Метод с именем Vec2.make или Vec2.of нельзя import static'нуть. Можно сделать Vec2.vec2, но тогда нужно будет, как частный случай второго идентификатора, постоянно вспоминать, почему в одном месте большие буквы, а в другом — маленькие: Vec2 myVec = vec2(1, 1.5).
>Чтобы обработать этот случай не кросс-продуктом, а просто сравнениями координат.
Но как вы распознаете этот случай без кросс-морепродуктов — иными словами, как вы собрались узнать истинность выражения o1*o2 < 0 && o3*o4 < 0, не имея на руках o1, o2, o3, o4? Можно воспользоваться читерскими свойствами умножений и переместить проверки на == 0 после вычисления каждого кросса:
o1 = orientation(...)
if (o1 == 0 && ...) ...
o2 = orientation(...)
if (o2 == 0 && ...) ...
Но, во-первых, чтобы воспользоваться этой «оптимизацией», придётся не только постоянно искать пересечения отрезков с 3 коллинеарными точками (часто таким занимаетесь, с повёрнутыми-то фигурками?), но ещё и учитывать порядок проверок, чтобы передавать точки, которые наиболее вероятно лежат на одной прямой, в нужных местах (не много пользы будет от шортката после вычисления o3-o4, верно?), во-вторых, это с высокой вероятностью окажется вовсе контрпродуктивным: если вы вычислили значение и тут же его используете, вы рискуете заплатить больше, чем обычно, за латентность операций, которые в ходе вычисления были задействованы. Например, если латентность умножения — 5 тактов, а его «reciprocal throughput» (число тактов, через которое процессор начнёт исполнять следующую независимую инструкцию) — 2, то если результат умножения понадобится вотпрямщас, вы рискуете прождать лишние 3 такта (или все 33, если исходные числа ожидались из памяти, или сами были результатом какого-нибудь там деления), в которые процессор мог бы исполнять следующую инструкцию — словом, начать вычислять o2, пока o1 ещё в процессе, а не ждать окончания вычисления o1, чтобы решить, шорткатиться на ту несчастную ветку или нет.
>мой внутренний голос приобретает индийский акцент, когда читаю текст
...И тут я впервые увидел вашу закоммиченную реализацию segmentsIntersect. Как у вас только язык поднимается. Снимайте штаны. ;P
>Сначала подумал, что как-то не очень выглядит, но потом передумал.
Это нужно только для выбора оси разбиения, но в оригинале у меня вектор реализован, наоборот, как
data: array[0 .. 1] of float;
property x: float read data[0] write data[0];
property y: float read data[1] write data[1];
и get(cid) является естественным способом доступа к компонентам.
Про самобалансирующиеся деревья я сказал не подумав и это может в итоге оказаться абсолютно несостоятельной идеей, как минимум неочевидно, какой порядок ввести в пространстве:
— Пусть размер блока — 1 метр строительный тетрис. Округлим координаты до метров или дециметров, сплющим этой вот нумерацией пар Кантора и упорядочим соответственно полученным числам. Но тем самым объекты будут упорядочены «кругами» (ромбами), и описанные объёмы на всех уровнях, кроме, может, парочки нижних, окажутся описанными вокруг объёма, сравнимого с объёмом всей сцены, и т. о. бесполезными.
— Пусть сцена гарантированно находится в квадрате 100м×100м и нам нужно сравнить 2 точки, A и B. Разделим сцену вертикально, на левую и правую половинки. Если точки находятся в разных половинках, то та, что в правой — больше. Иначе возьмём половинку, в которой они находятся, и разделим горизонтально, на верхнюю и нижнюю. Если точки в разных, то та, что в верхней — больше. Иначе возьмём половинку (уже четвертинку всего) и разделим вертикально... и так далее. Так результат должен быть лучше: мы вроде как лексикографически упорядочили точки по их пути в квадродереве, поэтому близкие будут обычно близко, но при этом, в отличие от ванильного квадродерева, наша иерархия делит детей не по половинкам (четвертинкам) родительского объёма, а как угодно. Но мне пока лень это проверять, и это может оказаться-таки не лучше этого самого квадродерева.
P. S.
>страшилка на ночь
>04:33:13
>Более идиоматично сделать это итератором, но они вроде как не умеют rewind
Хмм, да. Вроде как в джавумандже есть ListIterator, который умеет ходить и вперёд, и назад, но произвольного доступа да, нет. Да и вернуться назад в начало тоже нельзя сделать в одну строку.
Нет, не это, было бы тупо до подобной мелочи докапываться. Да, мне изначально было интересно, почему не список, но ты уже >Бонус ответил. По нятно, прикольно, да, мда, буду иметь это в голове.
>Node — внутренний класс, его не предполагается выдавать пользователю. Может, ещё через геттеры и сеттеры с ним работать?
Ну, нет, но просто, по-моему, это немного error-prone вручую выставлять поля вместо того, чтобы сделать конструктор. Я бы точно постоянно забывал что-нибудь так выставить, если бы так делал.
>пока возможно, не new'аем узлы, а достаём из этого списка и используем снова
Они ведь уже вторичные, ими неприятно пользоваться, надо просто убить их.
>Я не знаю, не возникнет ли конфликтов, когда в области видимости есть и тип, и метод, названные одинаково
Нетт, так можно. Я поэтому и указал это, потому что ето выглядело так, как будто ты купил велосипед, но вместо того, чтобы кататься на нём, таскаешь его сзади на верёвочке.
>Но как вы распознаете этот случай без кросс-морепродуктов
А, я, наверное, не так сказал, я не в целях оптимизации это предлагал, а, чтобы в теории была получше точность? меньше вероятность ошибки из-за floating point вычислений? надёжнее? И я буквально просто хотел заменить строку "o1 != o2 && o3 != o4" на "o1 ∗ o2 < 0 && o3 ∗ o4 < 0", чтобы, например, если o1 == 0 и o2 != 0, то проверка принадлежности точки на продолжении отрезка делалась не через кросс-продукты (o3 != o4), а просто сравнениями (o1 == 0 && onSegment(a0, a1, b0)). В кросс-продуктах, наверное, есть шанс того, что-то переполнится, ну и просто там чуть больше вычислений, а значит, больше погрешность, но я сейчас даже не знаю, имеет ли это хотя бы какой-то смысл.
>reciprocal throughput
Хм, ето интересно.
>...И тут я впервые увидел вашу закоммиченную реализацию segmentsIntersect. Как у вас только язык поднимается. Снимайте штаны. ;P
Даа, я уже понял, как это всё лучше сделать, дда.
>Разделим сцену вертикально, на левую и правую половинки. Если точки находятся в разных половинках, то та, что в правой — больше.
Хм, по нятно.
>страшилка на ночь
>04:33:13
Так вот я и не уснул.
>Более идиоматично сделать это итератором, но они вроде как не умеют rewind
Хмм, да. Вроде как в джавумандже есть ListIterator, который умеет ходить и вперёд, и назад, но произвольного доступа да, нет. Да и вернуться назад в начало тоже нельзя сделать в одну строку.
Нет, не это, было бы тупо до подобной мелочи докапываться. Да, мне изначально было интересно, почему не список, но ты уже >Бонус ответил. По нятно, прикольно, да, мда, буду иметь это в голове.
>Node — внутренний класс, его не предполагается выдавать пользователю. Может, ещё через геттеры и сеттеры с ним работать?
Ну, нет, но просто, по-моему, это немного error-prone вручую выставлять поля вместо того, чтобы сделать конструктор. Я бы точно постоянно забывал что-нибудь так выставить, если бы так делал.
>пока возможно, не new'аем узлы, а достаём из этого списка и используем снова
Они ведь уже вторичные, ими неприятно пользоваться, надо просто убить их.
>Я не знаю, не возникнет ли конфликтов, когда в области видимости есть и тип, и метод, названные одинаково
Нетт, так можно. Я поэтому и указал это, потому что ето выглядело так, как будто ты купил велосипед, но вместо того, чтобы кататься на нём, таскаешь его сзади на верёвочке.
>Но как вы распознаете этот случай без кросс-морепродуктов
А, я, наверное, не так сказал, я не в целях оптимизации это предлагал, а, чтобы в теории была получше точность? меньше вероятность ошибки из-за floating point вычислений? надёжнее? И я буквально просто хотел заменить строку "o1 != o2 && o3 != o4" на "o1 ∗ o2 < 0 && o3 ∗ o4 < 0", чтобы, например, если o1 == 0 и o2 != 0, то проверка принадлежности точки на продолжении отрезка делалась не через кросс-продукты (o3 != o4), а просто сравнениями (o1 == 0 && onSegment(a0, a1, b0)). В кросс-продуктах, наверное, есть шанс того, что-то переполнится, ну и просто там чуть больше вычислений, а значит, больше погрешность, но я сейчас даже не знаю, имеет ли это хотя бы какой-то смысл.
>reciprocal throughput
Хм, ето интересно.
>...И тут я впервые увидел вашу закоммиченную реализацию segmentsIntersect. Как у вас только язык поднимается. Снимайте штаны. ;P
Даа, я уже понял, как это всё лучше сделать, дда.
>Разделим сцену вертикально, на левую и правую половинки. Если точки находятся в разных половинках, то та, что в правой — больше.
Хм, по нятно.
>страшилка на ночь
>04:33:13
Так вот я и не уснул.
А, я вроде ещё хотел что-то написать, но я забыл, блин, ну, просто, да, блин, как-то, да. Блин.
Вот мэшап, который мне понравился, услышал его где-то случайно и удачно нагуглил
https://youtu.be/VqKOI8oTIFo
Я думал, там что-то интересное, а там какие-то слишком очевидные вещи
https://youtu.be/YIDbhVPHZbs
А вот, всё, больше ничего нет.
Как организовать своё время без дебильных табличек и ежедневников, которые у меня не хватает терпения вести, если я постоянно не укладываюсь ни в какие дедлайны. Как просыпаться утром хотя бы на час пораньше, чтобы сдвинуть режим немного назад. Как следить за тем, что ты ешь. Если я что-то нахожу дома, то я это съедаю незамедлительно, если не нахожу - то специально сам ничего не покупаю. Блин, ну вот. Чего вообще, как вообще жить надо.
>>355664
Чего вы вдруг, мне такое нельзя говорить. Возвращайтесь, когда я стану достойным.
Если кто-то тут не знал, как решать системы линейных уравнений,
то вы и не узнаете, потому что мои конспекты понятны только мне.
https://poppyfanboy.github.io/2020/03/29/Systems-of-Linear-Equations-1.html
Я, ну, я знал это, но я абсолютно забыл некоторые более сложные вещи, поэтому начал с простого.
Единственное - мне опять никак не хотела выпадать вторая комната с ангелом. Если честно, я так и не понял, как работают шансы на выпадение этих комнат после босс-файта, хотя я лениво играю в это прямо с первого дня выхода ремастера айзека. Я всегда думал, что в первую очередь спавнится комната дьявола, и, если она не заспавнилась, то тогда есть шанс на комнату ангела. И я не помню точно, были ли у меня подозрения на то, что я тут в чём-то не прав, но в интернете как-то я нигде не нашёл нормального объяснения того, как зависят спавны этих комнат.
Проблема с лостом в том, что ты никак не можешь понизить шансы на выпадение комнаты дьявола, потому что по сути не можешь получить урон, только умереть. И ты можешь разве что повысить шансы на комнату ангела, но, если шансы на комнату дьявола 100%, то ты по сути ничего не можешь сделать: ты оберчён на то, чтобы получить эту комнату. И ты даже не можешь ничего взять оттуда, потому что тогда ты точно не сможешь потом получить комнату ангела.
И ещё я понял, что за лоста невозможно использовать комнату для жертвоприношений, потому что если ты умираешь в ней, то ты респавнишься в предыдущей комнате. И даже, если зайти в комнату жертвоприношений, выйти, коммитнуть судоку бомбами, зареспавниться в комнате жертвоприношений и наступить на шипы, то всё равно респавнишься в соседней комнате. То есть даже, если бы я в теории наступил на шипы достаточное количество раз и заспавнил босса-ангела, то меня бы сразу после этого выбросило из комнаты и босс бы задеспавнился.
И ещё я понял, что для того, чтобы собрать ключ, нужны две разные половинки, так что просто продублировать одну половинку не прокатит.
Короче, в итоге я дошёл до сундука с двумя одинаковыми половинками ключа, с не особо крутым, то достаточно сильным комбо, уже приготовился смириться с тем, что я потратил эти сорок минут в никуда, как жесть внезапно в секретной секретной комнате оказывается статуя ангела, и, да, я дальше, наконец-то, попадаю к мега сатане и прохожу его. И мне за это даже ничего не дали, только анлокнулся какой-то бэбик для мультиплеера, в который мне не с кем (да и не сильно хочется) играть, мда.
Я чувствую, что такими темпами я никогда не закончу проходить это, особено с учётом того, что я играю в это не то чтобы много. Мне всё ещё остаётся пройти кучу всего за кипера, грид мод за лоста и примерно все челленджи, чтобы по крайней мере пройти всё, что можно. Но я как бы и не тороплюсь никуда, ещё лет шесть, и я точно закончу.
Кстати, забавный момент в том, что у меня не пройден ни один челлендж, так что у меня из всех рун открыта только void rune, так что если мне попадается мешочек с рунами, тоо я просто гарантированно получаю миллирад воид рун, которые, на самом деле, довольно неплохие, особенно, когда играешь за лоста, которому не нужны всякие HP-апы и айтемы на полёт (только этих айтемов довольно много). Но почему-то вот в какой-то из последних разов, в который я играл, и мне выпал мешочек с рунами, тогда мне ни одна руна не выпала, это странно.
Аа, я как-то не знаю, во что можно поиграть. Я вроде хотел пройти дополнения второго дарк солса, ниер автомату, заброшенного якудзу зиро, заброшенные ремастеры креша и спайро, плейстейшн версии первых трёх игр по гарри поттеру. Но как-то ничего из этого особо сильно не привлекает, в особенности второй дарк соулс. Я как бы чувствую необходимость в том, чтобы пройти его полностью, потому что первый, третий и секиро я уже прошёл, а плейстейшена у меня нет, так что это единственная непройденная доступная мне соулс игра. Но, с другой стороны, это второй дарк, он играется ужасно, там странные тайминги перекатов, он весь какой-то обесцвеченный, скучный и безжизненный. И ниер автомату тоже не особо так хочется играть. Не знаю, корочее.
В манкрафт тоже не хочется, кстати: я так и не смог сделать ту ферму жителей: сначала они почему-то постоянно сбегали из клетки, а потом отказывались делать детей. Не исключено, что из-за того, что рядом были три жителя, которых они-таки смогли сделать, но у меня уже нет шанса, чтобы проверить это: я случайно упал к ним в клетку, и не смог выбраться. Пришлось всех прикончить. После этого абсолютно нет желания продолжать играть. Нет, это не как в прошлый раз, я уже не запускал игру недели полторы и до сих пор не тянет.
А я даже не знаю, что хотел сказать, я растянул на кучу символов то, как я прошёл босса в айзеке и то, как мне лень играть в игры.
Единственное - мне опять никак не хотела выпадать вторая комната с ангелом. Если честно, я так и не понял, как работают шансы на выпадение этих комнат после босс-файта, хотя я лениво играю в это прямо с первого дня выхода ремастера айзека. Я всегда думал, что в первую очередь спавнится комната дьявола, и, если она не заспавнилась, то тогда есть шанс на комнату ангела. И я не помню точно, были ли у меня подозрения на то, что я тут в чём-то не прав, но в интернете как-то я нигде не нашёл нормального объяснения того, как зависят спавны этих комнат.
Проблема с лостом в том, что ты никак не можешь понизить шансы на выпадение комнаты дьявола, потому что по сути не можешь получить урон, только умереть. И ты можешь разве что повысить шансы на комнату ангела, но, если шансы на комнату дьявола 100%, то ты по сути ничего не можешь сделать: ты оберчён на то, чтобы получить эту комнату. И ты даже не можешь ничего взять оттуда, потому что тогда ты точно не сможешь потом получить комнату ангела.
И ещё я понял, что за лоста невозможно использовать комнату для жертвоприношений, потому что если ты умираешь в ней, то ты респавнишься в предыдущей комнате. И даже, если зайти в комнату жертвоприношений, выйти, коммитнуть судоку бомбами, зареспавниться в комнате жертвоприношений и наступить на шипы, то всё равно респавнишься в соседней комнате. То есть даже, если бы я в теории наступил на шипы достаточное количество раз и заспавнил босса-ангела, то меня бы сразу после этого выбросило из комнаты и босс бы задеспавнился.
И ещё я понял, что для того, чтобы собрать ключ, нужны две разные половинки, так что просто продублировать одну половинку не прокатит.
Короче, в итоге я дошёл до сундука с двумя одинаковыми половинками ключа, с не особо крутым, то достаточно сильным комбо, уже приготовился смириться с тем, что я потратил эти сорок минут в никуда, как жесть внезапно в секретной секретной комнате оказывается статуя ангела, и, да, я дальше, наконец-то, попадаю к мега сатане и прохожу его. И мне за это даже ничего не дали, только анлокнулся какой-то бэбик для мультиплеера, в который мне не с кем (да и не сильно хочется) играть, мда.
Я чувствую, что такими темпами я никогда не закончу проходить это, особено с учётом того, что я играю в это не то чтобы много. Мне всё ещё остаётся пройти кучу всего за кипера, грид мод за лоста и примерно все челленджи, чтобы по крайней мере пройти всё, что можно. Но я как бы и не тороплюсь никуда, ещё лет шесть, и я точно закончу.
Кстати, забавный момент в том, что у меня не пройден ни один челлендж, так что у меня из всех рун открыта только void rune, так что если мне попадается мешочек с рунами, тоо я просто гарантированно получаю миллирад воид рун, которые, на самом деле, довольно неплохие, особенно, когда играешь за лоста, которому не нужны всякие HP-апы и айтемы на полёт (только этих айтемов довольно много). Но почему-то вот в какой-то из последних разов, в который я играл, и мне выпал мешочек с рунами, тогда мне ни одна руна не выпала, это странно.
Аа, я как-то не знаю, во что можно поиграть. Я вроде хотел пройти дополнения второго дарк солса, ниер автомату, заброшенного якудзу зиро, заброшенные ремастеры креша и спайро, плейстейшн версии первых трёх игр по гарри поттеру. Но как-то ничего из этого особо сильно не привлекает, в особенности второй дарк соулс. Я как бы чувствую необходимость в том, чтобы пройти его полностью, потому что первый, третий и секиро я уже прошёл, а плейстейшена у меня нет, так что это единственная непройденная доступная мне соулс игра. Но, с другой стороны, это второй дарк, он играется ужасно, там странные тайминги перекатов, он весь какой-то обесцвеченный, скучный и безжизненный. И ниер автомату тоже не особо так хочется играть. Не знаю, корочее.
В манкрафт тоже не хочется, кстати: я так и не смог сделать ту ферму жителей: сначала они почему-то постоянно сбегали из клетки, а потом отказывались делать детей. Не исключено, что из-за того, что рядом были три жителя, которых они-таки смогли сделать, но у меня уже нет шанса, чтобы проверить это: я случайно упал к ним в клетку, и не смог выбраться. Пришлось всех прикончить. После этого абсолютно нет желания продолжать играть. Нет, это не как в прошлый раз, я уже не запускал игру недели полторы и до сих пор не тянет.
А я даже не знаю, что хотел сказать, я растянул на кучу символов то, как я прошёл босса в айзеке и то, как мне лень играть в игры.
У нэру новая песенка.
https://youtu.be/Dun11cIEo9s
А ещё, оказывается, у machine girl новый альбом вышел (уже несколько месяцев назад, но я примерно столько и не слушал их).
https://youtu.be/Dun11cIEo9s
https://youtu.be/zsUmFuFOG7I
https://youtu.be/ABJRtLhZy_s
Ээ, не знаю, мне просто понравилось это, хотя это какой-то стыдный контент, в том числе видеоряд.
https://youtu.be/XzfWbi9xkgs
Кстати, почти с первого прошёл в TBoI хард грид мод за лоста. Правда, конечно, не совсем честно: мне опять удачно выпали +9 жизней, а ещё красная таблетка, которой я скипнул вторую фазу финального босса. Без этого я бы даже с более крутым комбо не смог бы пройти эту вот вторую фазу, потому что вообще не понимаю, как её можно проходить без урона: у босса прожектайлы начинают взрываться, и очень часто от этих взрывов невозможно увернуться. С только одним правом на ошибку вообще не понимаю, как его побеждать.
>А ещё, оказывается, у machine girl новый альбом вышел (уже несколько месяцев назад, но я примерно столько и не слушал их).
https://youtu.be/Ch7a0nr91y8
Да что ж такое, с каких пор ютуб стал таким мусором, почему-то через share -> copy копируется ссылка на предыдущее открытое видео. А ещё зачем-то добавили таймаут при фоновом воспроизведении видео: если долго не трогаешь вкладку, то видео стопается, и тебе надо вручную его опять запускать. А ещё зачем-то сделали так, что если добавляешь видео в плейлист, то ты автоматически переходишь в начало этого плейлиста, и дальше играется этот плейлист по порядку, хотя, блин, в этом нет никакого смысла, я, может, хочу продолжать листать рекомендованные видео. И плейлисты вообще в целом работают ужасно: иногда, если нажимаешь на какое-то видео в плейлисте, то оно открывается просто как отдельное видео, а не как видео из плейлиста, и после него запускаются рандомные рекомендованные видео.
И иногда по какой-то странной причине видео начинают добавляться в конец плейлиста, а не в начало, и тебе надо нажимать на кнопочку, чтобы снова отсортировать плейлист по порядку добавления. Я так понимаю, что это происходит тогда, когда ты случайно переставляешь два видео, и тем самым включаешь режим кастомного расположения видео в плейлисте. Но, блин, почему нельзя сделать так, чтобы новые видео по умолчанию добавлялись в начало, а не в конец?
https://poppyfanboy.github.io/2020/04/08/Matrices-3.html
В прошлый раз я там остановился не определении произведения матриц как матрицы композиции отображений, а теперь там сначала была формула для оценки ранга произведения матриц, а потом, наконец-то, было про квадратные матрицы и всякие штуки с ними связанные.
У меня никогда не было интуиции относительно того, какой размерности получится произведение матриц, и вообще оно меня постоянно сбивает с толку, в частности, как-то не думал о том, что строка умноженная на матрицу - это снова строка, потому что высота результирующей матрицы берётся из первой матрицы в произведении, и, аналогично, матрица, умноженная на столбец - это снова столбец. И вот так можно расписать произведение матриц (квадратные скобки - вертикальная ориентация, круглые - горизонтальная, андерскор - строка, крышечка - столбец):
> A x B = [A_1, ..., A_n] x (B^1, ..., B^m) = [A_1 x B, ..., A_n x B] = (A x B^1, ..., A x B^m)
Кстати, как раз вот эти два представления произведения (набор строк и набор столбцов) используются в доказательстве того, что при умножении матрицы на другую ранг не увеличивается: из этого представления видно соответствие между столбцовым/строковым базисами результата произведения и соответствующими базисами матриц A и B.
Ещё я совсем не знал, что из всех квадратных матриц коммутативностью со всеми остальными квадратными матрицами обладают только скалярные матрицы (это те, в которых по диагонали размазано одно число, а остальные элементы нулевые). Это доказывается, если взять произвольную матрицу в теории коммутативную с другими квадратными матрицами, и попробовать умножить её слева и справа на матрицу, в которой только один элемент выставлен в единицу, и приравнять результаты.
Про обратные матрицы я тоже всё забыл. Матрица обратимо только, когда она невырожденна. Необходимость этого условия легко следует из того, что n = rank(E) = rank(AB) ≤ rank(A) ≤ n. А для достаточности надо вывести обратную для произвольной невырожденной. Для этого берётся столбцовый базис исходной матрицы, через него выражается стандартный базис (который [ 1, 0, 0, ... ], [ 0, 1, 0, ... ]) и столбцы координат составляются в матрицу, которая окажется правой обратной к исходной матрице.
Существование левой обратной матрицы можно доказать, используя только что доказанную штуку: транспонирование матрицы вырожденной её не делает, и это биекцивное отображение множества матриц самого на себя, так что, если сделать так: E = E^T = (AA')^T = (A')^T A^T, то это будет означать существование левой обратной для любой невырожденной.
А дальше, если верно AA' = E и A'' A = E, то из этого будет следовать их равенство:
> A' = E A' = (A'' A) A' = A'' (A A') = A'' E = A''
И из этого всего можно также уже вывести то, что если есть хотя бы левая/правая обратная матрица, то будет и просто обратная, и они будут равны, потому что из существования левой/правой обратной следует невырожденность, а из невырожденности следует существование просто обратной.
И ещё там была теорема о том, что умножение матрицы на невырожденную не меняет её ранга, и это доказывается через довольно странный математический при кол (B - невырожденная):
> rank(AB) ≤ rank(A) = rank(ABB^{-1}) ≤ rank(AB)
И точно так же для умножения слева. Не знаю, какой это имеет практический смысл, вот такие модные доказательства, как мне кажется, иногда скрывают то, какой вообще смысл имеют доказываемые утверждения.
https://poppyfanboy.github.io/2020/04/08/Matrices-3.html
В прошлый раз я там остановился не определении произведения матриц как матрицы композиции отображений, а теперь там сначала была формула для оценки ранга произведения матриц, а потом, наконец-то, было про квадратные матрицы и всякие штуки с ними связанные.
У меня никогда не было интуиции относительно того, какой размерности получится произведение матриц, и вообще оно меня постоянно сбивает с толку, в частности, как-то не думал о том, что строка умноженная на матрицу - это снова строка, потому что высота результирующей матрицы берётся из первой матрицы в произведении, и, аналогично, матрица, умноженная на столбец - это снова столбец. И вот так можно расписать произведение матриц (квадратные скобки - вертикальная ориентация, круглые - горизонтальная, андерскор - строка, крышечка - столбец):
> A x B = [A_1, ..., A_n] x (B^1, ..., B^m) = [A_1 x B, ..., A_n x B] = (A x B^1, ..., A x B^m)
Кстати, как раз вот эти два представления произведения (набор строк и набор столбцов) используются в доказательстве того, что при умножении матрицы на другую ранг не увеличивается: из этого представления видно соответствие между столбцовым/строковым базисами результата произведения и соответствующими базисами матриц A и B.
Ещё я совсем не знал, что из всех квадратных матриц коммутативностью со всеми остальными квадратными матрицами обладают только скалярные матрицы (это те, в которых по диагонали размазано одно число, а остальные элементы нулевые). Это доказывается, если взять произвольную матрицу в теории коммутативную с другими квадратными матрицами, и попробовать умножить её слева и справа на матрицу, в которой только один элемент выставлен в единицу, и приравнять результаты.
Про обратные матрицы я тоже всё забыл. Матрица обратимо только, когда она невырожденна. Необходимость этого условия легко следует из того, что n = rank(E) = rank(AB) ≤ rank(A) ≤ n. А для достаточности надо вывести обратную для произвольной невырожденной. Для этого берётся столбцовый базис исходной матрицы, через него выражается стандартный базис (который [ 1, 0, 0, ... ], [ 0, 1, 0, ... ]) и столбцы координат составляются в матрицу, которая окажется правой обратной к исходной матрице.
Существование левой обратной матрицы можно доказать, используя только что доказанную штуку: транспонирование матрицы вырожденной её не делает, и это биекцивное отображение множества матриц самого на себя, так что, если сделать так: E = E^T = (AA')^T = (A')^T A^T, то это будет означать существование левой обратной для любой невырожденной.
А дальше, если верно AA' = E и A'' A = E, то из этого будет следовать их равенство:
> A' = E A' = (A'' A) A' = A'' (A A') = A'' E = A''
И из этого всего можно также уже вывести то, что если есть хотя бы левая/правая обратная матрица, то будет и просто обратная, и они будут равны, потому что из существования левой/правой обратной следует невырожденность, а из невырожденности следует существование просто обратной.
И ещё там была теорема о том, что умножение матрицы на невырожденную не меняет её ранга, и это доказывается через довольно странный математический при кол (B - невырожденная):
> rank(AB) ≤ rank(A) = rank(ABB^{-1}) ≤ rank(AB)
И точно так же для умножения слева. Не знаю, какой это имеет практический смысл, вот такие модные доказательства, как мне кажется, иногда скрывают то, какой вообще смысл имеют доказываемые утверждения.
В целом как-то не особо хорошо себя чувствую. Сегодня чуть лучше, но до этого пару дней подряд без какой-либо очевидной причины болела голова, это странно.
Возможно, я совершил ошибку, переместив пк со стола в кровать, не уверен, что это на что либо влияет. И, мне кажется, что так даже немного лучше posture по сравнению с тем, как я сижу за столом.
Кстати, посмотрел берсерка и академию ведьмочек, неплохие мультики.
https://github.com/PoppyFanboy/Tetris-Game/releases
У меня вроде работает и на винде, и на мда, хотя я там в одном месте впихнул платформо-зависимые слеши, но мне уже лень исправлять что-либо.
Я тут починил то, о чём говорил где-то выше, поднёс источник света ближе к игровому полю, чтобы блоки освещались по-разному, сделал так, чтобы, если зажимаешь кнопку влево/вправо, то после задержки фигурка начинала двигаться в этом направлении непрерывно, поправил рандом, добавил задержку перед тем, как фигурка зафиксируется, чтобы можно было при необходимости таскать её по дну, добавил гейм овер и рестарт. Ghost mode работает странно, волл кики можно эксплоитить (бесконечно вращать фигурку).
Начинается всё очень медленно, но оно усложняется, да и я бы не сказал, что даже так сильно просто. Цель уровня L - набрать 500L очков. 10 уровень - это уже ад, в том числе для глаз, потому что анимации начинают выглядеть как-то странно и дёрганно.
Скорее всего, надо было начислять количество очков пропорциональное текущему уровню, а ещё добавить в программу command line аргумент - начальный уровень, но уже нет. Я больше не хочу видеть это, это просто ужасно.
https://permadi.com/1996/05/ray-casting-tutorial-table-of-contents
I want that.
На данный момент, тем не менее, ничего не сделал существенного: только вроде реа лизовал тайловую карту, в которой каждая плитка представляется интеджером, и в нём там выставляются биты, которые определяют то, что находится на плитке. И скопировал сколько-то кода из тетреса (создание окна для приложения, реакция на нажатия кнопочек, game loop, такие штуки), попытался его немного улучшить, особо не получилось, но, по крайней мере там стало немного меньше строк.
Жесть ошибкой в тетресе было переплетать вместе логику игры с тем, как она отображается на экран, здесь я постараюсь их держать как можно дальше друг от друга.
Пока что не знаю, мне бы для начала хотя бы сам дви жок, если можно так сказать, реализовать. И я потом хочу попробовать прилепить к нему генерацию рандомных лабиринтов, чтобы было прикольно. А дальше пока что не знаю, не хочу никуда загадывать, потому что может получиться как с тетресом, что я там даже хотел там сделать какие-то всякие режимы, а в итоге бросил, наспех закончив какие-то совсем-совсем нужные вещи, потому что устал от него и не мог больше видеть.
Опять как-то так вышло, что весь день почти ничего не делал. Добавил проверку столкновений игрока и стен, но это ещё вроде вчера было, так что теперь можно стукаться о стены и скользить вдоль них. Попробовал сделать кастование лучей, но оно почему-то иногда странно работает, возникают лишние стены и иногда пропадают нужные, и вообще я потратил на это слишком много времени, потому что постоянно фейлился с тригонометрией, а ещё в итоге расставил знаки во всех формулах экспериментальным путём.
Лучи продлеваются точностью до плиток, так что там довольно мало перебора получается. При этом столкновения с вертикальными и горизонтальными границами стен (если смотреть сверху, как на карте) считаются отдельно, а потом выбирается ближайшее из них. Сначала находится пересечение луча с границами клетки, в которой находится игрок, а дальше луч итеративно продлевается на 1 клетку по одной координате и на сколько-то (там через тангенс просто считается) по другой, и за каждую такую итерацию ты попадаешь в новую границу какой-то клетки. Вотт как на картинке для горизонтальных пересечений.
Ебал твой рот пошел нахуй
Не помню, чтобы я говорил что-то такое, время есть, но я в совершенстве не могу им пользоваться. Проблема только во мне.
>>361544
Уже не имеет значения. К тому же, если мне чего-то не хочется, то это не означает, что мне это не нужно. Это работает только в обратную сторону, ящитаю.
Не особо что-либо делал.
Немного почитал про многопоточность в джавве
https://poppyfanboy.github.io/java concurrency/2020/04/07/Java-Concurrency-2.html
https://poppyfanboy.github.io/java concurrency/2020/04/17/Java-Concurrency-3.html
Не знаю, что об этом говорить, потому что для меня это всё не особо имеет смысл, потому что на практике я разве что создавал отдельный поток для тетреса, и на этом всё.
Попытался добавить текстуру стены, но чего-то всё стало внезапно тормозить, хотя я выделяю вертикальную полоску из текстуры с помощью BufferedImage.getSubImage(), который
>Returns a subimage defined by a specified rectangular region. The returned BufferedImage shares the same data array as the original image.
Но я вызываю sub image при каждом рендере, так что, наверное, тормозит из-за оверхеда при создании нового объекта? А если сохранить все полоски текстуры заранее, то вроде как не тормозит даже на большом разрешении (видеорелейтед с предзагруженными полосками).
Ещё на предыдущем видео у меня было всё немного сломанным, потому что я рандомно расставил коэффициенты, с которыми всё отображалось на экран. А теперь здесь есть picture plane (который равен размеру экрана), на который проецируются стены. Расстояние до PP вычисляется через его размер и угол обзора, и дальше при известной настоящей высоте стен можно вычислить высоту проекции. Теперь в теории всё выглядит так, как будто всё состоит из кубических блоков, а камера на ходится на высоте половины высоты блока.
Появился пол, отрисовывающийся попиксельно, а не как стены - полосками. Через глаз наблюдателя и точку на экране проводится линия, для которой находится пересечение с полом. И дальше по точке пересечения определяется нужный пиксель на текстуре.
Плюс ещё я там зачем-то каждый отдельный пиксель пола затемнял, хотя в этом нет особого смысла: можно поверх пола наложить такой полупрозрачный градиент. На видеорелейтед так сделал и всё значительно быстрее стало работать, но всё ещё тормозит относительно версии без пола.
Хм, казалось это просто текстура поверх плейнов
> Через глаз наблюдателя и точку на экране проводится линия, для которой находится пересечение с полом. И дальше по точке пересечения определяется нужный пиксель на текстуре.
Рей кастинг
Верёвочку одолжить?
Я не умею.
Сегодня снова ничего не делал, не знаю, сколько ещё такое будет продолжаться, но я просто не могу и ничего не выходит заставлять себя делать, и я даже не понимаю, куда уходит время.
Почитал ту книжку по гиту немного, которая на их сайте. Я её, наверное, раза три начинал листать, но дальше объяснения того, что такое version control system, не доходил, потому что мне было лень и нет особой необходимости.
https://poppyfanboy.github.io/git/2020/04/16/Git-1.html
Но, на самом деле, я не думаю, что это так бесполезно, потому что тут как с каким-нибудь вимом с его дефолтным туториалом: там есть слишком много всего и вряд ли хотя бы половина пригодится на практике, но по крайней мере ты знаешь, что он может, и, может быть, среди всех лишних штук ты найдёшь что-то действительно полезное, что сильно упростит тебе всё. К тому же я знаю только, как делать простейшие вещи вроде коммитов и мерджев веток.
Вот тут из полезного я не знал, что
- у git status есть нормальная сокращённая версия (флаг --short)
- git diff может выводить в виме side-by-side код до изменений и после (вернее git difftool). От обычного вывода мне становится плохо, потому что там выводится винегрет из удалённого и добавленного кода
- и ещё я не знал, что в гит командах можно использовать звёздочки, но их надо эскейпить обратным слешем. Если не эскейпить, то звёздочка будет нерекурсивной, то есть "git add ⁎.java" добавит файлы только из текущей директории, а "git add \⁎.java" - из всех. Иногда работает и без эскейпа, но, например, git add у меня никогда не хотел работать с простыми звёздочками, из-за чего я просто писал "git add .", потому что вбивать полный путь до каждого файла слишком долго
- и про git mv и git rm я не знал
Там ещё были какие-то штуки для git log, которые фильтровали его вывод, но они не особо полезны.
https://youtu.be/kma6T8OAQ-Q
Ещё полистал какое-то видео, думал, что там будет про многопоточность в целом, но там было про то, что в джавве, и там в основном было про то, что я как раз недавно сам посмотрел, но просто упорядоченное и в одном месте.
Из чего-то, что я не видел, там были только две какие-то функции (закон амдала и универсальный закон масштабируемости). Первая функция - частный случай второй, а вторая - это штука, которая показывает, какой можно ожидать прирост производительности в распараллеленной программе по сравнению с однопоточной, она вот так выглядит: C(N) = N / (1 + α(N - 1) + βN(N - 1)), где альфа - это доля критических секций, как я понял (если α = 1, то программу нельзя распараллелить и она не получает выгоды по сравнению с однопоточным вариантом), N - количество ядер процессора, а β - то, как часто потокам надо взаимодействовать друг с другом, и этот коэффициент вносит больший негативный вклад и может всё заруинить ещё сильнее, потому что количество возможных взаимодействий между потоками растёт квадратично относительно количества потоков. Короче, показывают просто, как может зависеть производительность многопоточной программы в зависимости от каких-то там факторов.
Ещё там был пример того, почему нужно синхронизировать не только запись, но и чтение. Запускаются 10 потоков, каждый из которых делает 100 синхронизированных записей в один и тот же ArrayList, после чего просто делается busy wait с проверкой того, не достиг ли list.size() 1000 элементов, при этом list.size() не синхронизирован.
Из-за того, что list.size() просто возвращает значение поля size внутри листа и это поле не помечено как volatile, то вот это:
> while (list.size() < 1000);
Может оптимизироваться вот так:
> int size = list.size;
> if (size < 1000) while (true);
Потому что компилятор не предполагает, что size может быть изменён из другого потока, это справедливое применение out-of-order-execution. Так что тут всё может зависнуть.
Ещё там было про double checked locking, который очень легко сделать неправильно, если не пометить поле с целевым вот этим целевым объектом как volatile, и про то, как в теории операция записи в поле объекта может быть неатомарной (но на деле в джавваве она атомарна за счёт выравнивания полей объекта, как я понял): если адрес объекта лежит на стыке кэш-линий и, если только одна из этих кэш-линий загружена, то процессор берёт значение из первой кэш-линии, загружает вторую кэш-линию, берёт значение из второй. Из-за этого можно было бы в теории наблюдать бессмысленное значение в переменной для адреса объекта, data race, всё плоххо. Но в дажваве вроде как благодаря выравниванию такого нет и запись в поле объекта всегда атомарна. Но я не особо вообще понимаю, как это всё работает, главное, что атомарна, да.
И ещё понял, что я неправильно у себя в голове произносил volatile и concurrency. И что у concurrency различаются британское и американское произношения. И, оказывается, да, то, что любой объект может в джавве использоваться как лок, влияет на производительность JVM, это очевидно, но просто он явно сказал это. И изначально так делалось из предположения о том, что локи - это мега круто.
Хотел ещё дальше почитать, но не почитал, а только понабрал побольше ссылок с википедии, которые можно посмотреть. Посмотрел только про дедлоки немного, что это формально и как с ними справляются операционные системы
https://poppyfanboy.github.io/java concurrency/2020/04/20/Java-Concurrency-4.html
И больше ничего, короче, по сути ничего не посмотрел.
Я не умею.
Сегодня снова ничего не делал, не знаю, сколько ещё такое будет продолжаться, но я просто не могу и ничего не выходит заставлять себя делать, и я даже не понимаю, куда уходит время.
Почитал ту книжку по гиту немного, которая на их сайте. Я её, наверное, раза три начинал листать, но дальше объяснения того, что такое version control system, не доходил, потому что мне было лень и нет особой необходимости.
https://poppyfanboy.github.io/git/2020/04/16/Git-1.html
Но, на самом деле, я не думаю, что это так бесполезно, потому что тут как с каким-нибудь вимом с его дефолтным туториалом: там есть слишком много всего и вряд ли хотя бы половина пригодится на практике, но по крайней мере ты знаешь, что он может, и, может быть, среди всех лишних штук ты найдёшь что-то действительно полезное, что сильно упростит тебе всё. К тому же я знаю только, как делать простейшие вещи вроде коммитов и мерджев веток.
Вот тут из полезного я не знал, что
- у git status есть нормальная сокращённая версия (флаг --short)
- git diff может выводить в виме side-by-side код до изменений и после (вернее git difftool). От обычного вывода мне становится плохо, потому что там выводится винегрет из удалённого и добавленного кода
- и ещё я не знал, что в гит командах можно использовать звёздочки, но их надо эскейпить обратным слешем. Если не эскейпить, то звёздочка будет нерекурсивной, то есть "git add ⁎.java" добавит файлы только из текущей директории, а "git add \⁎.java" - из всех. Иногда работает и без эскейпа, но, например, git add у меня никогда не хотел работать с простыми звёздочками, из-за чего я просто писал "git add .", потому что вбивать полный путь до каждого файла слишком долго
- и про git mv и git rm я не знал
Там ещё были какие-то штуки для git log, которые фильтровали его вывод, но они не особо полезны.
https://youtu.be/kma6T8OAQ-Q
Ещё полистал какое-то видео, думал, что там будет про многопоточность в целом, но там было про то, что в джавве, и там в основном было про то, что я как раз недавно сам посмотрел, но просто упорядоченное и в одном месте.
Из чего-то, что я не видел, там были только две какие-то функции (закон амдала и универсальный закон масштабируемости). Первая функция - частный случай второй, а вторая - это штука, которая показывает, какой можно ожидать прирост производительности в распараллеленной программе по сравнению с однопоточной, она вот так выглядит: C(N) = N / (1 + α(N - 1) + βN(N - 1)), где альфа - это доля критических секций, как я понял (если α = 1, то программу нельзя распараллелить и она не получает выгоды по сравнению с однопоточным вариантом), N - количество ядер процессора, а β - то, как часто потокам надо взаимодействовать друг с другом, и этот коэффициент вносит больший негативный вклад и может всё заруинить ещё сильнее, потому что количество возможных взаимодействий между потоками растёт квадратично относительно количества потоков. Короче, показывают просто, как может зависеть производительность многопоточной программы в зависимости от каких-то там факторов.
Ещё там был пример того, почему нужно синхронизировать не только запись, но и чтение. Запускаются 10 потоков, каждый из которых делает 100 синхронизированных записей в один и тот же ArrayList, после чего просто делается busy wait с проверкой того, не достиг ли list.size() 1000 элементов, при этом list.size() не синхронизирован.
Из-за того, что list.size() просто возвращает значение поля size внутри листа и это поле не помечено как volatile, то вот это:
> while (list.size() < 1000);
Может оптимизироваться вот так:
> int size = list.size;
> if (size < 1000) while (true);
Потому что компилятор не предполагает, что size может быть изменён из другого потока, это справедливое применение out-of-order-execution. Так что тут всё может зависнуть.
Ещё там было про double checked locking, который очень легко сделать неправильно, если не пометить поле с целевым вот этим целевым объектом как volatile, и про то, как в теории операция записи в поле объекта может быть неатомарной (но на деле в джавваве она атомарна за счёт выравнивания полей объекта, как я понял): если адрес объекта лежит на стыке кэш-линий и, если только одна из этих кэш-линий загружена, то процессор берёт значение из первой кэш-линии, загружает вторую кэш-линию, берёт значение из второй. Из-за этого можно было бы в теории наблюдать бессмысленное значение в переменной для адреса объекта, data race, всё плоххо. Но в дажваве вроде как благодаря выравниванию такого нет и запись в поле объекта всегда атомарна. Но я не особо вообще понимаю, как это всё работает, главное, что атомарна, да.
И ещё понял, что я неправильно у себя в голове произносил volatile и concurrency. И что у concurrency различаются британское и американское произношения. И, оказывается, да, то, что любой объект может в джавве использоваться как лок, влияет на производительность JVM, это очевидно, но просто он явно сказал это. И изначально так делалось из предположения о том, что локи - это мега круто.
Хотел ещё дальше почитать, но не почитал, а только понабрал побольше ссылок с википедии, которые можно посмотреть. Посмотрел только про дедлоки немного, что это формально и как с ними справляются операционные системы
https://poppyfanboy.github.io/java concurrency/2020/04/20/Java-Concurrency-4.html
И больше ничего, короче, по сути ничего не посмотрел.
Немного оптимизировал (так говорят, да?) теперь вроде работает чуть быстрее. На видео это не сильно видно (ещё и из-за того, что в предыдущей версии фпсы зависели от количества видимых пикселей пола, если бы не было стен, то оно бы тормозило сильнее), но оно точно стало быстрее: то, что есть сейчас, почти плавно работает в разрешении 1024x768 с максимальным качеством (ну, в смысле в каждый пиксель отрисовывается что-то значащее), а предыдущий вариант в этом разрешении уже умирал.
По советам Рируру вместо вычисления координат для каждого видимого пикселя пола сделал так, что для каждой строки вычисляется коордианата на текстуре для левого и для правого пикселей, а дальше между ними остальные линейно интерполируются. Ещё немного улучшил рейкастинг: до этого у меня пересечения с ближайшей горизонтальной и ближайшей вертикальной (с вида сверху) стенками вычислялись последовательно кастом двух лучей в одном направлении, так что в итоге всё зависело от того, сколько летит самый длинный луч из двух. А теперь эти два луча кастятся условно одновременно и, если один уже попал в препятствие, а второй уже пролетел больше, то всё прерывается и возвращается длина первого луча.
Ещё заменил использование всяких g.drawImage, g.drawLine и g.fillRect на прямое взаимодействие с буфером (там просто массив int'ов) картинки, которую потом отрисовываю на экран одним вызовом g.drawImage. Потому что, как оказалось, все эти методы очень плохо управляются с тем, чтобы отрисовывать пиксели/тонкие полоски в больших количествах. А больше, как я понимаю, в стандартной библиотеке жадвжады ничего нет для этого. И всё довольно неплохо улучшилось благодаря этому.
И теперь главным боттлнеком было затемнение, которое у меня реализовывалось просто как наложение полупрозрачных полосок, так что я заменил его изменением яркости пикселей.
https://stackoverflow.com/a/25508988
Вот такую забавную штуку нашёл, сам не проверял, работает ли она, но тут использовал для того, чтобы временно закрасить верхнюю часть чёрным.
В итоге всё равно такое ощущение, что оно прямо на грани того, чтобы оно вот-вот начало тормозить так, что это было бы уже совсем неприятно, но я пока что не знаю, что ещё можно оптимизировать.
Немного оптимизировал (так говорят, да?) теперь вроде работает чуть быстрее. На видео это не сильно видно (ещё и из-за того, что в предыдущей версии фпсы зависели от количества видимых пикселей пола, если бы не было стен, то оно бы тормозило сильнее), но оно точно стало быстрее: то, что есть сейчас, почти плавно работает в разрешении 1024x768 с максимальным качеством (ну, в смысле в каждый пиксель отрисовывается что-то значащее), а предыдущий вариант в этом разрешении уже умирал.
По советам Рируру вместо вычисления координат для каждого видимого пикселя пола сделал так, что для каждой строки вычисляется коордианата на текстуре для левого и для правого пикселей, а дальше между ними остальные линейно интерполируются. Ещё немного улучшил рейкастинг: до этого у меня пересечения с ближайшей горизонтальной и ближайшей вертикальной (с вида сверху) стенками вычислялись последовательно кастом двух лучей в одном направлении, так что в итоге всё зависело от того, сколько летит самый длинный луч из двух. А теперь эти два луча кастятся условно одновременно и, если один уже попал в препятствие, а второй уже пролетел больше, то всё прерывается и возвращается длина первого луча.
Ещё заменил использование всяких g.drawImage, g.drawLine и g.fillRect на прямое взаимодействие с буфером (там просто массив int'ов) картинки, которую потом отрисовываю на экран одним вызовом g.drawImage. Потому что, как оказалось, все эти методы очень плохо управляются с тем, чтобы отрисовывать пиксели/тонкие полоски в больших количествах. А больше, как я понимаю, в стандартной библиотеке жадвжады ничего нет для этого. И всё довольно неплохо улучшилось благодаря этому.
И теперь главным боттлнеком было затемнение, которое у меня реализовывалось просто как наложение полупрозрачных полосок, так что я заменил его изменением яркости пикселей.
https://stackoverflow.com/a/25508988
Вот такую забавную штуку нашёл, сам не проверял, работает ли она, но тут использовал для того, чтобы временно закрасить верхнюю часть чёрным.
В итоге всё равно такое ощущение, что оно прямо на грани того, чтобы оно вот-вот начало тормозить так, что это было бы уже совсем неприятно, но я пока что не знаю, что ещё можно оптимизировать.
Ого, пасибо. Игрой, правда, на данный момент это даже с натяжкой нельзя назвать. Поворот, да, пока что только стрелками, было лень разбираться с мышкой. К тому же сейчас ещё физически нельзя посмотреть вверх/вниз, так что не получится полноценно протестировать управление мышкой.
Чуть позже когда справлюсь с ленью, применю все советы Рируру по оптимизации, которые он мне надавал, и разберусь с тем, как можно реализовать возможность смотреть вверх/вниз переделаю под мышшь.
1) Пикрелейтед.
2) Если ты ничего не делаешь, зачем что-то писать, если можно не писать ничего? Лично моя стратегия именно в этом и заключается, доволен ей как удав.
3) Идея для весёлой вечеринки с друзьями: MIP-ТЕКСТУРИРОВАНИЕ.
Сейчас ты делаешь все выборки из основной текстуры.
Но если её размер 100×100, а на экране она занимает 10×10 пикселей (1/100 от всех пикселей текстуры), то при каждом движении камеры эти 1/100 изменяются на совершенно другие 1/100, и потому вся текстура мерцает. Это хорошо видно на поверхностях, находящихся далеко или под большим углом к камере.
Чтобы это устранить, ты можешь сгенерировать пирамиду текстур, каждая из которых вдвое меньше предыдущей по всем измерениям. Проще всего это сделать с квадратными текстурами, размеры которых являются степенями двойки:
>512×512 → 256×256 → 128×128 → 64×64 → 32×32 → 16×16 → 8×8 → 4×4 → 2×2 → 1×1.
Каждую следующую уменьшенную версию (MIP-уровень, от «multum in parvo», или LOD, от «level of detail») легко получить, усреднив соответствующие 4 (2×2) пиксела предыдущей. Назовём это «box-фильтр 2×2».
Впрочем, почти с тем же комфортом можно использовать пирамиду, начинающуюся с текстуры произвольных размеров:
>600×300 → 300×150 → 150×75 → 75×37 → 37×18 → 18×9 → 9×4 → 4×2 → 2×1 → 1×1.
Единственная проблема — собственно генерация таких уровней. При уменьшении текстуры, размер которой не делится на 2, исходные пикселы box-фильтра перестанут иметь целые координаты, и по-хорошему их придётся выбирать по дробным с линейной интерполяцией. Назовём получение пиксела по целым координатам pixel(), а выборку по дробным с интерполяцией — sample(). Тогда sample(6,5; 6,3) — это
>x0 = lerp(pixel(6; 6); pixel(6; 7); 0,3)
>x1 = lerp(pixel(7; 6); pixel(7; 7); 0,3)
>result = lerp(x0; x1; 0,5)
При уменьшении текстуры 5×5 до текстуры 2×2 левый верхний пиксел результата с координатами (0; 0) будет результатом усреднения четырёх выборок:
>0,25 × (sample(0,125; 0,125) + sample(1,375; 0,125) + sample(0,125; 1,375) + sample(1,375; 1,375))
А правый нижний, с координатами (1, 1) —
>0,25 × (sample(2,625; 2,625) + sample(3,875; 2,625) + sample(2,625; 3,875) + sample(3,875; 3,875))
То есть сетка пиксельных выборок из оригинальной текстуры для уполовинивания box-фильтром — (0,125; 1,375; 2,625; 3,875). Она построена из соображений универсальности в применении как к незатайленной, так и к затайленной текстурам: для незатайленной важна симметричность относительно координат оригинальной текстуры [0; 4], а для затайленной — цикличность по модулю 5 и расстояние между выборками сквозь край, равное расстоянию между остальными (1,25): 3,875 + 1,25 = 5,125 = 0,125.
Общая формула — что-то вроде sampleDist = IN_SIZE / (2×OUT_SIZE), start = (IN_SIZE - 2×OUT_SIZE) / (2×OUT_SIZE) / 2, i-я координата сетки — start + i×sampleDist. В примере выше по обоим измерениям IN_SIZE = 5 и OUT_SIZE = 2, так что sampleDist = 1,25 и start = 0,125.
Ну да ладно, положим, MIP-уровни у нас на руках. Теперь для текстурирования можно выбирать тот, который близок к своему будущему экранному размеру и потому не будет так мерцать.
Это не только улучшает внешний вид, но и внезапно становится серьёзной оптимизацией для сколь-нибудь больших текстур. Пикселы экрана, рисуемые один за другим, обычно находятся по соседству. Если мы при маленьком экранном размере поверхности делали «мерцающие» выборки из большой основной текстуры, то они прыгали по всей её площади, что недружественно к кэшу. Выборки же из меньшей текстуры имеют лучшую локальность: для соседних пикселей экрана они тоже находятся по соседству в текстуре.
Как выбрать MIP-уровень?
В твоём случае, с рисованием всего горизонтальными или вертикальными полосками, можно придумать какую-нибудь ad hoc-эвристику: пусть видимый размер полоски в пикселах — stripPixels, а расстояние между текстурными координатами на её краях — texDistance, тогда мы можем делать выборки из уровня текстуры, размер которого ближе всего к stripPixels / texDistance (наверное, желательно дополнительно учесть сплющивание текстуры под углом — скажем, домножить этот размер на косинус угла между нормалью к стене и направлением на камеру).
В общем же случае используются градиенты (частные производные, блеан, я на самом деле не знаю, что это, не шарю во всём этом матане, извините) координат текстуры T = (u; v) по координатам пикселей на экране (sx; sy).
Например, ∂T/∂sx можно вычислить по формуле symmetric derivative как разность между координатой текстуры в экранном пикселе справа от текущего и слева, делённую на 2, или более точно с использованием координат текстуры в (1) текущем пикселе, его (2) правом и (3) левом соседях через 3-point numerical differentiation, ну ты понял. Аналогично для ∂T/∂sy по вертикали.
Градиент, длина которого равна 1, означает, что вся текстура в направлении, по которому он взят, умещается в 1 экранный пиксель.
Градиент, длина которого равна 0,01, означает, что текстура умещается в 100 экранных пикселей.
Условимся, что основной MIP-уровень (напр. 512×512) — 0-й, следующий (256×256) — 1-й, и так далее. Последний, 1×1 — в нашем случае 9-й, last_level.
Тогда большим градиентам соответствуют большие по номеру (меньшего разрешения) уровни, а сам номер уровня выражается чем-то вроде clamp(last_level - log₂(grad.len), 0, last_level).
Но когда это я успел перейти от двух градиентов к некому grad?
В самом деле, если градиенты ∂T/∂sx и ∂T/∂sy сильно различаются (это как раз и происходит с полом и стенами под углом), возникает сильная неоднозначность при выборе уровня, причём оба варианта по-своему плохие.
Представим, что оригинальная текстура имела размер 400×400, а фрагмент стены/пола с ней на экране имеет размер 100×10.
1. Если выбрать уровень на основании меньшего градиента (100×100) — то текстура будет мерцать, хотя и не так сильно, как если бы мы вообще не использовали MIP и делали выборки из основного уровня 400×400.
2. Если выбрать уровень на основании большего градиента (10×10) — текстура будет размытой.
Обычно размытая текстура лучше мерцающей, так что предпочтительнее вариант 2, либо можно даже попробовать взять что-то среднее между обоими.
Фундаментально же эта проблема решается анизотропной фильтрацией (https://en.wikipedia.org/wiki/Anisotropic_filtering): выбираем уровень на основании меньшего градиента, и делаем из него несколько выборок (например, до 8) на основании его отличия от большего (https://computergraphics.stackexchange.com/a/1437).
Градиенты и анизотропия — это высший пилотаж, но базовое MIP-текстурирование должно реализовываться просто и давать какое-никакое визуальное улучшение.
1) Пикрелейтед.
2) Если ты ничего не делаешь, зачем что-то писать, если можно не писать ничего? Лично моя стратегия именно в этом и заключается, доволен ей как удав.
3) Идея для весёлой вечеринки с друзьями: MIP-ТЕКСТУРИРОВАНИЕ.
Сейчас ты делаешь все выборки из основной текстуры.
Но если её размер 100×100, а на экране она занимает 10×10 пикселей (1/100 от всех пикселей текстуры), то при каждом движении камеры эти 1/100 изменяются на совершенно другие 1/100, и потому вся текстура мерцает. Это хорошо видно на поверхностях, находящихся далеко или под большим углом к камере.
Чтобы это устранить, ты можешь сгенерировать пирамиду текстур, каждая из которых вдвое меньше предыдущей по всем измерениям. Проще всего это сделать с квадратными текстурами, размеры которых являются степенями двойки:
>512×512 → 256×256 → 128×128 → 64×64 → 32×32 → 16×16 → 8×8 → 4×4 → 2×2 → 1×1.
Каждую следующую уменьшенную версию (MIP-уровень, от «multum in parvo», или LOD, от «level of detail») легко получить, усреднив соответствующие 4 (2×2) пиксела предыдущей. Назовём это «box-фильтр 2×2».
Впрочем, почти с тем же комфортом можно использовать пирамиду, начинающуюся с текстуры произвольных размеров:
>600×300 → 300×150 → 150×75 → 75×37 → 37×18 → 18×9 → 9×4 → 4×2 → 2×1 → 1×1.
Единственная проблема — собственно генерация таких уровней. При уменьшении текстуры, размер которой не делится на 2, исходные пикселы box-фильтра перестанут иметь целые координаты, и по-хорошему их придётся выбирать по дробным с линейной интерполяцией. Назовём получение пиксела по целым координатам pixel(), а выборку по дробным с интерполяцией — sample(). Тогда sample(6,5; 6,3) — это
>x0 = lerp(pixel(6; 6); pixel(6; 7); 0,3)
>x1 = lerp(pixel(7; 6); pixel(7; 7); 0,3)
>result = lerp(x0; x1; 0,5)
При уменьшении текстуры 5×5 до текстуры 2×2 левый верхний пиксел результата с координатами (0; 0) будет результатом усреднения четырёх выборок:
>0,25 × (sample(0,125; 0,125) + sample(1,375; 0,125) + sample(0,125; 1,375) + sample(1,375; 1,375))
А правый нижний, с координатами (1, 1) —
>0,25 × (sample(2,625; 2,625) + sample(3,875; 2,625) + sample(2,625; 3,875) + sample(3,875; 3,875))
То есть сетка пиксельных выборок из оригинальной текстуры для уполовинивания box-фильтром — (0,125; 1,375; 2,625; 3,875). Она построена из соображений универсальности в применении как к незатайленной, так и к затайленной текстурам: для незатайленной важна симметричность относительно координат оригинальной текстуры [0; 4], а для затайленной — цикличность по модулю 5 и расстояние между выборками сквозь край, равное расстоянию между остальными (1,25): 3,875 + 1,25 = 5,125 = 0,125.
Общая формула — что-то вроде sampleDist = IN_SIZE / (2×OUT_SIZE), start = (IN_SIZE - 2×OUT_SIZE) / (2×OUT_SIZE) / 2, i-я координата сетки — start + i×sampleDist. В примере выше по обоим измерениям IN_SIZE = 5 и OUT_SIZE = 2, так что sampleDist = 1,25 и start = 0,125.
Ну да ладно, положим, MIP-уровни у нас на руках. Теперь для текстурирования можно выбирать тот, который близок к своему будущему экранному размеру и потому не будет так мерцать.
Это не только улучшает внешний вид, но и внезапно становится серьёзной оптимизацией для сколь-нибудь больших текстур. Пикселы экрана, рисуемые один за другим, обычно находятся по соседству. Если мы при маленьком экранном размере поверхности делали «мерцающие» выборки из большой основной текстуры, то они прыгали по всей её площади, что недружественно к кэшу. Выборки же из меньшей текстуры имеют лучшую локальность: для соседних пикселей экрана они тоже находятся по соседству в текстуре.
Как выбрать MIP-уровень?
В твоём случае, с рисованием всего горизонтальными или вертикальными полосками, можно придумать какую-нибудь ad hoc-эвристику: пусть видимый размер полоски в пикселах — stripPixels, а расстояние между текстурными координатами на её краях — texDistance, тогда мы можем делать выборки из уровня текстуры, размер которого ближе всего к stripPixels / texDistance (наверное, желательно дополнительно учесть сплющивание текстуры под углом — скажем, домножить этот размер на косинус угла между нормалью к стене и направлением на камеру).
В общем же случае используются градиенты (частные производные, блеан, я на самом деле не знаю, что это, не шарю во всём этом матане, извините) координат текстуры T = (u; v) по координатам пикселей на экране (sx; sy).
Например, ∂T/∂sx можно вычислить по формуле symmetric derivative как разность между координатой текстуры в экранном пикселе справа от текущего и слева, делённую на 2, или более точно с использованием координат текстуры в (1) текущем пикселе, его (2) правом и (3) левом соседях через 3-point numerical differentiation, ну ты понял. Аналогично для ∂T/∂sy по вертикали.
Градиент, длина которого равна 1, означает, что вся текстура в направлении, по которому он взят, умещается в 1 экранный пиксель.
Градиент, длина которого равна 0,01, означает, что текстура умещается в 100 экранных пикселей.
Условимся, что основной MIP-уровень (напр. 512×512) — 0-й, следующий (256×256) — 1-й, и так далее. Последний, 1×1 — в нашем случае 9-й, last_level.
Тогда большим градиентам соответствуют большие по номеру (меньшего разрешения) уровни, а сам номер уровня выражается чем-то вроде clamp(last_level - log₂(grad.len), 0, last_level).
Но когда это я успел перейти от двух градиентов к некому grad?
В самом деле, если градиенты ∂T/∂sx и ∂T/∂sy сильно различаются (это как раз и происходит с полом и стенами под углом), возникает сильная неоднозначность при выборе уровня, причём оба варианта по-своему плохие.
Представим, что оригинальная текстура имела размер 400×400, а фрагмент стены/пола с ней на экране имеет размер 100×10.
1. Если выбрать уровень на основании меньшего градиента (100×100) — то текстура будет мерцать, хотя и не так сильно, как если бы мы вообще не использовали MIP и делали выборки из основного уровня 400×400.
2. Если выбрать уровень на основании большего градиента (10×10) — текстура будет размытой.
Обычно размытая текстура лучше мерцающей, так что предпочтительнее вариант 2, либо можно даже попробовать взять что-то среднее между обоими.
Фундаментально же эта проблема решается анизотропной фильтрацией (https://en.wikipedia.org/wiki/Anisotropic_filtering): выбираем уровень на основании меньшего градиента, и делаем из него несколько выборок (например, до 8) на основании его отличия от большего (https://computergraphics.stackexchange.com/a/1437).
Градиенты и анизотропия — это высший пилотаж, но базовое MIP-текстурирование должно реализовываться просто и давать какое-никакое визуальное улучшение.
Модные формулы, part 2,5
Я досконально изучил тему (прочитал первые 2 ссылки в гугле) и авторитетно заявляю, что помимо 3 уже известных нам жизнеспособных форм хранения 2D-поворота — угла, матрицы и Rotation2D — существует 4-я.
Слегка изменим Rotation2D: будем хранить в нём половинный угол вместо полного. Назовём полученную сущность Spinor2D или просто спиннером.
Получился прямой, теперь уже без оговорки «почти», аналог кватерниона, со всеми его полезными, не очень полезными и прямо вредными свойствами.
Но прежде сразу заметим 2 вещи.
— Spinor2D и Rotation2D легко переводятся друг в друга по школьным формулам:
cos 2α = cos²α ‒ sin²α = 1 ‒ 2 × sin²α = 2 × cos²α ‒ 1
sin 2α = 2 × sin α × cos α
cos α/2 = ±√(1 + cos α)/2
sin α/2 = ±√(1 ‒ cos α)/2
— Поскольку Spinor2D хранит половинный угол, для поворота точки его нужно применить дважды, или, что то же самое, выразить функции от полного угла через функции от половинного.
В Rotation2D{cosa, sina} точка (cosa, sina) лежала где-то на полной окружности — [-180°; 180°), и непосредственно выражала поворот. Ему можно поставить в соответствие Spinor2D{cosha, sinha} с точкой (cosha, sinha), лежащей где-то в диапазоне половинных углов — [-90°; 90°), то есть в правой полуплоскости.
А что происходит, если (cosha, sinha) лежит в левой полуплоскости? (Такой спиннер не может получиться непосредственно из нормализованного угла, но легко вылезет в результате комбинации спиннеров.)
Ничего страшного не происходит: Spinor2D{x, y} и Spinor2D{‒x, ‒y} выражают один и тот же поворот. Это отражается в формулах двойного угла: они не зависят от умножения sin α и cos α одновременно на ‒1. Например, если половинный угол был 100° (Ⅰ квадрант), то полный будет 200° = -160°, половинный от ‒160° — ‒80° (Ⅳ квадрант), а это и есть противоположность 100°.
Это позволяет легко интерполировать повороты, выраженные спиннерами, по кратчайшей дуге. Такую интерполяцию всегда можно свести к интерполяции векторов с острым (максимум — прямым) углом между ними: если угол между векторами (cosha, sinha) тупой (dot этих векторов — косинус угла — отрицателен), то переворачиваем один из спиннеров.
Точным способом интерполяции векторов по дуге является SLERP (https://en.wikipedia.org/wiki/Slerp):
function slerp_precise(a, b, t)
ab_cos = dot(a, b)
if ab_cos < 0 then b = -b; ab_cos = -ab_cos; end
if ab_cos < 0.99999 then
ab_angle = acos(ab_cos)
ab_sin = sqrt(1 - sqr(ab_cos))
return (a × sin((1 - t) × ab_angle) + b × sin(t × ab_angle)) / ab_sin
else
return normalize(a + t × (b - a)) // Слишком близкие повороты — вырожденный случай.
end
end
Вообще-то slerp условно работает и для тупых углов, так что slerp'ать можно было и Rotation2D'ы. Но:
— При интерполяции между противоположными векторами (поворотами) slerp(Rotation2D) схлопнется. Случай с ab_sin ≈ 0 мы обрабатываем линейной интерполяцией в предположении почти совпадающих векторов, с противоположными это не сработает. Впрочем, таких поворотов в любом случае захочется избегать: slerp(Spinor2D) пусть и не схлопнется, но выберет непредсказуемую дугу. Поэтому важнее следующий пункт.
— Slerp — тяжёлая операция. Однако у неё есть быстрые аппроксимации. Например:
1. (https://zeux.io/2015/07/23/approximating-slerp/ + https://zeux.io/2016/05/05/optimizing-slerp/)
function slerp_fast(a, b, t)
ab_cos = dot(a, b)
if ab_cos < 0 then b = -b; ab_cos = -ab_cos; end
ot = t + t × (t - 0.5) × (t - 1) × (
(1.0904 + ab_cos × (-3.2452 + ab_cos × (3.55645 - ab_cos × 1.43519))) × sqr(t - 0.5) + (0.848013 + ab_cos × (-1.06021 + ab_cos × 0.215638)))
return normalize(a + ot × (b - a))
end
Это работает в 4 раза быстрее, с погрешностями в пределах пары тысячных долей градуса.
2. Внезапно, тупая линейная интерполяция с ренормализацией:
function nlerp(a, b, t)
if dot(a, b) < 0 then b = -b end
return normalize(a + t × (b - a))
end
Это работает ещё быстрее. В точках t=0, t=0,5 и t=1 интерполяция является точной (в точке 0,5 об этом можно думать как о совпадении бисектриссы и медианы к основанию в равнобедренном треугольнике). При интерполяции поворотов с большим (близким к 180°) расстоянием между ними максимальная погрешность достигает 4°, а при интерполяции поворотов, расстояние между которыми не превышает 90°, погрешность не превышает 0,5°.
Так вот, эти аппроксимации усложняются или вовсе портятся при интерполяции по тупому углу.
Spinor2D же позволяет всегда интерполировать по острому.
Теперь в нашем арсенале следующие представления поворотов:
1. Непосредственный угол.
Достоинства:
— Самая компактная форма, какую только можно представить.
— Единственная из рассматриваемых форм, которая может представлять кратные повороты, т. е. повороты более чем на ±180°.
— Простая интерполяция, в том числе по кратчайшей дуге: function lerp_over_shortest_arc(a, b, t) return a + t × normalize_angle(b - a) end.
Недостатки:
— Очень медленно применяется к точке.
2. Матрица 2×2.
Достоинства:
— Может представлять не только поворот.
Недостатки:
— Может представлять не только поворот.
3. Rotation2D
— Компактная и очень быстрая форма.
4. Spinor2D
— Компактная и практически столь же быстрая, что и Rotation2D.
— Легко и точно интерполируется.
На моём нищебродском i7-9750H на 1 ядре за 1 секунду можно выполнить:
— 14 млн поворотов Vec2 с помощью голого угла;
— 8 млн «референсных» интерполяций Rotation2D, реализованных как lerp_over_shortest_arc(a.ToAngle, b.ToAngle, t).ToRotation;
— 14 млн Spinor2D.slerp_precise, или 12 млн Rotation2D.slerp_precise, реализованных как преобразование аргументов в Spinor2D, затем Spinor2D.slerp_precise, затем преобразование результата назад в Rotation2D;
— 58 млн Spinor2D.slerp_fast / 33 млн Rotation2D.slerp_fast через Spinor2D.slerp_fast;
— 70 млн Spinor2D.nlerp / 40 млн Rotation2D.nlerp через Spinor2D.nlerp;
— 161 млн Spinor2D.nlerp_raw без проверок разнонаправленности. В заранее заданной цепочке поворотов, такой как ключевые кадры анимации, можно заранее перевернуть спиннер, образующий тупой угол со своим предшественником, чтобы не обрабатывать этот случай в nlerp;
— 320 млн комбинаций Spinor2D × Spinor2D или полностью идентичных Rotation2D × Rotation2D;
— 310 млн поворотов Vec2 с помощью Spinor2D;
— 325 млн поворотов Vec2 с помощью Rotation2D.
Последние две цифры выглядят неожиданно (казалось бы, в Spinor2D.apply почти удвоенное количество операций), но дают основание утверждать, что Spinor2D может использоваться как универсальная и даже единственная форма поворотов.
Модные формулы, part 2,5
Я досконально изучил тему (прочитал первые 2 ссылки в гугле) и авторитетно заявляю, что помимо 3 уже известных нам жизнеспособных форм хранения 2D-поворота — угла, матрицы и Rotation2D — существует 4-я.
Слегка изменим Rotation2D: будем хранить в нём половинный угол вместо полного. Назовём полученную сущность Spinor2D или просто спиннером.
Получился прямой, теперь уже без оговорки «почти», аналог кватерниона, со всеми его полезными, не очень полезными и прямо вредными свойствами.
Но прежде сразу заметим 2 вещи.
— Spinor2D и Rotation2D легко переводятся друг в друга по школьным формулам:
cos 2α = cos²α ‒ sin²α = 1 ‒ 2 × sin²α = 2 × cos²α ‒ 1
sin 2α = 2 × sin α × cos α
cos α/2 = ±√(1 + cos α)/2
sin α/2 = ±√(1 ‒ cos α)/2
— Поскольку Spinor2D хранит половинный угол, для поворота точки его нужно применить дважды, или, что то же самое, выразить функции от полного угла через функции от половинного.
В Rotation2D{cosa, sina} точка (cosa, sina) лежала где-то на полной окружности — [-180°; 180°), и непосредственно выражала поворот. Ему можно поставить в соответствие Spinor2D{cosha, sinha} с точкой (cosha, sinha), лежащей где-то в диапазоне половинных углов — [-90°; 90°), то есть в правой полуплоскости.
А что происходит, если (cosha, sinha) лежит в левой полуплоскости? (Такой спиннер не может получиться непосредственно из нормализованного угла, но легко вылезет в результате комбинации спиннеров.)
Ничего страшного не происходит: Spinor2D{x, y} и Spinor2D{‒x, ‒y} выражают один и тот же поворот. Это отражается в формулах двойного угла: они не зависят от умножения sin α и cos α одновременно на ‒1. Например, если половинный угол был 100° (Ⅰ квадрант), то полный будет 200° = -160°, половинный от ‒160° — ‒80° (Ⅳ квадрант), а это и есть противоположность 100°.
Это позволяет легко интерполировать повороты, выраженные спиннерами, по кратчайшей дуге. Такую интерполяцию всегда можно свести к интерполяции векторов с острым (максимум — прямым) углом между ними: если угол между векторами (cosha, sinha) тупой (dot этих векторов — косинус угла — отрицателен), то переворачиваем один из спиннеров.
Точным способом интерполяции векторов по дуге является SLERP (https://en.wikipedia.org/wiki/Slerp):
function slerp_precise(a, b, t)
ab_cos = dot(a, b)
if ab_cos < 0 then b = -b; ab_cos = -ab_cos; end
if ab_cos < 0.99999 then
ab_angle = acos(ab_cos)
ab_sin = sqrt(1 - sqr(ab_cos))
return (a × sin((1 - t) × ab_angle) + b × sin(t × ab_angle)) / ab_sin
else
return normalize(a + t × (b - a)) // Слишком близкие повороты — вырожденный случай.
end
end
Вообще-то slerp условно работает и для тупых углов, так что slerp'ать можно было и Rotation2D'ы. Но:
— При интерполяции между противоположными векторами (поворотами) slerp(Rotation2D) схлопнется. Случай с ab_sin ≈ 0 мы обрабатываем линейной интерполяцией в предположении почти совпадающих векторов, с противоположными это не сработает. Впрочем, таких поворотов в любом случае захочется избегать: slerp(Spinor2D) пусть и не схлопнется, но выберет непредсказуемую дугу. Поэтому важнее следующий пункт.
— Slerp — тяжёлая операция. Однако у неё есть быстрые аппроксимации. Например:
1. (https://zeux.io/2015/07/23/approximating-slerp/ + https://zeux.io/2016/05/05/optimizing-slerp/)
function slerp_fast(a, b, t)
ab_cos = dot(a, b)
if ab_cos < 0 then b = -b; ab_cos = -ab_cos; end
ot = t + t × (t - 0.5) × (t - 1) × (
(1.0904 + ab_cos × (-3.2452 + ab_cos × (3.55645 - ab_cos × 1.43519))) × sqr(t - 0.5) + (0.848013 + ab_cos × (-1.06021 + ab_cos × 0.215638)))
return normalize(a + ot × (b - a))
end
Это работает в 4 раза быстрее, с погрешностями в пределах пары тысячных долей градуса.
2. Внезапно, тупая линейная интерполяция с ренормализацией:
function nlerp(a, b, t)
if dot(a, b) < 0 then b = -b end
return normalize(a + t × (b - a))
end
Это работает ещё быстрее. В точках t=0, t=0,5 и t=1 интерполяция является точной (в точке 0,5 об этом можно думать как о совпадении бисектриссы и медианы к основанию в равнобедренном треугольнике). При интерполяции поворотов с большим (близким к 180°) расстоянием между ними максимальная погрешность достигает 4°, а при интерполяции поворотов, расстояние между которыми не превышает 90°, погрешность не превышает 0,5°.
Так вот, эти аппроксимации усложняются или вовсе портятся при интерполяции по тупому углу.
Spinor2D же позволяет всегда интерполировать по острому.
Теперь в нашем арсенале следующие представления поворотов:
1. Непосредственный угол.
Достоинства:
— Самая компактная форма, какую только можно представить.
— Единственная из рассматриваемых форм, которая может представлять кратные повороты, т. е. повороты более чем на ±180°.
— Простая интерполяция, в том числе по кратчайшей дуге: function lerp_over_shortest_arc(a, b, t) return a + t × normalize_angle(b - a) end.
Недостатки:
— Очень медленно применяется к точке.
2. Матрица 2×2.
Достоинства:
— Может представлять не только поворот.
Недостатки:
— Может представлять не только поворот.
3. Rotation2D
— Компактная и очень быстрая форма.
4. Spinor2D
— Компактная и практически столь же быстрая, что и Rotation2D.
— Легко и точно интерполируется.
На моём нищебродском i7-9750H на 1 ядре за 1 секунду можно выполнить:
— 14 млн поворотов Vec2 с помощью голого угла;
— 8 млн «референсных» интерполяций Rotation2D, реализованных как lerp_over_shortest_arc(a.ToAngle, b.ToAngle, t).ToRotation;
— 14 млн Spinor2D.slerp_precise, или 12 млн Rotation2D.slerp_precise, реализованных как преобразование аргументов в Spinor2D, затем Spinor2D.slerp_precise, затем преобразование результата назад в Rotation2D;
— 58 млн Spinor2D.slerp_fast / 33 млн Rotation2D.slerp_fast через Spinor2D.slerp_fast;
— 70 млн Spinor2D.nlerp / 40 млн Rotation2D.nlerp через Spinor2D.nlerp;
— 161 млн Spinor2D.nlerp_raw без проверок разнонаправленности. В заранее заданной цепочке поворотов, такой как ключевые кадры анимации, можно заранее перевернуть спиннер, образующий тупой угол со своим предшественником, чтобы не обрабатывать этот случай в nlerp;
— 320 млн комбинаций Spinor2D × Spinor2D или полностью идентичных Rotation2D × Rotation2D;
— 310 млн поворотов Vec2 с помощью Spinor2D;
— 325 млн поворотов Vec2 с помощью Rotation2D.
Последние две цифры выглядят неожиданно (казалось бы, в Spinor2D.apply почти удвоенное количество операций), но дают основание утверждать, что Spinor2D может использоваться как универсальная и даже единственная форма поворотов.
Я как-нибудь потом почитаю.
>>365708
Понятно, но на уровне haha slerp-slerp. Откуда конкретно эти смешные формулы с синусами берутся - понятия не имею.
----
Чего-то как-то всё мега-уныло и плохо. Вот я вроде пробовал что-то делать, но всё идёт слишком туго, такое ощущение, что мне часть мозга вырезали и теперь всё вдвое тяжелее.
Хм, я думаю, может, вот так тут писать. Можно накапливать в этот буфер и только, когда полностью заполняется, делать flush. Но всё равно, видимо получится просто несколько опять ничего не делал подряд.
Wed Apr 29, 21:50
Вот, я думаю, надо бы нормальное дерево сделать, но для начала хотя бы несбалансированное, как там делаются красно-чёрмные, я уже чего-то забыл, это там же они являются просто удобной реализацией 2-3 деревьев, но я уже даже, что это за 2-3 деревья не помню.
И unbalanced BST - это вроде же легко, но я вечно делаю какие-то тупые ошибки, которые я напрочь просто не вижу, пока мне прямо в лицо ими не тыкают. И я подумал, что может, было бы неплохо сделать тесты, но тут я максимально завис, потому что генерировать тесты и проверять их как-то сложно.
Wed Apr 29, 23:40
Ну вот я вроде сделал несбалансированное дерево (как Map<K, V> из джавва библиотеки), и вроде как всё выглядит нормально, но всё равно совершенно точно оно не будет работать. В курсе, который я смотрел сколько-то месяцев назад, многие методы дерева реализовывались рекурсивно, но это ведь неправильно, если дерево несбалансированное: тогда будет (может) использоваться линейная память, потому что там не сможет быть применена хвостовая рекурсия (вот там пример реализации ниже).
Вот так там, например, реализовывался метод put(key, value):
> public void put(Key key, Value value) {
> root = put(root, key, value);
> }
>
> // Возвращает либо старую ссылку на x, либо ссылку на новую,
> // только что созданную вершину
> private Node put(Node x, Key key, Value value) {
> if (x == null)
> return new Node(key, value);
>
> int cmp = key.compareTo(x.key);
> if (cmp < 0)
> x.left = put(x.left, key, value);
> else if (cmp > 0)
> x.right = put(x.right, key, value);
> else
> x.value = value;
> return x;
> }
Thu Apr 30, 03:57
Блин, я чего-то, да, немного куда-то провалился. Меня убедили в том, чтобы я перезалил игру змейку, которую я делал миллиард световых лет назад, и вообще, то был не я, и недавно она погибла в связи с непредвиденной экстерминацией моего гитхатаба. Так что я заодно решил её немного подправить, вытащив read-only ресурсы из папки с кодом в туда, куда надо и проделав примерно то же самое с файликом для сохранения таблицы рекордов (ну и, на самом деле, без этого нельзя было бы всё собрать в один executable jar потом). А потом я попытался собрать всё вместе с javafx в один jar, но в итоге ничего не получилось, и пришлось просто скопировать чей-то build.gradle. И, как оказалось, у javafx отдельные реализации для лексуса, мака и венды, так что, мда, получились три архива, каждый из которых весит под 8 мегабайтов.
Ага, дааа, только мне всё ещё нужно написать тесты для своего тупого дерева, в котором я уже обнаружил, что забыл в hibbard deletion для случая с двумя ненулевыми детьми свопнуть не только значения ключей, но и значения, которые, ну, значения (с которыми ключи ассоциируются).
Thu Apr 30, 04:54
Хм, ну, вроде сделал какие-то тесты. В итоге, конечно, неправильно их делал, потому что те сообщения об ошибках, которые выводят тесты, ничего не говорят о структуре дерева, и, когда что-то шло не так, приходилось всё равно тестировать на маленьких деревьях, но ладно. Помимо той ерунды, что я выше упоминал, поправил штуку, которая определяет порядковый номер ключа, и всё, остальное вроде как работает корректно. Вроде.
Я пытался писать более-менее нормально, но всё равно выглядит как-то не очень. Какая-то длинная колбаса на триста строк.
Относительно давно уже стараюсь укладываться в 80 символов максимум на каждой строке, но это только приносит мне боль пока что, когда приходится делать уродливые переносы, лишь бы уложиться. Но вообще по воспоминаниям раньше мне становилось плохо, когда я видел слишком длинные строки кода, так что, возможно, я просто забыл, как это плохо плохо плохо, когда тебе приходится скроллить не только вверх/вниз, но ещё и влево/вправо.
И ещё я перестал ставить кудрявые скобки для циклов и if'ов, у которых однострочное тело. И вообще иногда стал писать их в одну строку, если это что-то уровня if (...) return ... Не знаю, насколько это плохо и насколько хорошо.
И ещё я там решил не укладываться в стандартный Map<K, V> интерфейс, потому что он подразумевает под собой реализацию всякой ерунды вроде map view'ов и прочего. В исходном коде TreeMap вообще что-то жуткое, там какая-то своя иерархия внутренних классов и вообще ничего не понятно. И ещё он требует чихаться исключениями по поводу и без, а я теперь их избегаю выбрасывать. Во всяком случае стараюсь. Один раз бросаю, если пытаешься вызвать tree.put(null, value), потому что, ну, это вообще не имеет смысла.
Fri May 1, 03:28
Чего-то в итоге я ничего толком не сделал, только одну задачку, связанную с BST (ну, наверное, с бинарными деревьями в целом): там надо было сделать из бинарного дерева двусвязный список, переставив ссылки, и это довольно легко рекурсивно решается. И я заодно добавил своему дереву на всякий случай интерфейс, чтобы немного асбстрагироваться от конкретной реализации. Но при тестировании решения этой задачки всё равно использовал конкретную реализацию, потому что интерфейс у меня не предоставляет методов для изменения вершин дерева напрямую.
И ещё узнал про то, как можно восстановить бинарное дерево по его обходам, из видео какой-то индийской женщины. Если есть inorder и preorder обходы (ну, или с inorder и postorder полностью аналогично), то можно точно восстановить его структуру: preorder даёт нам корень, который делит inorder обход на два inorder обхода: для левого и правого поддеревьев.
А вот, если есть postorder и preorder, то тут уже могут быть разные варианты. Например:
> pre: ABC
> post: CBA
Только по этому можно построить кучу разных деревьев (но все они будут по сути такой вертикальной связкой сосисок из трёх вершин). Однозначность может быть только, если дерево будет full (необязательно complete, а только full - это когда у каждой вершины либо два потомка, либо ни одного). Кстати, не знаю, почему так, но мне не особо важно это формально доказывать. (Видимо, следует просто из того, что это так для дерева из трёх вершин.)
Так вот, если есть эти два обхода, то из preorder можно найти корень дерева, а сразу за ним идёт корень левого поддерева, с помощью которого можно выделить postorder обход левого дерева, а там найдётся и postorder правого, из которого достаётся корень правго поддерева, с помощью которого делим preorder обход на два. Короче, мы делим каждый из обходов на два куска и рекурсивно продолжаем делить их дальше. Это проще на примере показать, но мне лень.
Но это всё для лоховских произвольных бинарных деревьев. Если у нас есть BST, то нам достаточно только preorder/postorder обхода для восстановления структуры, потому что inorder в случае BST - это тупо все элементы, перечисленные в возрастающем порядке. Так что можно брать первый элемент preorder, раскидывать остальные элементы на две группы (меньшие и большие его), и дальше всё рекурсивно.
Правда, не знаю, как это на деле реализовать. самый простой вариант - отсортировать preorder/postorder и сохранить его отдельно, но это ведь тупо. К тому же это будет работать же за квадратное время: на каждом уровне дерева надо за линейное время найти корень в inorder обходе, а если дерево вырожденное, то уровней N штук, так что мда. И вдобавок к этому ещё и будет требовать дополнительно O(N) памяти.
Fri May 1, 16:42
Блин, ну вот опять - самая главная проблема в том, что я не знаю, что мне делать. Сижу филадельфию смотрю, мда. Но, вот, блин, что я там хотел: посмотреть дальше про BST, почитать там ссылки, которые я насобирал, в конце концов, дальше делать рекйкастинг, но, да.
Sat May 2, 01:41
Ну да, в итоге весь день ничего не делал. Только реализовал решения вот тех штук, о которых говорил выше (построение деревьев из разных вариантов обходов). И нашёл в интернете вот такое https://www.geeksforgeeks.org/construct-bst-from-given-preorder-traversal-set-2/ решение задачки про построение BST из preorder обхода за линейное время и линейную память, которое я почти не понял.
Там суть в том, что мы поддерживаем стек из вершин от корневой до текущей, но без правых поворотов. Пока ключи в обходе уменьшаются, идём налево, если следующий ключ больше предыдущего, то поднимаемся наверх до нужной вершины, один раз переходим направо, после чего опять налево.
Я как-нибудь потом почитаю.
>>365708
Понятно, но на уровне haha slerp-slerp. Откуда конкретно эти смешные формулы с синусами берутся - понятия не имею.
----
Чего-то как-то всё мега-уныло и плохо. Вот я вроде пробовал что-то делать, но всё идёт слишком туго, такое ощущение, что мне часть мозга вырезали и теперь всё вдвое тяжелее.
Хм, я думаю, может, вот так тут писать. Можно накапливать в этот буфер и только, когда полностью заполняется, делать flush. Но всё равно, видимо получится просто несколько опять ничего не делал подряд.
Wed Apr 29, 21:50
Вот, я думаю, надо бы нормальное дерево сделать, но для начала хотя бы несбалансированное, как там делаются красно-чёрмные, я уже чего-то забыл, это там же они являются просто удобной реализацией 2-3 деревьев, но я уже даже, что это за 2-3 деревья не помню.
И unbalanced BST - это вроде же легко, но я вечно делаю какие-то тупые ошибки, которые я напрочь просто не вижу, пока мне прямо в лицо ими не тыкают. И я подумал, что может, было бы неплохо сделать тесты, но тут я максимально завис, потому что генерировать тесты и проверять их как-то сложно.
Wed Apr 29, 23:40
Ну вот я вроде сделал несбалансированное дерево (как Map<K, V> из джавва библиотеки), и вроде как всё выглядит нормально, но всё равно совершенно точно оно не будет работать. В курсе, который я смотрел сколько-то месяцев назад, многие методы дерева реализовывались рекурсивно, но это ведь неправильно, если дерево несбалансированное: тогда будет (может) использоваться линейная память, потому что там не сможет быть применена хвостовая рекурсия (вот там пример реализации ниже).
Вот так там, например, реализовывался метод put(key, value):
> public void put(Key key, Value value) {
> root = put(root, key, value);
> }
>
> // Возвращает либо старую ссылку на x, либо ссылку на новую,
> // только что созданную вершину
> private Node put(Node x, Key key, Value value) {
> if (x == null)
> return new Node(key, value);
>
> int cmp = key.compareTo(x.key);
> if (cmp < 0)
> x.left = put(x.left, key, value);
> else if (cmp > 0)
> x.right = put(x.right, key, value);
> else
> x.value = value;
> return x;
> }
Thu Apr 30, 03:57
Блин, я чего-то, да, немного куда-то провалился. Меня убедили в том, чтобы я перезалил игру змейку, которую я делал миллиард световых лет назад, и вообще, то был не я, и недавно она погибла в связи с непредвиденной экстерминацией моего гитхатаба. Так что я заодно решил её немного подправить, вытащив read-only ресурсы из папки с кодом в туда, куда надо и проделав примерно то же самое с файликом для сохранения таблицы рекордов (ну и, на самом деле, без этого нельзя было бы всё собрать в один executable jar потом). А потом я попытался собрать всё вместе с javafx в один jar, но в итоге ничего не получилось, и пришлось просто скопировать чей-то build.gradle. И, как оказалось, у javafx отдельные реализации для лексуса, мака и венды, так что, мда, получились три архива, каждый из которых весит под 8 мегабайтов.
Ага, дааа, только мне всё ещё нужно написать тесты для своего тупого дерева, в котором я уже обнаружил, что забыл в hibbard deletion для случая с двумя ненулевыми детьми свопнуть не только значения ключей, но и значения, которые, ну, значения (с которыми ключи ассоциируются).
Thu Apr 30, 04:54
Хм, ну, вроде сделал какие-то тесты. В итоге, конечно, неправильно их делал, потому что те сообщения об ошибках, которые выводят тесты, ничего не говорят о структуре дерева, и, когда что-то шло не так, приходилось всё равно тестировать на маленьких деревьях, но ладно. Помимо той ерунды, что я выше упоминал, поправил штуку, которая определяет порядковый номер ключа, и всё, остальное вроде как работает корректно. Вроде.
Я пытался писать более-менее нормально, но всё равно выглядит как-то не очень. Какая-то длинная колбаса на триста строк.
Относительно давно уже стараюсь укладываться в 80 символов максимум на каждой строке, но это только приносит мне боль пока что, когда приходится делать уродливые переносы, лишь бы уложиться. Но вообще по воспоминаниям раньше мне становилось плохо, когда я видел слишком длинные строки кода, так что, возможно, я просто забыл, как это плохо плохо плохо, когда тебе приходится скроллить не только вверх/вниз, но ещё и влево/вправо.
И ещё я перестал ставить кудрявые скобки для циклов и if'ов, у которых однострочное тело. И вообще иногда стал писать их в одну строку, если это что-то уровня if (...) return ... Не знаю, насколько это плохо и насколько хорошо.
И ещё я там решил не укладываться в стандартный Map<K, V> интерфейс, потому что он подразумевает под собой реализацию всякой ерунды вроде map view'ов и прочего. В исходном коде TreeMap вообще что-то жуткое, там какая-то своя иерархия внутренних классов и вообще ничего не понятно. И ещё он требует чихаться исключениями по поводу и без, а я теперь их избегаю выбрасывать. Во всяком случае стараюсь. Один раз бросаю, если пытаешься вызвать tree.put(null, value), потому что, ну, это вообще не имеет смысла.
Fri May 1, 03:28
Чего-то в итоге я ничего толком не сделал, только одну задачку, связанную с BST (ну, наверное, с бинарными деревьями в целом): там надо было сделать из бинарного дерева двусвязный список, переставив ссылки, и это довольно легко рекурсивно решается. И я заодно добавил своему дереву на всякий случай интерфейс, чтобы немного асбстрагироваться от конкретной реализации. Но при тестировании решения этой задачки всё равно использовал конкретную реализацию, потому что интерфейс у меня не предоставляет методов для изменения вершин дерева напрямую.
И ещё узнал про то, как можно восстановить бинарное дерево по его обходам, из видео какой-то индийской женщины. Если есть inorder и preorder обходы (ну, или с inorder и postorder полностью аналогично), то можно точно восстановить его структуру: preorder даёт нам корень, который делит inorder обход на два inorder обхода: для левого и правого поддеревьев.
А вот, если есть postorder и preorder, то тут уже могут быть разные варианты. Например:
> pre: ABC
> post: CBA
Только по этому можно построить кучу разных деревьев (но все они будут по сути такой вертикальной связкой сосисок из трёх вершин). Однозначность может быть только, если дерево будет full (необязательно complete, а только full - это когда у каждой вершины либо два потомка, либо ни одного). Кстати, не знаю, почему так, но мне не особо важно это формально доказывать. (Видимо, следует просто из того, что это так для дерева из трёх вершин.)
Так вот, если есть эти два обхода, то из preorder можно найти корень дерева, а сразу за ним идёт корень левого поддерева, с помощью которого можно выделить postorder обход левого дерева, а там найдётся и postorder правого, из которого достаётся корень правго поддерева, с помощью которого делим preorder обход на два. Короче, мы делим каждый из обходов на два куска и рекурсивно продолжаем делить их дальше. Это проще на примере показать, но мне лень.
Но это всё для лоховских произвольных бинарных деревьев. Если у нас есть BST, то нам достаточно только preorder/postorder обхода для восстановления структуры, потому что inorder в случае BST - это тупо все элементы, перечисленные в возрастающем порядке. Так что можно брать первый элемент preorder, раскидывать остальные элементы на две группы (меньшие и большие его), и дальше всё рекурсивно.
Правда, не знаю, как это на деле реализовать. самый простой вариант - отсортировать preorder/postorder и сохранить его отдельно, но это ведь тупо. К тому же это будет работать же за квадратное время: на каждом уровне дерева надо за линейное время найти корень в inorder обходе, а если дерево вырожденное, то уровней N штук, так что мда. И вдобавок к этому ещё и будет требовать дополнительно O(N) памяти.
Fri May 1, 16:42
Блин, ну вот опять - самая главная проблема в том, что я не знаю, что мне делать. Сижу филадельфию смотрю, мда. Но, вот, блин, что я там хотел: посмотреть дальше про BST, почитать там ссылки, которые я насобирал, в конце концов, дальше делать рекйкастинг, но, да.
Sat May 2, 01:41
Ну да, в итоге весь день ничего не делал. Только реализовал решения вот тех штук, о которых говорил выше (построение деревьев из разных вариантов обходов). И нашёл в интернете вот такое https://www.geeksforgeeks.org/construct-bst-from-given-preorder-traversal-set-2/ решение задачки про построение BST из preorder обхода за линейное время и линейную память, которое я почти не понял.
Там суть в том, что мы поддерживаем стек из вершин от корневой до текущей, но без правых поворотов. Пока ключи в обходе уменьшаются, идём налево, если следующий ключ больше предыдущего, то поднимаемся наверх до нужной вершины, один раз переходим направо, после чего опять налево.
>Я как-нибудь потом почитаю.
Чел, я сейчас тёщу убью (https://vk.com/wall-48287065_6402). А ну читай, сидит деревья крутит.
>Откуда конкретно эти смешные формулы с синусами берутся - понятия не имею.
Читай: http://allenchou.net/2018/05/game-math-deriving-the-slerp-formula/.
Но я их до сих пор использовала как чёрный ящик, и тебе тоже не предполагалось об этом задумываться, потому что пост был не про slerp. Ты бы ещё к формулам двойных углов прицепился — я же их тоже без вывода привела!
Да, тут кое-какие очевидные опечатки/ошибки (о неочевидных я сама не знаю):
— 100° — это, разумеется, Ⅱ квадрант, я просто набирала пост в блокноте, который отображал квадратик вместо римской цифры, а саму цифру вводила Alt-кодом. Римские цифры есть в японской IME, но тупой файрфокс, если его неделю не перезапускать, начинает тупить по 10 секунд в момент переключения на IME, поэтому я избегала её использовать. Вводить же римские цифры латинскими буквами как-то неинтересно.
— По качественному и количественному составу операций применение Rotation2D к вектору абсолютно идентично комбинациям ротейшнов или спиннеров друг с другом (два сложения и четыре умножения), и результаты по времени тоже были ожидаемо идентичными, просто я в одном случае округлила до десятков миллионов, а в другом — нет, поэтому и получились «320 и 325 млн».
— Формула MIP-уровня на основании длины градиента — clamp(last_level + log₂(grad.len), 0, last_level), логарифм длины градиента практически всегда отрицателен (⇔ длина градиента практически всегда меньше единицы, ⇔ MIP-уровень практически всегда крупнее 1×1). Я изначально написала верно, но когда перечитывала пост перед отправкой, о чём-то замечталась.
И можно кое-что уточнить:
— Slerp схлопывается, потому что предполагает, что результат может быть выражен линейной комбинацией входных векторов, а для коллинеарных векторов это не выполняется.
— Дуга между противоположными поворотами будет непредсказуемой не только из-за погрешности, но и из-за того, что один и тот же поворот может быть выражен двумя противонаправленными спинорами (r и ‒r), которые стали таковыми в ходе своих неизвестных приключений (например, если жук с УЗОРОМ НА ЖОПЕ постепенно развернулся на 360°, он переведёт свой спинор в противоположное состояние, а прокрутившись ещё кружок, т. е. в сумме 720° — вернёт в исходное) и к которым, несмотря на то, что они выражают одинаковый поворот, некоторый спинор V будет (без проверки dot ≥ 0) интерполироваться по комплементарным дугам:
(V → r)
(V → ‒r)
Условие dot ≥ 0 выбирает из этих дуг меньшую, но в крайнем случае, когда дуги (почти) равны и соответствующие им повороты отстоят на 180°, однозначной меньшей дуги не будет. На практике от этого не жарко и не холодно и ты всё равно не захочешь разворачиваться на 180°, не уточнив направление — например, путём вставки промежуточного ключевого кадра с поворотом на 90°.
>Я как-нибудь потом почитаю.
Чел, я сейчас тёщу убью (https://vk.com/wall-48287065_6402). А ну читай, сидит деревья крутит.
>Откуда конкретно эти смешные формулы с синусами берутся - понятия не имею.
Читай: http://allenchou.net/2018/05/game-math-deriving-the-slerp-formula/.
Но я их до сих пор использовала как чёрный ящик, и тебе тоже не предполагалось об этом задумываться, потому что пост был не про slerp. Ты бы ещё к формулам двойных углов прицепился — я же их тоже без вывода привела!
Да, тут кое-какие очевидные опечатки/ошибки (о неочевидных я сама не знаю):
— 100° — это, разумеется, Ⅱ квадрант, я просто набирала пост в блокноте, который отображал квадратик вместо римской цифры, а саму цифру вводила Alt-кодом. Римские цифры есть в японской IME, но тупой файрфокс, если его неделю не перезапускать, начинает тупить по 10 секунд в момент переключения на IME, поэтому я избегала её использовать. Вводить же римские цифры латинскими буквами как-то неинтересно.
— По качественному и количественному составу операций применение Rotation2D к вектору абсолютно идентично комбинациям ротейшнов или спиннеров друг с другом (два сложения и четыре умножения), и результаты по времени тоже были ожидаемо идентичными, просто я в одном случае округлила до десятков миллионов, а в другом — нет, поэтому и получились «320 и 325 млн».
— Формула MIP-уровня на основании длины градиента — clamp(last_level + log₂(grad.len), 0, last_level), логарифм длины градиента практически всегда отрицателен (⇔ длина градиента практически всегда меньше единицы, ⇔ MIP-уровень практически всегда крупнее 1×1). Я изначально написала верно, но когда перечитывала пост перед отправкой, о чём-то замечталась.
И можно кое-что уточнить:
— Slerp схлопывается, потому что предполагает, что результат может быть выражен линейной комбинацией входных векторов, а для коллинеарных векторов это не выполняется.
— Дуга между противоположными поворотами будет непредсказуемой не только из-за погрешности, но и из-за того, что один и тот же поворот может быть выражен двумя противонаправленными спинорами (r и ‒r), которые стали таковыми в ходе своих неизвестных приключений (например, если жук с УЗОРОМ НА ЖОПЕ постепенно развернулся на 360°, он переведёт свой спинор в противоположное состояние, а прокрутившись ещё кружок, т. е. в сумме 720° — вернёт в исходное) и к которым, несмотря на то, что они выражают одинаковый поворот, некоторый спинор V будет (без проверки dot ≥ 0) интерполироваться по комплементарным дугам:
(V → r)
(V → ‒r)
Условие dot ≥ 0 выбирает из этих дуг меньшую, но в крайнем случае, когда дуги (почти) равны и соответствующие им повороты отстоят на 180°, однозначной меньшей дуги не будет. На практике от этого не жарко и не холодно и ты всё равно не захочешь разворачиваться на 180°, не уточнив направление — например, путём вставки промежуточного ключевого кадра с поворотом на 90°.
Thu May 14, 15:01
Не знаю, почему-то стало ещё хуже, чем было. Идея с таймстемпами глупая, к чему они вообще нужны тут? Разве что для того, чтобы как-то разграничивать куски текста плюс они смотрятся неплохо. Может, всё же оставить. Не понимаю, почему меня так сильно выворачивает наизнанку и кушает изнутри, не знаю, как иначе описать, не знаю, что делать.
Максимально наивная идея - вкатываться вайти, но для этого надо будет как минимум нормально доразобраться с Core Java, вспомнить, как писать SQL-запросы, посмотреть, что такое Spring Framework и Hibernate, посмотреть немного про паттерны. И ещё кое-какие не такие важные в этом контексте штуки. Но вообще стоит ли пробовать с моими безумными социальными умениями, если мне страшно даже о мысли об интервью и у меня нет ни малейшего представления о том, что такое работа.
???
Посмотрю немного общих штук про многопоточность, чтобы примерно знать.
Critical Sections
Очевидно, для них нужно свойство mutual exclusion, но также нужно, чтобы была свобода от дедлоков, то есть, чтобы если в любой момент времени несколько потоков хотят войти в критическую секцию, то рано или поздно по крайней мере один из них пройдёт дальше (при условии, что критическая секция гарантированно проходится за конечное время, конечно). Вообще freedom from deadlock - это самая слабая из форм liveness - то, насколько concurrent система живая, гарантия того, что произойдёт что-то благоприятное (в этом случае это гарантия, что произойдёт что-то хорошее хотя бы для кого-то).
Более сильное условие живучести - это lockout-freedom или свобода от голодования. Гарантия того, что если поток ждёт, то рано или поздно ему дадут войти в критическую секцию. Голодание может быть вызвано утечкой ресурсов (resource leak): когда какие-то ресурсы (например, память, file handles, те же lock'и) не отпускаются после того, как он были захвачены, из-за чего никто не может воспользоваться ресурсом. Как правило эти проблемы связаны с процессами, работающими достаточно долгое время, потому что операционная система автоматически освобождает ресурсы завершённых процессов, что частично предотвращает проблемы с утечками ресурсов.
Также голодание может иметь место для процессов в scheduling системах, когда потоку очень долго не достаётся ресурс (CPU time) и нет никакой гарантии, что в итоге когда-то потом ему вообще он достанется. Частный пример для priority-based scheduling систем - это priority inversion: если, допустим, low priority процесс L захватывает ресурс R, то high priority процесс, который тоже хотел бы воспользоваться ресурсом R, не может продолжить своё выполнение, пока L не закончит работу с ресурсом. Возможное простое решение - если два процесса используют один ресурс и более приоритетный прерывает выполнение менее приоритетного, то scheduling система освобождает общий ресурс и отдаёт его high priority процессу. Но это всё довольно легко ломается тем, если бы L сначала прервался процессом M (middle priority), который бы не пользовался ресурсом R, а потом M прервался бы процессом H. И там вроде как есть другие решения, но я как-нибудь потом посмотрю про планировщики.
Максимально мощное условие живучести - bounded waiting: время ожидания не только в теории конечно, но ещё и ограничено (функцией от количества потоков).
Mutex Hardware Solutions
Наиболее простой и очевидный способ реализации критической секции на уровне железа в случае с одним процессором - выключить прерывания во время того, как выполняется критическая секция, тогда, очевидно, во время критической секции не будут выполняться никакие обработчики прерываний, так что процесс не сможет быть прерван физически. В этом же и минус. >Also, if a process halts during its critical section, control will never be returned to another process, effectively halting the entire system. Как бы интуитивно понятно, но не знаю, всё равно не понимаю. Это потому что context switch триггерится, собственно, тоже через прерывания?
>The system clock is typically implemented as a programmable interval timer that periodically interrupts the CPU, which then starts executing a timer interrupt service routine. This routine typically adds one tick to the system clock (a simple coвыставлunter) and handles other periodic housekeeping tasks (preemption, etc.) before returning to the task the CPU was executing before the interruption.
Короче, в любом случае, мне не особо важно это.
Другой подход - использовать спинлоки. Плюс в том, что если использовать из предположения о том, что если известно, что lock отпустится в течение тайм слайса, выделенного потоку, который находится в busy wait, то можно избежать оверхеда контекст свича. Ну а ещё очень легко реализуется. Чаще всего такое используется на уровне ядра ОС и ещё где-то на низком уровне как небольшая оптимизация: немного покрутиться в активном ожидании перед контекст свичем, в остальных же случаях, видимо, это плохая идея, потому что тогда scheduler будет выделять процессорное время на бесполезный busy wait, а это не только трата ресурсов, но ещё и откладывание освобождения lock'а (меньше времени для потока, который удерживает лок, в случае с плохим планировщиком вообще никакого времени для нужного потока). В джавве на эту тему есть java.lang.Thread.onSpinWait(), который можно засунуть внутрь busy wait цикла, чтобы указать виртуальной машине на то, что это действительно busy wait и по возможности лучше выделять больше ресурсов другим потокам. Но опять же, лучше вообще такое не использовать.
Можно было подумать, что спинлок можно реализовать вот так (я не знаю всяких амесблеров, поэтому порядок аргументов какой придётся):
> spin:
> LOAD R0, Lock
> STORE #1, Lock
> CMP R0, #0
> JNZ spin
> # critical section
Но проблема в том, что выполнение потока тут может прерваться после LOAD, из-за чего в регистре будет устаревшая информация. Чтобы всё корректно реализовать, понадобится атомарная инструкция TSL (test and set lock): она загружает в регистр значение по указанному адресу и записывает какое-то ненулевое значение по этому же адресу. В итоге выглядеть будет вот так:
> spin:
> TSL R0, Lock
> CMP R0, #0
> JNZ spin
> # critical section
Самый большой недостаток, как я понял, в том, что эта TSL не всегда может быть реализована на какой-то конкретной архитектуре. Ну и ещё, очевидно, тут может иметь место голодование: другой поток может сколько угодно раз отбирать у тебя флаг Lock. Ещё вроде как проблема в том, что из-за того, что надо поддерживать когерентность кэша, если много потоков одновременно делают TSL, то всё может тормозить. Так что лучше будет, видимо, сделать так:
> do {
> while (lock);
> } while TSL(lock);
В вики это test and test-and-set.
Третий храдварный подход - использовать неблокирующие алгоритмы. Они тоже реализуются с использованием особенных ассемблерных инструкций. Compare-and-swap (CAS) - сравнивает значение по адресу памяти с заданным (old) и, если они совпадают, то записывает туда новое (new). Возвращает то значение, которое она изначально прочитала, чтобы можно было понять, произошла ли запись (или просто boolean, такая вариация обычно называется compare-and-set). Обычно CAS используется вот в таком сценарии: читаем старое значение, вычисляем новое, с помощью CAS пробуем заменить старое на новое. Если не удаётся, то пробуем ещё раз. Очевидно, если много потоков пытаются так обновить, например, какой-нибудь счётчик, то всё будет работать немного медленнее, так что можно делать exponential backoff - экспоненциально увеличивать задержку между запросами (+ возможно, рандомизированная составляющая).
С алгоритмами, использующими CAS, может возникунуть ABA problem - это, когда в промежутке между чтением старого значения и, собственно CAS, значение дважды меняется, второй раз - на то, что было изначально. И вроде значение то же, но оно может быть использовано в другом контексте. Одно из решений - дополнительно вести счётчик количества апдейтов для структуры данных, рассчитывая на то, что за время приостановки выполнения потока счётчик не переполнится. Чтобы уменьшить шансы переполнения, можно вести отдельные счётчики для отдельных элементов внутри структуры данных. Ну, это самый простой способ решения проблемы, там вроде ещё есть какие-то но я потом никогда не посмотрю.
Load-link и store-conditional (LL/SC) - тоже относятся к реализациям lock-free алгоритмов. LL достаёт текущее значение по адресу в памяти, SC после LL пишет туда только, если это место в памяти никем не апдейтилось после LL. Получается, что это как обычное чтение + CAS, только без ABA problem. И вроде это звучит лучше, но, судя по всему судя по википедии если между LL и SC есть достаточно много, то велика вероятность, что SC зафейлится без конкретной причины (ну, то есть в теории всё может работать, а на практике - нет).
И ещё есть fetch-and-add (FAA). По сути это просто атомарный +=a.
Thu May 14, 15:01
Не знаю, почему-то стало ещё хуже, чем было. Идея с таймстемпами глупая, к чему они вообще нужны тут? Разве что для того, чтобы как-то разграничивать куски текста плюс они смотрятся неплохо. Может, всё же оставить. Не понимаю, почему меня так сильно выворачивает наизнанку и кушает изнутри, не знаю, как иначе описать, не знаю, что делать.
Максимально наивная идея - вкатываться вайти, но для этого надо будет как минимум нормально доразобраться с Core Java, вспомнить, как писать SQL-запросы, посмотреть, что такое Spring Framework и Hibernate, посмотреть немного про паттерны. И ещё кое-какие не такие важные в этом контексте штуки. Но вообще стоит ли пробовать с моими безумными социальными умениями, если мне страшно даже о мысли об интервью и у меня нет ни малейшего представления о том, что такое работа.
???
Посмотрю немного общих штук про многопоточность, чтобы примерно знать.
Critical Sections
Очевидно, для них нужно свойство mutual exclusion, но также нужно, чтобы была свобода от дедлоков, то есть, чтобы если в любой момент времени несколько потоков хотят войти в критическую секцию, то рано или поздно по крайней мере один из них пройдёт дальше (при условии, что критическая секция гарантированно проходится за конечное время, конечно). Вообще freedom from deadlock - это самая слабая из форм liveness - то, насколько concurrent система живая, гарантия того, что произойдёт что-то благоприятное (в этом случае это гарантия, что произойдёт что-то хорошее хотя бы для кого-то).
Более сильное условие живучести - это lockout-freedom или свобода от голодования. Гарантия того, что если поток ждёт, то рано или поздно ему дадут войти в критическую секцию. Голодание может быть вызвано утечкой ресурсов (resource leak): когда какие-то ресурсы (например, память, file handles, те же lock'и) не отпускаются после того, как он были захвачены, из-за чего никто не может воспользоваться ресурсом. Как правило эти проблемы связаны с процессами, работающими достаточно долгое время, потому что операционная система автоматически освобождает ресурсы завершённых процессов, что частично предотвращает проблемы с утечками ресурсов.
Также голодание может иметь место для процессов в scheduling системах, когда потоку очень долго не достаётся ресурс (CPU time) и нет никакой гарантии, что в итоге когда-то потом ему вообще он достанется. Частный пример для priority-based scheduling систем - это priority inversion: если, допустим, low priority процесс L захватывает ресурс R, то high priority процесс, который тоже хотел бы воспользоваться ресурсом R, не может продолжить своё выполнение, пока L не закончит работу с ресурсом. Возможное простое решение - если два процесса используют один ресурс и более приоритетный прерывает выполнение менее приоритетного, то scheduling система освобождает общий ресурс и отдаёт его high priority процессу. Но это всё довольно легко ломается тем, если бы L сначала прервался процессом M (middle priority), который бы не пользовался ресурсом R, а потом M прервался бы процессом H. И там вроде как есть другие решения, но я как-нибудь потом посмотрю про планировщики.
Максимально мощное условие живучести - bounded waiting: время ожидания не только в теории конечно, но ещё и ограничено (функцией от количества потоков).
Mutex Hardware Solutions
Наиболее простой и очевидный способ реализации критической секции на уровне железа в случае с одним процессором - выключить прерывания во время того, как выполняется критическая секция, тогда, очевидно, во время критической секции не будут выполняться никакие обработчики прерываний, так что процесс не сможет быть прерван физически. В этом же и минус. >Also, if a process halts during its critical section, control will never be returned to another process, effectively halting the entire system. Как бы интуитивно понятно, но не знаю, всё равно не понимаю. Это потому что context switch триггерится, собственно, тоже через прерывания?
>The system clock is typically implemented as a programmable interval timer that periodically interrupts the CPU, which then starts executing a timer interrupt service routine. This routine typically adds one tick to the system clock (a simple coвыставлunter) and handles other periodic housekeeping tasks (preemption, etc.) before returning to the task the CPU was executing before the interruption.
Короче, в любом случае, мне не особо важно это.
Другой подход - использовать спинлоки. Плюс в том, что если использовать из предположения о том, что если известно, что lock отпустится в течение тайм слайса, выделенного потоку, который находится в busy wait, то можно избежать оверхеда контекст свича. Ну а ещё очень легко реализуется. Чаще всего такое используется на уровне ядра ОС и ещё где-то на низком уровне как небольшая оптимизация: немного покрутиться в активном ожидании перед контекст свичем, в остальных же случаях, видимо, это плохая идея, потому что тогда scheduler будет выделять процессорное время на бесполезный busy wait, а это не только трата ресурсов, но ещё и откладывание освобождения lock'а (меньше времени для потока, который удерживает лок, в случае с плохим планировщиком вообще никакого времени для нужного потока). В джавве на эту тему есть java.lang.Thread.onSpinWait(), который можно засунуть внутрь busy wait цикла, чтобы указать виртуальной машине на то, что это действительно busy wait и по возможности лучше выделять больше ресурсов другим потокам. Но опять же, лучше вообще такое не использовать.
Можно было подумать, что спинлок можно реализовать вот так (я не знаю всяких амесблеров, поэтому порядок аргументов какой придётся):
> spin:
> LOAD R0, Lock
> STORE #1, Lock
> CMP R0, #0
> JNZ spin
> # critical section
Но проблема в том, что выполнение потока тут может прерваться после LOAD, из-за чего в регистре будет устаревшая информация. Чтобы всё корректно реализовать, понадобится атомарная инструкция TSL (test and set lock): она загружает в регистр значение по указанному адресу и записывает какое-то ненулевое значение по этому же адресу. В итоге выглядеть будет вот так:
> spin:
> TSL R0, Lock
> CMP R0, #0
> JNZ spin
> # critical section
Самый большой недостаток, как я понял, в том, что эта TSL не всегда может быть реализована на какой-то конкретной архитектуре. Ну и ещё, очевидно, тут может иметь место голодование: другой поток может сколько угодно раз отбирать у тебя флаг Lock. Ещё вроде как проблема в том, что из-за того, что надо поддерживать когерентность кэша, если много потоков одновременно делают TSL, то всё может тормозить. Так что лучше будет, видимо, сделать так:
> do {
> while (lock);
> } while TSL(lock);
В вики это test and test-and-set.
Третий храдварный подход - использовать неблокирующие алгоритмы. Они тоже реализуются с использованием особенных ассемблерных инструкций. Compare-and-swap (CAS) - сравнивает значение по адресу памяти с заданным (old) и, если они совпадают, то записывает туда новое (new). Возвращает то значение, которое она изначально прочитала, чтобы можно было понять, произошла ли запись (или просто boolean, такая вариация обычно называется compare-and-set). Обычно CAS используется вот в таком сценарии: читаем старое значение, вычисляем новое, с помощью CAS пробуем заменить старое на новое. Если не удаётся, то пробуем ещё раз. Очевидно, если много потоков пытаются так обновить, например, какой-нибудь счётчик, то всё будет работать немного медленнее, так что можно делать exponential backoff - экспоненциально увеличивать задержку между запросами (+ возможно, рандомизированная составляющая).
С алгоритмами, использующими CAS, может возникунуть ABA problem - это, когда в промежутке между чтением старого значения и, собственно CAS, значение дважды меняется, второй раз - на то, что было изначально. И вроде значение то же, но оно может быть использовано в другом контексте. Одно из решений - дополнительно вести счётчик количества апдейтов для структуры данных, рассчитывая на то, что за время приостановки выполнения потока счётчик не переполнится. Чтобы уменьшить шансы переполнения, можно вести отдельные счётчики для отдельных элементов внутри структуры данных. Ну, это самый простой способ решения проблемы, там вроде ещё есть какие-то но я потом никогда не посмотрю.
Load-link и store-conditional (LL/SC) - тоже относятся к реализациям lock-free алгоритмов. LL достаёт текущее значение по адресу в памяти, SC после LL пишет туда только, если это место в памяти никем не апдейтилось после LL. Получается, что это как обычное чтение + CAS, только без ABA problem. И вроде это звучит лучше, но, судя по всему судя по википедии если между LL и SC есть достаточно много, то велика вероятность, что SC зафейлится без конкретной причины (ну, то есть в теории всё может работать, а на практике - нет).
И ещё есть fetch-and-add (FAA). По сути это просто атомарный +=a.
Это LamazeP, но ты, видимо, уже сам успел найти. Кстати, только недавно узнал, что это он написал эти всякие меметичные песенки: фуккирету, PoPiPo и трипл баку.
bonk souls
Мне почему-то захотелось вдруг поиграть в первый дарк в онлайне на начальных локациях, хотя я не очень люблю онлайновые игры в целом и не умею убивать людей в пвп. Но там проблема в том, что для красного ока нужно убить четырёх королей, что сделать на низком SL и с +5 оружием относительно проблематично (урон слишком маленький, не успеваешь убить одного, спавнится уже второй, и они тебя вместе ганкают). Я видел на ютубах максимально аутичный способ сделать это на SL1: нафармить с синих дрейков чешуек (10 человечности и золотое кольцо с змеем для повышения шансов дропа), обменять их на полную драконью форму и забить королей руками. Но у меня не хватило терпения таким заниматься: шанс выпадения чешуек слишком маленький, а для драконьего ковенанта игра уже слишком мертва. Другой вариант — на SL7 за воина (три уровня в силу для двуручного хвата меча): +0 меч нито (при матчмейкинге считается за +5 обычное оружие) в сочетании с power within и c сетом хавела (каменный, может, тоже сойдёт) или просто только с кольцом с красным камнем. Первый вариант не пробовал, второй более-менее работает, если тебе повезёт и ни один из королей не кинет в тебя магический прожектайл, пока будешь далеко от него (у щита с эмблемой недостаточно хорошая защита от магии, чтобы тебя не ваншотнуло с 20% здоровья, необходимыми для эффекта кольца). Я этой ерундой занимался примерно во время стимовской летней распродажи, так что в игре было по ощущениям много новых людей на начальных локациях. Можно было бы даже подумать, что онлайн не сдох, даже ковенант нито работал в некоторых местах, но уже где-то начиная с токсичного болота количество людей сильно падало. А до мета пвп левела мне было лень качаться, да и в любом случае, я всего лишь хотел избивать младенцев, так что забил.
projected rectangle штука
Узнал о штуке, которая называется projected rectangle collision или что-то такое. AABB решает проблему пересечения двух прямоугольников, выровненных по осям координат, и при пересечении разрешает конфликт, выталкивая один из прямоугольников в сторону так, чтобы в результате он прошёл наименьшее расстояние, но такой подход может плохо работать на быстро движущемся прямоугольнике: за один фрейм он может достаточно глубоко войти в другой AABB, чтобы его вытолкнуло с другой стороны. И вот эта штука как раз решает эту проблему. Основная идея в том, что имея вектор перемещения, на который прямоугольник сдвинется за следующий фрейм, мы хотим найти точку на нём, в которой движущийся прямоугольник (RectA) ровно прилегает к тому, против которого мы тестируем пересечение (RectB).
Определим вектор перемещения начальной точкой P и конечной Q. Обе относятся к середине прямоугольника RectA, но сейчас будем думать только о пересечении луча (коллинеарного вектору перемещения) с RectB: как только решим этот вопрос, пересечение RectA с RectB тривиально решится пересечением луча с фиктивным прямоугольником, обрисованным вокруг RectB (с каждой стороны добавить половину соответствующей размерности RectA). P(t) = P + (Q − P) · t — параметризованная прямая, для которой надо найти наименьший параметр t' > 0, ближайшую точку пересечения с RectB. Мы довольно легко можем найти точки пересечения луча с продолжениями сторон
> t_nearX = (RectB.x − P.x) / (Q.x − P.x)
> t_farX = (RectB.x + RectB.size.x − P.x) / (Q.x − P.x)
благодаря тому, что прямоугольники выровнены по осям координат. Названия "near" и "far" — условности, так будем называть точки пересечения с левой и правой сторонами RectB соответственно. Аналогично определяются t_nearY и t_farY для верхней и нижней тут и далее под сторонами подразумеваются их продолжения сторон. Сами точки пересечения обозначим Nx, Fx, Ny и Fy. Если провести несколько параллельных лучей, то можно заметить, что условие t_nearX < t_farY & t_nearY < t_farX выполняется только лишь, когда луч проходит через RectB. Это и будет критерием.
Мы рассмотрели только случай с лучами, падающими слева-направо сверху-вниз, однако все остальные случаи симметричны: для них можно оставить ту же проверку, но перед ней поменять местами значения параметров для отдельных координат far и near, если окажется, что near > far. (Мы ведь можем просто перевернуть рисунок и получить опять уже рассмотренный первый случай.) Один из угловых случаев — когда луч параллелен стороне прямоугольника и пересекает другую в одной точке, но это в теории должно обработаться обычной проверкой, потому что плюс и минус бесконечности, возникающие при вычислениях, корректно обрабатываются. Ещё один угловой случай — когда P(t) совпадает с продолжением одной из сторон. Тогда возникает деление нуля на ноль при вычислении одного из t. Вопрос того, как такое обрабатывать, видимо, зависит от конкретного применения этой штуки, для простоты, наверное, можно игнорировать (если при вычислениях где-то случился NaN, считать, что коллизии нет).
Теперь обе точки N и F пересечения можно определить как
> t_nearMax = max(t_nearX, t_nearY)
> N = (P(t_nearMax), P(t_nearMax))
> F = (min, min)
Ну, то есть самая далёкая из ближайших и самая ближайшая из далёких. Также можно найти нормаль к ближайшей точке пересечения, написав что-то такое (ось ординат направлена вверх, ось абсцисс — направо):
> if (t_nearX > t_nearY):
> if ((Q − P).x < 0):
> norm = (1, 0)
> else:
> norm = (−1, 0)
> elif (t_nearX > t_nearY):
> if ((Q − P).y < 0):
> norm = (0, 1)
> else:
> norm = (0, −1)
Первый if определяет, попал луч в левую/правую сторону или в верхнюю/нижнюю. Ну, а второй уточняет это по тому, куда был направлен луч. Если t_nearX = t_nearY, то это попадание прямо в угол. В таком случае можно, видимо, либо тупо выбрать что-то любое на первой развилке, либо пройти по обоим и сложить результаты, получив что-то вроде (1, −1), например? Во втором случае, мне кажется, так можно застрять внутри RectB. Может быть, можно сравнить составляющие вектора скорости и выбрать на основе того, какая больше? Не знаю.
Зная точку пересечения, можно немного откорректировать вектор скорости. Так, чтобы на следующем фрейме RectA оказался вплотную к RectB:
> velocity.x += |velocity.x| · (1 − t_nearMax) · norm.x
Всё это прекрасно работает в рамках одного RectB, но, когда тестируем коллизии против нескольких статичных прямоугольников, могут возникнуть проблемы из-за порядка, в котором коллизии разрешаются. Допустим, RectA двигается поверх нескольких вплотную стоящих прямоугольников, при этом на него действуют сила тяжести, так что на каждом фрейме вектор скорости направлен наискосок вниз. Если разрешать коллизии в порядке, направленном в ту же сторону, куда движется RectA, проблем не будет, но, если разрешать в противоположном, то RectA начнёт застревать на краях статичных прямоугольников. Всё потому, что он будет сначала вытолкнут в сторону противоположную направлению движения, и только потом наверх, а не наоборот. (Сложно словами объяснить, на картинке понятно.)
Решение проблемы — перебирать статичные прямоугольники в порядке возрастания времени до столкновения с ними (то есть t_nearMax — ключ сортировки). Это вносит проблемы с тем, что сортировка — тяжёлая штука и может оказаться боттлнеком, так что нужно будет применить какие-нибудь меры оптимизации вроде выравнивания статичных прямоугольников по сетке или создания для них какого-то другого разделения пространства, чтобы отсеивать те, что точно не смогут пересечься с RectA.
Это LamazeP, но ты, видимо, уже сам успел найти. Кстати, только недавно узнал, что это он написал эти всякие меметичные песенки: фуккирету, PoPiPo и трипл баку.
bonk souls
Мне почему-то захотелось вдруг поиграть в первый дарк в онлайне на начальных локациях, хотя я не очень люблю онлайновые игры в целом и не умею убивать людей в пвп. Но там проблема в том, что для красного ока нужно убить четырёх королей, что сделать на низком SL и с +5 оружием относительно проблематично (урон слишком маленький, не успеваешь убить одного, спавнится уже второй, и они тебя вместе ганкают). Я видел на ютубах максимально аутичный способ сделать это на SL1: нафармить с синих дрейков чешуек (10 человечности и золотое кольцо с змеем для повышения шансов дропа), обменять их на полную драконью форму и забить королей руками. Но у меня не хватило терпения таким заниматься: шанс выпадения чешуек слишком маленький, а для драконьего ковенанта игра уже слишком мертва. Другой вариант — на SL7 за воина (три уровня в силу для двуручного хвата меча): +0 меч нито (при матчмейкинге считается за +5 обычное оружие) в сочетании с power within и c сетом хавела (каменный, может, тоже сойдёт) или просто только с кольцом с красным камнем. Первый вариант не пробовал, второй более-менее работает, если тебе повезёт и ни один из королей не кинет в тебя магический прожектайл, пока будешь далеко от него (у щита с эмблемой недостаточно хорошая защита от магии, чтобы тебя не ваншотнуло с 20% здоровья, необходимыми для эффекта кольца). Я этой ерундой занимался примерно во время стимовской летней распродажи, так что в игре было по ощущениям много новых людей на начальных локациях. Можно было бы даже подумать, что онлайн не сдох, даже ковенант нито работал в некоторых местах, но уже где-то начиная с токсичного болота количество людей сильно падало. А до мета пвп левела мне было лень качаться, да и в любом случае, я всего лишь хотел избивать младенцев, так что забил.
projected rectangle штука
Узнал о штуке, которая называется projected rectangle collision или что-то такое. AABB решает проблему пересечения двух прямоугольников, выровненных по осям координат, и при пересечении разрешает конфликт, выталкивая один из прямоугольников в сторону так, чтобы в результате он прошёл наименьшее расстояние, но такой подход может плохо работать на быстро движущемся прямоугольнике: за один фрейм он может достаточно глубоко войти в другой AABB, чтобы его вытолкнуло с другой стороны. И вот эта штука как раз решает эту проблему. Основная идея в том, что имея вектор перемещения, на который прямоугольник сдвинется за следующий фрейм, мы хотим найти точку на нём, в которой движущийся прямоугольник (RectA) ровно прилегает к тому, против которого мы тестируем пересечение (RectB).
Определим вектор перемещения начальной точкой P и конечной Q. Обе относятся к середине прямоугольника RectA, но сейчас будем думать только о пересечении луча (коллинеарного вектору перемещения) с RectB: как только решим этот вопрос, пересечение RectA с RectB тривиально решится пересечением луча с фиктивным прямоугольником, обрисованным вокруг RectB (с каждой стороны добавить половину соответствующей размерности RectA). P(t) = P + (Q − P) · t — параметризованная прямая, для которой надо найти наименьший параметр t' > 0, ближайшую точку пересечения с RectB. Мы довольно легко можем найти точки пересечения луча с продолжениями сторон
> t_nearX = (RectB.x − P.x) / (Q.x − P.x)
> t_farX = (RectB.x + RectB.size.x − P.x) / (Q.x − P.x)
благодаря тому, что прямоугольники выровнены по осям координат. Названия "near" и "far" — условности, так будем называть точки пересечения с левой и правой сторонами RectB соответственно. Аналогично определяются t_nearY и t_farY для верхней и нижней тут и далее под сторонами подразумеваются их продолжения сторон. Сами точки пересечения обозначим Nx, Fx, Ny и Fy. Если провести несколько параллельных лучей, то можно заметить, что условие t_nearX < t_farY & t_nearY < t_farX выполняется только лишь, когда луч проходит через RectB. Это и будет критерием.
Мы рассмотрели только случай с лучами, падающими слева-направо сверху-вниз, однако все остальные случаи симметричны: для них можно оставить ту же проверку, но перед ней поменять местами значения параметров для отдельных координат far и near, если окажется, что near > far. (Мы ведь можем просто перевернуть рисунок и получить опять уже рассмотренный первый случай.) Один из угловых случаев — когда луч параллелен стороне прямоугольника и пересекает другую в одной точке, но это в теории должно обработаться обычной проверкой, потому что плюс и минус бесконечности, возникающие при вычислениях, корректно обрабатываются. Ещё один угловой случай — когда P(t) совпадает с продолжением одной из сторон. Тогда возникает деление нуля на ноль при вычислении одного из t. Вопрос того, как такое обрабатывать, видимо, зависит от конкретного применения этой штуки, для простоты, наверное, можно игнорировать (если при вычислениях где-то случился NaN, считать, что коллизии нет).
Теперь обе точки N и F пересечения можно определить как
> t_nearMax = max(t_nearX, t_nearY)
> N = (P(t_nearMax), P(t_nearMax))
> F = (min, min)
Ну, то есть самая далёкая из ближайших и самая ближайшая из далёких. Также можно найти нормаль к ближайшей точке пересечения, написав что-то такое (ось ординат направлена вверх, ось абсцисс — направо):
> if (t_nearX > t_nearY):
> if ((Q − P).x < 0):
> norm = (1, 0)
> else:
> norm = (−1, 0)
> elif (t_nearX > t_nearY):
> if ((Q − P).y < 0):
> norm = (0, 1)
> else:
> norm = (0, −1)
Первый if определяет, попал луч в левую/правую сторону или в верхнюю/нижнюю. Ну, а второй уточняет это по тому, куда был направлен луч. Если t_nearX = t_nearY, то это попадание прямо в угол. В таком случае можно, видимо, либо тупо выбрать что-то любое на первой развилке, либо пройти по обоим и сложить результаты, получив что-то вроде (1, −1), например? Во втором случае, мне кажется, так можно застрять внутри RectB. Может быть, можно сравнить составляющие вектора скорости и выбрать на основе того, какая больше? Не знаю.
Зная точку пересечения, можно немного откорректировать вектор скорости. Так, чтобы на следующем фрейме RectA оказался вплотную к RectB:
> velocity.x += |velocity.x| · (1 − t_nearMax) · norm.x
Всё это прекрасно работает в рамках одного RectB, но, когда тестируем коллизии против нескольких статичных прямоугольников, могут возникнуть проблемы из-за порядка, в котором коллизии разрешаются. Допустим, RectA двигается поверх нескольких вплотную стоящих прямоугольников, при этом на него действуют сила тяжести, так что на каждом фрейме вектор скорости направлен наискосок вниз. Если разрешать коллизии в порядке, направленном в ту же сторону, куда движется RectA, проблем не будет, но, если разрешать в противоположном, то RectA начнёт застревать на краях статичных прямоугольников. Всё потому, что он будет сначала вытолкнут в сторону противоположную направлению движения, и только потом наверх, а не наоборот. (Сложно словами объяснить, на картинке понятно.)
Решение проблемы — перебирать статичные прямоугольники в порядке возрастания времени до столкновения с ними (то есть t_nearMax — ключ сортировки). Это вносит проблемы с тем, что сортировка — тяжёлая штука и может оказаться боттлнеком, так что нужно будет применить какие-нибудь меры оптимизации вроде выравнивания статичных прямоугольников по сетке или создания для них какого-то другого разделения пространства, чтобы отсеивать те, что точно не смогут пересечься с RectA.
>сортировка — тяжёлая штука
Нет, нет, нет, нет, нет!
twinkle star ГиПеРпРоСтРаНсТвЕнНыЕ ДеРеВьЯ star twinkle умеют выполнять частичную, да и полную, сортировку по расстоянию до произвольной точки P.
Мне лень это делать кодом, потому что частичная сортировка даёт такой себе результат (его можно использовать, но на него неприятно смотреть, а мы же тут эбаут зрелищность), а полная — сложна. Но я распишу словами.
— Для частичной сортировки достаточно слегка модифицировать обход дерева: в промежуточных узлах спускаться не «сначала в левый узел, потом в правый», а «сначала в ближний к P, потом в дальний». Понятно, что какая-то часть дальнего узла может быть ближе какой-то части ближнего, а то и вообще дальний и ближний не определяться однозначно. Но если полная отсортированность для нас не жизненно важна, то частичная, получаемая таким нетребовательным образом, может оказаться оптимальной.
— Для полной сортировки нужно вести КУЧУ (очередь с приоритетом) с 2 типами элементов: промежуточные узлы дерева либо объекты сцены. КЛЮЧОМ в КУЧЕ будет: для промежуточных узлов — минимальное расстояние от P до AABB узла (в 2D это расстояние до его стороны или угловой точки), для объектов — расстояние от P до любой нужной точки объекта. (Из построения, любая точка объекта находится внутри его AABB.)
Изначально добавляем в пустую КУЧУ корень дерева.
Далее, пока куча непуста:
1. Извлекаем минимальный элемент из кучи.
1.1. Если мы извлекли объект — то он будет очередным элементом отсортированного списка.
1.2. Если мы извлекли промежуточный узел — добавляем в кучу всех его непосредственных детей (left, right и объекты-items).
Спрашивается, а чем это отличается от сортировки кучей — как если бы мы, безо всяких деревьев, просто добавили все объекты сцены в кучу и затем извлекали в отсортированном порядке?
Во-первых, при сортировке кучей её средний размер составит N/2, а максимальный ровно N — число объектов в сцене. Если в сцене 100 000 объектов, в куче изначально будет столько же элементов.
При ведении же кучи из узлов её средний и максимальный размеры будут... ну, элементов 10–20. Более формально, скорее всего, это будет величина порядка log(N) +(×?) MIN_LEAFS_IN_NODE. А это гораздо лучше, чем 100 000: высота кучи отличается вряд ли больше чем на порядок (лол), но прыжки, кэш, локальность, ну ты шаришь. Наверное, специально построенным примером её можно выродить и до размера N, но на настоящих сценах такого https://www.youtube.com/watch?v=tTGCHUj6rMs не будет. А ведь сложность сортировки кучей составляет, строго говоря, не N log N, а именно N log AverageHeapSize.
Во-вторых, зачастую нам нужны объекты лишь в каком-то радиусе. Ну так как только мы стали получать объекты за пределами этого радиуса, мы просто останавливаемся! Тогда, если в нашей сцене N=100 000 объектов, а в радиус попали Q=10, то объём работы, выполненной, чтобы это определить, будет приближен к Q + log(N) = O(Q + ε). Совершенно очевидно, что сложность O(Q) — линейная зависимость от числа возвращаемых объектов — является минимально возможной.
Ебать эти деревья охуенные, да? Вот бы мне девочку-гиперпространственное дерево.
>сортировка — тяжёлая штука
Нет, нет, нет, нет, нет!
twinkle star ГиПеРпРоСтРаНсТвЕнНыЕ ДеРеВьЯ star twinkle умеют выполнять частичную, да и полную, сортировку по расстоянию до произвольной точки P.
Мне лень это делать кодом, потому что частичная сортировка даёт такой себе результат (его можно использовать, но на него неприятно смотреть, а мы же тут эбаут зрелищность), а полная — сложна. Но я распишу словами.
— Для частичной сортировки достаточно слегка модифицировать обход дерева: в промежуточных узлах спускаться не «сначала в левый узел, потом в правый», а «сначала в ближний к P, потом в дальний». Понятно, что какая-то часть дальнего узла может быть ближе какой-то части ближнего, а то и вообще дальний и ближний не определяться однозначно. Но если полная отсортированность для нас не жизненно важна, то частичная, получаемая таким нетребовательным образом, может оказаться оптимальной.
— Для полной сортировки нужно вести КУЧУ (очередь с приоритетом) с 2 типами элементов: промежуточные узлы дерева либо объекты сцены. КЛЮЧОМ в КУЧЕ будет: для промежуточных узлов — минимальное расстояние от P до AABB узла (в 2D это расстояние до его стороны или угловой точки), для объектов — расстояние от P до любой нужной точки объекта. (Из построения, любая точка объекта находится внутри его AABB.)
Изначально добавляем в пустую КУЧУ корень дерева.
Далее, пока куча непуста:
1. Извлекаем минимальный элемент из кучи.
1.1. Если мы извлекли объект — то он будет очередным элементом отсортированного списка.
1.2. Если мы извлекли промежуточный узел — добавляем в кучу всех его непосредственных детей (left, right и объекты-items).
Спрашивается, а чем это отличается от сортировки кучей — как если бы мы, безо всяких деревьев, просто добавили все объекты сцены в кучу и затем извлекали в отсортированном порядке?
Во-первых, при сортировке кучей её средний размер составит N/2, а максимальный ровно N — число объектов в сцене. Если в сцене 100 000 объектов, в куче изначально будет столько же элементов.
При ведении же кучи из узлов её средний и максимальный размеры будут... ну, элементов 10–20. Более формально, скорее всего, это будет величина порядка log(N) +(×?) MIN_LEAFS_IN_NODE. А это гораздо лучше, чем 100 000: высота кучи отличается вряд ли больше чем на порядок (лол), но прыжки, кэш, локальность, ну ты шаришь. Наверное, специально построенным примером её можно выродить и до размера N, но на настоящих сценах такого https://www.youtube.com/watch?v=tTGCHUj6rMs не будет. А ведь сложность сортировки кучей составляет, строго говоря, не N log N, а именно N log AverageHeapSize.
Во-вторых, зачастую нам нужны объекты лишь в каком-то радиусе. Ну так как только мы стали получать объекты за пределами этого радиуса, мы просто останавливаемся! Тогда, если в нашей сцене N=100 000 объектов, а в радиус попали Q=10, то объём работы, выполненной, чтобы это определить, будет приближен к Q + log(N) = O(Q + ε). Совершенно очевидно, что сложность O(Q) — линейная зависимость от числа возвращаемых объектов — является минимально возможной.
Ебать эти деревья охуенные, да? Вот бы мне девочку-гиперпространственное дерево.
Понятно.
недоеденный кусок пятой части clrs
Я там в последний раз остановился на главе про опять оценку вероятностей всяких приколов с использованием индикаторных случайных величин (тех, которые могут принимать только значения 0 или 1). В частности там было про birthday paradox, про который я уже смотрел (>>316868 →). И основной посыл был в том, что при возможности нужно абузить линейность мат ожидания (которая работает даже на зависимых случайных величинах), разбивая большую задачу на несколько маленьких. С непривычки очень сильно затупил на большинстве задачек, ещё и теорию вероятностей частично забыл, так что тут будет много всякой простой ерунды.
дни рождения или дни рождений чего
> # Сколько человек должно быть в комнате, чтобы по крайней мере с вероятностью 0.5 у кого-то был ДР в тот же день, что и у тебя?
Тут, очевидно, надо было посчитать то, что проще всего — вероятность, что все остальные люди (k штук) родились в другой день, и вычесть эту вероятность из единицы: 1 − (364 / 365)^k ≥ 0.5.
> # Чтобы вероятность того, что два человека родились в конкретный выбранный день, была ≥ 0.5?
Опять же, тот же подход: находим вероятность, что никто не родился в этот день, потом — что родился только один человек, складываем и вычитаем из единицы: 1 − ((364 / 365)^k + k · (1 / 365) · (364 / 365)^(k − 1)) ≥ 0.5. (Количество человек, родившихся в один заданный день, — просто по биномиальному распределению.)
> # Чтобы было вероятно, что у трёх человек ДР в один день?
Обозначим за n количество дней в году. И опять то же самое: Pr{у трёх в один день} = 1 − Pr{у всех в разные дни} − Pr{есть по крайней мере один день, в который родились ровно два человека, ни в один день не родилось сразу три}. На самом деле, уже выглядит довольно сложно, но можно попробовать решить:
> Pr{у всех в разные} = n / n · (n − 1) / n · (n − 2) / n · ... · (n − (k − 1)) / n
>
> (k | 2) · (k − 2 | 2) · ... · (k − 2(i − 1) | 2) — количество упорядоченных наборов из i пар людей, у которых ДР в один день.
> (n | i) — количество способов выбрать набор дней рождения для набора из i пар людей. То есть на каждый набор дней рождения будет приходиться несколько отличающихся только порядком наборов пар людей.
> (1 / n^2)^i — вероятность получить какой-то набор из i пар людей. Требуется не только попарная независимость дней рождения, но и ещё, чтобы пары людей были совместно независимы. У меня немного болит голова, когда пытаюсь понять, что могла бы означать эта (не)зависимость ДР на практике. Для двух человек всё просто: близнецы и двойняшки нарушают независимость. Но вот, что насчёт зависимости пусть даже трёх ДР при независимости любых двух? Не могу придумать ничего лучше, чем заранее сказать, что в комнате не может быть такого, чтобы одновременно три человека имели одинаковые имена и они ещё бы и родились в один день, но это выглядит слишком искусственно.
> (1 − i / n) · (1 − i / n − 1 / n) · ... · (1 − i / n − (k − 2i − 1) / n) — вероятность для оставшихся (k − 2i) людей получить дни рождения в отличные от i занятых дней, чтобы случайно не образовался день, в который родились три и больше человек.
>
> Pr{есть ровно i > 0 пар людей с ДР в один день} = Все выражения, написанные выше, перемножить. Выражение для количества наборов пар складывается гармошкой при расписывании биномиальных коэффициентов по определению, штуки вида (1 + x) можно оценить экспонентой, но всё равно получится сложное выражение, которое потом придётся просуммировать по количеству пар i = 1..⌊k / 2⌋, чтобы подставить это выражение ещё в другое, которое опять придётся как-то оценивать.
Это то, как можно было бы в тупую найти аналитическое решение, после чего, скажем, перебором по n найти момент, когда вероятность получить тройку людей, была бы больше 0.5. А можно поступить проще и сделать более грубую оценку через индикаторные переменные. Пусть X_ijm = 1, если у i, j и m ДР в один день. Вероятность этого равна 1 / n^2 (i задаёт конкретный день; вероятность, что в него попадут одновременно j и m равна (1 / n) · (1 / n)).
> E[X] = ΣE[X_ijm] = (k | 3) · (1 / n^2) ≥? 1
> k · (k − 1) · (k − 2) ≥? 6n^2
Левая часть монотонно возрастает при k ≥ 3, так что нужное значение можно найти экспоненциальным поиском.
На самом деле, с вариантом с аналитическим жутким решением всё не так плохо: его жуткость линейно зависит от того, сколько дней рождения мы хотим получить в один день. Для четырёх человек только лишь добавится одно слагаемое, которое будет аналогично тому, что про пары людей, только с тройками.
шары и урны слишком поздно вспомнил слово корзинка
> # Бросаем шары в урны, при каждом броске есть одинаковая вероятность попасть в каждую из урн. Урн b штук. Сколько шаров в среднем надо закинуть, чтобы можно было ожидать, что в каждой урне есть по крайней мере один шар?
Эта задачка ещё называется coupon collector's problem. Решается, на самом деле, не очень очевидно (в плане выбора случайных величин): пусть n_i — количество бросков, необходимых для i-ого попадания в пустую урну (считая от последнего попадания). E[n_i] = 1 / ((b − i + 1) / b) = b / (b − i + 1), потому что геометрическое распределение и перед i-ым новым попаданием у нас есть (b − i + 1) пустых урн. E[n] = ΣE[n_i] = Σ{b / (b − i + 1)} = b · Σ{1 / i} = b · (ln(b) + O(1)).
> # Кидаем n шаров в n урн. E[пустых урн] — ? E[урн с ровно одним шаром] — ?
С этим тоже почему-то затупил. Пусть X_i — i-ая урна пуста после всех n бросков, тогда:
> E[X] = ΣE[X_i] = [все n раз надо промахнуться мимо i-ой урны] = Σ(1 − 1 / n)^n = n · (1 − 1 / n)^n
Теперь со вторым вопросом: пусть Y_i — в i-ой урне оказался ровно один шар после всех бросков, тогда:
> E[Y] = ΣE[Y_i] = [из всех бросков попасть должен только один, снова биномиальное распределение] = Σ(n | 1) · (1 / n) · (1 − 1 / n)^(n − 1) = n · (1 − 1 / n)^(n − 1)
страйки
> # Подбрасываем честную монетку N раз. Нужно оценить длину максимальной непрерывной последовательности орлов.
Здесь используется по духу тот же способ, что и при оценке частичной суммы гармонического ряда (>>304136 →): разделить элементы суммы и оценить каждый кусочек отдельно. На самом деле, как я понял, здесь нельзя так просто прийти к хорошей оценке, если не иметь никакого предположения, задачка звучала как "докажите, что максимальная длина равна Θ(lgN)". Ох, это уже глупо пытаться уместить в текст, на картинке всё расписано. Да и то, что выше, это ведь просто какие-то глупые задачки, не понимаю, зачем это писать. Мне кажется, если не напишу, то не запомню. Я ничего не запоминаю и ничего не помню.
Э-э, ну, вывод из всего этого такой, что при подбрасываниях монетки мы скорее получим логарифмический страйк, чем нет, но и чего-то длиннее, чем это, мы, вероятно, выбить не сможем. И там ещё было упражнение про то, что нижнюю границу можно ещё немного уточнить: с вероятностью (1 − 1 / n) можем получить что-то чуть хуже, чем логарифм: страйк длиной lgN − 2lglgN.
То же самое можно было бы сделать и с использованием индикаторных случайных величин, хоть и довольно грубо. Пусть X_ik — есть ли, начиная с i-ой позиции, страйк в k орлов. Тогда E[X] = ΣE[X_ik] = Σ1 / 2^k = (n − k + 1) / 2^k — мат ожидание количества страйков длины k. Правда, как-то странно, потому что так мы посчитаем два раза страйк длины (k + 1), например. Если попробовать k = c · lgN, то получится, что E[X] = Θ(1 / n^(c − 1)). Даже при c = 2 ожидаемое количество страйков < 1, а при c = 0.5 оно супер-возрастает. Отсюда можно интуитивно сделать вывод о том, что в среднем длина максимального страйка будет Θ(lgN). Но это какое-то слишком странное обоснование.
Понятно.
недоеденный кусок пятой части clrs
Я там в последний раз остановился на главе про опять оценку вероятностей всяких приколов с использованием индикаторных случайных величин (тех, которые могут принимать только значения 0 или 1). В частности там было про birthday paradox, про который я уже смотрел (>>316868 →). И основной посыл был в том, что при возможности нужно абузить линейность мат ожидания (которая работает даже на зависимых случайных величинах), разбивая большую задачу на несколько маленьких. С непривычки очень сильно затупил на большинстве задачек, ещё и теорию вероятностей частично забыл, так что тут будет много всякой простой ерунды.
дни рождения или дни рождений чего
> # Сколько человек должно быть в комнате, чтобы по крайней мере с вероятностью 0.5 у кого-то был ДР в тот же день, что и у тебя?
Тут, очевидно, надо было посчитать то, что проще всего — вероятность, что все остальные люди (k штук) родились в другой день, и вычесть эту вероятность из единицы: 1 − (364 / 365)^k ≥ 0.5.
> # Чтобы вероятность того, что два человека родились в конкретный выбранный день, была ≥ 0.5?
Опять же, тот же подход: находим вероятность, что никто не родился в этот день, потом — что родился только один человек, складываем и вычитаем из единицы: 1 − ((364 / 365)^k + k · (1 / 365) · (364 / 365)^(k − 1)) ≥ 0.5. (Количество человек, родившихся в один заданный день, — просто по биномиальному распределению.)
> # Чтобы было вероятно, что у трёх человек ДР в один день?
Обозначим за n количество дней в году. И опять то же самое: Pr{у трёх в один день} = 1 − Pr{у всех в разные дни} − Pr{есть по крайней мере один день, в который родились ровно два человека, ни в один день не родилось сразу три}. На самом деле, уже выглядит довольно сложно, но можно попробовать решить:
> Pr{у всех в разные} = n / n · (n − 1) / n · (n − 2) / n · ... · (n − (k − 1)) / n
>
> (k | 2) · (k − 2 | 2) · ... · (k − 2(i − 1) | 2) — количество упорядоченных наборов из i пар людей, у которых ДР в один день.
> (n | i) — количество способов выбрать набор дней рождения для набора из i пар людей. То есть на каждый набор дней рождения будет приходиться несколько отличающихся только порядком наборов пар людей.
> (1 / n^2)^i — вероятность получить какой-то набор из i пар людей. Требуется не только попарная независимость дней рождения, но и ещё, чтобы пары людей были совместно независимы. У меня немного болит голова, когда пытаюсь понять, что могла бы означать эта (не)зависимость ДР на практике. Для двух человек всё просто: близнецы и двойняшки нарушают независимость. Но вот, что насчёт зависимости пусть даже трёх ДР при независимости любых двух? Не могу придумать ничего лучше, чем заранее сказать, что в комнате не может быть такого, чтобы одновременно три человека имели одинаковые имена и они ещё бы и родились в один день, но это выглядит слишком искусственно.
> (1 − i / n) · (1 − i / n − 1 / n) · ... · (1 − i / n − (k − 2i − 1) / n) — вероятность для оставшихся (k − 2i) людей получить дни рождения в отличные от i занятых дней, чтобы случайно не образовался день, в который родились три и больше человек.
>
> Pr{есть ровно i > 0 пар людей с ДР в один день} = Все выражения, написанные выше, перемножить. Выражение для количества наборов пар складывается гармошкой при расписывании биномиальных коэффициентов по определению, штуки вида (1 + x) можно оценить экспонентой, но всё равно получится сложное выражение, которое потом придётся просуммировать по количеству пар i = 1..⌊k / 2⌋, чтобы подставить это выражение ещё в другое, которое опять придётся как-то оценивать.
Это то, как можно было бы в тупую найти аналитическое решение, после чего, скажем, перебором по n найти момент, когда вероятность получить тройку людей, была бы больше 0.5. А можно поступить проще и сделать более грубую оценку через индикаторные переменные. Пусть X_ijm = 1, если у i, j и m ДР в один день. Вероятность этого равна 1 / n^2 (i задаёт конкретный день; вероятность, что в него попадут одновременно j и m равна (1 / n) · (1 / n)).
> E[X] = ΣE[X_ijm] = (k | 3) · (1 / n^2) ≥? 1
> k · (k − 1) · (k − 2) ≥? 6n^2
Левая часть монотонно возрастает при k ≥ 3, так что нужное значение можно найти экспоненциальным поиском.
На самом деле, с вариантом с аналитическим жутким решением всё не так плохо: его жуткость линейно зависит от того, сколько дней рождения мы хотим получить в один день. Для четырёх человек только лишь добавится одно слагаемое, которое будет аналогично тому, что про пары людей, только с тройками.
шары и урны слишком поздно вспомнил слово корзинка
> # Бросаем шары в урны, при каждом броске есть одинаковая вероятность попасть в каждую из урн. Урн b штук. Сколько шаров в среднем надо закинуть, чтобы можно было ожидать, что в каждой урне есть по крайней мере один шар?
Эта задачка ещё называется coupon collector's problem. Решается, на самом деле, не очень очевидно (в плане выбора случайных величин): пусть n_i — количество бросков, необходимых для i-ого попадания в пустую урну (считая от последнего попадания). E[n_i] = 1 / ((b − i + 1) / b) = b / (b − i + 1), потому что геометрическое распределение и перед i-ым новым попаданием у нас есть (b − i + 1) пустых урн. E[n] = ΣE[n_i] = Σ{b / (b − i + 1)} = b · Σ{1 / i} = b · (ln(b) + O(1)).
> # Кидаем n шаров в n урн. E[пустых урн] — ? E[урн с ровно одним шаром] — ?
С этим тоже почему-то затупил. Пусть X_i — i-ая урна пуста после всех n бросков, тогда:
> E[X] = ΣE[X_i] = [все n раз надо промахнуться мимо i-ой урны] = Σ(1 − 1 / n)^n = n · (1 − 1 / n)^n
Теперь со вторым вопросом: пусть Y_i — в i-ой урне оказался ровно один шар после всех бросков, тогда:
> E[Y] = ΣE[Y_i] = [из всех бросков попасть должен только один, снова биномиальное распределение] = Σ(n | 1) · (1 / n) · (1 − 1 / n)^(n − 1) = n · (1 − 1 / n)^(n − 1)
страйки
> # Подбрасываем честную монетку N раз. Нужно оценить длину максимальной непрерывной последовательности орлов.
Здесь используется по духу тот же способ, что и при оценке частичной суммы гармонического ряда (>>304136 →): разделить элементы суммы и оценить каждый кусочек отдельно. На самом деле, как я понял, здесь нельзя так просто прийти к хорошей оценке, если не иметь никакого предположения, задачка звучала как "докажите, что максимальная длина равна Θ(lgN)". Ох, это уже глупо пытаться уместить в текст, на картинке всё расписано. Да и то, что выше, это ведь просто какие-то глупые задачки, не понимаю, зачем это писать. Мне кажется, если не напишу, то не запомню. Я ничего не запоминаю и ничего не помню.
Э-э, ну, вывод из всего этого такой, что при подбрасываниях монетки мы скорее получим логарифмический страйк, чем нет, но и чего-то длиннее, чем это, мы, вероятно, выбить не сможем. И там ещё было упражнение про то, что нижнюю границу можно ещё немного уточнить: с вероятностью (1 − 1 / n) можем получить что-то чуть хуже, чем логарифм: страйк длиной lgN − 2lglgN.
То же самое можно было бы сделать и с использованием индикаторных случайных величин, хоть и довольно грубо. Пусть X_ik — есть ли, начиная с i-ой позиции, страйк в k орлов. Тогда E[X] = ΣE[X_ik] = Σ1 / 2^k = (n − k + 1) / 2^k — мат ожидание количества страйков длины k. Правда, как-то странно, потому что так мы посчитаем два раза страйк длины (k + 1), например. Если попробовать k = c · lgN, то получится, что E[X] = Θ(1 / n^(c − 1)). Даже при c = 2 ожидаемое количество страйков < 1, а при c = 0.5 оно супер-возрастает. Отсюда можно интуитивно сделать вывод о том, что в среднем длина максимального страйка будет Θ(lgN). Но это какое-то слишком странное обоснование.
Я не понимаю абсолютно ничего в электричестве, но, судя по всему, да, наверное, нельзя (в плане вреда для электроприбора, на человека всё равно) тыкать железкой в штуки под напряжением. Или можно, но, если ты не заземлён, а может и так нельзя, короче, не понимаю ничего, никогда не понимал физику и прочее такое. Не знаю, может быть, починит потом, может, там ещё что-то подохло в бп, может, в самом компе что-то подохло, так что придётся ещё неопределённое время довольствоваться своим старым компом, который уже и не совсем мой, но им всё равно сейчас и до этого не очень активно пользовались. Тут внезапно добавилось много каких-то крошечных плашек оперативки с тех пор, как комп стал не моим, из два-ядра-два-гига он превратился в два-ядра-шесть-гигов монстра. Можно открывать миллиард вкладок в браузере, но цп слишком слабый всё равно. Единственный плюс всей этой ситуации в том, что забрав эту пеку, я вместе с ней утащил и немного тусклый квадратный монитор к ней, поставил рядом со своим нормальным широким. Боюсь, что теперь слишком привыкну и будет больно обратно на один переходить. Впрочем, может быть, и нет: особых плюсов, кроме того, что можно мультики на соседнем экране смотреть, я не заметил пока что.
Недавно решил, наверное, снова начать бегать, потому что чувствую себя совсем-совсем плохо из-за того, что очень мало двигаюсь. Пару недель до этого катался на велосипеде немного, но это не совсем то, потому что слишком слабая нагрузка: только поначалу было тяжело, но потом быстро адаптируешься и нужно либо постоянно быстро ехать, либо ехать постоянно в гору, либо очень долго ехать, чтобы уставать, либо иметь велосипед со скоростями, чтобы сделать побольше передаточное число, какое умное слово, не знаю, что это, кажется, тупо соотношение количеств зубьев на звёздочках. Правда, сейчас это очень сложно назвать бегом: больше двух минут подряд даже в медленном темпе я не выдерживаю и перехожу на шаг на пару минут. Пока что только два раза бегал, сегодня, наверное, снова пойду. Я думал, что волосы будут мешать сильно, но оказалось, что нет. Минус одна причина подвергать себя социальному изнасилованию и идти в парикмахерскую, чтобы убрать этот ужас с головы. Правда, социальных изнасилований в ближайшее время избежать не удастся и становится тошно только от одной мысли, что придётся что-то там говорить с незнакомыми людьми. Да, это ужасно, примерно как то, что я влез, ну, как влез, без обязательств, конечно, в какую-то глупую недостажировку про непонятно что, на которую брали абсолютно всех, и одним из заданий, с выполнением которого я сильно опоздал, но вроде как конкретных сроков там нет, было сверстать копию экрана какого-нибудь мессенджера. Я потратил на это целый день и чувствую себя слишком выжатым: ты пытаешься что-то сделать, но оно никогда не делается так, как ты хочешь, отвратительно. В итоге получился какой-то жуткий флексбоксовый монстр, ещё не проверяли, не знаю, примут ли такое. Может быть, цсс гридами было бы проще, но уже неважно, если не примут, то, скорее всего, не буду переделывать, слишком сложно, мой первый и последний подобный опыт с этим хтмл цсс.
Хочу сделать штуку, которая логинится во вконтакт и что-нибудь в нём делает, но чего-то у меня какие-то слишком большие проблемы с отправкой запросов через apache httpclient, чтобы залогиниться: слишком поздно понял, что URI builder оттуда же меняет некоторые символы на коды, эскейпнутые знаком процента, а вконтакт почему-то наоборот хочет, чтобы я отправлял прямо так что-то такое (не прямо такое, но примерно)
>https://oauth.vk.com/authorize?client_id=123&redirect_uri=https://oauth.vk.com/blank.html&response_type=token
А если использовать процентовые коды в значении параметр redirect_uri, то он делает несколько раз редирект и даёт какой-то странный ответ. Может быть, вместо стороннего можно использовать httpclient из одиннадцатой джавы, не знаю, лучше ли он, короче, не знаю пока что, потом попробую сделать.
Я не понимаю абсолютно ничего в электричестве, но, судя по всему, да, наверное, нельзя (в плане вреда для электроприбора, на человека всё равно) тыкать железкой в штуки под напряжением. Или можно, но, если ты не заземлён, а может и так нельзя, короче, не понимаю ничего, никогда не понимал физику и прочее такое. Не знаю, может быть, починит потом, может, там ещё что-то подохло в бп, может, в самом компе что-то подохло, так что придётся ещё неопределённое время довольствоваться своим старым компом, который уже и не совсем мой, но им всё равно сейчас и до этого не очень активно пользовались. Тут внезапно добавилось много каких-то крошечных плашек оперативки с тех пор, как комп стал не моим, из два-ядра-два-гига он превратился в два-ядра-шесть-гигов монстра. Можно открывать миллиард вкладок в браузере, но цп слишком слабый всё равно. Единственный плюс всей этой ситуации в том, что забрав эту пеку, я вместе с ней утащил и немного тусклый квадратный монитор к ней, поставил рядом со своим нормальным широким. Боюсь, что теперь слишком привыкну и будет больно обратно на один переходить. Впрочем, может быть, и нет: особых плюсов, кроме того, что можно мультики на соседнем экране смотреть, я не заметил пока что.
Недавно решил, наверное, снова начать бегать, потому что чувствую себя совсем-совсем плохо из-за того, что очень мало двигаюсь. Пару недель до этого катался на велосипеде немного, но это не совсем то, потому что слишком слабая нагрузка: только поначалу было тяжело, но потом быстро адаптируешься и нужно либо постоянно быстро ехать, либо ехать постоянно в гору, либо очень долго ехать, чтобы уставать, либо иметь велосипед со скоростями, чтобы сделать побольше передаточное число, какое умное слово, не знаю, что это, кажется, тупо соотношение количеств зубьев на звёздочках. Правда, сейчас это очень сложно назвать бегом: больше двух минут подряд даже в медленном темпе я не выдерживаю и перехожу на шаг на пару минут. Пока что только два раза бегал, сегодня, наверное, снова пойду. Я думал, что волосы будут мешать сильно, но оказалось, что нет. Минус одна причина подвергать себя социальному изнасилованию и идти в парикмахерскую, чтобы убрать этот ужас с головы. Правда, социальных изнасилований в ближайшее время избежать не удастся и становится тошно только от одной мысли, что придётся что-то там говорить с незнакомыми людьми. Да, это ужасно, примерно как то, что я влез, ну, как влез, без обязательств, конечно, в какую-то глупую недостажировку про непонятно что, на которую брали абсолютно всех, и одним из заданий, с выполнением которого я сильно опоздал, но вроде как конкретных сроков там нет, было сверстать копию экрана какого-нибудь мессенджера. Я потратил на это целый день и чувствую себя слишком выжатым: ты пытаешься что-то сделать, но оно никогда не делается так, как ты хочешь, отвратительно. В итоге получился какой-то жуткий флексбоксовый монстр, ещё не проверяли, не знаю, примут ли такое. Может быть, цсс гридами было бы проще, но уже неважно, если не примут, то, скорее всего, не буду переделывать, слишком сложно, мой первый и последний подобный опыт с этим хтмл цсс.
Хочу сделать штуку, которая логинится во вконтакт и что-нибудь в нём делает, но чего-то у меня какие-то слишком большие проблемы с отправкой запросов через apache httpclient, чтобы залогиниться: слишком поздно понял, что URI builder оттуда же меняет некоторые символы на коды, эскейпнутые знаком процента, а вконтакт почему-то наоборот хочет, чтобы я отправлял прямо так что-то такое (не прямо такое, но примерно)
>https://oauth.vk.com/authorize?client_id=123&redirect_uri=https://oauth.vk.com/blank.html&response_type=token
А если использовать процентовые коды в значении параметр redirect_uri, то он делает несколько раз редирект и даёт какой-то странный ответ. Может быть, вместо стороннего можно использовать httpclient из одиннадцатой джавы, не знаю, лучше ли он, короче, не знаю пока что, потом попробую сделать.
>https://oauth.vk.com/authorize?client_id=123&redirect_uri=https://oauth.vk.com/blank.html&response_type=token
Что-то не знаю, чего я хотел добиться, пихая две звёздочки между "https:" и "//" в двух местах, чтобы эта штука не распознавалась как ссылка. Надо засовывать все четыре, наверное.
Ээ, да, надо все четыре, но пока что только получается от двух до трёх минут. Как-то совсем не очень получилось побегать, если в прошлые разы большинство интервалов бега были по три минуты где-то, то в этот раз пятьдесят на пятьдесят: по три минуты и по две, и вдобавок немного дольше отдыхал, иногда больше двух минут. Не знаю, точно, сколько суммарно бежал, запутался в секундомере, а мозг слишком smoli, чтобы держать эти числа в голове. По пути обратно прямо около входа в парадную стоял какой-то крип (ну, я так говорю, будто я выгляжу как кто-то менее подозрительный, но я по крайней мере не ошиваюсь у чужих парадных, как будто с целью проскочить вместе с жильцом, который бы открыл дверь), поэтому пришлось лезть одиннадцать этажей пешком по лестнице, у которой отдельная дверь. Из-за этого совсем сильно устал. Может быть, стоит бежать полторы минуты и одну отдыхать, но делать так чётко определённо стабильно на протяжении минут двадцати пяти, и только потом переходить на большие интервалы бега. Я где-то из наверняка сомнительного источника слышал что-то про то, что физическая активность положительно влияет на мозг, но, наверное, мозга у меня нет, потому что после бега максимум хочется лежать и умирать, и наоборот, такое ощущение, что голова иссохла и череп стягивается вовнутрь. К тому же бегать не то чтобы приятно из-за пыли и машин, когда возвращаешься домой, чувствуешь себя покрытым тонким слоем грязи сверху донизу. Надо бы не только бегать, но ещё и ходить ногами, но завтра целый день будет мерзкий дождь (я из будущего, поэтому точно знаю), не хочу на улицу выходить.
Кстати, в душевом едоке довольно неплохой первый эндинг, мне нравится, а ето же довольно редко такое, что в эндинге хорошая песенка. Другое такое исключение - эндинг мирай никки и, наверное, всё, больше не слышал крутых эндингов. Ну, я и мультиков довольно мало смотрел, так что вряд ли можно на основе своего опыта выводить даже предположение. Ещё смотрел клеймор, gantz (который многосерийный мультик) и лаки стар, там тоже опенинги очень понравились. Когда клеймор смотрел, думал поеду крышей из-за слишком часто используемых звуков колокольчика: он в точности такой же, как звук при использовании катализатора-колокольчика в третьем данк солсе, и это скорее не звук обычного колокольчика с язычком и, ну, колокольчиковым телом, не знаю, как оно называется, а больше похоже на... Блин, я даже не знаю, ассоциация с колокольчиком у меня походу только из-за бонк солса. Как если бы пыльцу феи насыпали в коробочку и разово тряхнули. Ээ, так вот, я теперь и в душекушателе его пару раз услышал, становится не по себе. Вспоминается видео с ютуба, в котором мужик жаловался на то, что какое-то записанное аудио вроде звуков ликующей толпы используется слишком часто в фильмах и его это сводит с ума. Там масштабы побольше, и он, кажется (мне лень искать то видео), с десяток фильмов перечислил.
27/05
И, видимо, бп всё же окончательно сдох, что мною, на самом деле, ожидалось, учитывая прикольные эффекты, которыми сопровождалось подыхание. Папа сказал, что чинить его уже не особо вариант, раз не помогла замена диодного моста и предохранителя, нужно нормально разбираться, но у нас дома нет осциллографа, а без него, судя по всему, тяжело понимать, что там внутри происходит, короче, проще новый бп купить. Я заодно хочу посмотреть на то, как там правильно подключать бп к компу: никогда не знал, как это делается. Пока выбирал бп, узнал, что у него есть широченный 24-пиновый кабель для питания материнки, 4x4 для процессора, сколько-то кабелей SATA для дисков и PCI-E для дополнительного питания видеокарт. Это как-то сбивает с толку, потому что PCI-E и SATA - это, видимо, не названия коннекторов или разъёмов, а названия интерфейсов передачи данных, в стандарт которых включены и штуки, которые передают данные, и штуки, которые питают приколы, которые в этой передаче данных участвуют. То есть PCI-E - это и разъём на материнской плате, и коннектор дополнительного питания для видеокарты, например. Я, когда вытаскивал мёртвый бп сдуру выдернул из материнки ещё и всякие провода, которые подключены к передней панели (кнопка питания, ресета, светодиод, отображающий использование диска вроде, спикер ещё, но он просто болтается на коротком проводе), потому что.. Я думал, что они соединяют бп и материнку. Не знаю, неважно. Да. На деле же, конечно, они соединяют переднюю панель и материнскую плату. Не знаю, как в моей голове могло сложиться это всё другим образом. И я так и не понял, как их втыкать обратно, всё сделали за меня. Вроде на самой плате напечатано, как что втыкать, но это слабо помогает. Новый бп вроде работает и, кажется, даже тише прежнего, комп тоже в итоге запустился, кажется, внутри больше ничего не задело.
Кстати, я всё же, да, блин, я очень тупой, потому что get запрос, который я постил выше, я думал, что там в redirect_uri указан ещё один get запрос, а на самом деле значение этого параметра - просто https://oauth.vk.com/blank.html, а дальше следующий параметр, а не yada yada yada смотрите, какой я модный англичаненин, даже не знаю, к месту ли это ккороче, просто, неважно, я спутал "&" и "?", проблема была в этом, а не в percent-encoding. Кстати, я настолько тупой, что начал уже свои регексы писать для парсинга URI (не всех, конечно, только тех, которые мне бы могли понадобиться), когда ещё думал, что проблема в percent-encoding у URIBuilder, у которого нет легального способа вырубить эти процентики, хотя можно было бы в тупую переопределить метод build() в готовом URIBuilder, который строит строку, чтобы он из построенной строки убирал проценты. Но в этом тоже уже нет необходимости.
Блин, да, вообще ни в чём нет необходимости, потому что, оказывается, во вконтактовском API нужно какое-то особое индивидуальное приглашение, чтобы взаимодействовать с сообщениями. https://vk.com/dev/messages_api Конечно, я узнал об этом уже после того, как написал штуку, которая достаёт токен. А это довольно отстойно, потому что именно сообщения я вытащить и хотел. Может, можно как-то использовать безголовый браузер или попробовать на js скрипт написать и запустить в браузере, чтобы вытащить сообщения.
Короче, блин, ужасно плохой день, я ещё и не делал почти ничего весь день, только вот дописал штуку ту, короче, да, короче, до свидания.
Ээ, да, надо все четыре, но пока что только получается от двух до трёх минут. Как-то совсем не очень получилось побегать, если в прошлые разы большинство интервалов бега были по три минуты где-то, то в этот раз пятьдесят на пятьдесят: по три минуты и по две, и вдобавок немного дольше отдыхал, иногда больше двух минут. Не знаю, точно, сколько суммарно бежал, запутался в секундомере, а мозг слишком smoli, чтобы держать эти числа в голове. По пути обратно прямо около входа в парадную стоял какой-то крип (ну, я так говорю, будто я выгляжу как кто-то менее подозрительный, но я по крайней мере не ошиваюсь у чужих парадных, как будто с целью проскочить вместе с жильцом, который бы открыл дверь), поэтому пришлось лезть одиннадцать этажей пешком по лестнице, у которой отдельная дверь. Из-за этого совсем сильно устал. Может быть, стоит бежать полторы минуты и одну отдыхать, но делать так чётко определённо стабильно на протяжении минут двадцати пяти, и только потом переходить на большие интервалы бега. Я где-то из наверняка сомнительного источника слышал что-то про то, что физическая активность положительно влияет на мозг, но, наверное, мозга у меня нет, потому что после бега максимум хочется лежать и умирать, и наоборот, такое ощущение, что голова иссохла и череп стягивается вовнутрь. К тому же бегать не то чтобы приятно из-за пыли и машин, когда возвращаешься домой, чувствуешь себя покрытым тонким слоем грязи сверху донизу. Надо бы не только бегать, но ещё и ходить ногами, но завтра целый день будет мерзкий дождь (я из будущего, поэтому точно знаю), не хочу на улицу выходить.
Кстати, в душевом едоке довольно неплохой первый эндинг, мне нравится, а ето же довольно редко такое, что в эндинге хорошая песенка. Другое такое исключение - эндинг мирай никки и, наверное, всё, больше не слышал крутых эндингов. Ну, я и мультиков довольно мало смотрел, так что вряд ли можно на основе своего опыта выводить даже предположение. Ещё смотрел клеймор, gantz (который многосерийный мультик) и лаки стар, там тоже опенинги очень понравились. Когда клеймор смотрел, думал поеду крышей из-за слишком часто используемых звуков колокольчика: он в точности такой же, как звук при использовании катализатора-колокольчика в третьем данк солсе, и это скорее не звук обычного колокольчика с язычком и, ну, колокольчиковым телом, не знаю, как оно называется, а больше похоже на... Блин, я даже не знаю, ассоциация с колокольчиком у меня походу только из-за бонк солса. Как если бы пыльцу феи насыпали в коробочку и разово тряхнули. Ээ, так вот, я теперь и в душекушателе его пару раз услышал, становится не по себе. Вспоминается видео с ютуба, в котором мужик жаловался на то, что какое-то записанное аудио вроде звуков ликующей толпы используется слишком часто в фильмах и его это сводит с ума. Там масштабы побольше, и он, кажется (мне лень искать то видео), с десяток фильмов перечислил.
27/05
И, видимо, бп всё же окончательно сдох, что мною, на самом деле, ожидалось, учитывая прикольные эффекты, которыми сопровождалось подыхание. Папа сказал, что чинить его уже не особо вариант, раз не помогла замена диодного моста и предохранителя, нужно нормально разбираться, но у нас дома нет осциллографа, а без него, судя по всему, тяжело понимать, что там внутри происходит, короче, проще новый бп купить. Я заодно хочу посмотреть на то, как там правильно подключать бп к компу: никогда не знал, как это делается. Пока выбирал бп, узнал, что у него есть широченный 24-пиновый кабель для питания материнки, 4x4 для процессора, сколько-то кабелей SATA для дисков и PCI-E для дополнительного питания видеокарт. Это как-то сбивает с толку, потому что PCI-E и SATA - это, видимо, не названия коннекторов или разъёмов, а названия интерфейсов передачи данных, в стандарт которых включены и штуки, которые передают данные, и штуки, которые питают приколы, которые в этой передаче данных участвуют. То есть PCI-E - это и разъём на материнской плате, и коннектор дополнительного питания для видеокарты, например. Я, когда вытаскивал мёртвый бп сдуру выдернул из материнки ещё и всякие провода, которые подключены к передней панели (кнопка питания, ресета, светодиод, отображающий использование диска вроде, спикер ещё, но он просто болтается на коротком проводе), потому что.. Я думал, что они соединяют бп и материнку. Не знаю, неважно. Да. На деле же, конечно, они соединяют переднюю панель и материнскую плату. Не знаю, как в моей голове могло сложиться это всё другим образом. И я так и не понял, как их втыкать обратно, всё сделали за меня. Вроде на самой плате напечатано, как что втыкать, но это слабо помогает. Новый бп вроде работает и, кажется, даже тише прежнего, комп тоже в итоге запустился, кажется, внутри больше ничего не задело.
Кстати, я всё же, да, блин, я очень тупой, потому что get запрос, который я постил выше, я думал, что там в redirect_uri указан ещё один get запрос, а на самом деле значение этого параметра - просто https://oauth.vk.com/blank.html, а дальше следующий параметр, а не yada yada yada смотрите, какой я модный англичаненин, даже не знаю, к месту ли это ккороче, просто, неважно, я спутал "&" и "?", проблема была в этом, а не в percent-encoding. Кстати, я настолько тупой, что начал уже свои регексы писать для парсинга URI (не всех, конечно, только тех, которые мне бы могли понадобиться), когда ещё думал, что проблема в percent-encoding у URIBuilder, у которого нет легального способа вырубить эти процентики, хотя можно было бы в тупую переопределить метод build() в готовом URIBuilder, который строит строку, чтобы он из построенной строки убирал проценты. Но в этом тоже уже нет необходимости.
Блин, да, вообще ни в чём нет необходимости, потому что, оказывается, во вконтактовском API нужно какое-то особое индивидуальное приглашение, чтобы взаимодействовать с сообщениями. https://vk.com/dev/messages_api Конечно, я узнал об этом уже после того, как написал штуку, которая достаёт токен. А это довольно отстойно, потому что именно сообщения я вытащить и хотел. Может, можно как-то использовать безголовый браузер или попробовать на js скрипт написать и запустить в браузере, чтобы вытащить сообщения.
Короче, блин, ужасно плохой день, я ещё и не делал почти ничего весь день, только вот дописал штуку ту, короче, да, короче, до свидания.
Выкладки на графическом планшете рисуешь?
Вы видите копию треда, сохраненную 25 ноября в 01:10.
Скачать тред: только с превью, с превью и прикрепленными файлами.
Второй вариант может долго скачиваться. Файлы будут только в живых или недавно утонувших тредах. Подробнее
Если вам полезен архив М.Двача, пожертвуйте на оплату сервера.