package com.akjava.gwt.three.client.java.animation; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import com.akjava.gwt.lib.client.LogUtils; import com.akjava.gwt.three.client.gwt.boneanimation.AnimationBone; import com.akjava.gwt.three.client.gwt.boneanimation.NameAndVector3; import com.akjava.gwt.three.client.js.THREE; import com.akjava.gwt.three.client.js.core.Geometry; import com.akjava.gwt.three.client.js.math.Vector3; import com.akjava.gwt.three.client.js.math.Vector4; import com.google.common.base.Functions; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Ordering; import com.google.gwt.core.client.JsArray; import com.google.gwt.user.client.Window; /** * creating 2-bone indices and weight from geometry and bone position. * some algorithm is totally experimental. * @author aki * */ public class WeightBuilder { public static final int MODE_NearSingleBone=0; public static final int MODE_NearSpecial=1; public static final int MODE_NearAgressive=2; public static final int MODE_NearParentAndChildren=3; public static final int MODE_NearParentAndChildrenAgressive=5; public static final int MODE_MODE_Start_And_Half_ParentAndChildrenAgressive=6; public static final int MODE_FROM_GEOMETRY=4; //use geometry own public static final int MODE_ROOT_ALL=7; public static final String KEY_HALF="_half_"; public static final String KEY_ENDSITE="_ENDSITE_"; public static final String KEY_END="_end_"; public static final String KEY_START="_start_"; public static Vector4 findNearSingleBone(List<NameAndVector3> nameAndPositions,Vector3 pos,JsArray<AnimationBone> bones){ Vector3 pt=nameAndPositions.get(0).getVector3().clone(); Vector3 near=pt.sub(pos); int index1=nameAndPositions.get(0).getIndex(); double near1=pt.length(); for(int i=1;i<nameAndPositions.size();i++){ Vector3 npt=nameAndPositions.get(i).getVector3().clone(); near=npt.sub(pos); double l=near.length(); if(l<near1){ index1=nameAndPositions.get(i).getIndex(); near1=l; } } return THREE.Vector4(index1,index1,1,0); } public static Vector4 findNearBoneParentAndChildren(List<NameAndVector3> nameAndPositions,Vector3 pos,JsArray<AnimationBone> bones,int xloop){ Vector3 pt=nameAndPositions.get(0).getVector3().clone(); Vector3 near=pt.sub(pos); int index1=nameAndPositions.get(0).getIndex(); double near1=pt.length(); int index2=nameAndPositions.get(0).getIndex(); double near2=pt.length(); //First Bone must real bone. for(int i=1;i<nameAndPositions.size();i++){ if(nameAndPositions.get(i).getName().startsWith(KEY_HALF)){ continue; } Vector3 npt=nameAndPositions.get(i).getVector3().clone(); near=npt.sub(pos); double l=near.length(); if(l<near1){ int tmp=index1; double tmpL=near1; index1=nameAndPositions.get(i).getIndex(); near1=l; if(tmpL<near2){ index2=tmp; near2=tmpL; } }else if(l<near2){ index2=nameAndPositions.get(i).getIndex(); near2=l; } } int indexHalf=-1; double nearHalf=0; for(int i=1;i<nameAndPositions.size();i++){ if(!nameAndPositions.get(i).getName().startsWith(KEY_HALF)){ continue; } Vector3 npt=nameAndPositions.get(i).getVector3().clone(); near=npt.sub(pos); double l=near.length(); if(l<near1){ indexHalf=nameAndPositions.get(i).getIndex(); nearHalf=l; }else if(l<near2){ indexHalf=nameAndPositions.get(i).getIndex(); nearHalf=l; } } if(indexHalf!=-1){ if(bones.get(index1).getParent()==indexHalf || bones.get(indexHalf).getParent()==index1 ||indexHalf==index1){ index2=indexHalf; near2=nearHalf; } } //only child & parent /* * TODO future support * */ //near1*=near1*near1*near1; //near2*=near2*near2*near2; for(int i=0;i<xloop;i++){ near1*=near1; near2*=near2; } if(index1==index2){ return THREE.Vector4(index1,index1,1,0); }else{ double total=near1+near2; return THREE.Vector4(index1,index2,(total-near1)/total,(total-near2)/total); } } public static Vector4 findNearBoneStartAndHalfParentAndChildren(List<NameAndVector3> nameAndPositions,Vector3 pos,JsArray<AnimationBone> bones,int xloop){ //LogUtils.log("findNearBoneStartAndHalfParentAndChildren"); Vector3 pt=nameAndPositions.get(0).getVector3().clone(); Vector3 near=pt.sub(pos); int index1=nameAndPositions.get(0).getIndex(); double near1=pt.length(); int index2=nameAndPositions.get(0).getIndex(); double near2=pt.length(); //First Bone must real bone. for(int i=1;i<nameAndPositions.size();i++){ if(nameAndPositions.get(i).getName().startsWith(KEY_END)){ continue; } Vector3 npt=nameAndPositions.get(i).getVector3().clone(); near=npt.sub(pos); double l=near.length(); if(l<near1){ int tmp=index1; double tmpL=near1; index1=nameAndPositions.get(i).getIndex(); near1=l; if(tmpL<near2){ index2=tmp; near2=tmpL; } }else if(l<near2){ index2=nameAndPositions.get(i).getIndex(); near2=l; } } /* int indexHalf=-1; double nearHalf=0; for(int i=1;i<nameAndPositions.size();i++){ if(nameAndPositions.get(i).getName().startsWith(KEY_END)){ continue; } Vector3 npt=nameAndPositions.get(i).getVector3().clone(); near=npt.sub(pos); double l=near.length(); if(l<near1){ indexHalf=nameAndPositions.get(i).getIndex(); nearHalf=l; }else if(l<near2){ indexHalf=nameAndPositions.get(i).getIndex(); nearHalf=l; } } if(indexHalf!=-1){ if(bones.get(index1).getParent()==indexHalf || bones.get(indexHalf).getParent()==index1 ||indexHalf==index1){ index2=indexHalf; near2=nearHalf; } } */ //only child & parent /* * TODO future support * */ //near1*=near1*near1*near1; //near2*=near2*near2*near2; for(int i=0;i<xloop;i++){ near1*=near1; near2*=near2; } if(index1==index2){ return THREE.Vector4(index1,index1,1,0); }else{ double total=near1+near2; return THREE.Vector4(index1,index2,(total-near1)/total,(total-near2)/total); } } public static Vector4 findNearBoneAggresive(List<NameAndVector3> nameAndPositions,Vector3 pos,JsArray<AnimationBone> bones){ Vector3 pt=nameAndPositions.get(0).getVector3().clone(); Vector3 near=pt.sub(pos); int index1=nameAndPositions.get(0).getIndex(); double near1=pt.length(); int index2=nameAndPositions.get(0).getIndex(); double near2=pt.length(); for(int i=1;i<nameAndPositions.size();i++){ Vector3 npt=nameAndPositions.get(i).getVector3().clone(); near=npt.sub(pos); double l=near.length(); if(l<near1){ int tmp=index1; double tmpL=near1; index1=nameAndPositions.get(i).getIndex(); near1=l; if(tmpL<near2){ index2=tmp; near2=tmpL; } }else if(l<near2){ index2=nameAndPositions.get(i).getIndex(); near2=l; } } near1*=near1*near1*near1; near2*=near2*near2*near2; //only child & parent /* * TODO future support * if(bones.get(index1).getParent()!=index2 && bones.get(index2).getParent()!=index1){ index2=index1; } */ if(index1==index2){ return THREE.Vector4(index1,index1,1,0); }else{ double total=near1+near2; return THREE.Vector4(index1,index2,(total-near1)/total,(total-near2)/total); } } public static Vector4 findNearSpecial(List<NameAndVector3> nameAndPositions,Vector3 pos,JsArray<AnimationBone> bones,int vindex){ Vector3 pt=nameAndPositions.get(0).getVector3().clone(); Vector3 near=pt.sub(pos); int index1=nameAndPositions.get(0).getIndex(); double near1=near.length(); int index2=index1; double near2=near1; int nameIndex1=0; int nameIndex2=0; for(int i=1;i<nameAndPositions.size();i++){ Vector3 npt=nameAndPositions.get(i).getVector3().clone(); Vector3 subPos=npt.sub(pos); double l=subPos.length(); //if(vindex==250)log(nameAndPositions.get(i).getName()+","+l); if(l<near1){ int tmp=index1; double tmpL=near1; int tmpName=nameIndex1; index1=nameAndPositions.get(i).getIndex(); near1=l; nameIndex1=i; if(tmpL<near2){ index2=tmp; near2=tmpL; nameIndex2=tmpName; } }else if(l<near2){ index2=nameAndPositions.get(i).getIndex(); near2=l; nameIndex2=i; } } Map<Integer,Double> totalLength=new HashMap<Integer,Double>(); Map<Integer,Integer> totalIndex=new HashMap<Integer,Integer>(); //zero is largest Vector3 rootNear=nameAndPositions.get(0).getVector3().clone(); rootNear.sub(pos); for(int i=0;i<nameAndPositions.size();i++){ int index=nameAndPositions.get(i).getIndex(); Vector3 nearPos=nameAndPositions.get(i).getVector3().clone(); nearPos.sub(pos); double l=nearPos.length(); Double target=totalLength.get(index); double cvalue=0; if(target!=null){ cvalue=target.doubleValue(); } cvalue+=l; totalLength.put(index,cvalue); Integer count=totalIndex.get(index); int countV=0; if(count!=null){ countV=count; } countV++; totalIndex.put(index, countV); } //do average for end like head Integer[] keys=totalLength.keySet().toArray(new Integer[0]); for(int i=0;i<keys.length;i++){ int index=keys[i]; int count=totalIndex.get(index); totalLength.put(index, totalLength.get(index)/count); } if(index1==index2){ return THREE.Vector4(index1,index1,1,0); }else{ double near1Length=totalLength.get(index1); double near2Length=totalLength.get(index2); double total=near1Length+near2Length; return THREE.Vector4(index1,index2,(total-near1Length)/total,(total-near2Length)/total); } } public static void autoWeight(Geometry geometry,JsArray<AnimationBone> bones,List<List<Vector3>> endSites,int mode,JsArray<Vector4> bodyIndices,JsArray<Vector4> bodyWeight){ List<NameAndVector3> nameAndPositions=boneToNameAndPosition(bones,endSites); for(int i=0;i<geometry.vertices().length();i++){ Vector4 ret=null; if(mode==MODE_NearSingleBone){ ret=findNearSingleBone(nameAndPositions,geometry.vertices().get(i),bones); }else if(mode==MODE_NearSpecial){ ret=findNearSpecial(nameAndPositions,geometry.vertices().get(i),bones,i); }else if(mode==MODE_NearAgressive){ ret=findNearBoneAggresive(nameAndPositions,geometry.vertices().get(i),bones); }else if(mode==MODE_NearParentAndChildren){ ret=findNearBoneParentAndChildren(nameAndPositions,geometry.vertices().get(i),bones,2); }else if(mode==MODE_NearParentAndChildrenAgressive){ ret=findNearBoneParentAndChildren(nameAndPositions,geometry.vertices().get(i),bones,3); }else if(mode==MODE_MODE_Start_And_Half_ParentAndChildrenAgressive){ ret=findNearBoneStartAndHalfParentAndChildren(nameAndPositions,geometry.vertices().get(i),bones,3); }else if(mode==MODE_FROM_GEOMETRY){ ret=fromGeometry(geometry,i); }else if(mode==MODE_ROOT_ALL){ ret=THREE.Vector4(0,0,1,0); }else{ Window.alert("null mode"); } //now support only 2 bones Vector4 v4=THREE.Vector4(); v4.set(ret.getX(), ret.getY(), 0, 0); bodyIndices.push(v4); Vector4 v4w=THREE.Vector4(); v4w.set(ret.getZ(), ret.getW(), 0, 0); bodyWeight.push(v4w); } } public static boolean isHasIndex(Vector4 v4,int target){ return v4.getX()==target || v4.getY()==target; } public static int getAnother(Vector4 v4,int target){ if(v4.getX()==target){ return (int)v4.getY(); }else if(v4.getY()==target){ return (int)v4.getX(); } return -1; } public static void replaceIndex(Vector4 v4,int target,int replace){ if(v4.getX()==target){ v4.setX(replace); }else if(v4.getY()==target){ v4.setY(replace); } } public static boolean isConnected(Vector4 v4,JsArray<AnimationBone> bones){ //same if(v4.getX()==v4.getY()){ return true; } //100% value no need to check if(v4.getZ()==1 || v4.getW()==1){ return true; } return bones.get((int)v4.getX()).getParent()==v4.getY() || bones.get((int)v4.getY()).getParent()==v4.getX(); } //is this slow? public static Set<NameAndVector3> sortNameAndPosition(List<NameAndVector3> nameAndPositions,Vector3 vertexPos){ Map<NameAndVector3,Integer> map=new HashMap<NameAndVector3, Integer>(); final Ordering<NameAndVector3> orders=Ordering.natural().reverse().nullsLast().onResultOf(Functions.forMap(map, null)); LogUtils.log(ImmutableSortedMap.copyOf(map, orders)); return ImmutableSortedMap.copyOf(map, orders).keySet(); } public static int findClosedBone(List<NameAndVector3> nameAndPositions,Vector3 vertexPos,int ignoreIndex){ int minIndex=-1; double minLength=Double.MAX_VALUE; for(int i=0;i<nameAndPositions.size();i++){ if(nameAndPositions.get(i).getIndex()==ignoreIndex){ continue; } Vector3 bonePosition=nameAndPositions.get(i).getVector3().clone(); Vector3 diffPos=bonePosition.sub(vertexPos); double l=diffPos.length(); if(l<minLength){ minIndex=nameAndPositions.get(i).getIndex(); minLength=l; } } return minIndex; } public static void autoWeight(Geometry geometry,JsArray<AnimationBone> bones,int mode,JsArray<Vector4> bodyIndices,JsArray<Vector4> bodyWeight){ autoWeight(geometry,bones,null,mode,bodyIndices,bodyWeight); } private static Vector4 fromGeometry(Geometry geometry,int index) { Vector4 v4=THREE.Vector4(); v4.setX(geometry.getSkinIndices().get(index).getX()); v4.setY(geometry.getSkinIndices().get(index).getY()); v4.setZ(geometry.getSkinWeight().get(index).getX()); v4.setW(geometry.getSkinWeight().get(index).getY()); return v4; } public static List<NameAndVector3> boneToNameAndPosition(JsArray<AnimationBone> bones){ return boneToNameAndPosition(bones,null); } //add all bones start,half end position public static List<NameAndVector3> boneToNameAndPosition(JsArray<AnimationBone> bones,List<List<Vector3>> endSites){ List<NameAndVector3> lists=new ArrayList<NameAndVector3>(); List<Vector3> absolutePos=new ArrayList<Vector3>(); List<Integer> hasChild=new ArrayList<Integer>(); for(int i=0;i<bones.length();i++){ AnimationBone bone=bones.get(i); if(bone.getParent()!=-1){ hasChild.add(bone.getParent()); } } for(int i=0;i<bones.length();i++){ AnimationBone bone=bones.get(i); Vector3 pos=AnimationBone.jsArrayToVector3(bone.getPos()); String parentName=null; //add start //add center Vector3 parentPos=null; Vector3 endPos=null; int parentIndex=0; if(bone.getParent()!=-1){ parentIndex=bone.getParent(); parentName=bones.get(parentIndex).getName(); parentPos=absolutePos.get(parentIndex); if(pos.getX()!=0 || pos.getY()!=0 || pos.getZ()!=0){ endPos=pos.clone().multiplyScalar(.9).add(parentPos); Vector3 half=pos.clone().multiplyScalar(.5).add(parentPos); lists.add(new NameAndVector3(parentName,endPos,parentIndex));//start pos lists.add(new NameAndVector3(KEY_HALF+parentName,half,parentIndex));//half pos if(!hasChild.contains(i)){ //end // LogUtils.log("end "+bone.getName()); Vector3 end=pos.clone().multiplyScalar(2).add(parentPos); lists.add(new NameAndVector3(KEY_END+bone.getName(),end,i));//end pos } }else{ } } //add end if(parentPos!=null){ pos.add(parentPos); } absolutePos.add(pos); if(endSites!=null){ List<Vector3> ends=endSites.get(i); for(Vector3 endSite:ends){ lists.add(new NameAndVector3(KEY_ENDSITE+bone.getName(),pos.clone().add(endSite),i));// } } lists.add(new NameAndVector3(bone.getName(),pos,i));//end pos } return lists; } }