Compare commits
8 Commits
674c875249
...
ed04a8c4d2
Author | SHA1 | Date | |
---|---|---|---|
ed04a8c4d2 | |||
611f4055e9 | |||
00ff8fa91e | |||
3ee539bc9d | |||
44b0bcd776 | |||
cc456958e1 | |||
5f76aa1661 | |||
bd8c5ed97b |
@ -32,8 +32,11 @@ import ru.dbotthepony.mc.otm.worldgen.placement.StandardDeviationHeightProvider
|
|||||||
import ru.dbotthepony.mc.otm.registry.game.MBlocks
|
import ru.dbotthepony.mc.otm.registry.game.MBlocks
|
||||||
import ru.dbotthepony.mc.otm.registry.data.MWorldGenFeatures
|
import ru.dbotthepony.mc.otm.registry.data.MWorldGenFeatures
|
||||||
import ru.dbotthepony.mc.otm.worldgen.feature.BlackHolePlacerFeature
|
import ru.dbotthepony.mc.otm.worldgen.feature.BlackHolePlacerFeature
|
||||||
|
import ru.dbotthepony.mc.otm.worldgen.feature.DebugPlacerFeature
|
||||||
|
import ru.dbotthepony.mc.otm.worldgen.placement.ChainPlacement
|
||||||
import ru.dbotthepony.mc.otm.worldgen.placement.EnormousPlacement
|
import ru.dbotthepony.mc.otm.worldgen.placement.EnormousPlacement
|
||||||
import ru.dbotthepony.mc.otm.worldgen.placement.EllipsoidPlacement
|
import ru.dbotthepony.mc.otm.worldgen.placement.EllipsoidPlacement
|
||||||
|
import ru.dbotthepony.mc.otm.worldgen.placement.SplitPlacement
|
||||||
import ru.dbotthepony.mc.otm.worldgen.placement.WormPlacement
|
import ru.dbotthepony.mc.otm.worldgen.placement.WormPlacement
|
||||||
|
|
||||||
private object ConfiguredFeatures {
|
private object ConfiguredFeatures {
|
||||||
@ -97,7 +100,7 @@ fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
|
|||||||
context.register(PlacedFeatures.NORMAL_TRITANIUM, PlacedFeature(
|
context.register(PlacedFeatures.NORMAL_TRITANIUM, PlacedFeature(
|
||||||
ore,
|
ore,
|
||||||
listOf(
|
listOf(
|
||||||
CountPlacement.of(UniformInt.of(2, 6)),
|
CountPlacement.of(UniformInt.of(1, 3)),
|
||||||
InSquarePlacement.spread(),
|
InSquarePlacement.spread(),
|
||||||
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(10), 15.0))
|
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(10), 15.0))
|
||||||
)
|
)
|
||||||
@ -106,7 +109,7 @@ fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
|
|||||||
context.register(PlacedFeatures.DEEP_TRITANIUM, PlacedFeature(
|
context.register(PlacedFeatures.DEEP_TRITANIUM, PlacedFeature(
|
||||||
ore,
|
ore,
|
||||||
listOf(
|
listOf(
|
||||||
CountPlacement.of(UniformInt.of(4, 8)),
|
CountPlacement.of(UniformInt.of(3, 5)),
|
||||||
InSquarePlacement.spread(),
|
InSquarePlacement.spread(),
|
||||||
HeightRangePlacement.of(VeryBiasedToBottomHeight.of(VerticalAnchor.aboveBottom(4), VerticalAnchor.absolute(0), 16))
|
HeightRangePlacement.of(VeryBiasedToBottomHeight.of(VerticalAnchor.aboveBottom(4), VerticalAnchor.absolute(0), 16))
|
||||||
)
|
)
|
||||||
@ -119,28 +122,45 @@ fun registerPlacedFeatures(context: BootstrapContext<PlacedFeature>) {
|
|||||||
chunkScanRange = 24,
|
chunkScanRange = 24,
|
||||||
seedMix = 9284343575495L,
|
seedMix = 9284343575495L,
|
||||||
children = listOf(
|
children = listOf(
|
||||||
RarityFilter.onAverageOnceEvery(300),
|
RarityFilter.onAverageOnceEvery(140),
|
||||||
InSquarePlacement.spread(),
|
InSquarePlacement.spread(),
|
||||||
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(-40), 15.0)),
|
HeightRangePlacement.of(StandardDeviationHeightProvider(VerticalAnchor.absolute(-40), 15.0)),
|
||||||
WormPlacement(
|
SplitPlacement(
|
||||||
length = UniformInt.of(120, 400),
|
// "heart"
|
||||||
turnRateXY = WormPlacement.normalDistributedTurnRate(10f),
|
EllipsoidPlacement(
|
||||||
turnRateXZ = WormPlacement.normalDistributedTurnRate(60f),
|
count = UniformInt.of(600, 900),
|
||||||
turnSpeedXZ = WormPlacement.constantTurnRate(10f),
|
xLength = UniformFloat.of(9f, 12f),
|
||||||
turnSpeedXY = WormPlacement.constantTurnRate(4f),
|
yLength = UniformFloat.of(9f, 12f),
|
||||||
turnChanceXY = BooleanProvider.Unbiased(6),
|
zLength = UniformFloat.of(9f, 12f),
|
||||||
turnChanceXZ = BooleanProvider.BiasedLinear(0.6f, 5),
|
x = ClampedNormalFloat.of(0f, 0.4f, -1f, 1f),
|
||||||
maxTravelUp = 16,
|
y = ClampedNormalFloat.of(0f, 0.4f, -1f, 1f),
|
||||||
maxTravelDown = 16,
|
z = ClampedNormalFloat.of(0f, 0.4f, -1f, 1f),
|
||||||
),
|
),
|
||||||
EllipsoidPlacement(
|
// "branches"
|
||||||
count = UniformInt.of(15, 30),
|
ChainPlacement(
|
||||||
xLength = ConstantFloat.of(14f),
|
CountPlacement.of(UniformInt.of(2, 7)),
|
||||||
yLength = ConstantFloat.of(14f),
|
WormPlacement(
|
||||||
zLength = ConstantFloat.of(14f),
|
length = UniformInt.of(60, 120),
|
||||||
x = ClampedNormalFloat.of(0f, 0.4f, -1f, 1f),
|
turnRateXY = WormPlacement.normalDistributedTurnRate(2f, 3f),
|
||||||
y = ClampedNormalFloat.of(0f, 0.4f, -1f, 1f),
|
initialAngleXY = WormPlacement.normalDistributedTurnRate(3f, 4f),
|
||||||
z = ClampedNormalFloat.of(0f, 0.4f, -1f, 1f),
|
turnRateXZ = WormPlacement.normalDistributedTurnRate(30f),
|
||||||
|
turnSpeedXZ = WormPlacement.constantTurnRate(10f),
|
||||||
|
turnSpeedXY = WormPlacement.constantTurnRate(4f),
|
||||||
|
turnChanceXY = BooleanProvider.Unbiased(6),
|
||||||
|
turnChanceXZ = BooleanProvider.BiasedLinear(0.6f, 5),
|
||||||
|
maxTravelUp = 90,
|
||||||
|
maxTravelDown = 10,
|
||||||
|
),
|
||||||
|
EllipsoidPlacement(
|
||||||
|
count = UniformInt.of(3, 6),
|
||||||
|
xLength = ConstantFloat.of(4f),
|
||||||
|
yLength = ConstantFloat.of(4f),
|
||||||
|
zLength = ConstantFloat.of(4f),
|
||||||
|
x = ClampedNormalFloat.of(0f, 0.2f, -1f, 1f),
|
||||||
|
y = ClampedNormalFloat.of(0f, 0.2f, -1f, 1f),
|
||||||
|
z = ClampedNormalFloat.of(0f, 0.2f, -1f, 1f),
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -92,6 +92,36 @@ interface BooleanProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object AlwaysTrue : BooleanProvider, Instance, Type<AlwaysTrue> {
|
||||||
|
override fun instance(): Instance {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sample(random: RandomSource): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override val type: Type<*>
|
||||||
|
get() = this
|
||||||
|
|
||||||
|
override val codec: MapCodec<AlwaysTrue> = MapCodec.unit(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
object AlwaysFalse : BooleanProvider, Instance, Type<AlwaysFalse> {
|
||||||
|
override fun instance(): Instance {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sample(random: RandomSource): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override val type: Type<*>
|
||||||
|
get() = this
|
||||||
|
|
||||||
|
override val codec: MapCodec<AlwaysFalse> = MapCodec.unit(this)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val registryHolder = RegistryDelegate<Type<*>>("boolean_provider") {
|
private val registryHolder = RegistryDelegate<Type<*>>("boolean_provider") {
|
||||||
defaultKey(ResourceLocation(OverdriveThatMatters.MOD_ID, "zero"))
|
defaultKey(ResourceLocation(OverdriveThatMatters.MOD_ID, "zero"))
|
||||||
@ -105,6 +135,8 @@ interface BooleanProvider {
|
|||||||
init {
|
init {
|
||||||
registrar.register("unbiased") { Unbiased.Companion }
|
registrar.register("unbiased") { Unbiased.Companion }
|
||||||
registrar.register("linear_bias") { BiasedLinear.Companion }
|
registrar.register("linear_bias") { BiasedLinear.Companion }
|
||||||
|
registrar.register("true") { AlwaysTrue }
|
||||||
|
registrar.register("false") { AlwaysFalse }
|
||||||
}
|
}
|
||||||
|
|
||||||
val CODEC: Codec<BooleanProvider> by lazy {
|
val CODEC: Codec<BooleanProvider> by lazy {
|
||||||
|
@ -4,8 +4,10 @@ import net.minecraft.core.registries.BuiltInRegistries
|
|||||||
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
|
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
|
||||||
import net.neoforged.bus.api.IEventBus
|
import net.neoforged.bus.api.IEventBus
|
||||||
import ru.dbotthepony.mc.otm.registry.MDeferredRegister
|
import ru.dbotthepony.mc.otm.registry.MDeferredRegister
|
||||||
|
import ru.dbotthepony.mc.otm.worldgen.placement.ChainPlacement
|
||||||
import ru.dbotthepony.mc.otm.worldgen.placement.EllipsoidPlacement
|
import ru.dbotthepony.mc.otm.worldgen.placement.EllipsoidPlacement
|
||||||
import ru.dbotthepony.mc.otm.worldgen.placement.EnormousPlacement
|
import ru.dbotthepony.mc.otm.worldgen.placement.EnormousPlacement
|
||||||
|
import ru.dbotthepony.mc.otm.worldgen.placement.SplitPlacement
|
||||||
import ru.dbotthepony.mc.otm.worldgen.placement.WormPlacement
|
import ru.dbotthepony.mc.otm.worldgen.placement.WormPlacement
|
||||||
|
|
||||||
object MPlacementModifiers {
|
object MPlacementModifiers {
|
||||||
@ -18,4 +20,6 @@ object MPlacementModifiers {
|
|||||||
val ENORMOUS by registry.register("enormous") { PlacementModifierType { EnormousPlacement.CODEC } }
|
val ENORMOUS by registry.register("enormous") { PlacementModifierType { EnormousPlacement.CODEC } }
|
||||||
val ELLIPSOID_PLACEMENT by registry.register("ellipsoid") { PlacementModifierType { EllipsoidPlacement.CODEC } }
|
val ELLIPSOID_PLACEMENT by registry.register("ellipsoid") { PlacementModifierType { EllipsoidPlacement.CODEC } }
|
||||||
val WORM_PLACEMENT by registry.register("worm") { PlacementModifierType { WormPlacement.CODEC } }
|
val WORM_PLACEMENT by registry.register("worm") { PlacementModifierType { WormPlacement.CODEC } }
|
||||||
|
val SPLIT by registry.register("split") { PlacementModifierType { SplitPlacement.CODEC} }
|
||||||
|
val CHAIN by registry.register("chain") { PlacementModifierType { ChainPlacement.CODEC} }
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package ru.dbotthepony.mc.otm.worldgen.placement
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.mojang.serialization.Codec
|
||||||
|
import com.mojang.serialization.MapCodec
|
||||||
|
import net.minecraft.core.BlockPos
|
||||||
|
import net.minecraft.util.RandomSource
|
||||||
|
import net.minecraft.world.level.levelgen.placement.PlacementContext
|
||||||
|
import net.minecraft.world.level.levelgen.placement.PlacementModifier
|
||||||
|
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
|
||||||
|
import ru.dbotthepony.mc.otm.registry.data.MPlacementModifiers
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Daisy-chaining placements. Required only when using placement modifiers which operate on children list
|
||||||
|
*/
|
||||||
|
class ChainPlacement(
|
||||||
|
val children: List<PlacementModifier>
|
||||||
|
) : PlacementModifier() {
|
||||||
|
constructor(vararg children: PlacementModifier) : this(ImmutableList.copyOf(children))
|
||||||
|
|
||||||
|
override fun getPositions(context: PlacementContext, random: RandomSource, pos: BlockPos): Stream<BlockPos> {
|
||||||
|
var stream = Stream.of(pos)
|
||||||
|
children.forEach { c -> stream = stream.flatMap { c.getPositions(context, random, it) } }
|
||||||
|
return stream
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun type(): PlacementModifierType<*> {
|
||||||
|
return MPlacementModifiers.CHAIN
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val CODEC: MapCodec<ChainPlacement> = Codec.list(PlacementModifier.CODEC).xmap(::ChainPlacement, ChainPlacement::children).fieldOf("children")
|
||||||
|
}
|
||||||
|
}
|
@ -20,9 +20,7 @@ import kotlin.math.PI
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regular ellipsoid ("cloud") placement, suitable to be used as non-primary placement modifier
|
* Ellipsoid ("cloud") placement
|
||||||
*
|
|
||||||
* This placement modifier is designed to be terminal; other placement modifiers after this MUST NOT be placed
|
|
||||||
*/
|
*/
|
||||||
data class EllipsoidPlacement(
|
data class EllipsoidPlacement(
|
||||||
/**
|
/**
|
||||||
|
@ -7,6 +7,7 @@ import com.mojang.serialization.Codec
|
|||||||
import com.mojang.serialization.MapCodec
|
import com.mojang.serialization.MapCodec
|
||||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||||
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
import net.minecraft.Util
|
import net.minecraft.Util
|
||||||
import net.minecraft.core.BlockPos
|
import net.minecraft.core.BlockPos
|
||||||
import net.minecraft.core.SectionPos
|
import net.minecraft.core.SectionPos
|
||||||
@ -50,7 +51,7 @@ class EnormousPlacement(
|
|||||||
) : PlacementModifier() {
|
) : PlacementModifier() {
|
||||||
private class GeneratedChunk(positions: Stream<BlockPos>) {
|
private class GeneratedChunk(positions: Stream<BlockPos>) {
|
||||||
// TODO: memory inefficient
|
// TODO: memory inefficient
|
||||||
private val positions = ArrayList<BlockPos>()
|
private val positions = ObjectOpenHashSet<BlockPos>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
positions.forEach { this.positions.add(it) }
|
positions.forEach { this.positions.add(it) }
|
||||||
@ -65,20 +66,22 @@ class EnormousPlacement(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val level2cache = WeakHashMap<WorldGenLevel, Cache<ChunkPos, GeneratedChunk>>()
|
private val level2cache = Caffeine.newBuilder()
|
||||||
private val lock = ReentrantLock()
|
.scheduler(Scheduler.systemScheduler())
|
||||||
|
.executor(Util.backgroundExecutor())
|
||||||
|
.expireAfterAccess(Duration.ofMinutes(10))
|
||||||
|
.weakKeys()
|
||||||
|
.build<WorldGenLevel, Cache<ChunkPos, GeneratedChunk>>()
|
||||||
|
|
||||||
private fun getCache(level: WorldGenLevel): Cache<ChunkPos, GeneratedChunk> {
|
private fun getCache(level: WorldGenLevel): Cache<ChunkPos, GeneratedChunk> {
|
||||||
return lock.withLock {
|
return level2cache.get(level) {
|
||||||
level2cache.computeIfAbsent(level) {
|
Caffeine.newBuilder()
|
||||||
Caffeine.newBuilder()
|
.scheduler(Scheduler.systemScheduler())
|
||||||
.scheduler(Scheduler.systemScheduler())
|
.executor(Util.backgroundExecutor())
|
||||||
.executor(Util.backgroundExecutor())
|
.maximumSize(16384L)
|
||||||
.maximumSize(16384L)
|
.expireAfterWrite(Duration.ofMinutes(5))
|
||||||
.expireAfterWrite(Duration.ofMinutes(5))
|
.softValues()
|
||||||
.softValues()
|
.build()
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package ru.dbotthepony.mc.otm.worldgen.placement
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.mojang.serialization.Codec
|
||||||
|
import com.mojang.serialization.MapCodec
|
||||||
|
import net.minecraft.core.BlockPos
|
||||||
|
import net.minecraft.util.RandomSource
|
||||||
|
import net.minecraft.world.level.levelgen.placement.PlacementContext
|
||||||
|
import net.minecraft.world.level.levelgen.placement.PlacementModifier
|
||||||
|
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
|
||||||
|
import ru.dbotthepony.mc.otm.registry.data.MPlacementModifiers
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Or "shard" placement, if you will.
|
||||||
|
*
|
||||||
|
* Basically, allows multiple [PlacementModifier]s to split/branch off from provided point.
|
||||||
|
*/
|
||||||
|
class SplitPlacement(
|
||||||
|
val children: List<PlacementModifier>
|
||||||
|
) : PlacementModifier() {
|
||||||
|
constructor(vararg children: PlacementModifier) : this(ImmutableList.copyOf(children))
|
||||||
|
|
||||||
|
override fun getPositions(context: PlacementContext, random: RandomSource, pos: BlockPos): Stream<BlockPos> {
|
||||||
|
return children.stream().flatMap { it.getPositions(context, random, pos) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun type(): PlacementModifierType<*> {
|
||||||
|
return MPlacementModifiers.SPLIT
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val CODEC: MapCodec<SplitPlacement> = Codec.list(PlacementModifier.CODEC).xmap(::SplitPlacement, SplitPlacement::children).fieldOf("children")
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,9 @@ import kotlin.math.cos
|
|||||||
import kotlin.math.sign
|
import kotlin.math.sign
|
||||||
import kotlin.math.sin
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates one block thick "worm" positions which twists and turns
|
||||||
|
*/
|
||||||
class WormPlacement(
|
class WormPlacement(
|
||||||
val length: IntProvider,
|
val length: IntProvider,
|
||||||
val turnChanceXZ: BooleanProvider,
|
val turnChanceXZ: BooleanProvider,
|
||||||
@ -43,9 +46,9 @@ class WormPlacement(
|
|||||||
) : PlacementModifier() {
|
) : PlacementModifier() {
|
||||||
private inner class Worm(private val random: RandomSource, private var position: Vector) {
|
private inner class Worm(private val random: RandomSource, private var position: Vector) {
|
||||||
private var remainingDistance = length.sample(random)
|
private var remainingDistance = length.sample(random)
|
||||||
private var xzRotation = random.nextDouble(-PI / 2.0, PI / 2.0)
|
private var xzRotation = random.nextDouble(-PI, PI)
|
||||||
private var xyRotation = normalizeAngle(initialAngleXY.sample(random).toDouble())
|
private var xyRotation = normalizeAngle(initialAngleXY.sample(random).toDouble())
|
||||||
private var xzTargetRotation = random.nextDouble(-PI / 2.0, PI / 2.0)
|
private var xzTargetRotation = random.nextDouble(-PI, PI)
|
||||||
private var xyTargetRotation = normalizeAngle(initialAngleXY.sample(random).toDouble())
|
private var xyTargetRotation = normalizeAngle(initialAngleXY.sample(random).toDouble())
|
||||||
|
|
||||||
private var xzSin = sin(xzRotation)
|
private var xzSin = sin(xzRotation)
|
||||||
@ -187,14 +190,14 @@ class WormPlacement(
|
|||||||
return UniformFloat.of(-rad, increment(rad))
|
return UniformFloat.of(-rad, increment(rad))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun normalDistributedTurnRate(deviation: Float): FloatProvider {
|
fun normalDistributedTurnRate(deviation: Float, mean: Float = 0f): FloatProvider {
|
||||||
return normalDistributedTurnRateRadians(deviation * DEGREES_TO_RADIANS)
|
return normalDistributedTurnRateRadians(deviation * DEGREES_TO_RADIANS, mean * DEGREES_TO_RADIANS)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun normalDistributedTurnRateRadians(deviation: Float): FloatProvider {
|
fun normalDistributedTurnRateRadians(deviation: Float, mean: Float = 0f): FloatProvider {
|
||||||
require(deviation >= 0f) { "Provided value must be non-negative, $deviation given" }
|
require(deviation >= 0f) { "Provided value must be non-negative, $deviation given" }
|
||||||
if (deviation == 0f) return ConstantFloat.ZERO
|
if (deviation == 0f) return ConstantFloat.ZERO
|
||||||
return ClampedNormalFloat.of(0f, deviation, -PI.toFloat() * 2f, PI.toFloat() * 2f)
|
return ClampedNormalFloat.of(mean, deviation, -PI.toFloat() * 2f, PI.toFloat() * 2f)
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val DEGREES_TO_RADIANS = PI.toFloat() / 180f
|
private const val DEGREES_TO_RADIANS = PI.toFloat() / 180f
|
||||||
|
Loading…
Reference in New Issue
Block a user