Files
ReplayModCinematic/src/main/java/com/replaymod/render/CameraPathExporter.java

228 lines
9.8 KiB
Java

package com.replaymod.render;
import com.replaymod.core.ReplayMod;
import com.replaymod.core.versions.MCVer;
import com.replaymod.replay.camera.CameraEntity;
import de.javagl.jgltf.impl.v2.Accessor;
import de.javagl.jgltf.impl.v2.Animation;
import de.javagl.jgltf.impl.v2.AnimationChannel;
import de.javagl.jgltf.impl.v2.AnimationChannelTarget;
import de.javagl.jgltf.impl.v2.AnimationSampler;
import de.javagl.jgltf.impl.v2.Asset;
import de.javagl.jgltf.impl.v2.Buffer;
import de.javagl.jgltf.impl.v2.BufferView;
import de.javagl.jgltf.impl.v2.Camera;
import de.javagl.jgltf.impl.v2.CameraPerspective;
import de.javagl.jgltf.impl.v2.GlTF;
import de.javagl.jgltf.impl.v2.Node;
import de.javagl.jgltf.model.io.v2.GltfAssetV2;
import de.javagl.jgltf.model.io.v2.GltfAssetWriterV2;
import de.johni0702.minecraft.gui.utils.lwjgl.vector.Quaternion;
import de.johni0702.minecraft.gui.utils.lwjgl.vector.Vector4f;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.Entity;
import org.apache.commons.io.FilenameUtils;
import org.lwjgl.opengl.GL11;
//#if MC>=11400
import net.minecraft.util.math.Vec3d;
//#else
//#endif
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import static com.replaymod.core.utils.Utils.configure;
public class CameraPathExporter {
private final MinecraftClient mc = MCVer.getMinecraft();
private final RenderSettings settings;
private int framesDone;
private ByteBuffer timeBuffer;
private ByteBuffer cameraTranslationBuffer;
private ByteBuffer cameraRotationBuffer;
public CameraPathExporter(RenderSettings settings) {
this.settings = settings;
}
public void setup(int totalFrames) {
timeBuffer = ByteBuffer.allocate(4 * totalFrames).order(ByteOrder.LITTLE_ENDIAN);
cameraTranslationBuffer = ByteBuffer.allocate(4 * totalFrames * 3).order(ByteOrder.LITTLE_ENDIAN);
cameraRotationBuffer = ByteBuffer.allocate(4 * totalFrames * 4).order(ByteOrder.LITTLE_ENDIAN);
}
public void recordFrame(float tickDelta) {
//#if MC>=10800
Entity entity = mc.getCameraEntity() == null ? mc.player : mc.getCameraEntity();
//#else
//$$ Entity entity = mc.renderViewEntity == null ? mc.thePlayer : mc.renderViewEntity;
//#endif
//#if MC>=11400
net.minecraft.client.render.Camera camera = mc.gameRenderer.getCamera();
Vec3d vec = camera.getPos();
float x = (float) vec.getX();
float y = (float) vec.getY();
float z = (float) vec.getZ();
float yaw = camera.getYaw() + 180;
float pitch = camera.getPitch();
//#else
//#if MC>=10800
//$$ float eyeHeight = entity.getEyeHeight();
//#else
//$$ float eyeHeight = 1.62f - entity.yOffset;
//#endif
//$$ float x = (float) (entity.prevPosX + (entity.posX - entity.prevPosX) * tickDelta);
//$$ float y = (float) (entity.prevPosY + (entity.posY - entity.prevPosY) * tickDelta + eyeHeight);
//$$ float z = (float) (entity.prevPosZ + (entity.posZ - entity.prevPosZ) * tickDelta);
//$$ float yaw = entity.prevRotationYaw + (entity.rotationYaw - entity.prevRotationYaw) * tickDelta + 180;
//$$ float pitch = entity.prevRotationPitch + (entity.rotationPitch - entity.prevRotationPitch) * tickDelta;
//#endif
float roll = entity instanceof CameraEntity ? ((CameraEntity) entity).roll : 0;
Quaternion quatYaw = new Quaternion();
Quaternion quatPitch = new Quaternion();
Quaternion quatRoll = new Quaternion();
quatYaw.setFromAxisAngle(new Vector4f(0, -1, 0, (float) Math.toRadians(yaw)));
quatPitch.setFromAxisAngle(new Vector4f(-1, 0, 0, (float) Math.toRadians(pitch)));
quatRoll.setFromAxisAngle(new Vector4f(0, 0, -1, (float) Math.toRadians(roll)));
Quaternion quaternion = new Quaternion(0, 0, 0, 1);
Quaternion.mul(quaternion, quatYaw, quaternion);
Quaternion.mul(quaternion, quatPitch, quaternion);
Quaternion.mul(quaternion, quatRoll, quaternion);
quaternion.normalise(quaternion);
float[] translation = new float[] { x, y, z };
float[] rotation = new float[] { quaternion.getX(), quaternion.getY(), quaternion.getZ(), quaternion.getW() };
timeBuffer.putFloat(framesDone / (float) settings.getFramesPerSecond());
for (float f : translation) {
cameraTranslationBuffer.putFloat(f);
}
for (float f : rotation) {
cameraRotationBuffer.putFloat(f);
}
framesDone++;
}
public void finish() throws IOException {
int timeBufferSize = timeBuffer.rewind().remaining();
int cameraTranslationBufferSize = cameraTranslationBuffer.rewind().remaining();
int cameraRotationBufferSize = cameraRotationBuffer.rewind().remaining();
int binaryDataSize = 0;
binaryDataSize += timeBufferSize;
binaryDataSize += cameraTranslationBufferSize;
binaryDataSize += cameraRotationBufferSize;
ByteBuffer binaryData = ByteBuffer.allocate(binaryDataSize);
int timeBufferOffset = binaryData.position();
binaryData.put(timeBuffer);
int cameraTranslationBufferOffset = binaryData.position();
binaryData.put(cameraTranslationBuffer);
int cameraRotationBufferOffset = binaryData.position();
binaryData.put(cameraRotationBuffer);
binaryData.rewind();
GlTF glTF = new GlTF();
glTF.setAsset(configure(new Asset(), asset -> {
asset.setVersion("2.0");
asset.setGenerator("ReplayMod v" + ReplayMod.instance.getVersion());
}));
glTF.addAnimations(configure(new Animation(), animation -> {
animation.addChannels(configure(new AnimationChannel(), channel -> {
channel.setTarget(configure(new AnimationChannelTarget(), target -> {
target.setNode(0);
target.setPath("translation");
}));
channel.setSampler(0);
}));
animation.addChannels(configure(new AnimationChannel(), channel -> {
channel.setTarget(configure(new AnimationChannelTarget(), target -> {
target.setNode(0);
target.setPath("rotation");
}));
channel.setSampler(1);
}));
animation.addSamplers(configure(new AnimationSampler(), sampler -> {
sampler.setInput(0);
sampler.setOutput(1);
}));
animation.addSamplers(configure(new AnimationSampler(), sampler -> {
sampler.setInput(0);
sampler.setOutput(2);
}));
}));
glTF.addCameras(configure(new Camera(), camera -> {
camera.setType("perspective");
camera.setPerspective(configure(new CameraPerspective(), perspective -> {
float aspectRatio = (float) settings.getVideoWidth() / (float) settings.getVideoHeight();
perspective.setAspectRatio(aspectRatio);
perspective.setYfov((float) Math.toRadians(mc.options.fov));
perspective.setZnear(0.05f);
perspective.setZfar((float) mc.options.viewDistance * 16 * 4);
}));
}));
glTF.addNodes(configure(new Node(), node -> node.setCamera(0)));
glTF.addBuffers(configure(new Buffer(), buffer -> buffer.setByteLength(binaryData.limit())));
// Time
glTF.addBufferViews(configure(new BufferView(), bufferView -> {
bufferView.setBuffer(0);
bufferView.setByteOffset(timeBufferOffset);
bufferView.setByteLength(timeBufferSize);
}));
glTF.addAccessors(configure(new Accessor(), accessor -> {
accessor.setBufferView(0);
accessor.setType("SCALAR");
accessor.setComponentType(GL11.GL_FLOAT);
accessor.setCount(framesDone);
}));
// Camera translation
glTF.addBufferViews(configure(new BufferView(), bufferView -> {
bufferView.setBuffer(0);
bufferView.setByteOffset(cameraTranslationBufferOffset);
bufferView.setByteLength(cameraTranslationBufferSize);
}));
glTF.addAccessors(configure(new Accessor(), accessor -> {
accessor.setBufferView(1);
accessor.setType("VEC3");
accessor.setComponentType(GL11.GL_FLOAT);
accessor.setCount(framesDone);
}));
// Camera rotation
glTF.addBufferViews(configure(new BufferView(), bufferView -> {
bufferView.setBuffer(0);
bufferView.setByteOffset(cameraRotationBufferOffset);
bufferView.setByteLength(cameraRotationBufferSize);
}));
glTF.addAccessors(configure(new Accessor(), accessor -> {
accessor.setBufferView(2);
accessor.setType("VEC4");
accessor.setComponentType(GL11.GL_FLOAT);
accessor.setCount(framesDone);
}));
java.nio.file.Path videoPath = settings.getOutputFile().toPath();
java.nio.file.Path glbBasePath = Files.isDirectory(videoPath)
? videoPath.resolve("camera.glb")
: videoPath.resolveSibling(FilenameUtils.getBaseName(videoPath.getFileName().toString()) + ".glb");
java.nio.file.Path glbPath = glbBasePath;
for (int i = 0; Files.exists(glbPath); i++) {
String baseName = FilenameUtils.getBaseName(glbBasePath.getFileName().toString());
glbPath = glbBasePath.resolveSibling(baseName + "." + i + ".glb");
}
try (OutputStream out = Files.newOutputStream(glbPath)) {
GltfAssetV2 asset = new GltfAssetV2(glTF, binaryData);
new GltfAssetWriterV2().writeBinary(asset, out);
}
}
}