mirror of
https://github.com/Creators-of-Create/Create.git
synced 2025-03-04 06:44:40 +01:00
Accretion
- Combine rotating/actor instance types - Preference towards actor, but the arbitrary pivot was never used - Inline KineticInstance - Remove magic 3/10 constant in rotating shader, instead use renderSeconds and multiply by 6 ahead of time - Now the rotational speed is in degrees per second
This commit is contained in:
parent
1fbfeb6f00
commit
b2891263d6
12 changed files with 109 additions and 166 deletions
|
@ -6,9 +6,9 @@ import com.simibubi.create.content.kinetics.simpleRelays.ShaftBlock;
|
|||
|
||||
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
|
||||
import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Direction.Axis;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
public abstract class KineticBlockEntityVisual<T extends KineticBlockEntity> extends AbstractBlockEntityVisual<T> {
|
||||
|
@ -32,7 +32,7 @@ public abstract class KineticBlockEntityVisual<T extends KineticBlockEntity> ext
|
|||
protected final void updateRotation(RotatingInstance instance, Direction.Axis axis, float speed) {
|
||||
instance.setRotationAxis(axis)
|
||||
.setRotationOffset(getRotationOffset(axis))
|
||||
.setRotationalSpeed(speed)
|
||||
.setRotationalSpeed(speed * RotatingInstance.SPEED_MULTIPLIER)
|
||||
.setColor(blockEntity)
|
||||
.setChanged();
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ public abstract class KineticBlockEntityVisual<T extends KineticBlockEntity> ext
|
|||
|
||||
protected final RotatingInstance setup(RotatingInstance key, Direction.Axis axis, float speed) {
|
||||
key.setRotationAxis(axis)
|
||||
.setRotationalSpeed(speed)
|
||||
.setRotationalSpeed(speed * RotatingInstance.SPEED_MULTIPLIER)
|
||||
.setRotationOffset(getRotationOffset(axis))
|
||||
.setColor(blockEntity)
|
||||
.setPosition(getVisualPosition())
|
||||
|
@ -76,7 +76,7 @@ public abstract class KineticBlockEntityVisual<T extends KineticBlockEntity> ext
|
|||
return shaft(rotationAxis());
|
||||
}
|
||||
|
||||
public static float rotationOffset(BlockState state, Axis axis, BlockPos pos) {
|
||||
public static float rotationOffset(BlockState state, Axis axis, Vec3i pos) {
|
||||
if (shouldOffset(axis, pos)) {
|
||||
return 22.5f;
|
||||
} else {
|
||||
|
@ -84,7 +84,7 @@ public abstract class KineticBlockEntityVisual<T extends KineticBlockEntity> ext
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean shouldOffset(Axis axis, BlockPos pos) {
|
||||
public static boolean shouldOffset(Axis axis, Vec3i pos) {
|
||||
// Sum the components of the other 2 axes.
|
||||
int x = (axis == Axis.X) ? 0 : pos.getX();
|
||||
int y = (axis == Axis.Y) ? 0 : pos.getY();
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
package com.simibubi.create.content.kinetics.base;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import dev.engine_room.flywheel.api.instance.InstanceHandle;
|
||||
import dev.engine_room.flywheel.api.instance.InstanceType;
|
||||
import dev.engine_room.flywheel.lib.instance.ColoredLitInstance;
|
||||
import net.createmod.catnip.utility.theme.Color;
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
||||
public class KineticInstance extends ColoredLitInstance {
|
||||
public float x;
|
||||
public float y;
|
||||
public float z;
|
||||
public float rotationalSpeed;
|
||||
public float rotationOffset;
|
||||
|
||||
protected KineticInstance(InstanceType<? extends KineticInstance> type, InstanceHandle handle) {
|
||||
super(type, handle);
|
||||
}
|
||||
|
||||
public KineticInstance setPosition(BlockPos pos) {
|
||||
return setPosition(pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
|
||||
public KineticInstance setPosition(Vector3f pos) {
|
||||
return setPosition(pos.x(), pos.y(), pos.z());
|
||||
}
|
||||
|
||||
public KineticInstance setPosition(float x, float y, float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public KineticInstance nudge(float x, float y, float z) {
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
this.z += z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public KineticInstance setColor(KineticBlockEntity blockEntity) {
|
||||
colorRgb(colorFromBE(blockEntity));
|
||||
return this;
|
||||
}
|
||||
|
||||
public KineticInstance setColor(Color c) {
|
||||
color(c.getRed(), c.getGreen(), c.getBlue());
|
||||
return this;
|
||||
}
|
||||
|
||||
public KineticInstance setRotationalSpeed(float rotationalSpeed) {
|
||||
this.rotationalSpeed = rotationalSpeed;
|
||||
return this;
|
||||
}
|
||||
|
||||
public KineticInstance setRotationOffset(float rotationOffset) {
|
||||
this.rotationOffset = rotationOffset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static int colorFromBE(KineticBlockEntity be) {
|
||||
if (be.hasNetwork())
|
||||
return Color.generateFromLong(be.network).getRGB();
|
||||
return 0xFFFFFF;
|
||||
}
|
||||
}
|
|
@ -1,20 +1,48 @@
|
|||
package com.simibubi.create.content.kinetics.base;
|
||||
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import dev.engine_room.flywheel.api.instance.InstanceHandle;
|
||||
import dev.engine_room.flywheel.api.instance.InstanceType;
|
||||
import dev.engine_room.flywheel.lib.instance.ColoredLitInstance;
|
||||
import net.createmod.catnip.utility.theme.Color;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Vec3i;
|
||||
|
||||
public class RotatingInstance extends ColoredLitInstance {
|
||||
public static final float SPEED_MULTIPLIER = 6;
|
||||
|
||||
public class RotatingInstance extends KineticInstance {
|
||||
public byte rotationAxisX;
|
||||
public byte rotationAxisY;
|
||||
public byte rotationAxisZ;
|
||||
public float x;
|
||||
public float y;
|
||||
public float z;
|
||||
/**
|
||||
* Speed in degrees per second
|
||||
*/
|
||||
public float rotationalSpeed;
|
||||
/**
|
||||
* Offset in degrees
|
||||
*/
|
||||
public float rotationOffset;
|
||||
|
||||
public RotatingInstance(InstanceType<? extends KineticInstance> type, InstanceHandle handle) {
|
||||
/**
|
||||
* Base rotation of the instance, applied before kinetic rotation
|
||||
*/
|
||||
public final Quaternionf rotation = new Quaternionf();
|
||||
|
||||
public RotatingInstance(InstanceType<? extends RotatingInstance> type, InstanceHandle handle) {
|
||||
super(type, handle);
|
||||
}
|
||||
|
||||
public static int colorFromBE(KineticBlockEntity be) {
|
||||
if (be.hasNetwork())
|
||||
return Color.generateFromLong(be.network).getRGB();
|
||||
return 0xFFFFFF;
|
||||
}
|
||||
|
||||
public RotatingInstance setRotationAxis(Direction.Axis axis) {
|
||||
Direction orientation = Direction.get(Direction.AxisDirection.POSITIVE, axis);
|
||||
return setRotationAxis(orientation.step());
|
||||
|
@ -31,4 +59,45 @@ public class RotatingInstance extends KineticInstance {
|
|||
return this;
|
||||
}
|
||||
|
||||
public RotatingInstance setPosition(Vec3i pos) {
|
||||
return setPosition(pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
|
||||
public RotatingInstance setPosition(Vector3f pos) {
|
||||
return setPosition(pos.x(), pos.y(), pos.z());
|
||||
}
|
||||
|
||||
public RotatingInstance setPosition(float x, float y, float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RotatingInstance nudge(float x, float y, float z) {
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
this.z += z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RotatingInstance setColor(KineticBlockEntity blockEntity) {
|
||||
colorRgb(colorFromBE(blockEntity));
|
||||
return this;
|
||||
}
|
||||
|
||||
public RotatingInstance setColor(Color c) {
|
||||
color(c.getRed(), c.getGreen(), c.getBlue());
|
||||
return this;
|
||||
}
|
||||
|
||||
public RotatingInstance setRotationalSpeed(float rotationalSpeed) {
|
||||
this.rotationalSpeed = rotationalSpeed;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RotatingInstance setRotationOffset(float rotationOffset) {
|
||||
this.rotationOffset = rotationOffset;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import org.joml.Quaternionf;
|
|||
|
||||
import com.simibubi.create.AllPartialModels;
|
||||
import com.simibubi.create.content.kinetics.base.KineticBlockEntityVisual;
|
||||
import com.simibubi.create.content.kinetics.base.KineticInstance;
|
||||
import com.simibubi.create.content.kinetics.base.RotatingInstance;
|
||||
import com.simibubi.create.content.processing.burner.ScrollInstance;
|
||||
import com.simibubi.create.foundation.render.AllInstanceTypes;
|
||||
|
@ -158,7 +157,7 @@ public class BeltVisual extends KineticBlockEntityVisual<BeltBlockEntity> {
|
|||
.rotation(q)
|
||||
.speed(0, speed * MAGIC_SCROLL_MULTIPLIER)
|
||||
.offset(0, bottom ? SCROLL_OFFSET_BOTTOM : SCROLL_OFFSET_OTHERWISE)
|
||||
.colorRgb(KineticInstance.colorFromBE(blockEntity))
|
||||
.colorRgb(RotatingInstance.colorFromBE(blockEntity))
|
||||
.setChanged();
|
||||
|
||||
return key;
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
package com.simibubi.create.content.kinetics.drill;
|
||||
|
||||
import org.joml.Quaternionf;
|
||||
|
||||
import com.simibubi.create.AllPartialModels;
|
||||
import com.simibubi.create.content.contraptions.actors.ActorInstance;
|
||||
import com.simibubi.create.content.contraptions.behaviour.MovementContext;
|
||||
import com.simibubi.create.content.contraptions.render.ActorVisual;
|
||||
import com.simibubi.create.content.kinetics.base.RotatingInstance;
|
||||
import com.simibubi.create.foundation.render.AllInstanceTypes;
|
||||
import com.simibubi.create.foundation.virtualWorld.VirtualRenderWorld;
|
||||
|
||||
|
@ -19,7 +17,7 @@ import net.minecraft.world.level.block.state.BlockState;
|
|||
|
||||
public class DrillActorVisual extends ActorVisual {
|
||||
|
||||
ActorInstance drillHead;
|
||||
RotatingInstance drillHead;
|
||||
private final Direction facing;
|
||||
|
||||
public DrillActorVisual(VisualizationContext visualizationContext, VirtualRenderWorld contraption, MovementContext context) {
|
||||
|
@ -38,21 +36,22 @@ public class DrillActorVisual extends ActorVisual {
|
|||
else
|
||||
eulerY = facing.toYRot() + ((axis == Direction.Axis.X) ? 180 : 0);
|
||||
|
||||
drillHead = instancerProvider.instancer(AllInstanceTypes.ACTOR, Models.partial(AllPartialModels.DRILL_HEAD))
|
||||
drillHead = instancerProvider.instancer(AllInstanceTypes.ROTATING, Models.partial(AllPartialModels.DRILL_HEAD))
|
||||
.createInstance();
|
||||
|
||||
drillHead.rotation.rotationXYZ(eulerX * Mth.DEG_TO_RAD, eulerY * Mth.DEG_TO_RAD, 0);
|
||||
|
||||
drillHead.setPosition(context.localPos)
|
||||
.setBlockLight(localBlockLight())
|
||||
.setRotationOffset(0)
|
||||
.setRotationAxis(0, 0, 1)
|
||||
.setLocalRotation(new Quaternionf().rotationXYZ(eulerX * Mth.DEG_TO_RAD, eulerY * Mth.DEG_TO_RAD, 0))
|
||||
.setSpeed(getSpeed(facing))
|
||||
.setChanged();
|
||||
.setRotationOffset(0)
|
||||
.setRotationAxis(0, 0, 1)
|
||||
.setRotationalSpeed(getSpeed(facing))
|
||||
.light(localBlockLight(), 0)
|
||||
.setChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginFrame() {
|
||||
drillHead.setSpeed(getSpeed(facing))
|
||||
drillHead.setRotationalSpeed(getSpeed(facing))
|
||||
.setChanged();
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ public class GearboxVisual extends KineticBlockEntityVisual<GearboxBlockEntity>
|
|||
.createInstance();
|
||||
|
||||
key.setRotationAxis(axis)
|
||||
.setRotationalSpeed(getSpeed(direction))
|
||||
.setRotationalSpeed(getSpeed(direction) * RotatingInstance.SPEED_MULTIPLIER)
|
||||
.setRotationOffset(getRotationOffset(axis)).setColor(blockEntity)
|
||||
.setPosition(getVisualPosition())
|
||||
.light(blockLight, skyLight)
|
||||
|
|
|
@ -60,7 +60,7 @@ public class MixerVisual extends EncasedCogVisual implements SimpleDynamicVisual
|
|||
|
||||
mixerHead.setPosition(getVisualPosition())
|
||||
.nudge(0, -renderedHeadOffset, 0)
|
||||
.setRotationalSpeed(speed * 2)
|
||||
.setRotationalSpeed(speed * 2 * RotatingInstance.SPEED_MULTIPLIER)
|
||||
.setChanged();
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import static com.simibubi.create.Create.asResource;
|
|||
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import com.simibubi.create.content.contraptions.actors.ActorInstance;
|
||||
import com.simibubi.create.content.kinetics.base.RotatingInstance;
|
||||
import com.simibubi.create.content.processing.burner.ScrollInstance;
|
||||
|
||||
|
@ -14,6 +13,7 @@ import dev.engine_room.flywheel.api.layout.IntegerRepr;
|
|||
import dev.engine_room.flywheel.api.layout.LayoutBuilder;
|
||||
import dev.engine_room.flywheel.lib.instance.SimpleInstanceType;
|
||||
import dev.engine_room.flywheel.lib.util.ExtraMemoryOps;
|
||||
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
|
@ -26,6 +26,7 @@ public class AllInstanceTypes {
|
|||
.vector("color", FloatRepr.NORMALIZED_UNSIGNED_BYTE, 4)
|
||||
.vector("light", IntegerRepr.SHORT, 2)
|
||||
.vector("overlay", IntegerRepr.SHORT, 2)
|
||||
.vector("rotation", FloatRepr.FLOAT, 4)
|
||||
.vector("pos", FloatRepr.FLOAT, 3)
|
||||
.scalar("speed", FloatRepr.FLOAT)
|
||||
.scalar("offset", FloatRepr.FLOAT)
|
||||
|
@ -38,14 +39,15 @@ public class AllInstanceTypes {
|
|||
MemoryUtil.memPutByte(ptr + 3, instance.alpha);
|
||||
ExtraMemoryOps.put2x16(ptr + 4, instance.light);
|
||||
ExtraMemoryOps.put2x16(ptr + 8, instance.overlay);
|
||||
MemoryUtil.memPutFloat(ptr + 12, instance.x);
|
||||
MemoryUtil.memPutFloat(ptr + 16, instance.y);
|
||||
MemoryUtil.memPutFloat(ptr + 20, instance.z);
|
||||
MemoryUtil.memPutFloat(ptr + 24, instance.rotationalSpeed);
|
||||
MemoryUtil.memPutFloat(ptr + 28, instance.rotationOffset);
|
||||
MemoryUtil.memPutByte(ptr + 32, instance.rotationAxisX);
|
||||
MemoryUtil.memPutByte(ptr + 33, instance.rotationAxisY);
|
||||
MemoryUtil.memPutByte(ptr + 34, instance.rotationAxisZ);
|
||||
ExtraMemoryOps.putQuaternionf(ptr + 12, instance.rotation);
|
||||
MemoryUtil.memPutFloat(ptr + 28, instance.x);
|
||||
MemoryUtil.memPutFloat(ptr + 32, instance.y);
|
||||
MemoryUtil.memPutFloat(ptr + 36, instance.z);
|
||||
MemoryUtil.memPutFloat(ptr + 40, instance.rotationalSpeed);
|
||||
MemoryUtil.memPutFloat(ptr + 44, instance.rotationOffset);
|
||||
MemoryUtil.memPutByte(ptr + 48, instance.rotationAxisX);
|
||||
MemoryUtil.memPutByte(ptr + 49, instance.rotationAxisY);
|
||||
MemoryUtil.memPutByte(ptr + 50, instance.rotationAxisZ);
|
||||
})
|
||||
.build();
|
||||
|
||||
|
@ -85,36 +87,6 @@ public class AllInstanceTypes {
|
|||
})
|
||||
.build();
|
||||
|
||||
public static final InstanceType<ActorInstance> ACTOR = SimpleInstanceType.builder(ActorInstance::new)
|
||||
.cullShader(asResource("instance/cull/actor.glsl"))
|
||||
.vertexShader(asResource("instance/actor.vert"))
|
||||
.layout(LayoutBuilder.create()
|
||||
.vector("pos", FloatRepr.FLOAT, 3)
|
||||
.vector("light", IntegerRepr.SHORT, 2)
|
||||
.scalar("offset", FloatRepr.FLOAT)
|
||||
.vector("axis", FloatRepr.NORMALIZED_BYTE, 3)
|
||||
.vector("rotation", FloatRepr.FLOAT, 4)
|
||||
.vector("rotationCenter", FloatRepr.NORMALIZED_BYTE, 3)
|
||||
.scalar("speed", FloatRepr.FLOAT)
|
||||
.build())
|
||||
.writer((ptr, instance) -> {
|
||||
MemoryUtil.memPutFloat(ptr, instance.x);
|
||||
MemoryUtil.memPutFloat(ptr + 4, instance.y);
|
||||
MemoryUtil.memPutFloat(ptr + 8, instance.z);
|
||||
MemoryUtil.memPutShort(ptr + 12, instance.blockLight);
|
||||
MemoryUtil.memPutShort(ptr + 14, instance.skyLight);
|
||||
MemoryUtil.memPutFloat(ptr + 16, instance.rotationOffset);
|
||||
MemoryUtil.memPutByte(ptr + 20, instance.rotationAxisX);
|
||||
MemoryUtil.memPutByte(ptr + 21, instance.rotationAxisY);
|
||||
MemoryUtil.memPutByte(ptr + 22, instance.rotationAxisZ);
|
||||
ExtraMemoryOps.putQuaternionf(ptr + 24, instance.rotation);
|
||||
MemoryUtil.memPutByte(ptr + 40, instance.rotationCenterX);
|
||||
MemoryUtil.memPutByte(ptr + 41, instance.rotationCenterY);
|
||||
MemoryUtil.memPutByte(ptr + 42, instance.rotationCenterZ);
|
||||
MemoryUtil.memPutFloat(ptr + 44, instance.speed);
|
||||
})
|
||||
.build();
|
||||
|
||||
public static void init() {
|
||||
// noop
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
#include "flywheel:util/matrix.glsl"
|
||||
#include "flywheel:util/quaternion.glsl"
|
||||
|
||||
void flw_instanceVertex(in FlwInstance instance) {
|
||||
float degrees = instance.offset + flw_renderSeconds * instance.speed;
|
||||
|
||||
vec4 kineticRot = quaternionDegrees(instance.axis, degrees);
|
||||
vec3 rotated = rotateByQuaternion(flw_vertexPos.xyz - instance.rotationCenter, kineticRot) + instance.rotationCenter;
|
||||
|
||||
flw_vertexPos.xyz = rotateByQuaternion(rotated - .5, instance.rotation) + instance.pos + .5;
|
||||
flw_vertexNormal = rotateByQuaternion(rotateByQuaternion(flw_vertexNormal, kineticRot), instance.rotation);
|
||||
flw_vertexLight = max(vec2(instance.light) / 256., flw_vertexLight);
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
void flw_transformBoundingSphere(in FlwInstance instance, inout vec3 center, inout float radius) {
|
||||
// The instance will spin about the rotation center, so we need to expand the radius to account for that
|
||||
float extraForKinetic = length(center - instance.rotationCenter);
|
||||
float extraForModel = length(center - 0.5);
|
||||
|
||||
radius += extraForKinetic + extraForModel;
|
||||
center += instance.pos;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
void flw_transformBoundingSphere(in FlwInstance instance, inout vec3 center, inout float radius) {
|
||||
// The instance will spin about (0.5, 0.5, 0.5), so we need to expand the radius to account for that
|
||||
// The instance will spin about the rotation center, so we need to expand the radius to account for that
|
||||
radius += length(center - 0.5);
|
||||
center += instance.pos;
|
||||
}
|
||||
|
|
|
@ -1,21 +1,15 @@
|
|||
#include "flywheel:util/matrix.glsl"
|
||||
|
||||
const float uTime = 0.;
|
||||
|
||||
mat3 kineticRotation(float offset, float speed, vec3 axis) {
|
||||
float degrees = offset + flw_renderTicks * speed * 3./10.;
|
||||
return rotationDegrees(axis, degrees);
|
||||
}
|
||||
#include "flywheel:util/quaternion.glsl"
|
||||
|
||||
void flw_instanceVertex(in FlwInstance instance) {
|
||||
mat3 spin = kineticRotation(instance.offset, instance.speed, instance.axis);
|
||||
float degrees = instance.offset + flw_renderSeconds * instance.speed;
|
||||
|
||||
vec3 worldPos = spin * (flw_vertexPos.xyz - .5);
|
||||
flw_vertexPos.xyz = worldPos.xyz + instance.pos + .5;
|
||||
vec4 kineticRot = quaternionDegrees(instance.axis, degrees);
|
||||
vec3 rotated = rotateByQuaternion(flw_vertexPos.xyz - .5, instance.rotation);
|
||||
|
||||
flw_vertexNormal = spin * flw_vertexNormal;
|
||||
flw_vertexOverlay = instance.overlay;
|
||||
flw_vertexPos.xyz = rotateByQuaternion(rotated, kineticRot) + instance.pos + .5;
|
||||
flw_vertexNormal = rotateByQuaternion(rotateByQuaternion(flw_vertexNormal, instance.rotation), kineticRot);
|
||||
flw_vertexLight = max(vec2(instance.light) / 256., flw_vertexLight);
|
||||
flw_vertexOverlay = instance.overlay;
|
||||
|
||||
#if defined(DEBUG_RAINBOW)
|
||||
flw_vertexColor = instance.color;
|
||||
|
|
Loading…
Add table
Reference in a new issue