00ll00
本帖最后由 00ll00 于 2020-3-2 12:32 编辑

用最不圆润的方法画画

模拟自然的系统:L-System




L-System画分形曲线(MC | C4D)




        Lindenmayer System,简称L-System,是由荷兰Utrecht大学的生物学和植物学家,匈牙利裔的林登麦伊尔(Aristid Lindenmayer)于1968年提出的有关生长发展中的细胞交互作用的数学模型,尤其被广泛应用于植物生长过程的研究。——百度


        简单地说L-System就是通过迭代函数绘制分形图案的系统,通过设置参数、前提和迭代规则可以快速地创建出如树枝、雷电、雪花等图案。这次在MC里实现的是一个弱化的L-System,能完成基础的一些绘制操作,通过书与笔录入配置,完全交互式操作,通透。






2021.12 数据,可能有更多内容用最不圆润的方法画画
模拟自然的系统:L-System



L-System画分形曲线(MC | C4D)


  Lindenmayer System,简称L-System,是由荷兰Utrecht大学的生物学和植物学家,匈牙利裔的林登麦伊尔(Aristid Lindenmayer)于1968年提出的有关生长发展中的细胞交互作用的数学模型,尤其被广泛应用于植物生长过程的研究。——百度


  简单地说L-System就是通过迭代函数绘制分形图案的系统,通过设置参数、前提和迭代规则可以快速地创建出如树枝、雷电、雪花等图案。这次在MC里实现的是一个弱化的L-System,能完成基础的一些绘制操作,通过书与笔录入配置,完全交互式操作,通透。






1.L-System


部分内容简要地摘自Houdini L-System节点介绍,点击查看原文


    L-System(Lindenmayer System),能通过简单的递归来创建分形几何体。
    其主体分为两大部分:迭代系统 生成器


迭代系统

    这里的迭代系统是一种针对字符的迭代,或者称为重写机制。举个例子,如果初始字符串(Premise)为"b",而迭代规则(Rules)为"b=a"和"a=ab",在第一次重写中就会把初始字符串中的所有字符"b"替换为字符"a",所有的字符"a"替换为"ab",因此第一次迭代后得到的字符串为"a"。若要进行下一次迭代,则把"a"作为初始字符串,于是得到"ab",以此类推。



刚才那个例子的迭代示意图


    然而实际上L系统的迭代器还有其他的功能,这里只进行了最低限度的模拟,即只允许以“字符=字符串”的格式来书写迭代规则,具体写法见下一节。


生成器


    L系统的生成器是一只Turtle,它根据收到的Turtle命令来决定自己的动作,比如F表示前进并画线,f表示前进但不画线,+表示左转90°,-表示右转90°(这里的参数是可以配置的)……于是我们可以使用简单的字符串来让Turtle画出一个“L”形状:



命令“FFF+F+FF-F+F+FF”执行过程


    Turtle命令也有一大堆,这里只实现了最少量的实用命令,具体见下一节。


结合


    看到这里,你大概已经猜到整个L系统是怎样运行的了:


  • 设置一个初始字符串,以及迭代次数、规则等
  • 迭代字符串
  • 把得到的字符串作为Turtle命令用于生成图形


一个迭代一次就多走一步并左转的L系统


    这里可以使用Turtle命令规定以外的字符作为一个重写的标志,这些字符在最后会被Turtle略过,如上图的"A"。当然在MC里能解析的字符有限,所以不能乱写,具体见下一节。
这是分形二次科赫岛(quadratic Koch island)的L系统实现:


初始字符串 (premise)
F-F-F-F
重新规则
F = F-F+F+FF-F-F+F
角度
90




这里是三次迭代中这个图形的生长过程,可以看出有分形的影子,但严格的说这并不是分形。如果将这个迭代进行无限次,那将成为真正的分形(大概?)


    而自然中很多地方都有分形的影子:一个树枝可以与一棵树有相似的结构,一道闪电取出其中一个分叉也是一道小闪电……所以这里说L系统是模拟自然的系统完全不过分。(其实L系统的发明者Aristid Lindenmayer本来就是把它视为模拟植物生长的数学模型


2.使用方法与示例
下载并安装好数据包后输入

代码:

  1. /function lsys:init
你会获得一个萝卜钓竿(L-System)和一本书(Setting)
在书的第一页写上L系统的参数、第二页写上L系统的迭代规则,保存,拿着书与笔F键,等待系统进行解析,解析完成后用萝卜钓竿对着方块右键,L系统就会从那个方块的位置开始绘图。


这个L系统支持的参数


  • Generations:迭代次数(正整数|次)(至少为1)
  • Step:默认步长(正整数|0.1格)
  • Angle:默认角度(正整数|度)
  • Premise:初始命令(字符串)

支持的Turtle命令有    可以参考这个来理解


  • F(l):前进一个步长l并绘图(l为正整数,若省略则为一个默认步长)
  • f(l):前进一个步长l但不绘图(l为正整数,若省略则为一个默认步长)
  • +(a):左转一个角度aa为正整数,若省略则为一个默认角度)
  • -(a):右转一个角度aa为正整数,若省略则为一个默认角度)
  • &(a):上仰一个角度aa为正整数,若省略则为一个默认角度)
  • ^(a):俯倾一个角度aa为正整数,若省略则为一个默认角度)
  • \(a):顺时针滚动一个角度aa为正整数,若省略则为一个默认角度)
  • /(a):逆时针滚动一个角度aa为正整数,若省略则为一个默认角度)
  • [:开启一个分支,用于创建分叉
  • ]:闭合一个分支,与"["成对使用

