-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathForester.py
More file actions
1264 lines (1122 loc) · 49.6 KB
/
Forester.py
File metadata and controls
1264 lines (1122 loc) · 49.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Version 10
"""This takes a base MineCraft level and adds or edits trees.
Written by Paul Spooner, with God's help.
See more at: http://www.peripheralarbor.com/minecraft/minecraftscripts.html"""
# Here are the variables you can edit.
# This is the name of the map to edit.
# Make a backup if you are experimenting!
LOADNAME = "TestB"
# How many trees do you want to add?
TREECOUNT = 5
# The trees will be added around the player's location, so you can just stand anywhere.
# How large an area do you want the trees to be in?
# for example, RADIUS = 10 will make place trees randomly in
# a circular area 20 blocks wide.
PLANT_CLEAR_RADIUS = 12
PLANT_RING_WIDTH = 7
# NOTE: tree density will be higher in the center than at the edges,
# unless you set INTERPOLATION to 'even' in the advanced section.
# Which shapes would you like the trees to be?
# these first three are best suited for small heights, from 5 - 10
# "normal" is the normal minecraft shape, it only gets taller and shorter
# "bamboo" a trunk with foliage, it only gets taller and shorter
# "palm" a trunk with a fan at the top, only gets taller and shorter
# "stickly" selects randomly from "normal", "bamboo" and "palm"
# these last five are best suited for very large trees, heights greater than 8
# "procedural" selects randomly from all these below varieties
# "round" procedural spherical shaped tree, can scale up to immense size
# "cone" procedural, like a pine tree, also can scale up to immense size
# "rainforest" many slender trees, most at the lower range of the height,
# with a few at the upper end.
# "mangrove" makes mangrove trees (see PLANTON below).
SHAPE = "round"
# What height should the trees be?
# Specifies the average height of the tree
# Examples:
# 5 is normal minecraft tree
# 3 is minecraft tree with foliage flush with the ground
# 10 is very tall trees, they will be hard to chop down
# NOTE: for round and conical, this affects the foliage size as well.
# CENTERHEIGHT is the height of the trees at the center of the area
# ie, when radius = 0
CENTERHEIGHT = 35
# EDGEHEIGHT is the height at the trees at the edge of the area.
# ie, when radius = RADIUS
EDGEHEIGHT = 20
# What should the variation in HEIGHT be?
# actual value +- variation
# default is 1
# Example:
# HEIGHT = 8 and HEIGHTVARIATION = 3 will result in
# trunk heights from 5 to 11
# value is clipped to a max of HEIGHT
# for a good rainforest, set this value not more than 1/2 of HEIGHT
HEIGHTVARIATION = 3
# Do you want branches, trunk, and roots?
# True makes all of that
# False does not create the trunk and branches, or the roots (even if they are
# enabled further down)
WOOD = True
# Trunk thickness multiplyer
# from zero (super thin trunk) to whatever huge number you can think of.
# Only works if SHAPE is not a "stickly" subtype
# Example:
# 1.0 is the default, it makes decently normal sized trunks
# 0.3 makes very thin trunks
# 4.0 makes a thick trunk (good for HOLLOWTRUNK).
# 10.5 will make a huge thick trunk. Not even kidding. Makes spacious
# hollow trunks though!
TRUNKTHICKNESS = 1.61
# Trunk height, as a fraction of the tree
# Only works on "round" shaped trees
# Sets the height of the crown, where the trunk ends and splits
# Examples:
# 0.7 the default value, a bit more than half of the height
# 0.3 good for a fan-like tree
# 1.0 the trunk will extend to the top of the tree, and there will be no crown
# 2.0 the trunk will extend out the top of the foliage, making the tree appear
# like a cluster of green grapes impaled on a spike.
TRUNKHEIGHT = 0.618
# Do you want the trunk and tree broken off at the top?
# removes about half of the top of the trunk, and any foliage
# and branches that would attach above it.
# Only works if SHAPE is not a "stickly" subtype
# This results in trees that are shorter than the height settings
# True does that stuff
# False makes a normal tree (default)
BROKENTRUNK = False
# Note, this works well with HOLLOWTRUNK (below) turned on as well.
# Do you want the trunk to be hollow (or filled) inside?
# Only works with larger sized trunks.
# Only works if SHAPE is not a "stickly" subtype
# True makes the trunk hollow (or filled with other stuff)
# False makes a solid trunk (default)
HOLLOWTRUNK = False
# Note, this works well with BROKENTRUNK set to true (above)
# Further note, you may want to use a large value for TRUNKTHICKNESS
# How many branches should there be?
# General multiplyer for the number of branches
# However, it will not make more branches than foliage clusters
# so to garuntee a branch to every foliage cluster, set it very high, like 10000
# this also affects the number of roots, if they are enabled.
# Examples:
# 1.0 is normal
# 0.5 will make half as many branches
# 2.0 will make twice as mnay branches
# 10000 will make a branch to every foliage cluster (I'm pretty sure)
BRANCHDENSITY = 1.0
# do you want roots from the bottom of the tree?
# Only works if SHAPE is "round" or "cone" or "procedural"
# "yes" roots will penetrate anything, and may enter underground caves.
# "tostone" roots will be stopped by stone (default see STOPSROOTS below).
# There may be some penetration.
# "hanging" will hang downward in air. Good for "floating" type maps
# (I really miss "floating" terrain as a default option)
# "no" roots will not be generated
ROOTS = "hanging"
# Do you want root buttresses?
# These make the trunk not-round at the base, seen in tropical or old trees.
# This option generally makes the trunk larger.
# Only works if SHAPE is "round" or "cone" or "procedural"
# Options:
# True makes root butresses
# False leaves them out
ROOTBUTTRESSES = True
# Do you want leaves on the trees?
# True there will be leaves
# False there will be no leaves
FOLIAGE = True
# How thick should the foliage be
# General multiplyer for the number of foliage clusters
# Examples:
# 1.0 is normal
# 0.3 will make very sparse spotty trees, half as many foliage clusters
# 2.0 will make dense foliage, better for the "rainforests" SHAPE
FOLIAGEDENSITY = 1.0
# Limit the tree height to the top of the map?
# True the trees will not grow any higher than the top of the map
# False the trees may be cut off by the top of the map
MAPHEIGHTLIMIT = True
# add lights in the middle of foliage clusters
# for those huge trees that get so dark underneath
# or for enchanted forests that should glow and stuff
# Only works if SHAPE is "round" or "cone" or "procedural"
# 0 makes just normal trees
# 1 adds one light inside the foliage clusters for a bit of light
# 2 adds two lights around the base of each cluster, for more light
# 4 adds lights all around the base of each cluster for lots of light
LIGHTTREE = 0
# Do you want to only place trees near existing trees?
# True will only plant new trees near existing trees.
# False will not check for existing trees before planting.
# NOTE: the taller the tree, the larger the forest needs to be to qualify
# OTHER NOTE: this feature has not been extensively tested.
# IF YOU HAVE PROBLEMS: SET TO False
ONLYINFORESTS = False
#####################
# Advanced options! #
#####################
# The following is an interface class for .mclevel data for minecraft savefiles.
# The following also includes a useful coordinate to index convertor and several
# other useful functions.
import mcInterface
from random import random, choice
from math import sqrt, sin, cos, pi
# What kind of material should the "wood" be made of?
# Randomly selects a log type by default: choice(mcInterface.blocktype_bark_logs)
# for your convenience, here are the log types
# ('oak_log', 'spruce_log', 'birch_log', 'jungle_log', 'acacia_log', 'dark_oak_log', 'mangrove_log', 'cherry_log')
wood_name = choice(mcInterface.blocktype_bark_logs)
#wood_name = 'birch_log'
WOODMAT = {"B":'minecraft:'+wood_name, "axis":'y'}
# What kind of material should the "leaves" be made of?
# defaults to choice(mcInterface.blocktype_leaves)
# for your convenience, here are the leaf types
# ('oak_leaves', 'spruce_leaves', 'birch_leaves', 'jungle_leaves', 'acacia_leaves', 'dark_oak_leaves', 'mangrove_leaves', 'cherry_leaves', 'azalea_leaves', 'flowering_azalea_leaves')
leaf_name = choice(mcInterface.blocktype_leaves)
LEAFMAT = {"B":'minecraft:'+leaf_name, 'persistent': 'true'}
# What kind of material should the "lights" be made of?
# defaults to 89 (glowstone)
LIGHTMAT = {"B":'sea_lantern'}
# What kind of material would you like the "hollow" trunk filled with?
# defaults to 0 (air)
TRUNKFILLMAT = {"B":'minecraft:air'}
# What kind of blocks should the trees be planted on?
# Use the Minecraft block name.
# default is mcInterface.blocktype_soils
PLANTON = mcInterface.blocktype_soils
# What kind of blocks should stop the roots?
# a list of block type names
# default is mcInterface.blocktype_stones
STOPSROOTS = mcInterface.blocktype_stones
# What kind of blocks should stop branches?
# same as STOPSROOTS above, but is always turned on
# defaults to stone, cobblestone, and glass
# set it to [] if you want branches to go through everything
BRANCHESPASS = ('air',) + mcInterface.blocktype_leaves
# How do you want to interpolate the tree placement?
# "linear" places more trees in the center
# "even" places trees evenly over the entire area
INTERPOLATION = "linear"
# How many times do you want to try to find a location?
# it will stop planing after MAXTRIES has been exceeded.
# Set to smaller numbers to abort quicker, or larger numbers
# if you want to keep trying for a while.
# NOTE: the number of trees will not exceed this number
# Default: 1000
MAXTRIES = 1000
# How tall is the minimum height tree?
# Please make them be higher than 1
# Default 3
MIN_TREE_HEIGHT = 3
# Do you want lots of text telling you waht is going on?
# True lots of text (default). Good for debugging. Waits at the end.
# False no text, a little faster. Closes when finished.
VERBOSE = False
# Do you want to hear about each individual tree?
# True some text describing each tree generation.
# False no text for individual trees.
VERBOSEINDIVIDUALS = False
# Do you want to hear about each feature?
# This will print generation status for each root, branch, cluster, and bole.
# Lots of text! Do not enable unless you want a whole lot of info!
# True every feature announced when completed
# False no feature announcements
VERBOSEFEATURES = False
##############################################################
# Don't edit below here unless you know what you are doing #
##############################################################
# input filtering
TREECOUNT = int(TREECOUNT)
if TREECOUNT < 0: TREECOUNT = 0
if SHAPE not in ["normal", "bamboo", "palm", "stickly",
"round", "cone", "procedural",
"rainforest", "mangrove"]:
if VERBOSE: print("SHAPE not set correctly, using 'procedural'.")
SHAPE = "procedural"
if CENTERHEIGHT < 1:
CENTERHEIGHT = 1
if EDGEHEIGHT < 1:
EDGEHEIGHT = 1
minheight = min(CENTERHEIGHT, EDGEHEIGHT)
if HEIGHTVARIATION > minheight:
HEIGHTVARIATION = minheight
if INTERPOLATION not in ["linear", "even"]:
if VERBOSE: print("INTERPOLATION not set correctly, using 'linear'.")
INTERPOLATION = "linear"
if WOOD not in [True, False]:
if VERBOSE: print("WOOD not set correctly, using True")
WOOD = True
if TRUNKTHICKNESS < 0.0:
TRUNKTHICKNESS = 0.0
if TRUNKHEIGHT < 0.0:
TRUNKHEIGHT = 0.0
if ROOTS not in ["yes", "tostone", "hanging", "no"]:
if VERBOSE: print("ROOTS not set correctly, using 'no' and creating no roots")
ROOTS = "no"
if ROOTBUTTRESSES not in [True, False]:
if VERBOSE: print("ROOTBUTTRESSES not set correctly, using False")
ROOTBUTTRESSES = False
if FOLIAGE not in [True, False]:
if VERBOSE: print("FOLIAGE not set correctly, using True")
ROOTBUTTRESSES = True
if FOLIAGEDENSITY < 0.0:
FOLIAGEDENSITY = 0.0
if BRANCHDENSITY < 0.0:
BRANCHDENSITY = 0.0
if MAPHEIGHTLIMIT not in [True, False]:
if VERBOSE: print("MAPHEIGHTLIMIT not set correctly, using False")
MAPHEIGHTLIMIT = False
if LIGHTTREE not in [0, 1, 2, 4]:
if VERBOSE: print("LIGHTTREE not set correctly, using 0 for no torches")
LIGHTTREE = 0
# assemble the material dictionaries
WOODINFO = WOODMAT.copy()
LEAFINFO = LEAFMAT.copy()
LIGHTINFO = LIGHTMAT.copy()
TRUNKFILLINFO = TRUNKFILLMAT.copy()
# some handy functions
def dist_to_mat(cord, vec, matidxlist, mcmap: mcInterface.SaveFile, invert=False, limit=0.0):
"""travel from cord along vec and return how far it was to a point of matidx
the distance is returned in number of iterations. If the edge of the map
is reached, then return the number of iterations as well.
if invert == True, search for anything other than those in matidxlist
"""
assert isinstance(mcmap, mcInterface.SaveFile)
block = mcmap.block
curcord = [i + .5 for i in cord]
iterations = 0
on_map = True
while on_map:
x = int(curcord[0])
y = int(curcord[1])
z = int(curcord[2])
blocktype = block(x, y, z)
if blocktype is None: break
blockstr = blocktype['B'].replace('minecraft:','')
if (blockstr in matidxlist) and (invert is False):
break
elif (blockstr not in matidxlist) and invert:
break
else:
curcord = [curcord[i] + vec[i] for i in range(3)]
iterations += 1
if limit and iterations > limit:
break
return iterations
# This is the end of the MCLevel interface.
# Now, on to the actual code.
def assign_value(x, y, z, values, save_file):
"""Assign an index value to a location in mcmap.
If the index is outside the bounds of the map, return None. If the
assignment succeeds, return True.
"""
result = save_file.set_block(x, y, z, values)
return result
class Tree(object):
"""Set up the interface for tree objects. Designed for subclassing.
"""
def prepare(self, mcmap):
"""initialize the internal values for the Tree object.
"""
return None
def maketrunk(self, mcmap):
"""Generate the trunk and enter it in mcmap.
"""
return 1
def makefoliage(self, mcmap):
"""Generate the foliage and enter it in mcmap.
Note, foliage will disintegrate if there is no log nearby"""
return 1
def copy(self, other):
"""Copy the essential values of the other tree object into self.
"""
self.pos = other.pos
self.height = other.height
def __init__(self, pos, height=1):
"""Accept values for the position and height of a tree.
Store them in self.
"""
self.pos = pos
self.height = height
class StickTree(Tree):
"""Set up the trunk for trees with a trunk width of 1 and simple geometry.
Designed for sublcassing. Only makes the trunk.
"""
def maketrunk(self, mcmap):
x = self.pos[0]
y = self.pos[1]
z = self.pos[2]
for i in range(self.height):
assign_value(x, y, z, WOODINFO, mcmap)
y += 1
return 1
class NormalTree(StickTree):
"""Set up the foliage for a 'normal' tree.
This tree will be a single bulb of foliage above a single width trunk.
This shape is very similar to the default Minecraft tree.
"""
foliage_shape = (3, 2)
def makefoliage(self, mcmap):
"""note, foliage will disintegrate if there is no foliage below, or
if there is no "log" block within range 2 (square) at the same level or
one level below"""
topy = self.pos[1] + self.height - 1
start = topy - 2
end = topy + 2
for y in range(start, end):
if y > start + 1:
rad = 1
else:
rad = 2
for xoff in range(-rad, rad + 1):
for zoff in range(-rad, rad + 1):
if (random() > 0.618
and abs(xoff) == abs(zoff)
and abs(xoff) == rad):
continue
x = self.pos[0] + xoff
z = self.pos[2] + zoff
assign_value(x, y, z, LEAFINFO, mcmap)
return 1
class BambooTree(StickTree):
"""Set up the foliage for a bamboo tree.
Make foliage sparse and adjacent to the trunk.
"""
foliage_shape = (1,)
def makefoliage(self, mcmap):
start = self.pos[1]
end = self.pos[1] + self.height + 1
for y in range(start, end):
for i in range(2):
xoff = choice([-1, 1])
zoff = choice([-1, 1])
x = self.pos[0] + xoff
z = self.pos[2] + zoff
assign_value(x, y, z, LEAFINFO, mcmap)
return 1
class PalmTree(StickTree):
"""Set up the foliage for a palm tree.
Make foliage stick out in four directions from the top of the trunk.
"""
foliage_shape = (3,)
def makefoliage(self, mcmap):
y = self.pos[1] + self.height
for xoff in range(-2, 3):
for zoff in range(-2, 3):
if abs(xoff) == abs(zoff):
x = self.pos[0] + xoff
z = self.pos[2] + zoff
assign_value(x, y, z, LEAFINFO, mcmap)
return 1
dir_orientation_map = ('x','y','z')
def crossection(center, radius, diraxis, matidx, mcmap):
"""Create a round section of type matidx in mcmap.
Passed values:
center = [x,y,z] for the coordinates of the center block
radius = <number> as the radius of the section. May be a float or int.
diraxis: The list index for the axis to make the section
perpendicular to. 0 indicates the x axis, 1 the y, 2 the z. The
section will extend along the other two axies.
matidx = <int> the integer value to make the section out of.
mcmap = the array generated by make_mcmap
matdata = <int> the integer value to make the block data value.
"""
rad = int(radius + .618)
if rad <= 0: return None
if 'axis' in matidx:
matidx['axis'] = dir_orientation_map[diraxis]
secidx1 = (diraxis - 1) % 3
secidx2 = (1 + diraxis) % 3
coord = [0, 0, 0]
for off1 in range(-rad, rad + 1):
for off2 in range(-rad, rad + 1):
thisdist = sqrt((abs(off1) + .5) ** 2 + (abs(off2) + .5) ** 2)
if thisdist > radius:
continue
pri = center[diraxis]
sec1 = center[secidx1] + off1
sec2 = center[secidx2] + off2
coord[diraxis] = pri
coord[secidx1] = sec1
coord[secidx2] = sec2
assign_value(int(coord[0]), int(coord[1]), int(coord[2]), matidx, mcmap)
def taperedcylinder(start, end, startsize, endsize, mcmap: mcInterface.SaveFile, blockdata):
"""Create a tapered cylinder in mcmap.
start and end are the beginning and ending coordinates of form [x,y,z].
startsize and endsize are the beginning and ending radius.
The material of the cylinder is WOODMAT.
"""
# delta is the coordinate vector for the difference between
# start and end.
delta = [int(end[i] - start[i]) for i in range(3)]
# primidx is the index (0,1,or 2 for x,y,z) for the coordinate
# which has the largest overall delta.
# noinspection PyTypeChecker
maxdist = max(delta, key=abs)
if maxdist == 0:
return None
primidx = delta.index(maxdist)
# secidx1 and secidx2 are the remaining indicies out of [0,1,2].
secidx1 = (primidx - 1) % 3
secidx2 = (1 + primidx) % 3
# primsign is the digit 1 or -1 depending on whether the limb is headed
# along the positive or negative primidx axis.
primsign = int(delta[primidx] / abs(delta[primidx]))
# secdelta1 and ...2 are the amount the associated values change
# for every step along the prime axis.
secdelta1 = delta[secidx1]
secfac1 = float(secdelta1) / delta[primidx]
secdelta2 = delta[secidx2]
secfac2 = float(secdelta2) / delta[primidx]
# Initialize coord. These values could be anything, since
# they are overwritten.
coord = [0, 0, 0]
# Loop through each crossection along the primary axis,
# from start to end.
endoffset = delta[primidx] + primsign
for primoffset in range(0, endoffset, primsign):
primloc = start[primidx] + primoffset
secloc1 = int(start[secidx1] + primoffset * secfac1)
secloc2 = int(start[secidx2] + primoffset * secfac2)
coord[primidx] = primloc
coord[secidx1] = secloc1
coord[secidx2] = secloc2
primdist = abs(delta[primidx])
radius = endsize + (startsize - endsize) * abs(delta[primidx]
- primoffset) / primdist
crossection(coord, radius, primidx, blockdata, mcmap)
class ProceduralTree(Tree):
"""Set up the methods for a larger more complicated tree.
This tree type has roots, a trunk, and branches all of varying width,
and many foliage clusters.
MUST BE SUBCLASSED. Specifically, self.foliage_shape must be set.
Subclass 'prepare' and 'shapefunc' to make different shaped trees.
"""
foliage_shape = (2.5, 3, 2.5)
branchslope = 0.55
def shapefunc(self, y):
"""Take y and return a radius for the location of the foliage cluster.
If no foliage cluster is to be created, return None
Designed for sublcassing. Only makes clusters close to the trunk.
"""
if random() < 100. / (self.height ** 2) and y < self.trunkheight:
return self.height * .12
return None
def foliagecluster(self, center, mcmap):
"""generate a round cluster of foliage at the location center.
The shape of the cluster is defined by the list self.foliage_shape.
This list must be set in a subclass of ProceduralTree.
"""
level_radius = self.foliage_shape
x = center[0]
y = center[1]
z = center[2]
for i in level_radius:
crossection([x, y, z], i, 1, LEAFINFO, mcmap)
y += 1
if VERBOSEFEATURES:
print("foliage cluster at: " + str(center) + " generated")
def makefoliage(self, mcmap):
"""Generate the foliage for the tree in mcmap.
"""
"""note, foliage will disintegrate if there is no foliage below, or
if there is no "log" block within range 2 (square) at the same level or
one level below"""
foliage_coords = self.foliage_cords
for coord in foliage_coords:
self.foliagecluster(coord, mcmap)
for cord in foliage_coords:
#assign_value(cord[0], cord[1], cord[2], WOODINFO, mcmap)
if LIGHTTREE == 1:
assign_value(cord[0], cord[1] + 1, cord[2], LIGHTINFO, mcmap)
elif LIGHTTREE in [2, 4]:
assign_value(cord[0] + 1, cord[1], cord[2], LIGHTINFO, mcmap)
assign_value(cord[0] - 1, cord[1], cord[2], LIGHTINFO, mcmap)
if LIGHTTREE == 4:
assign_value(cord[0], cord[1], cord[2] + 1, LIGHTINFO, mcmap)
assign_value(cord[0], cord[1], cord[2] - 1, LIGHTINFO, mcmap)
return len(foliage_coords)
def makebranches(self, mcmap):
"""Generate the branches and enter them in mcmap.
"""
treeposition = self.pos
height = self.height
topy = treeposition[1] + int(self.trunkheight + 0.5)
# endrad is the base radius of the branches at the trunk
endrad = self.trunkradius * (1 - self.trunkheight / height)
if endrad < 1.0:
endrad = 1.0
for coord in self.foliage_cords:
dist = (sqrt(float(coord[0] - treeposition[0]) ** 2 +
float(coord[2] - treeposition[2]) ** 2))
ydist = coord[1] - treeposition[1]
# value is a magic number that weights the probability
# of generating branches properly so that
# you get enough on small trees, but not too many
# on larger trees.
# Very difficult to get right... do not touch!
value = (self.branchdensity * 220 * height) / ((ydist + dist) ** 3)
if value < random():
continue
posy = coord[1]
slope = self.branchslope + (0.5 - random()) * .16
if coord[1] - dist * slope > topy:
# Another random rejection, for branches between
# the top of the trunk and the crown of the tree
threshhold = 1 / float(height)
if random() < threshhold:
continue
branchy = topy
basesize = endrad
else:
branchy = posy - dist * slope
basesize = (endrad + (self.trunkradius - endrad) *
(topy - branchy) / self.trunkheight)
startsize = (basesize * (1 + random()) * .618 *
(dist / height) ** 0.618)
rndr = sqrt(random()) * basesize * 0.618
rndang = random() * 2 * pi
rndx = int(rndr * sin(rndang) + 0.5)
rndz = int(rndr * cos(rndang) + 0.5)
startcoord = [treeposition[0] + rndx,
int(branchy),
treeposition[2] + rndz]
if startsize < 1.0:
startsize = 1.0
endsize = 1.0
taperedcylinder(startcoord, coord, startsize, endsize,
mcmap, WOODINFO)
if VERBOSEFEATURES:
print("branch to " + str(coord) + " generated")
def makeroots(self, rootbases, mcmap):
"""generate the roots and enter them in mcmap.
rootbases = [[x,z,base_radius], ...] and is the list of locations
the roots can originate from, and the size of that location.
"""
treeposition = self.pos
height = self.height
for coord in self.foliage_cords:
# First, set the threshhold for randomly selecting this
# coordinate for root creation.
dist = (sqrt(float(coord[0] - treeposition[0]) ** 2 +
float(coord[2] - treeposition[2]) ** 2))
ydist = coord[1] - treeposition[1]
value = (self.branchdensity * 220 * height) / ((ydist + dist) ** 3)
# Randomly skip roots, based on the above threshold
if value < random():
continue
# initialize the internal variables from a selection of
# starting locations.
rootbase = choice(rootbases)
rootx = rootbase[0]
rootz = rootbase[1]
rootbaseradius = rootbase[2]
# Offset the root origin location by a random amount
# (radialy) from the starting location.
rndr = (sqrt(random()) * rootbaseradius * .618)
rndang = random() * 2 * pi
rndx = int(rndr * sin(rndang) + 0.5)
rndz = int(rndr * cos(rndang) + 0.5)
rndy = int(random() * rootbaseradius * 0.5)
startcoord = [rootx + rndx, treeposition[1] + rndy, rootz + rndz]
# offset is the distance from the root base to the root tip.
offset = [startcoord[i] - coord[i] for i in range(3)]
# If this is a mangrove tree, make the roots longer.
if SHAPE == "mangrove":
offset = [int(val * 1.618 - 1.5) for val in offset]
endcoord = [startcoord[i] + offset[i] for i in range(3)]
rootstartsize = (rootbaseradius * 0.618 * abs(offset[1]) /
(height * 0.618))
if rootstartsize < 1.0:
rootstartsize = 1.0
endsize = 1.0
# If ROOTS is set to "tostone" or "hanging" we need to check
# along the distance for collision with existing materials.
if ROOTS in ["tostone", "hanging"]:
offlength = sqrt(float(offset[0]) ** 2 +
float(offset[1]) ** 2 +
float(offset[2]) ** 2)
if offlength < 1:
continue
rootmid = endsize
# vec is a unit vector along the direction of the root.
vec = [offset[i] / offlength for i in range(3)]
if ROOTS == "tostone":
searchindex = STOPSROOTS
elif ROOTS == "hanging":
searchindex = ['air']
# startdist is how many steps to travel before starting to
# search for the material. It is used to ensure that large
# roots will go some distance before changing directions
# or stopping.
startdist = int(random() * 3.6 * sqrt(rootstartsize) + 2.8)
# searchstart is the coordinate where the search should begin
searchstart = [startcoord[i] + startdist * vec[i]
for i in range(3)]
# dist stores how far the search went (including searchstart)
# before encountering the expected marterial.
dist = startdist + dist_to_mat(searchstart, vec,
searchindex, mcmap, limit=offlength)
# If the distance to the material is less than the length
# of the root, change the end point of the root to where
# the search found the material.
if dist < offlength:
# rootmid is the size of the crossection at endcoord.
rootmid += (rootstartsize -
endsize) * (1 - dist / offlength)
# endcoord is the midpoint for hanging roots,
# and the endpoint for roots stopped by stone.
endcoord = [startcoord[i] + int(vec[i] * dist)
for i in range(3)]
if ROOTS == "hanging":
# remaining_dist is how far the root had left
# to go when it was stopped.
remaining_dist = offlength - dist
# Initialize bottomcord to the stopping point of
# the root, and then hang straight down
# a distance of remaining_dist.
bottomcord = endcoord[:]
bottomcord[1] += -int(remaining_dist)
# Make the hanging part of the hanging root.
taperedcylinder(endcoord, bottomcord,
rootmid, endsize, mcmap, WOODINFO)
# make the beginning part of hanging or "tostone" roots
taperedcylinder(startcoord, endcoord,
rootstartsize, rootmid, mcmap, WOODINFO)
# If you aren't searching for stone or air, just make the root.
else:
taperedcylinder(startcoord, endcoord,
rootstartsize, endsize, mcmap, WOODINFO)
if VERBOSEFEATURES:
print("root to " + str(endcoord) + " generated")
def maketrunk(self, mcmap):
"""Generate the trunk, roots, and branches in mcmap.
"""
height = self.height
trunkheight = self.trunkheight
trunkradius = self.trunkradius
treeposition = self.pos
starty = treeposition[1]
midy = treeposition[1] + int(trunkheight * .382)
topy = treeposition[1] + int(trunkheight + 0.5)
# In this method, x and z are the position of the trunk.
x = treeposition[0]
z = treeposition[2]
end_size_factor = trunkheight / height
midrad = trunkradius * (1 - end_size_factor * .5)
endrad = trunkradius * (1 - end_size_factor)
if endrad < 1.0:
endrad = 1.0
if midrad < endrad:
midrad = endrad
# Make the root buttresses, if indicated
if ROOTBUTTRESSES or SHAPE == "mangrove":
# The start radius of the trunk should be a little smaller if we
# are using root buttresses.
startrad = trunkradius * .8
# rootbases is used later in self.makeroots(...) as
# starting locations for the roots.
rootbases = [[x, z, startrad]]
buttress_radius = trunkradius * 0.382
# posradius is how far the root buttresses should be offset
# from the trunk.
posradius = trunkradius
# In mangroves, the root buttresses are much more extended.
if SHAPE == "mangrove":
posradius = posradius * 2.618
num_of_buttresses = int(sqrt(trunkradius) + 3.5)
for i in range(num_of_buttresses):
rndang = random() * 2 * pi
thisposradius = posradius * (0.9 + random() * .2)
# thisx and thisz are the x and z position for the base of
# the root buttress.
thisx = x + int(thisposradius * sin(rndang))
thisz = z + int(thisposradius * cos(rndang))
# thisbuttressradius is the radius of the buttress.
# Currently, root buttresses do not taper.
thisbuttressradius = buttress_radius * (0.618 + random())
if thisbuttressradius < 1.0:
thisbuttressradius = 1.0
# Make the root buttress.
taperedcylinder([thisx, starty, thisz], [x, midy, z],
thisbuttressradius, thisbuttressradius,
mcmap, WOODINFO)
if VERBOSEFEATURES:
print("root buttress to " + str([thisx, starty, thisz]) + " generated")
# Add this root buttress as a possible location at
# which roots can spawn.
rootbases += [[thisx, thisz, thisbuttressradius]]
else:
# If root buttresses are turned off, set the trunk radius
# to normal size.
startrad = trunkradius
rootbases = [[x, z, startrad]]
# Make the lower and upper sections of the trunk.
taperedcylinder([x, starty, z], [x, midy, z], startrad, midrad,
mcmap, WOODINFO)
taperedcylinder([x, midy, z], [x, topy, z], midrad, endrad,
mcmap, WOODINFO)
# Make the branches
self.makebranches(mcmap)
# Make the roots, if indicated.
if ROOTS in ["yes", "tostone", "hanging"]:
self.makeroots(rootbases, mcmap)
# Hollow the trunk, if specified
# check to make sure that the trunk is large enough to be hollow
if trunkradius > 2 and HOLLOWTRUNK:
# wall thickness is actually the double the wall thickness
# it is a diameter difference, not a radius difference.
wall_thickness = (1 + trunkradius * 0.1 * random())
if wall_thickness < 1.3: wall_thickness = 1.3
base_radius = trunkradius - wall_thickness
if base_radius < 1: base_radius = 1.0
mid_radius = midrad - wall_thickness
top_radius = endrad - wall_thickness
# the starting x and y can be offset by up to the wall thickness.
base_offset = int(wall_thickness)
ix = int(x)
iz = int(z)
x_choices = [i for i in range(ix - base_offset,
ix + base_offset + 1)]
start_x = choice(x_choices)
z_choices = [i for i in range(iz - base_offset,
iz + base_offset + 1)]
start_z = choice(z_choices)
taperedcylinder([start_x, starty, start_z], [x, midy, z],
base_radius, mid_radius,
mcmap, TRUNKFILLINFO)
hollow_top_y = int(topy + trunkradius + 1.5)
taperedcylinder([x, midy, z], [x, hollow_top_y, z],
mid_radius, top_radius,
mcmap, TRUNKFILLINFO)
return len(self.foliage_cords)
def prepare(self, mcmap):
"""Initialize the internal values for the Tree object.
Primarily, sets up the foliage cluster locations.
"""
treeposition = self.pos
self.trunkradius = .618 * sqrt(self.height * TRUNKTHICKNESS)
if self.trunkradius < 1:
self.trunkradius = 1
if BROKENTRUNK:
self.trunkheight = self.height * (.3 + random() * .4)
yend = int(treeposition[1] + self.trunkheight + .5)
else:
self.trunkheight = self.height
yend = int(treeposition[1] + self.height)
self.branchdensity = BRANCHDENSITY / FOLIAGEDENSITY
topy = treeposition[1] + int(self.trunkheight + 0.5)
foliage_coords = []
ystart = treeposition[1]
num_of_clusters_per_y = int(1.5 + (FOLIAGEDENSITY *
self.height / 19.) ** 2)
if num_of_clusters_per_y < 1:
num_of_clusters_per_y = 1
# make sure we don't spend too much time off the top of the map
top_of_map = mcmap.map_height
if yend > top_of_map: yend = top_of_map
if ystart > top_of_map: ystart = top_of_map
# Outdated, since maps can be twice as tall now.
for y in range(yend, ystart, -1):
for i in range(num_of_clusters_per_y):
shapefac = self.shapefunc(y - ystart)
if shapefac is None:
continue
r = (sqrt(random()) + .328) * shapefac
theta = random() * 2 * pi
x = int(r * sin(theta)) + treeposition[0]
z = int(r * cos(theta)) + treeposition[2]
# if there are values to search in BRANCHESPASS
# then check to see if this cluster is blocked
# by stuff, like dirt or rock, or whatever
if len(BRANCHESPASS):
dist = (sqrt(float(x - treeposition[0]) ** 2 +
float(z - treeposition[2]) ** 2))
slope = self.branchslope
if y - dist * slope > topy:
# the top of the tree
starty = topy
else:
starty = y - dist * slope
# the start position of the search
start = [treeposition[0], starty, treeposition[2]]
offset = [x - treeposition[0],
y - starty,
z - treeposition[2]]
offlength = sqrt(offset[0] ** 2 + offset[1] ** 2 + offset[2] ** 2)
# if the branch is as short as... nothing, don't bother.
if offlength < 1: continue
# unit vector for the search
vec = [offset[i] / offlength for i in range(3)]
mat_dist = dist_to_mat(start, vec, BRANCHESPASS,
mcmap, limit=offlength + 3, invert=True)
# after all that, if you find something, don't add
# this coordinate to the list
if mat_dist < offlength + 2:
continue
foliage_coords += [[int(x), int(y), int(z)]]
self.foliage_cords = foliage_coords
class RoundTree(ProceduralTree):
"""This kind of tree is designed to resemble a deciduous tree.
"""
def prepare(self, mcmap):
self.branchslope = 0.382
ProceduralTree.prepare(self, mcmap)
self.foliage_shape = [2, 3, 3, 2.5, 1.6]
self.trunkradius = self.trunkradius * 0.8
self.trunkheight = TRUNKHEIGHT * self.trunkheight
def shapefunc(self, y):
twigs = ProceduralTree.shapefunc(self, y)
if twigs is not None:
return twigs
if y < self.height * (.282 + .1 * sqrt(random())):
return None
radius = self.height / 2.
adj = self.height / 2. - y
if adj == 0:
dist = radius
elif abs(adj) >= radius:
dist = 0
else:
dist = sqrt((radius ** 2) - (adj ** 2))
dist = dist * .618
return dist