dynmap/DynmapCore/src/main/java/org/dynmap/utils/PatchDefinition.java

403 lines
16 KiB
Java

package org.dynmap.utils;
import org.dynmap.Log;
import org.dynmap.modsupport.BlockSide;
import org.dynmap.renderer.RenderPatch;
import org.dynmap.renderer.RenderPatchFactory.SideVisible;
//
// (v = 1) umin umax
// x0+xv,y0+yv,z0+zv *-----|-------|--------* (u=1, v=1) x0,y0,z0 = lower left corner relative to cube origin (0,0,0 to 1,1,1)
// | | | | length of xu,yu,zu = width of whole texture (u=0 to u=1)
// |-----+=======+--------| vmax length of xv,yv,zv = height of whole texture (v=0 to v=1)
// | [visible] | umin to umax = clipping (visible portion of texture) horizontally
// |-----+=======+--------| vmin vmin to vmax = clipping (visible portion of texture) vertically
// (u=0,v=0)| | | |
// x0,y0,z0 *----|-------|--------* x0+xu, y0+yu, z0+zu (u = 1)
//
/* Define patch in surface-based models - origin (xyz), u-vector (xyz) v-vector (xyz), u limits and v limits */
public class PatchDefinition implements RenderPatch {
public double x0, y0, z0; /* Origin of patch (lower left corner of texture) */
public double xu, yu, zu; /* Coordinates of end of U vector (relative to origin) - corresponds to u=1.0 (lower right corner) */
public double xv, yv, zv; /* Coordinates of end of V vector (relative to origin) - corresponds to v=1.0 (upper left corner) */
public double umin, umax; /* Limits of patch - minimum and maximum u value */
public double vmin, vmax; /* Limits of patch - minimum and maximum v value */
public double vmaxatumax; /* Limits of patch - max v value at max u (allows triangle or trapezoid) */
public double vminatumax; /* Limits of patch - min v value at max u (allows triangle or trapezoid) */
public Vector3D u, v; /* U and V vector, relative to origin */
public SideVisible sidevis; /* Which side is visible */
public int textureindex;
public BlockStep step; /* Best approximation of orientation of surface, from top (positive determinent) */
private int hc;
/* Offset vector of middle of block */
private static final Vector3D offsetCenter = new Vector3D(0.5,0.5,0.5);
PatchDefinition() {
x0 = y0 = z0 = 0.0;
xu = zu = 0.0; yu = 1.0;
yv = zv = 0.0; xv = 1.0;
umin = vmin = 0.0;
umax = vmax = 1.0;
vmaxatumax = 1.0;
vminatumax = 0.0;
u = new Vector3D();
v = new Vector3D();
sidevis = SideVisible.BOTH;
textureindex = 0;
update();
}
PatchDefinition(PatchDefinition pd) {
this.x0 = pd.x0;
this.y0 = pd.y0;
this.z0 = pd.z0;
this.xu = pd.xu;
this.yu = pd.yu;
this.zu = pd.zu;
this.xv = pd.xv;
this.yv = pd.yv;
this.zv = pd.zv;
this.umin = pd.umin;
this.vmin = pd.vmin;
this.umax = pd.umax;
this.vmax = pd.vmax;
this.vmaxatumax = pd.vmaxatumax;
this.vminatumax = pd.vminatumax;
this.u = new Vector3D(pd.u);
this.v = new Vector3D(pd.v);
this.sidevis = pd.sidevis;
this.textureindex = pd.textureindex;
this.step = pd.step;
this.hc = pd.hc;
}
/**
* Construct patch, based on rotation of existing patch clockwise by N
* 90 degree steps
* @param orig - original patch to copy and rotate
* @param rotatex - x rotation in degrees
* @param rotatey - y rotation in degrees
* @param rotatez - z rotation in degrees
* @param textureindex - texture index for new patch (-1 = use same as original patch)
*/
PatchDefinition(PatchDefinition orig, int rotatex, int rotatey, int rotatez, int textureindex) {
Vector3D vec = new Vector3D(orig.x0, orig.y0, orig.z0);
rotate(vec, rotatex, rotatey, rotatez); /* Rotate origin */
x0 = vec.x; y0 = vec.y; z0 = vec.z;
/* Rotate U */
vec.x = orig.xu; vec.y = orig.yu; vec.z = orig.zu;
rotate(vec, rotatex, rotatey, rotatez); /* Rotate origin */
xu = vec.x; yu = vec.y; zu = vec.z;
/* Rotate V */
vec.x = orig.xv; vec.y = orig.yv; vec.z = orig.zv;
rotate(vec, rotatex, rotatey, rotatez); /* Rotate origin */
xv = vec.x; yv = vec.y; zv = vec.z;
umin = orig.umin; vmin = orig.vmin;
umax = orig.umax; vmax = orig.vmax;
vmaxatumax = orig.vmaxatumax;
vminatumax = orig.vminatumax;
sidevis = orig.sidevis;
this.textureindex = (textureindex < 0) ? orig.textureindex : textureindex;
u = new Vector3D();
v = new Vector3D();
update();
}
private void rotate(Vector3D vec, int xcnt, int ycnt, int zcnt) {
vec.subtract(offsetCenter); /* Shoft to center of block */
/* Do X rotation */
double rot = Math.toRadians(xcnt);
double nval = vec.z * Math.sin(rot) + vec.y * Math.cos(rot);
vec.z = vec.z * Math.cos(rot) - vec.y * Math.sin(rot);
vec.y = nval;
/* Do Y rotation */
rot = Math.toRadians(ycnt);
nval = vec.x * Math.cos(rot) - vec.z * Math.sin(rot);
vec.z = vec.x * Math.sin(rot) + vec.z * Math.cos(rot);
vec.x = nval;
/* Do Z rotation */
rot = Math.toRadians(zcnt);
nval = vec.y * Math.sin(rot) + vec.x * Math.cos(rot);
vec.y = vec.y * Math.cos(rot) - vec.x * Math.sin(rot);
vec.x = nval;
vec.add(offsetCenter); /* Shoft back to corner */
}
public void update(double x0, double y0, double z0, double xu,
double yu, double zu, double xv, double yv, double zv, double umin,
double umax, double vmin, double vmax, SideVisible sidevis,
int textureids, double vminatumax, double vmaxatumax) {
this.x0 = x0;
this.y0 = y0;
this.z0 = z0;
this.xu = xu;
this.yu = yu;
this.zu = zu;
this.xv = xv;
this.yv = yv;
this.zv = zv;
this.umin = umin;
this.umax = umax;
this.vmin = vmin;
this.vmax = vmax;
this.vmaxatumax = vmaxatumax;
this.vminatumax = vminatumax;
this.sidevis = sidevis;
this.textureindex = textureids;
update();
}
public void update() {
u.x = xu - x0; u.y = yu - y0; u.z = zu - z0;
v.x = xv - x0; v.y = yv - y0; v.z = zv - z0;
/* Compute hash code */
hc = (int)((Double.doubleToLongBits(x0 + xu + xv) >> 32) ^
(Double.doubleToLongBits(y0 + yu + yv) >> 34) ^
(Double.doubleToLongBits(z0 + yu + yv) >> 36) ^
(Double.doubleToLongBits(umin + umax + vmin + vmax + vmaxatumax) >> 38)) ^
(sidevis.ordinal() << 8) ^ textureindex;
/* Now compute normal of surface - U cross V */
double crossx = (u.y*v.z) - (u.z*v.y);
double crossy = (u.z*v.x) - (u.x*v.z);
double crossz = (u.x*v.y) - (u.y*v.x);
/* Now, find the largest component of the normal (dominant direction) */
if(Math.abs(crossx) > (Math.abs(crossy)*0.9)) { /* If X > 0.9Y */
if(Math.abs(crossx) > Math.abs(crossz)) { /* If X > Z */
if(crossx > 0) {
step = BlockStep.X_PLUS;
}
else {
step = BlockStep.X_MINUS;
}
}
else { /* Else Z >= X */
if(crossz > 0) {
step = BlockStep.Z_PLUS;
}
else {
step = BlockStep.Z_MINUS;
}
}
}
else { /* Else Y >= X */
if((Math.abs(crossy)*0.9) > Math.abs(crossz)) { /* If 0.9Y > Z */
if(crossy > 0) {
step = BlockStep.Y_PLUS;
}
else {
step = BlockStep.Y_MINUS;
}
}
else { /* Else Z >= Y */
if(crossz > 0) {
step = BlockStep.Z_PLUS;
}
else {
step = BlockStep.Z_MINUS;
}
}
}
}
public boolean validate() {
boolean good = true;
if((x0 < -1.0) || (x0 > 2.0)) {
Log.severe("Invalid x0=" + x0);
good = false;
}
if((y0 < -1.0) || (y0 > 2.0)) {
Log.severe("Invalid y0=" + y0);
good = false;
}
if((z0 < -1.0) || (z0 > 2.0)) {
Log.severe("Invalid z0=" + z0);
good = false;
}
if((xu < -1.0) || (xu > 2.0)) {
Log.severe("Invalid xu=" + xu);
good = false;
}
if((yu < -1.0) || (yu > 2.0)) {
Log.severe("Invalid yu=" + yu);
good = false;
}
if((zu < -1.0) || (zu > 2.0)) {
Log.severe("Invalid zu=" + zu);
good = false;
}
if((xv < -1.0) || (xv > 2.0)) {
Log.severe("Invalid xv=" + xv);
good = false;
}
if((yv < -1.0) || (yv > 2.0)) {
Log.severe("Invalid yv=" + yv);
good = false;
}
if((zv < -1.0) || (zv > 2.0)) {
Log.severe("Invalid zv=" + zv);
good = false;
}
if((umin < 0.0) || (umin > umax)) {
Log.severe("Invalid umin=" + umin);
good = false;
}
if((vmin < 0.0) || (vmin > vmax)) {
Log.severe("Invalid vmin=" + vmin);
good = false;
}
if(umax > 1.0) {
Log.severe("Invalid umax=" + umax);
good = false;
}
if(vmax > 1.0) {
Log.severe("Invalid vmax=" + vmax);
good = false;
}
if ((vminatumax < 0.0) || (vminatumax > vmaxatumax)) {
Log.severe("Invalid vminatumax=" + vminatumax);
good = false;
}
if(vmaxatumax > 1.0) {
Log.severe("Invalid vmaxatumax=" + vmaxatumax);
good = false;
}
return good;
}
@Override
public boolean equals(Object o) {
if(o == this)
return true;
if(o instanceof PatchDefinition) {
PatchDefinition p = (PatchDefinition)o;
if((hc == p.hc) && (textureindex == p.textureindex) &&
(x0 == p.x0) && (y0 == p.y0) && (z0 == p.z0) &&
(xu == p.xu) && (yu == p.yu) && (zu == p.zu) &&
(xv == p.xv) && (yv == p.yv) && (zv == p.zv) &&
(umin == p.umin) && (umax == p.umax) &&
(vmin == p.vmin) && (vmax == p.vmax) &&
(vmaxatumax == p.vmaxatumax) &&
(vminatumax == p.vminatumax) && (sidevis == p.sidevis)) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return hc;
}
@Override
public int getTextureIndex() {
return textureindex;
}
@Override
public String toString() {
return String.format("xyz0=%f/%f/%f,xyzU=%f/%f/%f,xyzV=%f/%f/%f,minU=%f,maxU=%f,vMin=%f/%f,vmax=%f/%f,side=%s,txtidx=%d",
x0, y0, z0, xu, yu, zu, xv, yv, zv, umin, umax, vmin, vminatumax, vmax, vmaxatumax, sidevis, textureindex);
}
//
// Update patch relative to typical parameters found in
// minecraft model files. Specifically, all coordinates are relative to 0-16 range for
// side of a cube, and relative to 0-16 range for U,V within a texture:
//
// from, to in model drive 'from', 'to' inputs
// face, uv of face, and texture in model drives face, uv, textureid
//
// @param from - vector of lower left corner of box (0-16 range for coordinates - min x, y, z)
// @param to - vector of upper right corner of box (0-16 range for coordinates max x, y, z)
// @param face - which face (determines use of xyz-min vs xyz-max
// @param uv - bounds on UV (umin, vmin, umax, vmax): if undefined, default based on face range (minecraft UV is relative to top left corner of texture)
// @param textureid - texture ID
public void updateModelFace(double[] from, double[] to, BlockSide face, double[] uv, int textureid) {
// Based on face, figure out coordinates of face corner (lower left for x0, y0, z0 - lower right for xu, yu, zy - top left for xv, yv, zv)
double x0 = 0, xu = 1, xv = 0, y0 = 0, yu = 0, yv = 1, z0 = 0, zu = 0, zv = 0;
double umin = 0, vmin = 0, umax = 1, vmax = 1;
switch (face) {
case BOTTOM:
case FACE_0:
case Y_MINUS:
// Bottom - Y-negative (top towards south (+Z), right towards east (+x))
x0 = xv = from[0] / 16.0; xu = to[0] / 16.0;
y0 = yu = yv = from[1] / 16.0; // Bottom
z0 = zu = from[2] / 16.0; zv = to[2] / 16.0;
umin = x0; umax = xu;
vmin = z0; vmax = zv;
break;
case TOP:
case FACE_1:
case Y_PLUS:
// Top - Y-positive (top towards north (-Z), right towards east (+x))
x0 = xv = from[0] / 16.0; xu = to[0] / 16.0;
y0 = yu = yv = to[1] / 16.0; // Top
z0 = zu = to[2] / 16.0; zv = from[2] / 16.0;
umin = x0; umax = xu;
vmin = 1 - z0; vmax = 1 - zv;
break;
case NORTH:
case FACE_2:
case Z_MINUS:
// North - Z-negative (top towards up (+Y), right towards west (-X))
x0 = xv = to[0] / 16.0; xu = from[0] / 16.0;
y0 = yu = from[1] / 16.0; yv = to[1] / 16.0;
z0 = zu = zv = from[2] / 16.0;
umin = 1 - x0; umax = 1 - xu;
vmin = y0; vmax = yv;
break;
case SOUTH:
case FACE_3:
case Z_PLUS:
// South - Z-positive (top towards up (+Y), right towards east (+X))
x0 = xv = from[0] / 16.0; xu = to[0] / 16.0;
y0 = yu = from[1] / 16.0; yv = to[1] / 16.0;
z0 = zu = zv = to[2] / 16.0;
umin = x0; umax = xu;
vmin = y0; vmax = yv;
break;
case WEST:
case FACE_4:
case X_MINUS:
// West - X-negative (top towards up (+Y), right towards south (+Z))
x0 = xu = xv = from[0] / 16.0;
y0 = yu = from[1] / 16.0; yv = to[1] / 16.0;
z0 = zv = from[2] / 16.0; zu = to[2] / 16.0;
umin = z0; umax = zu;
vmin = y0; vmax = yv;
break;
case EAST:
case FACE_5:
case X_PLUS:
// East - X-positive (top towards up (+Y), right towards north (-Z))
x0 = xu = xv = to[0] / 16.0;
y0 = yu = from[1] / 16.0; yv = to[1] / 16.0;
z0 = zv = to[2] / 16.0; zu = from[2] / 16.0;
umin = 1 - z0; umax = 1 - zu;
vmin = y0; vmax = yv;
break;
default:
Log.severe("Invalid side: " + face);
return;
}
// If uv provided, use it to override
if ((uv != null) && (uv.length == 4)) {
umin = uv[0] / 16.0;
vmin = 1 - (uv[3] / 16.0); // MC V is inverted from our V
umax = uv[2] / 16.0;
vmax = 1 - (uv[1] / 16.0); // MC V is inverted from our V
}
// Compute texture origin for u,y = 0,0, based on coordinates
// x0,y0,z0 = u=umin,v=vmin; xu,yu,zu = u=umax,v=vmin; xv,yv,zv = u=umin,v=vmax
// Compute U vector (based on proportion of umax-umin versus U offset
double uvectx = (xu - x0) / (umax - umin);
double uvecty = (yu - y0) / (umax - umin);
double uvectz = (zu - z0) / (umax - umin);
// Compute V vector (based on proportion of vmax-vmin versus V offset
double vvectx = (xv - x0) / (vmax - vmin);
double vvecty = (yv - y0) / (vmax - vmin);
double vvectz = (zv - z0) / (vmax - vmin);
// Compute origin based on U vector and umin and V vector and vmin vs x0,y0,z0
double ovectx = x0 - (uvectx * umin) - (vvectx * vmin);
double ovecty = y0 - (uvecty * umin) - (vvecty * vmin);
double ovectz = z0 - (uvectz * umin) - (vvectz * vmin);
update(ovectx, ovecty, ovectz, uvectx + ovectx, uvecty + ovecty, uvectz + ovectz, vvectx + ovectx, vvecty + ovecty, vvectz + ovectz,
umin, umax, vmin, vmax, SideVisible.TOP, textureid, vmin, vmax);
}
}