第一页参数定义写法示例
#Parameters


Generations=3
Step=50
Angle=60
Premise=//F(40)+A-B
注释以"#"开头,为避免解析出错,不要整个页面为空
换行以回车符为准
可以有空行
参数定义行中不能有空格,写法必须为“参数名=值”


如"F","+"等命令带参数的写法为“命令(正整数)”

第二页迭代规则写法示例
#Rules


A=FFB
B=AAA
注释以"#"开头,为避免解析出错,不要整个页面为空
换行以回车符为准
可以有空行
规则写法必须为“字符=字符串”



需要注意的是,这套字符串解析系统只支持了英文字母英文标点以及换行符,若出现其他字符会导致解析错误
另外避免出现10个及以上的连续空格,原因见后文


经典的参考示例(可直接复制粘贴)    更多有趣的示例看这里
1.L-System 默认树(植物)
   第一页
#Default Tree


Generations=7
Step=30
Angle=28
Premise=FFFA
  第二页
#Rules


A=[B]////[B]////[B]
B=&FFFA

2.谢尔宾斯基三角(分形)
   第一页
#Sierpinski Arrowhead


Generations=5
Step=30
Angle=60
Premise=YF
   第二页
#Rules


Y=XF+YF+X
X=YF-XF-Y



3.平面希尔伯特曲线(分形)
   第一页
#Hilbert Curve


Generations=4
Step=30
Angle=90
Premise=X
   第二页
#Rules


X=-YF+XFX+FY-
Y=+XF-YFY-FX+



4.草(植物)
   第一页
#++


Generations=1
Step=10
Angle=90
Premise=FFF[+F(70)][-F(70)]FF[+F(40)-FF][-F(40)+FF]ff[+F(40)-FF][-F(40)+FF]ff[+FF[-F(40)]FF][-FF[+F(40)]FF]ff[+F(60)]-F(60)
   第二页
#wwwww

所以Rules可以不写但是必须写点注释占位(艹



5.科赫雪花(分形)
   第一页
#Von Koch Snowflake


Generations=3
Step=30
Angle=60
Premise=F++F++F
   第二页
#Rules

F=F-F++F-F




3.核心原理(粗解)
    在MC里实现L系统主要涉及到MC的列表操作(用于实现迭代器和Turtle命令解析)、矩阵运算解三角函数(用于计算Turtle的方向),考虑到用tellraw或者用容器做键盘实在麻烦,于是用了一个还算热乎的技术——拆解字符串


    列表操作矩阵运算之类的好像没啥好说的,比较容易实现,那就主要讲一下拆解字符串的原理(run

列表操作


    主要用到了列表中元素的添加,删除和比对
    添加删除即data modify storage nsp path append ... 和data remove ...
    对比A和B即先把C set为A,再用execute store success ... run C赋值为B,若成功则A!=B,若失败则A==B


矩阵运算


    这里只用到了矩阵乘法,用于计算Turtle的转向操作
    实现起来就是一堆计分板乘法


解三角函数


    用于计算旋转矩阵,具体计算方法有许多大佬解释过了
    这里是用的刷实体取坐标的那种




拆解字符串    原作者为McTsts,为了添加对换行符的支持所以我重写了


    所谓的拆解,实际上是在拼凑:将可能出现的字符逐个试出来,于是需要将多个字符拼凑为字符串逐个字符比较的手段,于是就要用到两个指令的骚操作:enchant tag list


   借助一个例子来解释整个过程:假设有一个字符串为"123",我们现在想要把他拆解为字符列表["1","2","3"]


   Step1.把这个字符串用nbt组件的方式写到一个告示牌上,再转存到盔甲架[A]CustomName里,同时我们在允许的字符范围里随便猜一个字符(实际做法是二分法)假设是"0",用同样的方法把他转存到盔甲架[B]CustomName里,然后分别让两个cb对AB跑错误的enchant指令把JSON文本拍扁,此时这两个cb的LastOutput的结构为:
1:{enchant错误,"with":"123",...}
2:{enchant错误,"with":"0",...}   


    enchant指令在执行出现某些错误(比如执行对象不支持附魔、执行对象无手持物品)的时候会返回含有执行对象的输出信息,而这个信息与众不同的地方是里面出现的执行对象名称是被拍扁为字符串的,而其他命令返回含有执行对象的名字时是以JSON文本的形式。比如说有一个盔甲架,借助告示牌修改了它的{CustomName:'{"nbt":"string","storage":"ps:test","interpret":true}'},其中ps:test有{string:"123"},enchant执行错误的返回中会包含有字符串"123",而其他指令执行返回中则是包含"text":"{name:\'{\\"nbt\\":\\"string\\",\\"storage\\":\\"ps:test\\",\\"interpret\\":true}'}"之类的信息。所以我们可以用enchant的返回来拍扁拼凑出的字符串。而要获取到这个返回信息则需要在命令方块里运行这个命令,这与丢人钟大概是必须存在的最后的理由了吧。


    Step2.清空ABCustomName,用data modify的方式把这两个命令方块的LastOutput添加到BTags里,再分别给A添加三个tag:"a","b","c",B添加一个tag:"a, b, c"然后同时对AB执行tag list(也在cb里,因为要获取返回信息)。tag list指令在执行时会将执行对象的所有tag按字符顺序进行排序,这也就是用于判断我们猜的字符与实际字符的大小的方法。所以为了控制变量,我们必须要使AB的tag数量一致而添加这些奇奇怪怪的tag。这时对AB执行tag list的cb返回信息结构分别为:
3:[时间] 这玩意有3个标签:a, b, c   
4:[时间] 这玩意有3个标签:a, b, c, {enchant错误,"with":"0",...}, {enchant错误,"with":"123",...}
可以看出,第二个cb中已经对tag进行了排序,最后两个tag之间的差异仅为那两个字符串,所以实际上对排序造成影响的就是我们猜的那个"0"和源字符串的第一个字符"1"的先后顺序。那么如何知道这个排序的结果呢?


    Step3.将上一步中第二个cb的返回值经过告示牌转存到BCustomName中,然后在告示牌中把cb3、1、2的LastOutput按顺序拼起来,其中添加一些标点什么的使结构与cb4的LastOutput结构一致,再转存到ACustomName里。此时再次用两个enchantAB拍扁,得到的输出结构为:
5:{enchant错误,"with":"[时间] 这玩意有3个标签:a, b, c, {enchant错误,"with":"123",...}, {enchant错误,"with":"0",...}",...}
6:{enchant错误,"with":"[时间] 这玩意有3个标签:a, b, c, {enchant错误,"with":"0",...}, {enchant错误,"with":"123",...}",...}
由于tag list的排序与我们固定的顺序不一样,所以这两个返回信息也不相同。如果我们第一步猜的字符在"1"之后,则可以得到完全相同的输出信息。
    Step4.用data merge把cb5的输出set到cb6里,发现成功了(因为值不相同),所以我们就可以知道源字符串的第一个字符排在字符"0"的后面。这样我们就再猜一个"0"之后的字符继续循环。这里处理的字符总共不超过100个,用二分法把这个循环跑8次便能准确地猜出一个字符,也就是说在8次循环后我们只剩字符"1"了,于是把"1"固定下来,使用告示牌拼贴的方式添加要猜的第二个字符,继续循环。


    一些细节.
  1.可以看到,在每次获取LastOutput的时候都会有一个时间戳,虽然实际运行的时候这些cb都是在1tick里跑完一次比较,但是也不排除时间戳在其中发生改变的可能,于是还要在用到cb之前和用完cb之后分别添加一个cb,内容随意但是相同,用于判断在这个过程中时间戳是否改变,即此次比较结果是否可靠。
  2.实际在比较的时候在拍扁这一步中字符串之后还有一串以引号开头内容,这串有可能对tag list排序这一步造成影响,于是解决方法是分别在源字符串和拼凑的字符串后方加上一长串(10个)空格,因为在这里空格貌似是排在最小的字符,这样相当于把后面的那串内容隔开了。所以如果源字符串里出现了10个以上的连续字符串就可能使比较结果出错。



4.数据包下载&调试命令&参考资料
下载

这个数据包是从PSINMC里强行抠出来的,很有些杂乱(各种意义上)


调试命令


代码:

  1. /function lsys:init
为执行者初始化

代码:

  1. /function lsys:stop
终止当前所有绘画

代码:

  1. /function lsys:showresult
输出迭代结果



参考资料


    1.L系统的各种规则:Houdini L-System 节点使用说明
    2.很多有趣和实用的L系统预设:L-System User Notes -- Paul Bourke
    3.拆解字符串原作者的数据包:Minecraft-String-Utilities


感谢SPG告诉我MC能拆字符串的事,以及SPG是世界第一好用的数据包插件!!!的开发者!!!
感谢小豆提供的数学_运算包(小豆在不在论坛来着



Himcd
我表示我完全没看懂

Xiao2
岂不是相当于mc界面内的简单编程系统!!
我认为书与钓竿的处理是相当的棒

2782429220
我表示有点难awa我表示看懂很难

(=°ω°)丿
这次的内容的确挺有意思的(
不过感觉可能在一般的建筑中用不太到(
PSINMC 相比于 实用 感觉更偏向于 技术(
看似在咕咕咕,实则……的确在咕咕咕 SPG是世界第一好用的数据包插件!!!的开发者!!!
我同意(
论坛底色好像是 rgb(251, 242, 219) 也就是 #FBF2DB
最近想到了一个复制粘贴的功能(
先用胡萝卜钓竿选择两个点,然后将计算后的结果存储到结构方块里。
如果选择的范围太大,则考虑使用多个结构方块。
还考虑过跨存档转移建筑。
不过懒癌晚期就是了(
我都不知道我在说什么(

00ll00
本帖最后由 00ll00 于 2020-2-14 11:19 编辑
隐退 发表于 2020-2-14 10:53
这次的内容的确挺有意思的(
不过感觉可能在一般的建筑中用不太到(
PSINMC 相比于 实用 感觉更偏向 ...

这个还只能算做了一半,因为实际上我是打算用他来种树(或者其他植物(或者啥啥的
见鬼  好像卡出bug了,这条评论能删么qwq

00ll00
隐退 发表于 2020-2-14 10:53
这次的内容的确挺有意思的(
不过感觉可能在一般的建筑中用不太到(
PSINMC 相比于 实用 感觉更偏向 ...

这个还只能算做了一半,因为实际上我是打算用他来一键种树(或者其他植物(或者啥啥的
就是打算能做到带半径和材质的
不过还是好复杂……但是应该会实用

结构方块这个想法还是头一回见
有空试试(咕咕

时之虫
分型、迭代、…可以,这很数学

咕哒子寿司
原来这样啊,我懂了,谢谢楼主!

muhei
有点难,勉强懂了

kongbaiyo
强 摸鱼了这么久原来拆分字符串的技能被开发出来了吗

w279179736
兄弟加油期待好作品啊

鑫鑫超帅
兄弟加油期待好作品啊兄弟加油期待好作品啊

feixiang10086
MCBBS有你更精彩~

Kimess
感谢分享感谢分享

The_xialuoY
56565656565656565656565656563

asdasga
最近想到了一个复制粘贴的功能(

_素包子_
谢谢分享谢谢分享

Xli_L
mc有你更精彩啊

ptime
我表示有点难awa我表示看懂很难

xiaodouB
是不是有点复杂,我怎么看不懂

wangchenglin666
每日开拓眼界get

2366701370
18000569211 发表于 2020-2-13 22:47
我表示我完全没看懂

mcbbs有你更精彩~

shengjiang76
稍微试了一下,弄不懂

1962630186
可以可以,学到了,楼主牛皮

qazplmasdf2008
。。。。我草,真的是绝了,大佬就是一种在任何领域都能彰显自己的生物吗

ETAL
这办法有没有可能让mc拥有⚪

江上饮
真是太实用拉,我去试试

特种食尸鬼
好家伙,我感觉我懂了,但是还没有完全懂

xrq123123
一看就很牛逼的样子,但我看不懂

Meng_Xing1
好家伙,mc啥都能干

鲁撸橹噜
没看明白

13775644032

我表示有点难awa我表示看懂很难

SuYing1
        MCBBS有你更精彩~

钠钠钠镓
本帖最后由 钠钠钠镓 于 2022-4-10 10:44 编辑

提一个问题,在step2的时候,tag list后的LastOutput会包含目标的UUID

//6632
在MC里编程会秃头吗哈哈哈哈

idu88
自然是挺自然 就是有点费头发

1771475094
6666666666666666666666666666666666666666

TTong_0105
代码大佬666

mengxingshifen
不错的作品,感谢分享

小川昭
这,好难懂,不过好强qwq