mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-03 19:28:49 -04:00
Add transition biomes and beaches.
could also help with #344 (although that might require some more system changes)
This commit is contained in:
parent
0f754c44a0
commit
60f5da1724
@ -36,4 +36,18 @@
|
||||
.smoothness = 0.2,
|
||||
},
|
||||
},
|
||||
.transitionBiomes = .{
|
||||
.{
|
||||
.id = "cubyz:beach",
|
||||
.chance = 1,
|
||||
.width = 1,
|
||||
.properties = .{.land, .inland},
|
||||
},
|
||||
.{
|
||||
.id = "cubyz:ocean_shelf",
|
||||
.chance = 1,
|
||||
.width = 2,
|
||||
.properties = .{.land, .inland},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -36,4 +36,18 @@
|
||||
.smoothness = 0.2,
|
||||
},
|
||||
},
|
||||
.transitionBiomes = .{
|
||||
.{
|
||||
.id = "cubyz:beach",
|
||||
.chance = 1,
|
||||
.width = 1,
|
||||
.properties = .{.land, .inland},
|
||||
},
|
||||
.{
|
||||
.id = "cubyz:ocean_shelf",
|
||||
.chance = 1,
|
||||
.width = 2,
|
||||
.properties = .{.land, .inland},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -28,4 +28,18 @@
|
||||
.smoothness = 0.2,
|
||||
},
|
||||
},
|
||||
.transitionBiomes = .{
|
||||
.{
|
||||
.id = "cubyz:beach",
|
||||
.chance = 1,
|
||||
.width = 1,
|
||||
.properties = .{.land, .inland},
|
||||
},
|
||||
.{
|
||||
.id = "cubyz:ocean_shelf",
|
||||
.chance = 1,
|
||||
.width = 2,
|
||||
.properties = .{.land, .inland},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ pub const BiomeSample = struct {
|
||||
roughness: f32,
|
||||
hills: f32,
|
||||
mountains: f32,
|
||||
seed: u64,
|
||||
};
|
||||
|
||||
const ClimateMapFragmentPosition = struct {
|
||||
@ -42,8 +43,10 @@ pub const ClimateMapFragment = struct {
|
||||
pub const mapSize = 1 << mapShift;
|
||||
pub const mapMask: i32 = mapSize - 1;
|
||||
|
||||
pub const mapEntrysSize = mapSize >> MapFragment.biomeShift;
|
||||
|
||||
pos: ClimateMapFragmentPosition,
|
||||
map: [mapSize >> MapFragment.biomeShift][mapSize >> MapFragment.biomeShift]BiomeSample = undefined,
|
||||
map: [mapEntrysSize][mapEntrysSize]BiomeSample = undefined,
|
||||
|
||||
refCount: Atomic(u16) = .init(0),
|
||||
|
||||
|
@ -160,6 +160,7 @@ fn hashGeneric(input: anytype) u64 {
|
||||
.pointer => switch(@typeInfo(T).pointer.size) {
|
||||
.One => blk: {
|
||||
if(@typeInfo(@typeInfo(T).pointer.child) == .@"fn") break :blk 0;
|
||||
if(@typeInfo(T).pointer.child == Biome) return hashGeneric(input.id);
|
||||
if(@typeInfo(T).pointer.child == anyopaque) break :blk 0;
|
||||
break :blk hashGeneric(input.*);
|
||||
},
|
||||
@ -206,21 +207,25 @@ fn u32ToVec3(color: u32) Vec3f {
|
||||
|
||||
/// A climate region with special ground, plants and structures.
|
||||
pub const Biome = struct { // MARK: Biome
|
||||
const GenerationProperties = packed struct(u8) {
|
||||
const GenerationProperties = packed struct(u12) {
|
||||
// pairs of opposite properties. In-between values are allowed.
|
||||
hot: bool = false,
|
||||
temperate: bool = false,
|
||||
cold: bool = false,
|
||||
|
||||
inland: bool = false,
|
||||
land: bool = false,
|
||||
ocean: bool = false,
|
||||
|
||||
wet: bool = false,
|
||||
neitherWetNorDry: bool = false,
|
||||
dry: bool = false,
|
||||
|
||||
mountain: bool = false,
|
||||
lowTerrain: bool = false,
|
||||
antiMountain: bool = false, //???
|
||||
|
||||
pub fn fromZon(zon: ZonElement) GenerationProperties {
|
||||
pub fn fromZon(zon: ZonElement, initMidValues: bool) GenerationProperties {
|
||||
var result: GenerationProperties = .{};
|
||||
for(zon.toSlice()) |child| {
|
||||
const property = child.as([]const u8, "");
|
||||
@ -230,6 +235,13 @@ pub const Biome = struct { // MARK: Biome
|
||||
}
|
||||
}
|
||||
}
|
||||
if(initMidValues) {
|
||||
// Fill all mid values if no value was specified in a group:
|
||||
const val: u12 = @bitCast(result);
|
||||
const mask: u12 = 0b001001001001;
|
||||
const empty = ~val & ~val >> 1 & ~val >> 2 & mask;
|
||||
result = @bitCast(val | empty << 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
@ -260,6 +272,7 @@ pub const Biome = struct { // MARK: Biome
|
||||
vegetationModels: []SimpleStructureModel = &.{},
|
||||
stripes: []Stripe = &.{},
|
||||
subBiomes: main.utils.AliasTable(*const Biome) = .{.items = &.{}, .aliasData = &.{}},
|
||||
transitionBiomes: []TransitionBiome = &.{},
|
||||
maxSubBiomeCount: f32,
|
||||
subBiomeTotalChance: f32 = 0,
|
||||
preferredMusic: []const u8, // TODO: Support multiple possibilities that are chosen based on time and danger.
|
||||
@ -272,7 +285,7 @@ pub const Biome = struct { // MARK: Biome
|
||||
self.* = Biome {
|
||||
.id = main.globalAllocator.dupe(u8, id),
|
||||
.paletteId = paletteId,
|
||||
.properties = GenerationProperties.fromZon(zon.getChild("properties")),
|
||||
.properties = GenerationProperties.fromZon(zon.getChild("properties"), true),
|
||||
.isCave = zon.get(bool, "isCave", false),
|
||||
.radius = (maxRadius + minRadius)/2,
|
||||
.radiusVariation = (maxRadius - minRadius)/2,
|
||||
@ -304,6 +317,26 @@ pub const Biome = struct { // MARK: Biome
|
||||
result.value_ptr.append(main.globalAllocator, .{.biomeId = self.id, .chance = parent.get(f32, "chance", 1)});
|
||||
}
|
||||
|
||||
const transitionBiomeList = zon.getChild("transitionBiomes").toSlice();
|
||||
if(transitionBiomeList.len != 0) {
|
||||
const transitionBiomes = main.globalAllocator.alloc(UnfinishedTransitionBiomeData, transitionBiomeList.len);
|
||||
for(transitionBiomes, transitionBiomeList) |*dst, src| {
|
||||
dst.* = .{
|
||||
.biomeId = src.get([]const u8, "id", ""),
|
||||
.chance = src.get(f32, "chance", 1),
|
||||
.propertyMask = GenerationProperties.fromZon(src.getChild("properties"), false),
|
||||
.width = src.get(u8, "width", 2),
|
||||
};
|
||||
// Fill all unspecified property groups:
|
||||
var properties: u12 = @bitCast(dst.propertyMask);
|
||||
const mask: u12 = 0b001001001001;
|
||||
const empty = ~properties & ~properties >> 1 & ~properties >> 2 & mask;
|
||||
properties |= empty | empty << 1 | empty << 2;
|
||||
dst.propertyMask = @bitCast(properties);
|
||||
}
|
||||
unfinishedTransitionBiomes.put(main.globalAllocator.allocator, self.id, transitionBiomes) catch unreachable;
|
||||
}
|
||||
|
||||
self.structure = BlockStructure.init(main.globalAllocator, zon.getChild("ground_structure"));
|
||||
|
||||
const structures = zon.getChild("structures");
|
||||
@ -333,6 +366,7 @@ pub const Biome = struct { // MARK: Biome
|
||||
pub fn deinit(self: *Biome) void {
|
||||
self.subBiomes.deinit(main.globalAllocator);
|
||||
self.structure.deinit(main.globalAllocator);
|
||||
main.globalAllocator.free(self.transitionBiomes);
|
||||
main.globalAllocator.free(self.vegetationModels);
|
||||
main.globalAllocator.free(self.stripes);
|
||||
main.globalAllocator.free(self.preferredMusic);
|
||||
@ -438,16 +472,16 @@ pub const TreeNode = union(enum) { // MARK: TreeNode
|
||||
var chanceMiddle: f32 = 0;
|
||||
var chanceUpper: f32 = 0;
|
||||
for(currentSlice) |*biome| {
|
||||
var properties: u32 = @as(u8, @bitCast(biome.properties));
|
||||
var properties: u32 = @as(u12, @bitCast(biome.properties));
|
||||
properties >>= parameterShift;
|
||||
properties = properties & 3;
|
||||
if(properties == 0) {
|
||||
chanceMiddle += biome.chance;
|
||||
} else if(properties == 1) {
|
||||
properties = properties & 7;
|
||||
if(properties == 1) {
|
||||
chanceLower += biome.chance;
|
||||
} else if(properties == 2) {
|
||||
} else if(properties == 4) {
|
||||
chanceUpper += biome.chance;
|
||||
} else unreachable;
|
||||
} else {
|
||||
chanceMiddle += biome.chance;
|
||||
}
|
||||
}
|
||||
const totalChance = chanceLower + chanceMiddle + chanceUpper;
|
||||
chanceLower /= totalChance;
|
||||
@ -476,10 +510,10 @@ pub const TreeNode = union(enum) { // MARK: TreeNode
|
||||
list.deinit(main.stackAllocator);
|
||||
};
|
||||
for(currentSlice) |biome| {
|
||||
var properties: u32 = @as(u8, @bitCast(biome.properties));
|
||||
var properties: u32 = @as(u12, @bitCast(biome.properties));
|
||||
properties >>= parameterShift;
|
||||
const valueMap = [_]usize{1, 0, 2, 1};
|
||||
lists[valueMap[properties & 3]].appendAssumeCapacity(biome);
|
||||
const valueMap = [8]usize{1, 0, 1, 1, 2, 1, 1, 1};
|
||||
lists[valueMap[properties & 7]].appendAssumeCapacity(biome);
|
||||
}
|
||||
lowerIndex = lists[0].items.len;
|
||||
@memcpy(currentSlice[0..lowerIndex], lists[0].items);
|
||||
@ -488,9 +522,9 @@ pub const TreeNode = union(enum) { // MARK: TreeNode
|
||||
@memcpy(currentSlice[upperIndex..], lists[2].items);
|
||||
}
|
||||
|
||||
self.branch.children[0] = TreeNode.init(allocator, currentSlice[0..lowerIndex], parameterShift+2);
|
||||
self.branch.children[1] = TreeNode.init(allocator, currentSlice[lowerIndex..upperIndex], parameterShift+2);
|
||||
self.branch.children[2] = TreeNode.init(allocator, currentSlice[upperIndex..], parameterShift+2);
|
||||
self.branch.children[0] = TreeNode.init(allocator, currentSlice[0..lowerIndex], parameterShift+3);
|
||||
self.branch.children[1] = TreeNode.init(allocator, currentSlice[lowerIndex..upperIndex], parameterShift+3);
|
||||
self.branch.children[2] = TreeNode.init(allocator, currentSlice[upperIndex..], parameterShift+3);
|
||||
|
||||
return self;
|
||||
}
|
||||
@ -538,6 +572,7 @@ var biomes: main.List(Biome) = undefined;
|
||||
var caveBiomes: main.List(Biome) = undefined;
|
||||
var biomesById: std.StringHashMap(*Biome) = undefined;
|
||||
pub var byTypeBiomes: *TreeNode = undefined;
|
||||
|
||||
const UnfinishedSubBiomeData = struct {
|
||||
biomeId: []const u8,
|
||||
chance: f32,
|
||||
@ -547,6 +582,20 @@ const UnfinishedSubBiomeData = struct {
|
||||
};
|
||||
var unfinishedSubBiomes: std.StringHashMapUnmanaged(main.ListUnmanaged(UnfinishedSubBiomeData)) = .{};
|
||||
|
||||
const UnfinishedTransitionBiomeData = struct {
|
||||
biomeId: []const u8,
|
||||
chance: f32,
|
||||
propertyMask: Biome.GenerationProperties,
|
||||
width: u8,
|
||||
};
|
||||
const TransitionBiome = struct {
|
||||
biome: *const Biome,
|
||||
chance: f32,
|
||||
propertyMask: Biome.GenerationProperties,
|
||||
width: u8,
|
||||
};
|
||||
var unfinishedTransitionBiomes: std.StringHashMapUnmanaged([]UnfinishedTransitionBiomeData) = .{};
|
||||
|
||||
pub fn init() void {
|
||||
biomes = .init(main.globalAllocator);
|
||||
caveBiomes = .init(main.globalAllocator);
|
||||
@ -631,6 +680,32 @@ pub fn finishLoading() void {
|
||||
subBiomeDataList.deinit(main.globalAllocator);
|
||||
}
|
||||
unfinishedSubBiomes.clearAndFree(main.globalAllocator.allocator);
|
||||
|
||||
var transitionBiomeIterator = unfinishedTransitionBiomes.iterator();
|
||||
while(transitionBiomeIterator.next()) |transitionBiomeData| {
|
||||
const parentBiome = biomesById.get(transitionBiomeData.key_ptr.*) orelse unreachable;
|
||||
const transitionBiomes = transitionBiomeData.value_ptr.*;
|
||||
parentBiome.transitionBiomes = main.globalAllocator.alloc(TransitionBiome, transitionBiomes.len);
|
||||
for(parentBiome.transitionBiomes, transitionBiomes) |*res, src| {
|
||||
res.* = .{
|
||||
.biome = biomesById.get(src.biomeId) orelse {
|
||||
std.log.warn("Skipping transition biome with unknown id {s}", .{src.biomeId});
|
||||
res.* = .{
|
||||
.biome = &biomes.items[0],
|
||||
.chance = 0,
|
||||
.propertyMask = .{},
|
||||
.width = 0,
|
||||
};
|
||||
continue;
|
||||
},
|
||||
.chance = src.chance,
|
||||
.propertyMask = src.propertyMask,
|
||||
.width = src.width,
|
||||
};
|
||||
}
|
||||
main.globalAllocator.free(transitionBiomes);
|
||||
}
|
||||
unfinishedTransitionBiomes.clearAndFree(main.globalAllocator.allocator);
|
||||
}
|
||||
|
||||
pub fn hasRegistered(id: []const u8) bool {
|
||||
|
@ -188,13 +188,13 @@ const GenerationStructure = struct {
|
||||
|
||||
pub fn init(allocator: NeverFailingAllocator, wx: i32, wy: i32, width: u31, height: u31, tree: *TreeNode, worldSeed: u64) GenerationStructure {
|
||||
const self: GenerationStructure = .{
|
||||
.chunks = Array2D(*Chunk).init(allocator, 2 + @divExact(width, chunkSize), 2 + @divExact(height, chunkSize)),
|
||||
.chunks = Array2D(*Chunk).init(allocator, 4 + @divExact(width, chunkSize), 4 + @divExact(height, chunkSize)),
|
||||
};
|
||||
var x: u31 = 0;
|
||||
while(x < self.chunks.width) : (x += 1) {
|
||||
var y: u31 = 0;
|
||||
while(y < self.chunks.height) : (y += 1) {
|
||||
self.chunks.ptr(x, y).* = Chunk.init(allocator, tree, worldSeed, wx +% x*chunkSize -% chunkSize, wy +% y*chunkSize -% chunkSize);
|
||||
self.chunks.ptr(x, y).* = Chunk.init(allocator, tree, worldSeed, wx +% x*chunkSize -% 2*chunkSize, wy +% y*chunkSize -% 2*chunkSize);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
@ -207,7 +207,7 @@ const GenerationStructure = struct {
|
||||
self.chunks.deinit(allocator);
|
||||
}
|
||||
|
||||
fn findClosestBiomeTo(self: GenerationStructure, wx: i32, wy: i32, relX: u31, relY: u31) BiomeSample {
|
||||
fn findClosestBiomeTo(self: GenerationStructure, wx: i32, wy: i32, relX: i32, relY: i32, worldSeed: u64) BiomeSample {
|
||||
const x = wx +% relX*terrain.SurfaceMap.MapFragment.biomeSize;
|
||||
const y = wy +% relY*terrain.SurfaceMap.MapFragment.biomeSize;
|
||||
var closestDist = std.math.floatMax(f32);
|
||||
@ -218,15 +218,15 @@ const GenerationStructure = struct {
|
||||
var hills: f32 = 0;
|
||||
var mountains: f32 = 0;
|
||||
var totalWeight: f32 = 0;
|
||||
const cellX: i32 = relX/(chunkSize/terrain.SurfaceMap.MapFragment.biomeSize);
|
||||
const cellY: i32 = relY/(chunkSize/terrain.SurfaceMap.MapFragment.biomeSize);
|
||||
const cellX: i32 = @divFloor(relX, (chunkSize/terrain.SurfaceMap.MapFragment.biomeSize));
|
||||
const cellY: i32 = @divFloor(relY, (chunkSize/terrain.SurfaceMap.MapFragment.biomeSize));
|
||||
// Note that at a small loss of details we can assume that all BiomePoints are withing ±1 chunks of the current one.
|
||||
var dx: i32 = 0;
|
||||
while(dx <= 2) : (dx += 1) {
|
||||
var dx: i32 = 1;
|
||||
while(dx <= 3) : (dx += 1) {
|
||||
const totalX = cellX + dx;
|
||||
if(totalX < 0 or totalX >= self.chunks.width) continue;
|
||||
var dy: i32 = 0;
|
||||
while(dy <= 2) : (dy += 1) {
|
||||
var dy: i32 = 1;
|
||||
while(dy <= 3) : (dy += 1) {
|
||||
const totalY = cellY + dy;
|
||||
if(totalY < 0 or totalY >= self.chunks.height) continue;
|
||||
const chunk = self.chunks.get(@intCast(totalX), @intCast(totalY));
|
||||
@ -242,7 +242,7 @@ const GenerationStructure = struct {
|
||||
}
|
||||
weight *= weight;
|
||||
// The important bit is the ocean height, that's the only point where we actually need the transition point to be exact for beaches to occur.
|
||||
weight /= @abs(biomePoint.height - 16);
|
||||
weight /= @abs(biomePoint.height - 12);
|
||||
height += biomePoint.height*weight;
|
||||
roughness += biomePoint.biome.roughness*weight;
|
||||
hills += biomePoint.biome.hills*weight;
|
||||
@ -265,6 +265,7 @@ const GenerationStructure = struct {
|
||||
.roughness = roughness/totalWeight,
|
||||
.hills = hills/totalWeight,
|
||||
.mountains = mountains/totalWeight,
|
||||
.seed = random.initSeed2D(worldSeed, closestBiomePoint.pos),
|
||||
};
|
||||
}
|
||||
|
||||
@ -279,12 +280,14 @@ const GenerationStructure = struct {
|
||||
while(y < max[1]) : (y += 1) {
|
||||
const distSquare = vec.lengthSquare(Vec2f{x, y} - relPos);
|
||||
if(distSquare < relRadius*relRadius) {
|
||||
var seed = map.map[@intFromFloat(x)][@intFromFloat(y)].seed;
|
||||
map.map[@intFromFloat(x)][@intFromFloat(y)] = .{
|
||||
.biome = biome,
|
||||
.roughness = biome.roughness,
|
||||
.hills = biome.hills,
|
||||
.mountains = biome.mountains,
|
||||
.height = (@as(f32, @floatFromInt(biome.minHeight)) + @as(f32, @floatFromInt(biome.maxHeight)))/2, // TODO: Randomize
|
||||
.height = @as(f32, @floatFromInt(biome.minHeight)) + @as(f32, @floatFromInt(biome.maxHeight - biome.minHeight))*random.nextFloat(&seed),
|
||||
.seed = map.map[@intFromFloat(x)][@intFromFloat(y)].seed,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -323,14 +326,68 @@ const GenerationStructure = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toMap(self: GenerationStructure, map: *ClimateMapFragment, width: u31, height: u31, worldSeed: u64) void {
|
||||
var x: u31 = 0;
|
||||
while(x < width/terrain.SurfaceMap.MapFragment.biomeSize) : (x += 1) {
|
||||
var y: u31 = 0;
|
||||
while(y < height/terrain.SurfaceMap.MapFragment.biomeSize) : (y += 1) {
|
||||
map.map[x][y] = self.findClosestBiomeTo(map.pos.wx, map.pos.wy, x, y);
|
||||
fn addTransitionBiomes(comptime size: usize, comptime margin: usize, map: *[size][size]BiomeSample) void {
|
||||
const neighborData = main.stackAllocator.create([16][size][size]u12);
|
||||
defer main.stackAllocator.free(neighborData);
|
||||
for(0..size) |x| {
|
||||
for(0..size) |y| {
|
||||
neighborData[0][x][y] = @bitCast(map[x][y].biome.properties);
|
||||
}
|
||||
}
|
||||
for(1..neighborData.len) |i| {
|
||||
for(1..size - 1) |x| {
|
||||
for(1..size - 1) |y| {
|
||||
neighborData[i][x][y] = neighborData[i-1][x][y] | neighborData[i-1][x-1][y] | neighborData[i-1][x+1][y] | neighborData[i-1][x][y-1] | neighborData[i-1][x][y+1];
|
||||
}
|
||||
}
|
||||
}
|
||||
for(margin..size - margin) |x| {
|
||||
for(margin..size - margin) |y| {
|
||||
const point = map[x][y];
|
||||
if(point.biome.transitionBiomes.len == 0) {
|
||||
std.debug.assert(!std.mem.eql(u8, "cubyz:ocean", point.biome.id));
|
||||
continue;
|
||||
}
|
||||
var seed = point.seed;
|
||||
for(point.biome.transitionBiomes) |transitionBiome| {
|
||||
const biomeMask: u12 = @bitCast(transitionBiome.propertyMask);
|
||||
const neighborMask = neighborData[@min(neighborData.len - 1, transitionBiome.width)][x][y];
|
||||
// Check if all triplets have a matching entry:
|
||||
const mask: u12 = 0b001001001001;
|
||||
var result = biomeMask & neighborMask;
|
||||
result = (result | result >> 1 | result >> 2);
|
||||
if(result & mask == mask) {
|
||||
if(random.nextFloat(&seed) < transitionBiome.chance) {
|
||||
map[x][y] = .{
|
||||
.biome = transitionBiome.biome,
|
||||
.roughness = transitionBiome.biome.roughness,
|
||||
.hills = transitionBiome.biome.hills,
|
||||
.mountains = transitionBiome.biome.mountains,
|
||||
.height = @as(f32, @floatFromInt(transitionBiome.biome.minHeight)) + @as(f32, @floatFromInt(transitionBiome.biome.maxHeight - transitionBiome.biome.minHeight))*random.nextFloat(&seed),
|
||||
.seed = map[x][y].seed,
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toMap(self: GenerationStructure, map: *ClimateMapFragment, width: u31, height: u31, worldSeed: u64) void {
|
||||
const margin: u31 = chunkSize >> terrain.SurfaceMap.MapFragment.biomeShift;
|
||||
var preMap: [ClimateMapFragment.mapEntrysSize + 2*margin][ClimateMapFragment.mapEntrysSize + 2*margin]BiomeSample = undefined;
|
||||
var x: i32 = -@as(i32, margin);
|
||||
while(x < width/terrain.SurfaceMap.MapFragment.biomeSize + margin) : (x += 1) {
|
||||
var y: i32 = -@as(i32, margin);
|
||||
while(y < height/terrain.SurfaceMap.MapFragment.biomeSize + margin) : (y += 1) {
|
||||
preMap[@intCast(x + margin)][@intCast(y + margin)] = self.findClosestBiomeTo(map.pos.wx, map.pos.wy, x, y, worldSeed);
|
||||
}
|
||||
}
|
||||
addTransitionBiomes(ClimateMapFragment.mapEntrysSize + 2*margin, margin, &preMap);
|
||||
for(0..ClimateMapFragment.mapEntrysSize) |_x| {
|
||||
@memcpy(&map.map[_x], preMap[_x + margin][margin..][0..ClimateMapFragment.mapEntrysSize]);
|
||||
}
|
||||
|
||||
// Add some sub-biomes:
|
||||
var extraBiomes = main.List(BiomePoint).init(main.stackAllocator);
|
||||
|
Loading…
x
Reference in New Issue
Block a user