Skip to content

Instantly share code, notes, and snippets.

@ljfa-ag
Last active November 1, 2022 14:02
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ljfa-ag/cd137f5c741a0cfb0ead to your computer and use it in GitHub Desktop.
Save ljfa-ag/cd137f5c741a0cfb0ead to your computer and use it in GitHub Desktop.
Script for converting exported Techne models for Minecraft into other model formats
#!/usr/bin/env perl
# Released into Public Domain
# A quick and dirty script to convert ModelBase Java code as generated
# by Techne (or possibly Tabula) into Minecraft's JSON model format.
# It is quite limited (it does not handle rotations, as the JSON model format
# is limited in that regard anyway), but it is at least a help.
# Usage:
# modelbase_to_json input.java > output.json
# Note that Minecraft requires the texure to be square, so you might need to
# resize your texture
# If you instead want to convert to Wavefront OBJ, check out modelbase_to_obj.perl
# in this Gist below.
use strict;
use warnings;
use List::Util qw[max];
my %parts;
while(<>) {
if(/(\w+) = new ModelRenderer\(this, (\d+), (\d+)\);/) {
$parts{$1}{base_uv} = [$2, $3];
$parts{$1}{tex_size} = [64, 32];
}
elsif(/(\w+)\.addBox\((-?\d*\.?\d*)[Ff]?, (-?\d*\.?\d*)[Ff]?, (-?\d*\.?\d*)[Ff]?, (\d+), (\d+), (\d+)(,.*)?\);/) {
$parts{$1}{offset} = [$2, $3, $4];
$parts{$1}{size} = [$5, $6, $7];
}
elsif(/(\w+)\.setRotationPoint\((-?\d*\.?\d*)[Ff]?, (-?\d*\.?\d*)[Ff]?, (-?\d*\.?\d*)[Ff]?\);/) {
$parts{$1}{rot_point} = [$2, $3, $4];
}
elsif(/(\w+)\.setTextureSize\((\d+), (\d+)\);/) {
$parts{$1}{tex_size} = [$2, $3];
}
elsif(/(\w+)\.mirror = (true|false);/) {
#$parts{$1}{mirror} = ($2 eq "true"); #FIXME: This is being ignored
}
elsif(/setRotation\((\w+), (-?\d*\.?\d*)[Ff]?, (-?\d*\.?\d*)[Ff]?, (-?\d*\.?\d*)[Ff]?\)/) {
#$parts{$1}{rotation} = [$2, $3, $4];
#We ignore the rotation
if($2 != 0 || $3 != 0 || $4 != 0) {
print STDERR "Warning: Line $.: Nonzero rotations are not supported\n";
}
}
else {
#print STDERR "Ignoring line $.\n";
}
}
print qq({
"elements": [
);
my $first = 1;
foreach my $part (keys %parts) {
my $offset = $parts{$part}{offset};
my $size = $parts{$part}{size};
my $rot_point = $parts{$part}{rot_point};
#Textures need to be square
my $tex_size = max($parts{$part}{tex_size}->[0], $parts{$part}{tex_size}->[1]);
my $base_uv = $parts{$part}{base_uv};
my $xmin = 8 - $offset->[0] - $size->[0] - $rot_point->[0];
my $ymin = 24 - $offset->[1] - $size->[1] - $rot_point->[1];
my $zmin = 8 + $offset->[2] + $rot_point->[2];
my $xmax = 8 - $offset->[0] - $rot_point->[0];
my $ymax = 24 - $offset->[1] - $rot_point->[1];
my $zmax = 8 + $offset->[2] + $size->[2] + $rot_point->[2];
my @uvmin = (
[$base_uv->[0] + $size->[2] + 2*$size->[0], $base_uv->[1]], #down
[$base_uv->[0] + $size->[2] + $size->[0], $base_uv->[1] + $size->[2]], #up
[$base_uv->[0] + $size->[2], $base_uv->[1] + $size->[2]], #north
[$base_uv->[0] + 2*$size->[2] + $size->[0], $base_uv->[1] + $size->[2]], #south
[$base_uv->[0] + $size->[2] + $size->[0], $base_uv->[1] + $size->[2]], #west
[$base_uv->[0], $base_uv->[1] + $size->[2]]); #east
my @uvmax = (
[$uvmin[0][0] - $size->[0], $uvmin[0][1] + $size->[2]], #down
[$uvmin[1][0] - $size->[0], $uvmin[1][1] - $size->[2]], #up
[$uvmin[2][0] + $size->[0], $uvmin[2][1] + $size->[1]], #north
[$uvmin[3][0] + $size->[0], $uvmin[3][1] + $size->[1]], #south
[$uvmin[4][0] + $size->[2], $uvmin[4][1] + $size->[1]], #west
[$uvmin[5][0] + $size->[2], $uvmin[5][1] + $size->[1]]); #east
#Scale UVs from 0 to 16 with respect to texture size
foreach my $f (@uvmin) {
foreach (@$f) {
$_ = $_ / $tex_size * 16;
}
}
foreach my $f (@uvmax) {
foreach (@$f) {
$_ = $_ / $tex_size * 16;
}
}
print ",\n" unless $first;
$first = 0;
print
qq( {
"__comment": "$part",
"from": [$xmin, $ymin, $zmin],
"to": [$xmax, $ymax, $zmax],
"faces": {
"down": {"uv": [$uvmin[0][0], $uvmin[0][1], $uvmax[0][0], $uvmax[0][1]], "texture": "#all"},
"up": {"uv": [$uvmin[1][0], $uvmin[1][1], $uvmax[1][0], $uvmax[1][1]], "texture": "#all"},
"north": {"uv": [$uvmin[2][0], $uvmin[2][1], $uvmax[2][0], $uvmax[2][1]], "texture": "#all"},
"south": {"uv": [$uvmin[3][0], $uvmin[3][1], $uvmax[3][0], $uvmax[3][1]], "texture": "#all"},
"west": {"uv": [$uvmin[4][0], $uvmin[4][1], $uvmax[4][0], $uvmax[4][1]], "texture": "#all"},
"east": {"uv": [$uvmin[5][0], $uvmin[5][1], $uvmax[5][0], $uvmax[5][1]], "texture": "#all"}
}
});
}
print qq(
],
"textures": {
"all": "...",
"particle": "..."
}
}
);
#!/usr/bin/env perl
# Released into Public Domain
# A quick and dirty script to convert ModelBase Java code as generated
# by Techne (or maybe Tabula) into Wavefront OBJ.
# It is limited, but it is at least a help.
# Usage:
# modelbase_to_obj input.java > output.obj
# Note that you need to create an MTL file in order to see the textures.
# By default, the model refers to "model.mtl". You might need to adapt
# the first few lines of the generated OBJ file.
# This is how an MTL file might look like:
#
# newmtl model
# map_Ka modid:texture
# map_Kd modid:texture
# Note that Minecraft requires the texure to be square, so you might need to
# resize your texture.
use strict;
use warnings;
use experimental qw[signatures];
use List::Util qw[max];
use Math::Trig;
use Math::BLAS;
my %parts;
while(<>) {
if(/(\w+) = new ModelRenderer\(this, (\d+), (\d+)\);/) {
$parts{$1}{base_uv} = [$2, $3];
$parts{$1}{tex_size} = [64, 32];
}
elsif(/(\w+)\.addBox\((-?\d*\.?\d*)[Ff]?, (-?\d*\.?\d*)[Ff]?, (-?\d*\.?\d*)[Ff]?, (\d+), (\d+), (\d+)(,.*)?\);/) {
$parts{$1}{offset} = [$2, $3, $4];
$parts{$1}{size} = [$5, $6, $7];
$parts{$1}{rotation} = [0, 0, 0];
}
elsif(/(\w+)\.setRotationPoint\((-?\d*\.?\d*)[Ff]?, (-?\d*\.?\d*)[Ff]?, (-?\d*\.?\d*)[Ff]?\);/) {
$parts{$1}{rot_point} = [$2, $3, $4];
}
elsif(/(\w+)\.setTextureSize\((\d+), (\d+)\);/) {
$parts{$1}{tex_size} = [$2, $3];
}
elsif(/(\w+)\.mirror = (true|false);/) {
#$parts{$1}{mirror} = ($2 eq "true"); #FIXME: This is being ignored
}
elsif(/setRotation\((\w+), (-?\d*\.?\d*)[Ff]?, (-?\d*\.?\d*)[Ff]?, (-?\d*\.?\d*)[Ff]?\)/) {
$parts{$1}{rotation} = [$2, $3, $4];
}
else {
#print STDERR "Ignoring line $.\n";
}
}
my $vi = 0; #Vertex index
my $ti = 0; #UV index
print "# Generated by modelbase_to_obj
mtllib model.mtl
usemtl model\n\n";
foreach my $part (keys %parts) {
my $offset = $parts{$part}{offset};
my $size = $parts{$part}{size};
my $rot_point = $parts{$part}{rot_point};
my $rot = $parts{$part}{rotation};
#Textures need to be square
my $tex_size = max($parts{$part}{tex_size}->[0], $parts{$part}{tex_size}->[1]);
my $base_uv = $parts{$part}{base_uv};
#Vertices relative to the offset
my @verts = (
[0, 0, 0],
[$size->[0], 0, 0],
[$size->[0], $size->[1], 0],
[0, $size->[1], 0],
[0, 0, $size->[2]],
[$size->[0], 0, $size->[2]],
[$size->[0], $size->[1], $size->[2]],
[0, $size->[1], $size->[2]]);
#Transform vertices
my @rot_mat = rot_matrix(@$rot);
foreach (@verts) {
blas_axpby(3, $offset, $_);
my @tmp;
blas_gemv(3, 3, \@rot_mat, $_, \@tmp);
blas_waxpby(3, $rot_point, \@tmp, $_);
$_ = [(8 - $_->[0])/16, (24 - $_->[1])/16, (8 + $_->[2])/16]; #Rotate around the z-axis by 180°.
#Strange but that's how it works.
}
#The texture sheet looks like this:
# 1 2 3
# U D
# 4 5 6
#
# 7 4 5 8 9
# E N W S
# 10 11 12 13 14
my @uvs = (
[ $size->[2], 0],
[ $size->[2] + $size->[0], 0],
[ $size->[2] + 2*$size->[0], 0],
[ $size->[2], $size->[2]],
[ $size->[2] + $size->[0], $size->[2]],
[ $size->[2] + 2*$size->[0], $size->[2]],
[0, $size->[2]],
[2*$size->[2] + $size->[0], $size->[2]],
[2*$size->[2] + 2*$size->[0], $size->[2]],
[0, $size->[2] + $size->[1]],
[ $size->[2], $size->[2] + $size->[1]],
[ $size->[2] + $size->[0], $size->[2] + $size->[1]],
[2*$size->[2] + $size->[0], $size->[2] + $size->[1]],
[2*$size->[2] + 2*$size->[0], $size->[2] + $size->[1]]);
foreach (@uvs) {
$_->[0] += $base_uv->[0];
$_->[1] += $base_uv->[1];
$_->[0] /= $tex_size;
$_->[1] /= $tex_size;
}
#What a mess of indices. Here we go.
my @faces = (
[$vi+3, $ti+6, $vi+4, $ti+5, $vi+8, $ti+2, $vi+7, $ti+3], #down
[$vi+6, $ti+2, $vi+5, $ti+1, $vi+1, $ti+4, $vi+2, $ti+5], #up
[$vi+2, $ti+5, $vi+1, $ti+4, $vi+4, $ti+11, $vi+3, $ti+12], #north
[$vi+5, $ti+9, $vi+6, $ti+8, $vi+7, $ti+13, $vi+8, $ti+14], #south
[$vi+6, $ti+8, $vi+2, $ti+5, $vi+3, $ti+12, $vi+7, $ti+13], #west
[$vi+1, $ti+4, $vi+5, $ti+7, $vi+8, $ti+10, $vi+4, $ti+11]);#east
print "g $part\n";
foreach (@verts) {
print "v $_->[0] $_->[1] $_->[2]\n";
}
foreach (@uvs) {
print "vt $_->[0] $_->[1]\n";
}
foreach (@faces) {
print "f $_->[0]/$_->[1] $_->[2]/$_->[3] $_->[4]/$_->[5] $_->[6]/$_->[7]\n";
}
print "\n";
$vi += 8;
$ti += 14;
}
#Creates a rotation matrix from the specified Euler angles
sub rot_matrix($x, $y, $z) {
my @xrot = (
1, 0, 0,
0, cos($x), -sin($x),
0, sin($x), cos($x));
my @yrot = (
cos($y), 0, sin($y),
0, 1, 0,
-sin($y), 0, cos($y));
my @zrot = (
cos($z), -sin($z), 0,
sin($z), cos($z), 0,
0, 0, 1);
my @tmp;
blas_gemm(3, 3, 3, \@zrot, \@yrot, \@tmp);
my @matrix;
blas_gemm(3, 3, 3, \@tmp, \@xrot, \@matrix);
return @matrix;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment