package openmods.geometry; import com.google.common.base.Preconditions; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.Vec3; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; public abstract class BlockSpaceTransform { // Ok, so it's needlessly complicated, but I was trying to relax after trying to remember how to build transformation matrices from my linear algebra course private static class BytecodeClassLoader extends ClassLoader { private BytecodeClassLoader() { super(BytecodeClassLoader.class.getClassLoader()); } public Class<?> define(byte[] data) { return defineClass(null, data, 0, data.length); } } private static void createVarAccess(MethodVisitor mv, double value, int var) { if (value == 1) { mv.visitVarInsn(Opcodes.DLOAD, var); } else if (value == -1) { mv.visitInsn(Opcodes.DCONST_1); mv.visitVarInsn(Opcodes.DLOAD, var); mv.visitInsn(Opcodes.DSUB); } else { throw new IllegalArgumentException(); } } // there should be exactly one non-zero value in row private static void createGetLine(MethodVisitor mv, double x, double y, double z) { if (x != 0) { Preconditions.checkArgument(y == 0 && z == 0); createVarAccess(mv, x, 2); } else if (y != 0) { Preconditions.checkArgument(x == 0 && z == 0); createVarAccess(mv, y, 4); } else if (z != 0) { Preconditions.checkArgument(x == 0 && y == 0); createVarAccess(mv, z, 6); } else { throw new IllegalArgumentException(); } } private static void createTransformMethod(MethodVisitor mv, boolean invert) { // 0 - this (unused) // 1 - orientation // 2,3 - x // 4,5 - y // 6,7 - z mv.visitCode(); mv.visitVarInsn(Opcodes.ALOAD, 1); final String enumType = Type.getInternalName(Enum.class); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, enumType, "ordinal", Type.getMethodDescriptor(Type.INT_TYPE), false); final Orientation[] orientations = Orientation.values(); final Label defaultLabel = new Label(); final Label[] targets = new Label[orientations.length]; for (int i = 0; i < orientations.length; i++) targets[i] = new Label(); mv.visitTableSwitchInsn(0, orientations.length - 1, defaultLabel, targets); { mv.visitLabel(defaultLabel); final String excType = Type.getInternalName(IllegalArgumentException.class); final Type stringType = Type.getType(String.class); mv.visitTypeInsn(Opcodes.NEW, excType); mv.visitInsn(Opcodes.DUP); mv.visitVarInsn(Opcodes.ALOAD, 1); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Object.class), "toString", Type.getMethodDescriptor(stringType), false); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, excType, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, stringType), false); mv.visitInsn(Opcodes.ATHROW); } { final String createVectorDesc = Type.getMethodDescriptor(Type.getType(Vec3.class), Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE); final String selfType = Type.getInternalName(BlockSpaceTransform.class); for (int i = 0; i < orientations.length; i++) { mv.visitLabel(targets[i]); final Orientation orientation = orientations[i]; final Matrix3d mat = orientation.createTransformMatrix(); if (invert) mat.invertInplace(); createGetLine(mv, mat.m00, mat.m10, mat.m20); createGetLine(mv, mat.m01, mat.m11, mat.m21); createGetLine(mv, mat.m02, mat.m12, mat.m22); mv.visitMethodInsn(Opcodes.INVOKESTATIC, selfType, "createVector", createVectorDesc, false); mv.visitInsn(Opcodes.ARETURN); } } mv.visitMaxs(0, 0); mv.visitEnd(); } private static Class<? extends BlockSpaceTransform> createTransformClass() { final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); final String parentCls = Type.getInternalName(BlockSpaceTransform.class); final String name = parentCls + "$GeneratedImplementation$"; cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_SYNTHETIC, name, null, parentCls, new String[] {}); cw.visitSource(".dynamic", null); { MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, parentCls, "<init>", "()V", false); mv.visitInsn(Opcodes.RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } final String transformMethod = Type.getMethodDescriptor(Type.getType(Vec3.class), Type.getType(Orientation.class), Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE); { final MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "mapWorldToBlock", transformMethod, null, null); createTransformMethod(mv, true); } { final MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "mapBlockToWorld", transformMethod, null, null); createTransformMethod(mv, false); } cw.visitEnd(); final byte[] clsBytes = cw.toByteArray(); final BytecodeClassLoader loader = new BytecodeClassLoader(); @SuppressWarnings("unchecked") final Class<? extends BlockSpaceTransform> cls = (Class<? extends BlockSpaceTransform>)loader.define(clsBytes); return cls; } private static BlockSpaceTransform createTransformInstance() { try { final Class<? extends BlockSpaceTransform> transformClass = createTransformClass(); return transformClass.newInstance(); } catch (Throwable t) { throw new Error("Failed to create block space transformer", t); } } public static final BlockSpaceTransform instance = createTransformInstance(); public abstract Vec3 mapWorldToBlock(Orientation orientation, double x, double y, double z); public abstract Vec3 mapBlockToWorld(Orientation orientation, double x, double y, double z); // wrapper over obfuscated method protected static Vec3 createVector(double x, double y, double z) { return Vec3.createVectorHelper(x, y, z); } public AxisAlignedBB mapWorldToBlock(Orientation orientation, AxisAlignedBB aabb) { final Vec3 min = mapWorldToBlock(orientation, aabb.minX, aabb.minY, aabb.minZ); final Vec3 max = mapWorldToBlock(orientation, aabb.maxX, aabb.maxY, aabb.maxZ); return AabbUtils.createAabb(min.xCoord, min.yCoord, min.zCoord, max.xCoord, max.yCoord, max.zCoord); } public AxisAlignedBB mapBlockToWorld(Orientation orientation, AxisAlignedBB aabb) { final Vec3 min = mapBlockToWorld(orientation, aabb.minX, aabb.minY, aabb.minZ); final Vec3 max = mapBlockToWorld(orientation, aabb.maxX, aabb.maxY, aabb.maxZ); return AabbUtils.createAabb(min.xCoord, min.yCoord, min.zCoord, max.xCoord, max.yCoord, max.zCoord); } }