SDF, Noise, Blocks Helper, Spline Helper, Translation Helper
This commit is contained in:
parent
6a5584deae
commit
017d663af6
37 changed files with 4315 additions and 0 deletions
104
src/main/java/ru/bclib/sdf/PosInfo.java
Normal file
104
src/main/java/ru/bclib/sdf/PosInfo.java
Normal file
|
@ -0,0 +1,104 @@
|
|||
package ru.bclib.sdf;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
public class PosInfo implements Comparable<PosInfo> {
|
||||
private static final BlockState AIR = Blocks.AIR.defaultBlockState();
|
||||
private final Map<BlockPos, PosInfo> blocks;
|
||||
private final Map<BlockPos, PosInfo> add;
|
||||
private final BlockPos pos;
|
||||
private BlockState state;
|
||||
|
||||
public static PosInfo create(Map<BlockPos, PosInfo> blocks, Map<BlockPos, PosInfo> add, BlockPos pos) {
|
||||
return new PosInfo(blocks, add, pos);
|
||||
}
|
||||
|
||||
private PosInfo(Map<BlockPos, PosInfo> blocks, Map<BlockPos, PosInfo> add, BlockPos pos) {
|
||||
this.blocks = blocks;
|
||||
this.add = add;
|
||||
this.pos = pos;
|
||||
blocks.put(pos, this);
|
||||
}
|
||||
|
||||
public BlockState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public BlockState getState(BlockPos pos) {
|
||||
PosInfo info = blocks.get(pos);
|
||||
if (info == null) {
|
||||
info = add.get(pos);
|
||||
return info == null ? AIR : info.getState();
|
||||
}
|
||||
return info.getState();
|
||||
}
|
||||
|
||||
public void setState(BlockState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public void setState(BlockPos pos, BlockState state) {
|
||||
PosInfo info = blocks.get(pos);
|
||||
if (info != null) {
|
||||
info.setState(state);
|
||||
}
|
||||
}
|
||||
|
||||
public BlockState getState(Direction dir) {
|
||||
PosInfo info = blocks.get(pos.relative(dir));
|
||||
if (info == null) {
|
||||
info = add.get(pos.relative(dir));
|
||||
return info == null ? AIR : info.getState();
|
||||
}
|
||||
return info.getState();
|
||||
}
|
||||
|
||||
public BlockState getState(Direction dir, int distance) {
|
||||
PosInfo info = blocks.get(pos.relative(dir, distance));
|
||||
if (info == null) {
|
||||
return AIR;
|
||||
}
|
||||
return info.getState();
|
||||
}
|
||||
|
||||
public BlockState getStateUp() {
|
||||
return getState(Direction.UP);
|
||||
}
|
||||
|
||||
public BlockState getStateDown() {
|
||||
return getState(Direction.DOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return pos.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof PosInfo)) {
|
||||
return false;
|
||||
}
|
||||
return pos.equals(((PosInfo) obj).pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PosInfo info) {
|
||||
return this.pos.getY() - info.pos.getY();
|
||||
}
|
||||
|
||||
public BlockPos getPos() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public void setBlockPos(BlockPos pos, BlockState state) {
|
||||
PosInfo info = new PosInfo(blocks, add, pos);
|
||||
info.state = state;
|
||||
add.put(pos, info);
|
||||
}
|
||||
}
|
310
src/main/java/ru/bclib/sdf/SDF.java
Normal file
310
src/main/java/ru/bclib/sdf/SDF.java
Normal file
|
@ -0,0 +1,310 @@
|
|||
package ru.bclib.sdf;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.BlockPos.MutableBlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.ServerLevelAccessor;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import ru.bclib.structures.StructureWorld;
|
||||
import ru.bclib.util.BlocksHelper;
|
||||
|
||||
public abstract class SDF {
|
||||
private List<Function<PosInfo, BlockState>> postProcesses = Lists.newArrayList();
|
||||
private Function<BlockState, Boolean> canReplace = (state) -> {
|
||||
return state.getMaterial().isReplaceable();
|
||||
};
|
||||
|
||||
public abstract float getDistance(float x, float y, float z);
|
||||
|
||||
public abstract BlockState getBlockState(BlockPos pos);
|
||||
|
||||
public SDF addPostProcess(Function<PosInfo, BlockState> postProcess) {
|
||||
this.postProcesses.add(postProcess);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDF setReplaceFunction(Function<BlockState, Boolean> canReplace) {
|
||||
this.canReplace = canReplace;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void fillRecursive(ServerLevelAccessor world, BlockPos start) {
|
||||
Map<BlockPos, PosInfo> mapWorld = Maps.newHashMap();
|
||||
Map<BlockPos, PosInfo> addInfo = Maps.newHashMap();
|
||||
Set<BlockPos> blocks = Sets.newHashSet();
|
||||
Set<BlockPos> ends = Sets.newHashSet();
|
||||
Set<BlockPos> add = Sets.newHashSet();
|
||||
ends.add(new BlockPos(0, 0, 0));
|
||||
boolean run = true;
|
||||
|
||||
MutableBlockPos bPos = new MutableBlockPos();
|
||||
|
||||
while (run) {
|
||||
for (BlockPos center: ends) {
|
||||
for (Direction dir: Direction.values()) {
|
||||
bPos.set(center).move(dir);
|
||||
BlockPos wpos = bPos.offset(start);
|
||||
|
||||
if (!blocks.contains(bPos) && canReplace.apply(world.getBlockState(wpos))) {
|
||||
if (this.getDistance(bPos.getX(), bPos.getY(), bPos.getZ()) < 0) {
|
||||
BlockState state = getBlockState(wpos);
|
||||
PosInfo.create(mapWorld, addInfo, wpos).setState(state);
|
||||
add.add(bPos.immutable());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blocks.addAll(ends);
|
||||
ends.clear();
|
||||
ends.addAll(add);
|
||||
add.clear();
|
||||
|
||||
run &= !ends.isEmpty();
|
||||
}
|
||||
|
||||
List<PosInfo> infos = new ArrayList<PosInfo>(mapWorld.values());
|
||||
if (infos.size() > 0) {
|
||||
Collections.sort(infos);
|
||||
postProcesses.forEach((postProcess) -> {
|
||||
infos.forEach((info) -> {
|
||||
info.setState(postProcess.apply(info));
|
||||
});
|
||||
});
|
||||
infos.forEach((info) -> {
|
||||
BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState());
|
||||
});
|
||||
|
||||
infos.clear();
|
||||
infos.addAll(addInfo.values());
|
||||
Collections.sort(infos);
|
||||
postProcesses.forEach((postProcess) -> {
|
||||
infos.forEach((info) -> {
|
||||
info.setState(postProcess.apply(info));
|
||||
});
|
||||
});
|
||||
infos.forEach((info) -> {
|
||||
if (canReplace.apply(world.getBlockState(info.getPos()))) {
|
||||
BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void fillArea(ServerLevelAccessor world, BlockPos center, AABB box) {
|
||||
Map<BlockPos, PosInfo> mapWorld = Maps.newHashMap();
|
||||
Map<BlockPos, PosInfo> addInfo = Maps.newHashMap();
|
||||
|
||||
MutableBlockPos mut = new MutableBlockPos();
|
||||
for (int y = (int) box.minY; y <= box.maxY; y++) {
|
||||
mut.setY(y);
|
||||
for (int x = (int) box.minX; x <= box.maxX; x++) {
|
||||
mut.setX(x);
|
||||
for (int z = (int) box.minZ; z <= box.maxZ; z++) {
|
||||
mut.setZ(z);
|
||||
if (canReplace.apply(world.getBlockState(mut))) {
|
||||
BlockPos fpos = mut.subtract(center);
|
||||
if (this.getDistance(fpos.getX(), fpos.getY(), fpos.getZ()) < 0) {
|
||||
PosInfo.create(mapWorld, addInfo, mut.immutable()).setState(getBlockState(mut));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<PosInfo> infos = new ArrayList<PosInfo>(mapWorld.values());
|
||||
if (infos.size() > 0) {
|
||||
Collections.sort(infos);
|
||||
postProcesses.forEach((postProcess) -> {
|
||||
infos.forEach((info) -> {
|
||||
info.setState(postProcess.apply(info));
|
||||
});
|
||||
});
|
||||
infos.forEach((info) -> {
|
||||
BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState());
|
||||
});
|
||||
|
||||
infos.clear();
|
||||
infos.addAll(addInfo.values());
|
||||
Collections.sort(infos);
|
||||
postProcesses.forEach((postProcess) -> {
|
||||
infos.forEach((info) -> {
|
||||
info.setState(postProcess.apply(info));
|
||||
});
|
||||
});
|
||||
infos.forEach((info) -> {
|
||||
if (canReplace.apply(world.getBlockState(info.getPos()))) {
|
||||
BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void fillRecursiveIgnore(ServerLevelAccessor world, BlockPos start, Function<BlockState, Boolean> ignore) {
|
||||
Map<BlockPos, PosInfo> mapWorld = Maps.newHashMap();
|
||||
Map<BlockPos, PosInfo> addInfo = Maps.newHashMap();
|
||||
Set<BlockPos> blocks = Sets.newHashSet();
|
||||
Set<BlockPos> ends = Sets.newHashSet();
|
||||
Set<BlockPos> add = Sets.newHashSet();
|
||||
ends.add(new BlockPos(0, 0, 0));
|
||||
boolean run = true;
|
||||
|
||||
MutableBlockPos bPos = new MutableBlockPos();
|
||||
|
||||
while (run) {
|
||||
for (BlockPos center: ends) {
|
||||
for (Direction dir: Direction.values()) {
|
||||
bPos.set(center).move(dir);
|
||||
BlockPos wpos = bPos.offset(start);
|
||||
BlockState state = world.getBlockState(wpos);
|
||||
boolean ign = ignore.apply(state);
|
||||
if (!blocks.contains(bPos) && (ign || canReplace.apply(state))) {
|
||||
if (this.getDistance(bPos.getX(), bPos.getY(), bPos.getZ()) < 0) {
|
||||
PosInfo.create(mapWorld, addInfo, wpos).setState(ign ? state : getBlockState(bPos));
|
||||
add.add(bPos.immutable());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blocks.addAll(ends);
|
||||
ends.clear();
|
||||
ends.addAll(add);
|
||||
add.clear();
|
||||
|
||||
run &= !ends.isEmpty();
|
||||
}
|
||||
|
||||
List<PosInfo> infos = new ArrayList<PosInfo>(mapWorld.values());
|
||||
if (infos.size() > 0) {
|
||||
Collections.sort(infos);
|
||||
postProcesses.forEach((postProcess) -> {
|
||||
infos.forEach((info) -> {
|
||||
info.setState(postProcess.apply(info));
|
||||
});
|
||||
});
|
||||
infos.forEach((info) -> {
|
||||
BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState());
|
||||
});
|
||||
|
||||
infos.clear();
|
||||
infos.addAll(addInfo.values());
|
||||
Collections.sort(infos);
|
||||
postProcesses.forEach((postProcess) -> {
|
||||
infos.forEach((info) -> {
|
||||
info.setState(postProcess.apply(info));
|
||||
});
|
||||
});
|
||||
infos.forEach((info) -> {
|
||||
if (canReplace.apply(world.getBlockState(info.getPos()))) {
|
||||
BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void fillRecursive(StructureWorld world, BlockPos start) {
|
||||
Map<BlockPos, PosInfo> mapWorld = Maps.newHashMap();
|
||||
Map<BlockPos, PosInfo> addInfo = Maps.newHashMap();
|
||||
Set<BlockPos> blocks = Sets.newHashSet();
|
||||
Set<BlockPos> ends = Sets.newHashSet();
|
||||
Set<BlockPos> add = Sets.newHashSet();
|
||||
ends.add(new BlockPos(0, 0, 0));
|
||||
boolean run = true;
|
||||
|
||||
MutableBlockPos bPos = new MutableBlockPos();
|
||||
|
||||
while (run) {
|
||||
for (BlockPos center: ends) {
|
||||
for (Direction dir: Direction.values()) {
|
||||
bPos.set(center).move(dir);
|
||||
BlockPos wpos = bPos.offset(start);
|
||||
|
||||
if (!blocks.contains(bPos)) {
|
||||
if (this.getDistance(bPos.getX(), bPos.getY(), bPos.getZ()) < 0) {
|
||||
BlockState state = getBlockState(wpos);
|
||||
PosInfo.create(mapWorld, addInfo, wpos).setState(state);
|
||||
add.add(bPos.immutable());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blocks.addAll(ends);
|
||||
ends.clear();
|
||||
ends.addAll(add);
|
||||
add.clear();
|
||||
|
||||
run &= !ends.isEmpty();
|
||||
}
|
||||
|
||||
List<PosInfo> infos = new ArrayList<PosInfo>(mapWorld.values());
|
||||
Collections.sort(infos);
|
||||
postProcesses.forEach((postProcess) -> {
|
||||
infos.forEach((info) -> {
|
||||
info.setState(postProcess.apply(info));
|
||||
});
|
||||
});
|
||||
infos.forEach((info) -> {
|
||||
world.setBlock(info.getPos(), info.getState());
|
||||
});
|
||||
|
||||
infos.clear();
|
||||
infos.addAll(addInfo.values());
|
||||
Collections.sort(infos);
|
||||
postProcesses.forEach((postProcess) -> {
|
||||
infos.forEach((info) -> {
|
||||
info.setState(postProcess.apply(info));
|
||||
});
|
||||
});
|
||||
infos.forEach((info) -> {
|
||||
world.setBlock(info.getPos(), info.getState());
|
||||
});
|
||||
}
|
||||
|
||||
public Set<BlockPos> getPositions(ServerLevelAccessor world, BlockPos start) {
|
||||
Set<BlockPos> blocks = Sets.newHashSet();
|
||||
Set<BlockPos> ends = Sets.newHashSet();
|
||||
Set<BlockPos> add = Sets.newHashSet();
|
||||
ends.add(new BlockPos(0, 0, 0));
|
||||
boolean run = true;
|
||||
|
||||
MutableBlockPos bPos = new MutableBlockPos();
|
||||
|
||||
while (run) {
|
||||
for (BlockPos center: ends) {
|
||||
for (Direction dir: Direction.values()) {
|
||||
bPos.set(center).move(dir);
|
||||
BlockPos wpos = bPos.offset(start);
|
||||
BlockState state = world.getBlockState(wpos);
|
||||
if (!blocks.contains(wpos) && canReplace.apply(state)) {
|
||||
if (this.getDistance(bPos.getX(), bPos.getY(), bPos.getZ()) < 0) {
|
||||
add.add(bPos.immutable());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ends.forEach((end) -> blocks.add(end.offset(start)));
|
||||
ends.clear();
|
||||
ends.addAll(add);
|
||||
add.clear();
|
||||
|
||||
run &= !ends.isEmpty();
|
||||
}
|
||||
|
||||
return blocks;
|
||||
}
|
||||
}
|
34
src/main/java/ru/bclib/sdf/operator/SDFBinary.java
Normal file
34
src/main/java/ru/bclib/sdf/operator/SDFBinary.java
Normal file
|
@ -0,0 +1,34 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import ru.bclib.sdf.SDF;
|
||||
|
||||
public abstract class SDFBinary extends SDF {
|
||||
protected SDF sourceA;
|
||||
protected SDF sourceB;
|
||||
protected boolean firstValue;
|
||||
|
||||
public SDFBinary setSourceA(SDF sourceA) {
|
||||
this.sourceA = sourceA;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFBinary setSourceB(SDF sourceB) {
|
||||
this.sourceB = sourceB;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected void selectValue(float a, float b) {
|
||||
firstValue = a < b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
if (firstValue) {
|
||||
return sourceA.getBlockState(pos);
|
||||
} else {
|
||||
return sourceB.getBlockState(pos);
|
||||
}
|
||||
}
|
||||
}
|
22
src/main/java/ru/bclib/sdf/operator/SDFCoordModify.java
Normal file
22
src/main/java/ru/bclib/sdf/operator/SDFCoordModify.java
Normal file
|
@ -0,0 +1,22 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.mojang.math.Vector3f;
|
||||
|
||||
public class SDFCoordModify extends SDFUnary {
|
||||
private static final Vector3f POS = new Vector3f();
|
||||
private Consumer<Vector3f> function;
|
||||
|
||||
public SDFCoordModify setFunction(Consumer<Vector3f> function) {
|
||||
this.function = function;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
POS.set(x, y, z);
|
||||
function.accept(POS);
|
||||
return this.source.getDistance(POS.x(), POS.y(), POS.z());
|
||||
}
|
||||
}
|
19
src/main/java/ru/bclib/sdf/operator/SDFCopyRotate.java
Normal file
19
src/main/java/ru/bclib/sdf/operator/SDFCopyRotate.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
import ru.bclib.util.MHelper;
|
||||
|
||||
public class SDFCopyRotate extends SDFUnary {
|
||||
int count = 1;
|
||||
|
||||
public SDFCopyRotate setCount(int count) {
|
||||
this.count = count;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
float px = (float) Math.atan2(x, z);
|
||||
float pz = MHelper.length(x, z);
|
||||
return this.source.getDistance(px, y, pz);
|
||||
}
|
||||
}
|
21
src/main/java/ru/bclib/sdf/operator/SDFDisplacement.java
Normal file
21
src/main/java/ru/bclib/sdf/operator/SDFDisplacement.java
Normal file
|
@ -0,0 +1,21 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.mojang.math.Vector3f;
|
||||
|
||||
public class SDFDisplacement extends SDFUnary {
|
||||
private static final Vector3f POS = new Vector3f();
|
||||
private Function<Vector3f, Float> displace;
|
||||
|
||||
public SDFDisplacement setFunction(Function<Vector3f, Float> displace) {
|
||||
this.displace = displace;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
POS.set(x, y, z);
|
||||
return this.source.getDistance(x, y, z) + displace.apply(POS);
|
||||
}
|
||||
}
|
28
src/main/java/ru/bclib/sdf/operator/SDFFlatWave.java
Normal file
28
src/main/java/ru/bclib/sdf/operator/SDFFlatWave.java
Normal file
|
@ -0,0 +1,28 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
public class SDFFlatWave extends SDFDisplacement {
|
||||
private int rayCount = 1;
|
||||
private float intensity;
|
||||
private float angle;
|
||||
|
||||
public SDFFlatWave() {
|
||||
setFunction((pos) -> {
|
||||
return (float) Math.cos(Math.atan2(pos.x(), pos.z()) * rayCount + angle) * intensity;
|
||||
});
|
||||
}
|
||||
|
||||
public SDFFlatWave setRaysCount(int count) {
|
||||
this.rayCount = count;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFFlatWave setAngle(float angle) {
|
||||
this.angle = angle;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFFlatWave setIntensity(float intensity) {
|
||||
this.intensity = intensity;
|
||||
return this;
|
||||
}
|
||||
}
|
64
src/main/java/ru/bclib/sdf/operator/SDFHeightmap.java
Normal file
64
src/main/java/ru/bclib/sdf/operator/SDFHeightmap.java
Normal file
|
@ -0,0 +1,64 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
public class SDFHeightmap extends SDFDisplacement {
|
||||
private float intensity = 1F;
|
||||
private NativeImage map;
|
||||
private float offsetX;
|
||||
private float offsetZ;
|
||||
private float scale;
|
||||
private float cos = 1;
|
||||
private float sin = 0;
|
||||
|
||||
public SDFHeightmap() {
|
||||
setFunction((pos) -> {
|
||||
if (map == null) {
|
||||
return 0F;
|
||||
}
|
||||
float px = Mth.clamp(pos.x() * scale + offsetX, 0, map.getWidth() - 2);
|
||||
float pz = Mth.clamp(pos.z() * scale + offsetZ, 0, map.getHeight() - 2);
|
||||
float dx = (px * cos - pz * sin);
|
||||
float dz = (pz * cos + px * sin);
|
||||
int x1 = Mth.floor(dx);
|
||||
int z1 = Mth.floor(dz);
|
||||
int x2 = x1 + 1;
|
||||
int z2 = z1 + 1;
|
||||
dx = dx - x1;
|
||||
dz = dz - z1;
|
||||
float a = (map.getPixelRGBA(x1, z1) & 255) / 255F;
|
||||
float b = (map.getPixelRGBA(x2, z1) & 255) / 255F;
|
||||
float c = (map.getPixelRGBA(x1, z2) & 255) / 255F;
|
||||
float d = (map.getPixelRGBA(x2, z2) & 255) / 255F;
|
||||
a = Mth.lerp(dx, a, b);
|
||||
b = Mth.lerp(dx, c, d);
|
||||
return -Mth.lerp(dz, a, b) * intensity;
|
||||
});
|
||||
}
|
||||
|
||||
public SDFHeightmap setMap(NativeImage map) {
|
||||
this.map = map;
|
||||
offsetX = map.getWidth() * 0.5F;
|
||||
offsetZ = map.getHeight() * 0.5F;
|
||||
scale = map.getWidth();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFHeightmap setAngle(float angle) {
|
||||
sin = Mth.sin(angle);
|
||||
cos = Mth.cos(angle);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFHeightmap setScale(float scale) {
|
||||
this.scale = map.getWidth() * scale;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFHeightmap setIntensity(float intensity) {
|
||||
this.intensity = intensity;
|
||||
return this;
|
||||
}
|
||||
}
|
13
src/main/java/ru/bclib/sdf/operator/SDFIntersection.java
Normal file
13
src/main/java/ru/bclib/sdf/operator/SDFIntersection.java
Normal file
|
@ -0,0 +1,13 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
import ru.bclib.util.MHelper;
|
||||
|
||||
public class SDFIntersection extends SDFBinary {
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
float a = this.sourceA.getDistance(x, y, z);
|
||||
float b = this.sourceB.getDistance(x, y, z);
|
||||
this.selectValue(a, b);
|
||||
return MHelper.max(a, b);
|
||||
}
|
||||
}
|
8
src/main/java/ru/bclib/sdf/operator/SDFInvert.java
Normal file
8
src/main/java/ru/bclib/sdf/operator/SDFInvert.java
Normal file
|
@ -0,0 +1,8 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
public class SDFInvert extends SDFUnary {
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
return -this.source.getDistance(x, y, z);
|
||||
}
|
||||
}
|
60
src/main/java/ru/bclib/sdf/operator/SDFRadialNoiseMap.java
Normal file
60
src/main/java/ru/bclib/sdf/operator/SDFRadialNoiseMap.java
Normal file
|
@ -0,0 +1,60 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import ru.bclib.noise.OpenSimplexNoise;
|
||||
import ru.bclib.util.MHelper;
|
||||
|
||||
public class SDFRadialNoiseMap extends SDFDisplacement {
|
||||
private static final float SIN = Mth.sin(0.5F);
|
||||
private static final float COS = Mth.cos(0.5F);
|
||||
|
||||
private OpenSimplexNoise noise;
|
||||
private float intensity = 1F;
|
||||
private float radius = 1F;
|
||||
private short offsetX;
|
||||
private short offsetZ;
|
||||
|
||||
public SDFRadialNoiseMap() {
|
||||
setFunction((pos) -> {
|
||||
if (intensity == 0) {
|
||||
return 0F;
|
||||
}
|
||||
float px = pos.x() / radius;
|
||||
float pz = pos.z() / radius;
|
||||
float distance = MHelper.lengthSqr(px, pz);
|
||||
if (distance > 1) {
|
||||
return 0F;
|
||||
}
|
||||
distance = 1 - Mth.sqrt(distance);
|
||||
float nx = px * COS - pz * SIN;
|
||||
float nz = pz * COS + px * SIN;
|
||||
distance *= getNoise(nx * 0.75 + offsetX, nz * 0.75 + offsetZ);
|
||||
return distance * intensity;
|
||||
});
|
||||
}
|
||||
|
||||
private float getNoise(double x, double z) {
|
||||
return (float) noise.eval(x, z) + (float) noise.eval(x * 3 + 1000, z * 3) * 0.5F + (float) noise.eval(x * 9 + 1000, z * 9) * 0.2F;
|
||||
}
|
||||
|
||||
public SDFRadialNoiseMap setSeed(long seed) {
|
||||
noise = new OpenSimplexNoise(seed);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFRadialNoiseMap setIntensity(float intensity) {
|
||||
this.intensity = intensity;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFRadialNoiseMap setRadius(float radius) {
|
||||
this.radius = radius;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFRadialNoiseMap setOffset(int x, int z) {
|
||||
offsetX = (short) (x & 32767);
|
||||
offsetZ = (short) (z & 32767);
|
||||
return this;
|
||||
}
|
||||
}
|
21
src/main/java/ru/bclib/sdf/operator/SDFRotation.java
Normal file
21
src/main/java/ru/bclib/sdf/operator/SDFRotation.java
Normal file
|
@ -0,0 +1,21 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
import com.mojang.math.Quaternion;
|
||||
import com.mojang.math.Vector3f;
|
||||
|
||||
public class SDFRotation extends SDFUnary {
|
||||
private static final Vector3f POS = new Vector3f();
|
||||
private Quaternion rotation;
|
||||
|
||||
public SDFRotation setRotation(Vector3f axis, float rotationAngle) {
|
||||
rotation = new Quaternion(axis, rotationAngle, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
POS.set(x, y, z);
|
||||
POS.transform(rotation);
|
||||
return source.getDistance(POS.x(), POS.y(), POS.z());
|
||||
}
|
||||
}
|
15
src/main/java/ru/bclib/sdf/operator/SDFRound.java
Normal file
15
src/main/java/ru/bclib/sdf/operator/SDFRound.java
Normal file
|
@ -0,0 +1,15 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
public class SDFRound extends SDFUnary {
|
||||
private float radius;
|
||||
|
||||
public SDFRound setRadius(float radius) {
|
||||
this.radius = radius;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
return this.source.getDistance(x, y, z) - radius;
|
||||
}
|
||||
}
|
15
src/main/java/ru/bclib/sdf/operator/SDFScale.java
Normal file
15
src/main/java/ru/bclib/sdf/operator/SDFScale.java
Normal file
|
@ -0,0 +1,15 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
public class SDFScale extends SDFUnary {
|
||||
private float scale;
|
||||
|
||||
public SDFScale setScale(float scale) {
|
||||
this.scale = scale;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
return source.getDistance(x / scale, y / scale, z / scale) * scale;
|
||||
}
|
||||
}
|
19
src/main/java/ru/bclib/sdf/operator/SDFScale3D.java
Normal file
19
src/main/java/ru/bclib/sdf/operator/SDFScale3D.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
public class SDFScale3D extends SDFUnary {
|
||||
private float x;
|
||||
private float y;
|
||||
private float z;
|
||||
|
||||
public SDFScale3D setScale(float x, float y, float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
return source.getDistance(x / this.x, y / this.y, z / this.z);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
public class SDFSmoothIntersection extends SDFBinary {
|
||||
private float radius;
|
||||
|
||||
public SDFSmoothIntersection setRadius(float radius) {
|
||||
this.radius = radius;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
float a = this.sourceA.getDistance(x, y, z);
|
||||
float b = this.sourceB.getDistance(x, y, z);
|
||||
this.selectValue(a, b);
|
||||
float h = Mth.clamp(0.5F - 0.5F * (b - a) / radius, 0F, 1F);
|
||||
return Mth.lerp(h, b, a) + radius * h * (1F - h);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
public class SDFSmoothSubtraction extends SDFBinary {
|
||||
private float radius;
|
||||
|
||||
public SDFSmoothSubtraction setRadius(float radius) {
|
||||
this.radius = radius;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
float a = this.sourceA.getDistance(x, y, z);
|
||||
float b = this.sourceB.getDistance(x, y, z);
|
||||
this.selectValue(a, b);
|
||||
float h = Mth.clamp(0.5F - 0.5F * (b + a) / radius, 0F, 1F);
|
||||
return Mth.lerp(h, b, -a) + radius * h * (1F - h);
|
||||
}
|
||||
}
|
21
src/main/java/ru/bclib/sdf/operator/SDFSmoothUnion.java
Normal file
21
src/main/java/ru/bclib/sdf/operator/SDFSmoothUnion.java
Normal file
|
@ -0,0 +1,21 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
public class SDFSmoothUnion extends SDFBinary {
|
||||
private float radius;
|
||||
|
||||
public SDFSmoothUnion setRadius(float radius) {
|
||||
this.radius = radius;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
float a = this.sourceA.getDistance(x, y, z);
|
||||
float b = this.sourceB.getDistance(x, y, z);
|
||||
this.selectValue(a, b);
|
||||
float h = Mth.clamp(0.5F + 0.5F * (b - a) / radius, 0F, 1F);
|
||||
return Mth.lerp(h, b, a) - radius * h * (1F - h);
|
||||
}
|
||||
}
|
13
src/main/java/ru/bclib/sdf/operator/SDFSubtraction.java
Normal file
13
src/main/java/ru/bclib/sdf/operator/SDFSubtraction.java
Normal file
|
@ -0,0 +1,13 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
import ru.bclib.util.MHelper;
|
||||
|
||||
public class SDFSubtraction extends SDFBinary {
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
float a = this.sourceA.getDistance(x, y, z);
|
||||
float b = this.sourceB.getDistance(x, y, z);
|
||||
this.selectValue(a, b);
|
||||
return MHelper.max(a, -b);
|
||||
}
|
||||
}
|
19
src/main/java/ru/bclib/sdf/operator/SDFTranslate.java
Normal file
19
src/main/java/ru/bclib/sdf/operator/SDFTranslate.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
public class SDFTranslate extends SDFUnary {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
|
||||
public SDFTranslate setTranslate(float x, float y, float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
return source.getDistance(x - this.x, y - this.y, z - this.z);
|
||||
}
|
||||
}
|
19
src/main/java/ru/bclib/sdf/operator/SDFUnary.java
Normal file
19
src/main/java/ru/bclib/sdf/operator/SDFUnary.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import ru.bclib.sdf.SDF;
|
||||
|
||||
public abstract class SDFUnary extends SDF {
|
||||
protected SDF source;
|
||||
|
||||
public SDFUnary setSource(SDF source) {
|
||||
this.source = source;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
return source.getBlockState(pos);
|
||||
}
|
||||
}
|
13
src/main/java/ru/bclib/sdf/operator/SDFUnion.java
Normal file
13
src/main/java/ru/bclib/sdf/operator/SDFUnion.java
Normal file
|
@ -0,0 +1,13 @@
|
|||
package ru.bclib.sdf.operator;
|
||||
|
||||
import ru.bclib.util.MHelper;
|
||||
|
||||
public class SDFUnion extends SDFBinary {
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
float a = this.sourceA.getDistance(x, y, z);
|
||||
float b = this.sourceB.getDistance(x, y, z);
|
||||
this.selectValue(a, b);
|
||||
return MHelper.min(a, b);
|
||||
}
|
||||
}
|
39
src/main/java/ru/bclib/sdf/primitive/SDFCappedCone.java
Normal file
39
src/main/java/ru/bclib/sdf/primitive/SDFCappedCone.java
Normal file
|
@ -0,0 +1,39 @@
|
|||
package ru.bclib.sdf.primitive;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import ru.bclib.util.MHelper;
|
||||
|
||||
public class SDFCappedCone extends SDFPrimitive {
|
||||
private float radius1;
|
||||
private float radius2;
|
||||
private float height;
|
||||
|
||||
public SDFCappedCone setRadius1(float radius) {
|
||||
this.radius1 = radius;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFCappedCone setRadius2(float radius) {
|
||||
this.radius2 = radius;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFCappedCone setHeight(float height) {
|
||||
this.height = height;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
float qx = MHelper.length(x, z);
|
||||
float k2x = radius2 - radius1;
|
||||
float k2y = 2 * height;
|
||||
float cax = qx - MHelper.min(qx, (y < 0F) ? radius1 : radius2);
|
||||
float cay = Math.abs(y) - height;
|
||||
float mlt = Mth.clamp(MHelper.dot(radius2 - qx, height - y, k2x, k2y) / MHelper.dot(k2x, k2y, k2x, k2y), 0F, 1F);
|
||||
float cbx = qx - radius2 + k2x * mlt;
|
||||
float cby = y - height + k2y * mlt;
|
||||
float s = (cbx < 0F && cay < 0F) ? -1F : 1F;
|
||||
return s * (float) Math.sqrt(MHelper.min(MHelper.dot(cax, cay, cax, cay), MHelper.dot(cbx, cby, cbx, cby)));
|
||||
}
|
||||
}
|
24
src/main/java/ru/bclib/sdf/primitive/SDFCapsule.java
Normal file
24
src/main/java/ru/bclib/sdf/primitive/SDFCapsule.java
Normal file
|
@ -0,0 +1,24 @@
|
|||
package ru.bclib.sdf.primitive;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import ru.bclib.util.MHelper;
|
||||
|
||||
public class SDFCapsule extends SDFPrimitive {
|
||||
private float radius;
|
||||
private float height;
|
||||
|
||||
public SDFCapsule setRadius(float radius) {
|
||||
this.radius = radius;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFCapsule setHeight(float height) {
|
||||
this.height = height;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
return MHelper.length(x, y - Mth.clamp(y, 0, height), z) - radius;
|
||||
}
|
||||
}
|
8
src/main/java/ru/bclib/sdf/primitive/SDFFlatland.java
Normal file
8
src/main/java/ru/bclib/sdf/primitive/SDFFlatland.java
Normal file
|
@ -0,0 +1,8 @@
|
|||
package ru.bclib.sdf.primitive;
|
||||
|
||||
public class SDFFlatland extends SDFPrimitive {
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
return y;
|
||||
}
|
||||
}
|
26
src/main/java/ru/bclib/sdf/primitive/SDFHexPrism.java
Normal file
26
src/main/java/ru/bclib/sdf/primitive/SDFHexPrism.java
Normal file
|
@ -0,0 +1,26 @@
|
|||
package ru.bclib.sdf.primitive;
|
||||
|
||||
import ru.bclib.util.MHelper;
|
||||
|
||||
public class SDFHexPrism extends SDFPrimitive {
|
||||
private float radius;
|
||||
private float height;
|
||||
|
||||
public SDFHexPrism setRadius(float radius) {
|
||||
this.radius = radius;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFHexPrism setHeight(float height) {
|
||||
this.height = height;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
float px = Math.abs(x);
|
||||
float py = Math.abs(y);
|
||||
float pz = Math.abs(z);
|
||||
return MHelper.max(py - height, MHelper.max((px * 0.866025F + pz * 0.5F), pz) - radius);
|
||||
}
|
||||
}
|
49
src/main/java/ru/bclib/sdf/primitive/SDFLine.java
Normal file
49
src/main/java/ru/bclib/sdf/primitive/SDFLine.java
Normal file
|
@ -0,0 +1,49 @@
|
|||
package ru.bclib.sdf.primitive;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import ru.bclib.util.MHelper;
|
||||
|
||||
public class SDFLine extends SDFPrimitive {
|
||||
private float radius;
|
||||
private float x1;
|
||||
private float y1;
|
||||
private float z1;
|
||||
private float x2;
|
||||
private float y2;
|
||||
private float z2;
|
||||
|
||||
public SDFLine setRadius(float radius) {
|
||||
this.radius = radius;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFLine setStart(float x, float y, float z) {
|
||||
this.x1 = x;
|
||||
this.y1 = y;
|
||||
this.z1 = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFLine setEnd(float x, float y, float z) {
|
||||
this.x2 = x;
|
||||
this.y2 = y;
|
||||
this.z2 = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
float pax = x - x1;
|
||||
float pay = y - y1;
|
||||
float paz = z - z1;
|
||||
|
||||
float bax = x2 - x1;
|
||||
float bay = y2 - y1;
|
||||
float baz = z2 - z1;
|
||||
|
||||
float dpb = MHelper.dot(pax, pay, paz, bax, bay, baz);
|
||||
float dbb = MHelper.dot(bax, bay, baz, bax, bay, baz);
|
||||
float h = Mth.clamp(dpb / dbb, 0F, 1F);
|
||||
return MHelper.length(pax - bax * h, pay - bay * h, paz - baz * h) - radius;
|
||||
}
|
||||
}
|
31
src/main/java/ru/bclib/sdf/primitive/SDFPie.java
Normal file
31
src/main/java/ru/bclib/sdf/primitive/SDFPie.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
package ru.bclib.sdf.primitive;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import ru.bclib.util.MHelper;
|
||||
|
||||
public class SDFPie extends SDFPrimitive {
|
||||
private float sin;
|
||||
private float cos;
|
||||
private float radius;
|
||||
|
||||
public SDFPie setAngle(float angle) {
|
||||
this.sin = (float) Math.sin(angle);
|
||||
this.cos = (float) Math.cos(angle);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFPie setRadius(float radius) {
|
||||
this.radius = radius;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
float px = Math.abs(x);
|
||||
float l = MHelper.length(px, y, z) - radius;
|
||||
float m = MHelper.dot(px, z, sin, cos);
|
||||
m = Mth.clamp(m, 0, radius);
|
||||
m = MHelper.length(px - sin * m, z - cos * m);
|
||||
return MHelper.max(l, m * (float) Math.signum(cos * px - sin * z));
|
||||
}
|
||||
}
|
39
src/main/java/ru/bclib/sdf/primitive/SDFPrimitive.java
Normal file
39
src/main/java/ru/bclib/sdf/primitive/SDFPrimitive.java
Normal file
|
@ -0,0 +1,39 @@
|
|||
package ru.bclib.sdf.primitive;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import ru.bclib.sdf.SDF;
|
||||
|
||||
public abstract class SDFPrimitive extends SDF {
|
||||
protected Function<BlockPos, BlockState> placerFunction;
|
||||
|
||||
public SDFPrimitive setBlock(Function<BlockPos, BlockState> placerFunction) {
|
||||
this.placerFunction = placerFunction;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFPrimitive setBlock(BlockState state) {
|
||||
this.placerFunction = (pos) -> {
|
||||
return state;
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
public SDFPrimitive setBlock(Block block) {
|
||||
this.placerFunction = (pos) -> {
|
||||
return block.defaultBlockState();
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
return placerFunction.apply(pos);
|
||||
}
|
||||
|
||||
/*public abstract CompoundTag toNBT(CompoundTag root) {
|
||||
|
||||
}*/
|
||||
}
|
17
src/main/java/ru/bclib/sdf/primitive/SDFSphere.java
Normal file
17
src/main/java/ru/bclib/sdf/primitive/SDFSphere.java
Normal file
|
@ -0,0 +1,17 @@
|
|||
package ru.bclib.sdf.primitive;
|
||||
|
||||
import ru.bclib.util.MHelper;
|
||||
|
||||
public class SDFSphere extends SDFPrimitive {
|
||||
private float radius;
|
||||
|
||||
public SDFSphere setRadius(float radius) {
|
||||
this.radius = radius;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistance(float x, float y, float z) {
|
||||
return MHelper.length(x, y, z) - radius;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue