From 19c7b37582b30a0840c3b425d2c467439b14ba68 Mon Sep 17 00:00:00 2001 From: paulevsGitch Date: Fri, 11 Dec 2020 02:09:17 +0300 Subject: [PATCH] Ice Starfield biome --- .../betterend/particle/ParticleSnowflake.java | 96 ++++++++++++++++++ .../betterend/particle/ParticleSulphur.java | 1 + .../java/ru/betterend/registry/EndBiomes.java | 4 + .../ru/betterend/registry/EndFeatures.java | 2 + .../ru/betterend/registry/EndParticles.java | 3 + src/main/java/ru/betterend/util/MHelper.java | 31 ++++++ .../world/biome/BiomeIceStarfield.java | 17 ++++ .../features/terrain/IceStarFeature.java | 95 +++++++++++++++++ .../assets/betterend/particles/snowflake.json | 11 ++ .../textures/particle/snowflake_0.png | Bin 0 -> 1642 bytes .../textures/particle/snowflake_1.png | Bin 0 -> 1420 bytes .../textures/particle/snowflake_2.png | Bin 0 -> 1833 bytes .../textures/particle/snowflake_3.png | Bin 0 -> 1817 bytes .../textures/particle/snowflake_4.png | Bin 0 -> 1824 bytes .../textures/particle/snowflake_5.png | Bin 0 -> 1833 bytes .../textures/particle/snowflake_6.png | Bin 0 -> 1830 bytes 16 files changed, 260 insertions(+) create mode 100644 src/main/java/ru/betterend/particle/ParticleSnowflake.java create mode 100644 src/main/java/ru/betterend/world/biome/BiomeIceStarfield.java create mode 100644 src/main/java/ru/betterend/world/features/terrain/IceStarFeature.java create mode 100644 src/main/resources/assets/betterend/particles/snowflake.json create mode 100644 src/main/resources/assets/betterend/textures/particle/snowflake_0.png create mode 100644 src/main/resources/assets/betterend/textures/particle/snowflake_1.png create mode 100644 src/main/resources/assets/betterend/textures/particle/snowflake_2.png create mode 100644 src/main/resources/assets/betterend/textures/particle/snowflake_3.png create mode 100644 src/main/resources/assets/betterend/textures/particle/snowflake_4.png create mode 100644 src/main/resources/assets/betterend/textures/particle/snowflake_5.png create mode 100644 src/main/resources/assets/betterend/textures/particle/snowflake_6.png diff --git a/src/main/java/ru/betterend/particle/ParticleSnowflake.java b/src/main/java/ru/betterend/particle/ParticleSnowflake.java new file mode 100644 index 00000000..088a2b79 --- /dev/null +++ b/src/main/java/ru/betterend/particle/ParticleSnowflake.java @@ -0,0 +1,96 @@ +package ru.betterend.particle; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.particle.Particle; +import net.minecraft.client.particle.ParticleFactory; +import net.minecraft.client.particle.ParticleTextureSheet; +import net.minecraft.client.particle.SpriteBillboardParticle; +import net.minecraft.client.particle.SpriteProvider; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.particle.DefaultParticleType; +import net.minecraft.util.math.MathHelper; +import ru.betterend.util.MHelper; + +@Environment(EnvType.CLIENT) +public class ParticleSnowflake extends SpriteBillboardParticle { + private int ticks; + private double preVX; + private double preVY; + private double preVZ; + private double nextVX; + private double nextVY; + private double nextVZ; + + protected ParticleSnowflake(ClientWorld world, double x, double y, double z, double r, double g, double b, SpriteProvider sprites) { + super(world, x, y, z, r, g, b); + setSprite(sprites); + + this.maxAge = MHelper.randRange(150, 300, random); + this.scale = MHelper.randRange(0.05F, 0.3F, random); + this.setColorAlpha(0F); + + preVX = random.nextGaussian() * 0.015; + preVY = random.nextGaussian() * 0.015; + preVZ = random.nextGaussian() * 0.015; + + nextVX = random.nextGaussian() * 0.015; + nextVY = random.nextGaussian() * 0.015; + nextVZ = random.nextGaussian() * 0.015; + } + + @Override + public void tick() { + ticks ++; + if (ticks > 200) { + preVX = nextVX; + preVY = nextVY; + preVZ = nextVZ; + nextVX = random.nextGaussian() * 0.015; + nextVY = random.nextGaussian() * 0.015; + nextVZ = random.nextGaussian() * 0.015; + if (random.nextInt(4) == 0) { + nextVY = Math.abs(nextVY); + } + ticks = 0; + } + double delta = (double) ticks / 200.0; + + if (this.age <= 40) { + this.setColorAlpha(this.age / 40F); + } + else if (this.age >= this.maxAge - 40) { + this.setColorAlpha((this.maxAge - this.age) / 40F); + } + + if (this.age >= this.maxAge) { + this.markDead(); + } + + this.velocityX = MathHelper.lerp(delta, preVX, nextVX); + this.velocityY = MathHelper.lerp(delta, preVY, nextVY); + this.velocityZ = MathHelper.lerp(delta, preVZ, nextVZ); + + super.tick(); + } + + @Override + public ParticleTextureSheet getType() { + return ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT; + } + + @Environment(EnvType.CLIENT) + public static class FactorySnowflake implements ParticleFactory { + + private final SpriteProvider sprites; + + public FactorySnowflake(SpriteProvider sprites) { + this.sprites = sprites; + } + + @Override + public Particle createParticle(DefaultParticleType type, ClientWorld world, double x, double y, double z, double vX, double vY, double vZ) { + return new ParticleSnowflake(world, x, y, z, 1, 1, 1, sprites); + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/betterend/particle/ParticleSulphur.java b/src/main/java/ru/betterend/particle/ParticleSulphur.java index a188cb08..522690ed 100644 --- a/src/main/java/ru/betterend/particle/ParticleSulphur.java +++ b/src/main/java/ru/betterend/particle/ParticleSulphur.java @@ -28,6 +28,7 @@ public class ParticleSulphur extends SpriteBillboardParticle { this.maxAge = MHelper.randRange(150, 300, random); this.scale = MHelper.randRange(0.05F, 0.15F, random); + this.setColorAlpha(0); preVX = random.nextGaussian() * 0.015; preVY = random.nextGaussian() * 0.015; diff --git a/src/main/java/ru/betterend/registry/EndBiomes.java b/src/main/java/ru/betterend/registry/EndBiomes.java index f1db8621..35bdc6da 100644 --- a/src/main/java/ru/betterend/registry/EndBiomes.java +++ b/src/main/java/ru/betterend/registry/EndBiomes.java @@ -31,6 +31,7 @@ import ru.betterend.world.biome.BiomeChorusForest; import ru.betterend.world.biome.BiomeCrystalMountains; import ru.betterend.world.biome.BiomeDustWastelands; import ru.betterend.world.biome.BiomeFoggyMushroomland; +import ru.betterend.world.biome.BiomeIceStarfield; import ru.betterend.world.biome.BiomeMegalake; import ru.betterend.world.biome.BiomeMegalakeGrove; import ru.betterend.world.biome.BiomePaintedMountains; @@ -75,6 +76,9 @@ public class EndBiomes { public static final EndBiome BLOSSOMING_SPIRES = registerBiome(new BiomeBlossomingSpires(), BiomeType.LAND); public static final EndBiome SULPHUR_SPRINGS = registerBiome(new BiomeSulphurSprings(), BiomeType.LAND); + // Better End Void + public static final EndBiome ICE_STARFIELD = registerBiome(new BiomeIceStarfield(), BiomeType.VOID); + public static void register() {} public static void mutateRegistry(Registry biomeRegistry) { diff --git a/src/main/java/ru/betterend/registry/EndFeatures.java b/src/main/java/ru/betterend/registry/EndFeatures.java index d679299f..cb3be9c5 100644 --- a/src/main/java/ru/betterend/registry/EndFeatures.java +++ b/src/main/java/ru/betterend/registry/EndFeatures.java @@ -33,6 +33,7 @@ import ru.betterend.world.features.bushes.TenaneaBushFeature; import ru.betterend.world.features.terrain.EndLakeFeature; import ru.betterend.world.features.terrain.FloatingSpireFeature; import ru.betterend.world.features.terrain.GeyserFeature; +import ru.betterend.world.features.terrain.IceStarFeature; import ru.betterend.world.features.terrain.RoundCaveFeature; import ru.betterend.world.features.terrain.SpireFeature; import ru.betterend.world.features.terrain.SulphuricCaveFeature; @@ -116,6 +117,7 @@ public class EndFeatures { public static final EndFeature GEYSER = EndFeature.makeRawGenFeature("geyser", new GeyserFeature(), 8); public static final EndFeature SULPHURIC_LAKE = EndFeature.makeLakeFeature("sulphuric_lake", new SulphuricLakeFeature(), 8); public static final EndFeature SULPHURIC_CAVE = EndFeature.makeCountRawFeature("sulphuric_cave", new SulphuricCaveFeature(), 2); + public static final EndFeature ICE_STAR = EndFeature.makeRawGenFeature("ice_star", new IceStarFeature(), 15); // Ores // public static final EndFeature ENDER_ORE = EndFeature.makeOreFeature("ender_ore", EndBlocks.ENDER_ORE, 6, 3, 0, 4, 96); diff --git a/src/main/java/ru/betterend/registry/EndParticles.java b/src/main/java/ru/betterend/registry/EndParticles.java index ee73fc2f..6e43c3af 100644 --- a/src/main/java/ru/betterend/registry/EndParticles.java +++ b/src/main/java/ru/betterend/registry/EndParticles.java @@ -11,6 +11,7 @@ import ru.betterend.particle.InfusionParticle; import ru.betterend.particle.InfusionParticleType; import ru.betterend.particle.ParticleGeyser; import ru.betterend.particle.ParticleGlowingSphere; +import ru.betterend.particle.ParticleSnowflake; import ru.betterend.particle.ParticleSulphur; import ru.betterend.particle.PaticlePortalSphere; @@ -20,6 +21,7 @@ public class EndParticles { public static final ParticleType INFUSION = register("infusion", FabricParticleTypes.complex(InfusionParticleType.PARAMETERS_FACTORY)); public static final DefaultParticleType SULPHUR_PARTICLE = register("sulphur_particle"); public static final DefaultParticleType GEYSER_PARTICLE = registerFar("geyser_particle"); + public static final DefaultParticleType SNOWFLAKE = register("snowflake"); public static void register() { ParticleFactoryRegistry.getInstance().register(GLOWING_SPHERE, ParticleGlowingSphere.FactoryGlowingSphere::new); @@ -27,6 +29,7 @@ public class EndParticles { ParticleFactoryRegistry.getInstance().register(INFUSION, InfusionParticle.InfusionFactory::new); ParticleFactoryRegistry.getInstance().register(SULPHUR_PARTICLE, ParticleSulphur.FactorySulphur::new); ParticleFactoryRegistry.getInstance().register(GEYSER_PARTICLE, ParticleGeyser.FactoryGeyser::new); + ParticleFactoryRegistry.getInstance().register(SNOWFLAKE, ParticleSnowflake.FactorySnowflake::new); } private static DefaultParticleType register(String name) { diff --git a/src/main/java/ru/betterend/util/MHelper.java b/src/main/java/ru/betterend/util/MHelper.java index 5c6fc094..de241a06 100644 --- a/src/main/java/ru/betterend/util/MHelper.java +++ b/src/main/java/ru/betterend/util/MHelper.java @@ -2,11 +2,14 @@ package ru.betterend.util; import java.util.Random; +import net.minecraft.client.util.math.Vector3f; + public class MHelper { public static final float PI2 = (float) (Math.PI * 2); private static final int ALPHA = 255 << 24; public static final Random RANDOM = new Random(); private static final float RAD_TO_DEG = 57.295779513082320876798154814105F; + public static final float PHI = (float) (Math.PI * (3 - Math.sqrt(5))); public static int color(int r, int g, int b) { return ALPHA | (r << 16) | (g << 8) | b; @@ -247,4 +250,32 @@ public class MHelper { public static final float radiandToDegrees(float value) { return value * RAD_TO_DEG; } + + public static Vector3f cross(Vector3f vec1, Vector3f vec2) + { + float cx = vec1.getY() * vec2.getZ() - vec1.getZ() * vec2.getY(); + float cy = vec1.getZ() * vec2.getX() - vec1.getX() * vec2.getZ(); + float cz = vec1.getX() * vec2.getY() - vec1.getY() * vec2.getX(); + return new Vector3f(cx, cy, cz); + } + + public static Vector3f normalize(Vector3f vec) { + float length = lengthSqr(vec.getX(), vec.getY(), vec.getZ()); + if (length > 0) { + length = (float) Math.sqrt(length); + float x = vec.getX() / length; + float y = vec.getY() / length; + float z = vec.getZ() / length; + vec.set(x, y, z); + } + return vec; + } + + public static float angle(Vector3f vec1, Vector3f vec2) + { + float dot = vec1.getX() * vec2.getX() + vec1.getY() * vec2.getY() + vec1.getZ() * vec2.getZ(); + float length1 = lengthSqr(vec1.getX(), vec1.getY(), vec1.getZ()); + float length2 = lengthSqr(vec2.getX(), vec2.getY(), vec2.getZ()); + return (float) Math.acos(dot / Math.sqrt(length1 * length2)); + } } diff --git a/src/main/java/ru/betterend/world/biome/BiomeIceStarfield.java b/src/main/java/ru/betterend/world/biome/BiomeIceStarfield.java new file mode 100644 index 00000000..79b99265 --- /dev/null +++ b/src/main/java/ru/betterend/world/biome/BiomeIceStarfield.java @@ -0,0 +1,17 @@ +package ru.betterend.world.biome; + +import net.minecraft.entity.EntityType; +import ru.betterend.registry.EndFeatures; +import ru.betterend.registry.EndParticles; + +public class BiomeIceStarfield extends EndBiome { + public BiomeIceStarfield() { + super(new BiomeDefinition("ice_starfield") + .setFogColor(224, 245, 254) + .setFogDensity(2.2F) + .setFoliageColor(193, 244, 244) + .setParticles(EndParticles.SNOWFLAKE, 0.001F) + .addFeature(EndFeatures.ICE_STAR) + .addMobSpawn(EntityType.ENDERMAN, 20, 1, 4)); + } +} diff --git a/src/main/java/ru/betterend/world/features/terrain/IceStarFeature.java b/src/main/java/ru/betterend/world/features/terrain/IceStarFeature.java new file mode 100644 index 00000000..93a7d010 --- /dev/null +++ b/src/main/java/ru/betterend/world/features/terrain/IceStarFeature.java @@ -0,0 +1,95 @@ +package ru.betterend.world.features.terrain; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.util.math.Vector3f; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.StructureWorldAccess; +import net.minecraft.world.gen.chunk.ChunkGenerator; +import net.minecraft.world.gen.feature.DefaultFeatureConfig; +import ru.betterend.noise.OpenSimplexNoise; +import ru.betterend.util.MHelper; +import ru.betterend.util.sdf.SDF; +import ru.betterend.util.sdf.operator.SDFRotation; +import ru.betterend.util.sdf.operator.SDFTranslate; +import ru.betterend.util.sdf.operator.SDFUnion; +import ru.betterend.util.sdf.primitive.SDFCapedCone; +import ru.betterend.world.features.DefaultFeature; + +public class IceStarFeature extends DefaultFeature { + @Override + public boolean generate(StructureWorldAccess world, ChunkGenerator chunkGenerator, Random random, BlockPos pos, DefaultFeatureConfig config) { + float size = MHelper.randRange(5F, 15F, random); + int count = MHelper.randRange(7, 25, random); + List points = getFibonacciPoints(count); + SDF sdf = null; + SDF spike = new SDFCapedCone().setRadius1(3 + (size - 5) * 0.2F).setRadius2(0).setHeight(size).setBlock(Blocks.ICE); + spike = new SDFTranslate().setTranslate(0, size - 0.5F, 0).setSource(spike); + for (Vector3f point: points) { + SDF rotated = spike; + point = MHelper.normalize(point); + float angle = MHelper.angle(Vector3f.POSITIVE_Y, point); + if (angle > 0.01F && angle < 3.14F) { + Vector3f axis = MHelper.normalize(MHelper.cross(Vector3f.POSITIVE_Y, point)); + rotated = new SDFRotation().setRotation(axis, angle).setSource(spike); + } + else if (angle > 1) { + rotated = new SDFRotation().setRotation(Vector3f.POSITIVE_Y, (float) Math.PI).setSource(spike); + } + sdf = (sdf == null) ? rotated : new SDFUnion().setSourceA(sdf).setSourceB(rotated); + } + + int x1 = (pos.getX() >> 4) << 4; + int z1 = (pos.getZ() >> 4) << 4; + pos = new BlockPos(x1 + random.nextInt(16), MHelper.randRange(32, 128, random), z1 + random.nextInt(16)); + + OpenSimplexNoise noise1 = new OpenSimplexNoise(random.nextLong()); + OpenSimplexNoise noise2 = new OpenSimplexNoise(random.nextLong()); + + final boolean hasSnow = random.nextBoolean(); + BlockState layer = Blocks.SNOW.getDefaultState(); + BlockState snow = Blocks.SNOW_BLOCK.getDefaultState(); + BlockState blue = Blocks.BLUE_ICE.getDefaultState(); + BlockState packed = Blocks.PACKED_ICE.getDefaultState(); + sdf.setPostProcess((info) -> { + BlockPos bpos = info.getPos(); + + if (hasSnow && !info.getState().isOf(Blocks.SNOW) && info.getStateUp().isAir()) { + info.setBlockPos(bpos.up(), layer.with(Properties.LAYERS, MHelper.randRange(1, 3, random))); + //info.setBlockPos(bpos.up(), layer); + return snow; + } + + if (noise1.eval(bpos.getX(), bpos.getY(), bpos.getZ()) > 0.3) { + return packed; + } + else if (noise2.eval(bpos.getX(), bpos.getY(), bpos.getZ()) > 0.3) { + return blue; + } + else { + return info.getState(); + } + }).fillRecursive(world, pos); + + return true; + } + + private List getFibonacciPoints(int count) { + float max = count - 1; + List result = new ArrayList(count); + for (int i = 0; i < count; i++) { + float y = 1F - (i / max) * 2F; + float radius = (float) Math.sqrt(1F - y * y); + float theta = MHelper.PHI * i; + float x = (float) Math.cos(theta) * radius; + float z = (float) Math.sin(theta) * radius; + result.add(new Vector3f(x, y, z)); + } + return result; + } +} diff --git a/src/main/resources/assets/betterend/particles/snowflake.json b/src/main/resources/assets/betterend/particles/snowflake.json new file mode 100644 index 00000000..e5945670 --- /dev/null +++ b/src/main/resources/assets/betterend/particles/snowflake.json @@ -0,0 +1,11 @@ +{ + "textures": [ + "betterend:snowflake_0", + "betterend:snowflake_1", + "betterend:snowflake_2", + "betterend:snowflake_3", + "betterend:snowflake_4", + "betterend:snowflake_5", + "betterend:snowflake_6" + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/betterend/textures/particle/snowflake_0.png b/src/main/resources/assets/betterend/textures/particle/snowflake_0.png new file mode 100644 index 0000000000000000000000000000000000000000..e05607bf327ae5d942e95bbe8506e1473d9f44f2 GIT binary patch literal 1642 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/betterend/textures/particle/snowflake_3.png b/src/main/resources/assets/betterend/textures/particle/snowflake_3.png new file mode 100644 index 0000000000000000000000000000000000000000..2bded6bb2ca48de9c6149167c960002c0b75e070 GIT binary patch literal 1817 zcmb_dO>g5w81@Qj)m>B}goIXmvRn>)O+2C-sgSaXJ$S>*nfZX z_TAftVQdce`iF4e(AUOI`2YQ-pTB_Ht$gocX&5iQqOTjq*WcVRjGLFq@VGjTKA>Es zEhbbfTk|vrv|-%ap6868$jXf6ILSim&!2v=%tVCNQ9Hs>-jkDL@1l?oF7}7~;)Dmn z+P-hzn^Pc2WyQ>SI?YNt53L0+g|U8ZTjl~%orKno4rm@n2WC$d((JS_;uyQ8AGDkf zAszg->0%e#xNSQwayk?f>iA~;vmja_M)a`%UL6abLTgf0IkoNCY}T52Eme$dCkO%? zySD2hK%nwGtJoZ6<<^R!FH2q|d6lTl)Ql`vr&VZy(q#&1-q2=cT_z})J!iS?w6IQT z0TjH!<)_7T5nOOvPGu^yssyalV4+G;)WFx$rfGvNtKBnDROhx9T@KH4X&=f`ofcf~ zoSlW3u zUPOx`I>^e3Wn2#Wp#@S~Ng^mHWFlZ46pTEMxKCWv4H!o;YsVhd`bY z7e=CtYp;hE*G-nfs$VB->hx@imb9~p1k!7ozl4=)RLxi+cg9fDr<%)tlK7IHT`zsa zp>|zC5GS!jeoQ>nb+CsV(s7yVc#b6AD)qI}!HRfg#pjV;mz7UgHkPn)+Sc- zrVm%w)&G}_t@q2N!|eZ>Z}IKQ-U^Leyn+o|51W$$9-6a8W>BXBP7?m*50Bs!dUMd< z8Q!_%e?5M@{lfiUKRbFg`t4J4w|8S}_|?nb-om@x=$-Eev)4WX>>nGOz3(pP`tTa; M?)QIu_v1%@1FIA=vH$=8 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/betterend/textures/particle/snowflake_4.png b/src/main/resources/assets/betterend/textures/particle/snowflake_4.png new file mode 100644 index 0000000000000000000000000000000000000000..7365be92f9ee228550aa8c674f1e0195c059f53e GIT binary patch literal 1824 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 h=csef`QyOM4&uDyx%hYh%0f?b~-g{tKsRI|%>) literal 0 HcmV?d00001