render: paint video on the clicked wall face (no visible anchor block)
Some checks failed
build / build (push) Has been cancelled
Some checks failed
build / build (push) Has been cancelled
The anchor block becomes invisible and non-collidable; it exists only as a BlockEntity host in the air block adjacent to the clicked wall. The renderer now translates and rotates the textured quad so it sits flush against the surface of the wall the user actually clicked, on any of the six faces. Stick interaction: right-click face → place anchor at hit.relative(face), facing=face, open GUI right-click face with anchor already there → reopen the GUI sneak + left-click face with stick → delete the anchor on that face The anchor's selection outline / collision / occlusion are all empty, so the player can target the wall block behind it without interference. JavaCV / streaming polish: - Bump missing-JavaCV log to WARN so users notice when the runtime jar is not installed (previously buried at INFO). - Add HTTP resilience options: `timeout`, `reconnect`, `reconnect_streamed`, `reconnect_at_eof`, and a `user_agent` so picky servers don't 403 us.
This commit is contained in:
@@ -5,7 +5,7 @@ org.gradle.configuration-cache=false
|
|||||||
|
|
||||||
# Mod
|
# Mod
|
||||||
mod_id=video_player
|
mod_id=video_player
|
||||||
mod_version=0.3.1
|
mod_version=0.4.0
|
||||||
maven_group=com.ejclaw.videoplayer
|
maven_group=com.ejclaw.videoplayer
|
||||||
archives_base_name=video_player
|
archives_base_name=video_player
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,20 @@ public class VideoPlayerClient implements ClientModInitializer {
|
|||||||
);
|
);
|
||||||
|
|
||||||
AttackBlockCallback.EVENT.register((player, level, hand, pos, direction) -> {
|
AttackBlockCallback.EVENT.register((player, level, hand, pos, direction) -> {
|
||||||
if (level.isClientSide()
|
if (!level.isClientSide()) return InteractionResult.PASS;
|
||||||
&& player.getMainHandItem().getItem() instanceof VideoStickItem
|
if (!(player.getMainHandItem().getItem() instanceof VideoStickItem)) return InteractionResult.PASS;
|
||||||
&& level.getBlockEntity(pos) instanceof VideoAnchorBlockEntity) {
|
// The anchor itself is invisible / non-collidable so the player cannot left-click it
|
||||||
|
// directly. Sneak + left-click on the wall the video sits on → delete the anchor in
|
||||||
|
// the adjacent air block.
|
||||||
|
if (player.isShiftKeyDown()) {
|
||||||
|
BlockPos anchorPos = pos.relative(direction);
|
||||||
|
if (level.getBlockEntity(anchorPos) instanceof VideoAnchorBlockEntity) {
|
||||||
|
ClientPlayNetworking.send(new DeleteAnchorPayload(anchorPos));
|
||||||
|
return InteractionResult.SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Legacy / safety: if the player somehow targets the anchor block directly.
|
||||||
|
if (level.getBlockEntity(pos) instanceof VideoAnchorBlockEntity) {
|
||||||
ClientPlayNetworking.send(new DeleteAnchorPayload(pos));
|
ClientPlayNetworking.send(new DeleteAnchorPayload(pos));
|
||||||
return InteractionResult.SUCCESS;
|
return InteractionResult.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,26 @@ import net.minecraft.world.InteractionHand;
|
|||||||
import net.minecraft.world.InteractionResult;
|
import net.minecraft.world.InteractionResult;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.level.BlockGetter;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.block.BaseEntityBlock;
|
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||||
|
import net.minecraft.world.level.block.RenderShape;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.phys.BlockHitResult;
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
|
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||||
|
import net.minecraft.world.phys.shapes.Shapes;
|
||||||
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Anchor block — invisible, non-collidable host for {@link VideoAnchorBlockEntity}.
|
||||||
|
*
|
||||||
|
* <p>The block exists only so a {@link BlockEntity} can be attached to a position; visually it is
|
||||||
|
* completely empty (no model, no selection outline, no collision). The video itself is drawn by
|
||||||
|
* {@link com.ejclaw.videoplayer.client.render.VideoAnchorRenderer} flush against the wall the
|
||||||
|
* player clicked, not as a textured surface on this block.
|
||||||
|
*/
|
||||||
public class VideoAnchorBlock extends BaseEntityBlock {
|
public class VideoAnchorBlock extends BaseEntityBlock {
|
||||||
public static final MapCodec<VideoAnchorBlock> CODEC = simpleCodec(VideoAnchorBlock::new);
|
public static final MapCodec<VideoAnchorBlock> CODEC = simpleCodec(VideoAnchorBlock::new);
|
||||||
|
|
||||||
@@ -34,6 +47,31 @@ public class VideoAnchorBlock extends BaseEntityBlock {
|
|||||||
return new VideoAnchorBlockEntity(pos, state);
|
return new VideoAnchorBlockEntity(pos, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RenderShape getRenderShape(BlockState state) {
|
||||||
|
return RenderShape.INVISIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext ctx) {
|
||||||
|
return Shapes.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext ctx) {
|
||||||
|
return Shapes.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected VoxelShape getOcclusionShape(BlockState state) {
|
||||||
|
return Shapes.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean propagatesSkylightDown(BlockState state) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level level,
|
protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level level,
|
||||||
BlockPos pos, Player player, InteractionHand hand,
|
BlockPos pos, Player player, InteractionHand hand,
|
||||||
|
|||||||
@@ -108,9 +108,14 @@ public class JavaCvBackend implements VideoBackend {
|
|||||||
Method setOpt = grabberCls.getMethod("setOption", String.class, String.class);
|
Method setOpt = grabberCls.getMethod("setOption", String.class, String.class);
|
||||||
Method setSampleFormat = grabberCls.getMethod("setSampleFormat", int.class);
|
Method setSampleFormat = grabberCls.getMethod("setSampleFormat", int.class);
|
||||||
|
|
||||||
// mp4/http(s) network tuning
|
// HTTP(S) tuning for streaming URLs (e.g. webm via Range / chunked transfer).
|
||||||
try { setOpt.invoke(grabber, "rw_timeout", "5000000"); } catch (Throwable ignored) {}
|
try { setOpt.invoke(grabber, "rw_timeout", "10000000"); } catch (Throwable ignored) {}
|
||||||
try { setOpt.invoke(grabber, "stimeout", "5000000"); } catch (Throwable ignored) {}
|
try { setOpt.invoke(grabber, "timeout", "10000000"); } catch (Throwable ignored) {}
|
||||||
|
try { setOpt.invoke(grabber, "reconnect", "1"); } catch (Throwable ignored) {}
|
||||||
|
try { setOpt.invoke(grabber, "reconnect_streamed", "1"); } catch (Throwable ignored) {}
|
||||||
|
try { setOpt.invoke(grabber, "reconnect_at_eof", "1"); } catch (Throwable ignored) {}
|
||||||
|
try { setOpt.invoke(grabber, "user_agent",
|
||||||
|
"video_player/" + com.ejclaw.videoplayer.VideoPlayerMod.MOD_ID); } catch (Throwable ignored) {}
|
||||||
// Force interleaved signed 16-bit PCM so the audio sink path is single-shape.
|
// Force interleaved signed 16-bit PCM so the audio sink path is single-shape.
|
||||||
try { setSampleFormat.invoke(grabber, AV_SAMPLE_FMT_S16); } catch (Throwable ignored) {}
|
try { setSampleFormat.invoke(grabber, AV_SAMPLE_FMT_S16); } catch (Throwable ignored) {}
|
||||||
|
|
||||||
@@ -162,7 +167,10 @@ public class JavaCvBackend implements VideoBackend {
|
|||||||
if (audioLine == null) Thread.sleep(15);
|
if (audioLine == null) Thread.sleep(15);
|
||||||
}
|
}
|
||||||
} catch (ClassNotFoundException cnf) {
|
} catch (ClassNotFoundException cnf) {
|
||||||
VideoPlayerMod.LOG.info("[{}] JavaCV not on classpath; backend inactive", VideoPlayerMod.MOD_ID);
|
VideoPlayerMod.LOG.warn(
|
||||||
|
"[{}] JavaCV not on classpath — install org.bytedeco:javacv-platform (or javacv + ffmpeg natives)" +
|
||||||
|
" to enable video/audio playback. Anchor placeholder will remain visible.",
|
||||||
|
VideoPlayerMod.MOD_ID);
|
||||||
} catch (InterruptedException ie) {
|
} catch (InterruptedException ie) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
|
|||||||
@@ -20,15 +20,19 @@ import net.minecraft.world.phys.Vec3;
|
|||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SPEC §5.2 — submits a width×height textured quad in front of the anchor, oriented by facing.
|
* Draws the video as a textured quad <em>on the surface of the block the user clicked</em>.
|
||||||
*
|
*
|
||||||
* <p>Ported to 26.1.2's render-state pipeline: per-frame BE state is captured in
|
* <p>The anchor BE lives in the air block adjacent to the clicked wall. Its {@code facing}
|
||||||
* {@link State} via {@link #extractRenderState}, then drawn via
|
* field is the surface normal of the wall (= the {@link Direction} the player clicked). The
|
||||||
* {@link SubmitNodeCollector#submitCustomGeometry} during {@link #submit}.
|
* quad is rotated so its normal aligns with that direction and shifted so it sits flush against
|
||||||
|
* the wall surface, with a tiny outward offset to avoid z-fighting.
|
||||||
*/
|
*/
|
||||||
@Environment(EnvType.CLIENT)
|
@Environment(EnvType.CLIENT)
|
||||||
public class VideoAnchorRenderer implements BlockEntityRenderer<VideoAnchorBlockEntity, VideoAnchorRenderer.State> {
|
public class VideoAnchorRenderer implements BlockEntityRenderer<VideoAnchorBlockEntity, VideoAnchorRenderer.State> {
|
||||||
|
|
||||||
|
/** Tiny outward offset so the quad doesn't z-fight with the wall. */
|
||||||
|
private static final float SURFACE_EPSILON = 0.001F;
|
||||||
|
|
||||||
public VideoAnchorRenderer(BlockEntityRendererProvider.Context ctx) {
|
public VideoAnchorRenderer(BlockEntityRendererProvider.Context ctx) {
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
@@ -44,8 +48,7 @@ public class VideoAnchorRenderer implements BlockEntityRenderer<VideoAnchorBlock
|
|||||||
BlockEntityRenderState.extractBase(be, state, crumbling);
|
BlockEntityRenderState.extractBase(be, state, crumbling);
|
||||||
state.width = be.getWidth();
|
state.width = be.getWidth();
|
||||||
state.height = be.getHeight();
|
state.height = be.getHeight();
|
||||||
Direction facing = be.getFacing();
|
state.facing = be.getFacing();
|
||||||
state.yaw = facing.getAxis().isHorizontal() ? facing.toYRot() : 0F;
|
|
||||||
state.textureId = VideoPlayback.getOrStart(be);
|
state.textureId = VideoPlayback.getOrStart(be);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,25 +60,26 @@ public class VideoAnchorRenderer implements BlockEntityRenderer<VideoAnchorBlock
|
|||||||
final float w = state.width;
|
final float w = state.width;
|
||||||
final float h = state.height;
|
final float h = state.height;
|
||||||
final int light = state.lightCoords;
|
final int light = state.lightCoords;
|
||||||
|
final Direction f = state.facing == null ? Direction.NORTH : state.facing;
|
||||||
|
|
||||||
pose.pushPose();
|
pose.pushPose();
|
||||||
// Center quad on the anchor's top face, rotated to face the configured direction.
|
// 1) Move to the anchor block's center.
|
||||||
pose.translate(0.5F, 1.01F, 0.5F);
|
pose.translate(0.5F, 0.5F, 0.5F);
|
||||||
pose.mulPose(Axis.YP.rotationDegrees(-state.yaw));
|
// 2) Rotate local +Z to align with the wall's outward normal.
|
||||||
pose.translate(-w / 2.0F, 0F, 0F);
|
applyFaceRotation(pose, f);
|
||||||
|
// 3) Center the quad on origin (local XY) and push it back 0.5 - ε so it lands on the
|
||||||
|
// wall surface (the boundary face between the anchor's air block and the wall block).
|
||||||
|
pose.translate(-w / 2.0F, -h / 2.0F, -0.5F + SURFACE_EPSILON);
|
||||||
|
|
||||||
// Snapshot the matrix so the callback's matrix-aware addVertex works even though
|
|
||||||
// submitCustomGeometry hands us a fresh Pose (its `pose` parameter).
|
|
||||||
final Matrix4f mat = new Matrix4f(pose.last().pose());
|
final Matrix4f mat = new Matrix4f(pose.last().pose());
|
||||||
|
|
||||||
RenderType rt = RenderTypes.entityCutout(tex);
|
RenderType rt = RenderTypes.entityCutout(tex);
|
||||||
collector.submitCustomGeometry(pose, rt, (poseUnused, vc) -> {
|
collector.submitCustomGeometry(pose, rt, (poseUnused, vc) -> {
|
||||||
// Front face (visible from the direction the anchor faces)
|
// Front face (visible from outside, looking back at the wall)
|
||||||
emit(vc, mat, 0F, 0F, 0F, 0F, 1F, light);
|
emit(vc, mat, 0F, 0F, 0F, 0F, 1F, light);
|
||||||
emit(vc, mat, w, 0F, 0F, 1F, 1F, light);
|
emit(vc, mat, w, 0F, 0F, 1F, 1F, light);
|
||||||
emit(vc, mat, w, h, 0F, 1F, 0F, light);
|
emit(vc, mat, w, h, 0F, 1F, 0F, light);
|
||||||
emit(vc, mat, 0F, h, 0F, 0F, 0F, light);
|
emit(vc, mat, 0F, h, 0F, 0F, 0F, light);
|
||||||
// Back face (visible from behind)
|
// Back face (in case the player ends up on the other side, e.g. clipping into the wall)
|
||||||
emit(vc, mat, 0F, h, 0F, 0F, 0F, light);
|
emit(vc, mat, 0F, h, 0F, 0F, 0F, light);
|
||||||
emit(vc, mat, w, h, 0F, 1F, 0F, light);
|
emit(vc, mat, w, h, 0F, 1F, 0F, light);
|
||||||
emit(vc, mat, w, 0F, 0F, 1F, 1F, light);
|
emit(vc, mat, w, 0F, 0F, 1F, 1F, light);
|
||||||
@@ -85,6 +89,18 @@ public class VideoAnchorRenderer implements BlockEntityRenderer<VideoAnchorBlock
|
|||||||
pose.popPose();
|
pose.popPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Rotate so local +Z (the quad's outward normal in its base orientation) becomes world {@code f}. */
|
||||||
|
private static void applyFaceRotation(PoseStack pose, Direction f) {
|
||||||
|
switch (f) {
|
||||||
|
case SOUTH -> { /* identity: local +Z already faces world +Z (south) */ }
|
||||||
|
case NORTH -> pose.mulPose(Axis.YP.rotationDegrees(180F));
|
||||||
|
case EAST -> pose.mulPose(Axis.YP.rotationDegrees(-90F));
|
||||||
|
case WEST -> pose.mulPose(Axis.YP.rotationDegrees(90F));
|
||||||
|
case UP -> pose.mulPose(Axis.XP.rotationDegrees(-90F));
|
||||||
|
case DOWN -> pose.mulPose(Axis.XP.rotationDegrees(90F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void emit(com.mojang.blaze3d.vertex.VertexConsumer vc, Matrix4f mat,
|
private static void emit(com.mojang.blaze3d.vertex.VertexConsumer vc, Matrix4f mat,
|
||||||
float x, float y, float z, float u, float v, int light) {
|
float x, float y, float z, float u, float v, int light) {
|
||||||
vc.addVertex(mat, x, y, z)
|
vc.addVertex(mat, x, y, z)
|
||||||
@@ -110,6 +126,6 @@ public class VideoAnchorRenderer implements BlockEntityRenderer<VideoAnchorBlock
|
|||||||
public Identifier textureId;
|
public Identifier textureId;
|
||||||
public int width = 1;
|
public int width = 1;
|
||||||
public int height = 1;
|
public int height = 1;
|
||||||
public float yaw = 0F;
|
public Direction facing = Direction.NORTH;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,18 @@ import net.minecraft.world.level.Level;
|
|||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
|
||||||
/** Right-click empty face → place anchor + open GUI. Right-click existing anchor → edit. */
|
/**
|
||||||
|
* Right-click a block's face with the video stick:
|
||||||
|
* <ul>
|
||||||
|
* <li>If a video anchor already exists in the adjacent air (= an anchor already drawn on this
|
||||||
|
* face), reopen its config GUI.</li>
|
||||||
|
* <li>Otherwise place an invisible anchor in the adjacent air block, set its facing to the
|
||||||
|
* clicked face direction (so the renderer draws the quad flush against this face), and
|
||||||
|
* open the config GUI.</li>
|
||||||
|
* </ul>
|
||||||
|
* The anchor block itself is invisible / non-collidable, so visually no new block appears —
|
||||||
|
* the video just shows up on the face the user clicked.
|
||||||
|
*/
|
||||||
public class VideoStickItem extends Item {
|
public class VideoStickItem extends Item {
|
||||||
public VideoStickItem(Properties properties) {
|
public VideoStickItem(Properties properties) {
|
||||||
super(properties);
|
super(properties);
|
||||||
@@ -33,25 +44,27 @@ public class VideoStickItem extends Item {
|
|||||||
if (!(player instanceof ServerPlayer sp)) return InteractionResult.PASS;
|
if (!(player instanceof ServerPlayer sp)) return InteractionResult.PASS;
|
||||||
|
|
||||||
BlockPos hit = ctx.getClickedPos();
|
BlockPos hit = ctx.getClickedPos();
|
||||||
|
Direction face = ctx.getClickedFace();
|
||||||
|
BlockPos anchorPos = hit.relative(face);
|
||||||
|
|
||||||
// Existing anchor → edit
|
// Existing anchor on this face → reopen edit GUI.
|
||||||
if (sl.getBlockEntity(hit) instanceof VideoAnchorBlockEntity existing) {
|
if (sl.getBlockEntity(anchorPos) instanceof VideoAnchorBlockEntity existing) {
|
||||||
ServerPlayNetworking.send(sp, new OpenScreenPayload(hit, existing.toNbt()));
|
ServerPlayNetworking.send(sp, new OpenScreenPayload(anchorPos, existing.toNbt()));
|
||||||
return InteractionResult.SUCCESS;
|
return InteractionResult.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty face → place anchor on top of the clicked face
|
// Need an empty / replaceable space in front of the clicked face.
|
||||||
Direction side = ctx.getClickedFace();
|
BlockState there = sl.getBlockState(anchorPos);
|
||||||
BlockPos placeAt = hit.relative(side);
|
|
||||||
BlockState there = sl.getBlockState(placeAt);
|
|
||||||
if (!there.canBeReplaced()) return InteractionResult.PASS;
|
if (!there.canBeReplaced()) return InteractionResult.PASS;
|
||||||
|
|
||||||
Block anchor = VideoPlayerBlocks.VIDEO_ANCHOR;
|
Block anchor = VideoPlayerBlocks.VIDEO_ANCHOR;
|
||||||
sl.setBlock(placeAt, anchor.defaultBlockState(), Block.UPDATE_ALL);
|
sl.setBlock(anchorPos, anchor.defaultBlockState(), Block.UPDATE_ALL);
|
||||||
|
|
||||||
if (sl.getBlockEntity(placeAt) instanceof VideoAnchorBlockEntity be) {
|
if (sl.getBlockEntity(anchorPos) instanceof VideoAnchorBlockEntity be) {
|
||||||
be.setFacing(ctx.getHorizontalDirection().getOpposite());
|
// Surface normal of the wall we're painting on points outward in the same direction
|
||||||
ServerPlayNetworking.send(sp, new OpenScreenPayload(placeAt, be.toNbt()));
|
// as the face the player clicked.
|
||||||
|
be.setFacing(face);
|
||||||
|
ServerPlayNetworking.send(sp, new OpenScreenPayload(anchorPos, be.toNbt()));
|
||||||
}
|
}
|
||||||
return InteractionResult.SUCCESS;
|
return InteractionResult.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,12 @@ public final class VideoPlayerBlocks {
|
|||||||
|
|
||||||
public static final Block VIDEO_ANCHOR = register(
|
public static final Block VIDEO_ANCHOR = register(
|
||||||
"video_anchor",
|
"video_anchor",
|
||||||
BlockBehaviour.Properties.of().strength(1.0F).noOcclusion(),
|
BlockBehaviour.Properties.of()
|
||||||
|
.noCollision()
|
||||||
|
.noOcclusion()
|
||||||
|
.instabreak()
|
||||||
|
.replaceable()
|
||||||
|
.strength(0F),
|
||||||
VideoAnchorBlock::new
|
VideoAnchorBlock::new
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
{
|
{
|
||||||
"parent": "block/block",
|
|
||||||
"textures": {
|
"textures": {
|
||||||
"all": "video_player:block/video_anchor",
|
|
||||||
"particle": "video_player:block/video_anchor"
|
"particle": "video_player:block/video_anchor"
|
||||||
},
|
}
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"from": [0, 0, 0],
|
|
||||||
"to": [16, 2, 16],
|
|
||||||
"faces": {
|
|
||||||
"down": { "texture": "#all", "uv": [0, 0, 16, 16] },
|
|
||||||
"up": { "texture": "#all", "uv": [0, 0, 16, 16] },
|
|
||||||
"north": { "texture": "#all", "uv": [0, 0, 16, 2] },
|
|
||||||
"south": { "texture": "#all", "uv": [0, 0, 16, 2] },
|
|
||||||
"east": { "texture": "#all", "uv": [0, 0, 16, 2] },
|
|
||||||
"west": { "texture": "#all", "uv": [0, 0, 16, 2] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user