package com.akjava.gwt.threejsexamples.client.examples.animation.cloth; import java.util.List; import java.util.Map; import com.akjava.gwt.lib.client.LogUtils; import com.akjava.gwt.stats.client.Stats; import com.akjava.gwt.three.client.gwt.GWTParamUtils; import com.akjava.gwt.three.client.gwt.boneanimation.AnimationBone; import com.akjava.gwt.three.client.gwt.extras.Uniforms; import com.akjava.gwt.three.client.java.ui.example.AbstractExample; import com.akjava.gwt.three.client.java.utils.AnimationUtils; import com.akjava.gwt.three.client.java.utils.GWTThreeUtils; import com.akjava.gwt.three.client.js.THREE; import com.akjava.gwt.three.client.js.animation.AnimationClip; import com.akjava.gwt.three.client.js.animation.AnimationMixer; import com.akjava.gwt.three.client.js.animation.KeyframeTrack; import com.akjava.gwt.three.client.js.animation.tracks.QuaternionKeyframeTrack; import com.akjava.gwt.three.client.js.animation.tracks.VectorKeyframeTrack; import com.akjava.gwt.three.client.js.cameras.PerspectiveCamera; import com.akjava.gwt.three.client.js.core.Geometry; import com.akjava.gwt.three.client.js.extras.geometries.BoxGeometry; import com.akjava.gwt.three.client.js.extras.geometries.ParametricGeometry; import com.akjava.gwt.three.client.js.extras.geometries.SphereGeometry; import com.akjava.gwt.three.client.js.extras.helpers.ArrowHelper; import com.akjava.gwt.three.client.js.extras.helpers.SkeletonHelper; import com.akjava.gwt.three.client.js.lights.DirectionalLight; import com.akjava.gwt.three.client.js.materials.MeshPhongMaterial; import com.akjava.gwt.three.client.js.math.Quaternion; import com.akjava.gwt.three.client.js.math.Vector3; import com.akjava.gwt.three.client.js.objects.Bone; import com.akjava.gwt.three.client.js.objects.Mesh; import com.akjava.gwt.three.client.js.objects.SkinnedMesh; import com.akjava.gwt.three.client.js.renderers.WebGLRenderer; import com.akjava.gwt.three.client.js.scenes.Scene; import com.akjava.gwt.three.client.js.textures.Texture; import com.akjava.gwt.threejsexamples.client.examples.animation.cloth.Cloth.Particle; import com.akjava.gwt.threejsexamples.client.resources.Bundles; import com.google.common.collect.Maps; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArray; import com.google.gwt.core.client.JsArrayNumber; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.VerticalPanel; /* * TODO implement GUI */ public class ClothExample3 extends AbstractExample { Cloth cloth; private FocusPanel container; private Scene scene; private PerspectiveCamera camera; private ParametricGeometry clothGeometry; private Mesh sphere; private ArrowHelper arrow; private WebGLRenderer renderer; private Stats stats; private boolean rotate=false; private SkeletonHelper helper; private SkinnedMesh clothBoxMesh; JsArray<AnimationBone> createBone(Cloth cloth){ JsArray<AnimationBone> bones=JavaScriptObject.createArray().cast(); AnimationBone bone=AnimationUtils.createAnimationBone(); bone.setParent(-1); bone.setName("root"); bone.setPos(THREE.Vector3()); bones.push(bone); int index=1; for(int i=0;i<=cloth.w;i++){ int parent=0; Vector3 parentPos=THREE.Vector3(); for(int j=0;j<=cloth.h;j++){ int ind=(cloth.w+1)*j+i; Particle p=cloth.particles.get(ind); AnimationBone childBone=AnimationUtils.createAnimationBone(); childBone.setParent(parent); childBone.setName(i+","+j); childBone.setPos(p.getOriginal().clone().sub(parentPos)); bones.push(childBone); parent=index; index++; parentPos=p.getOriginal(); } } return bones; } AnimationMixer mixer; public void init(){ cloth=new Cloth(); container = createContainerPanel(); // scene scene = THREE.Scene(); scene.setFog(THREE.Fog( 0xcce0ff, 500, 10000 )); // camera camera = THREE.PerspectiveCamera( 30, getWindowInnerWidth() / getWindowInnerHeight(), 1, 10000 ); camera.getPosition().setY(50) ; camera.getPosition().setZ(1500); scene.add( camera ); // lights DirectionalLight light; //materials; scene.add(THREE.AmbientLight( 0x666666 ) ); light = THREE.DirectionalLight( 0xdfebff, 1.75 ); light.getPosition().set( 50, 200, 100 ); light.getPosition().multiplyScalar( 1.3 ); light.setCastShadow(true); //light.shadowCameraVisible = true; //light.setShadowMapWidth(1024); light.getShadow().getMapSize().setWidth(1024); //light.setShadowMapHeight(1024); light.getShadow().getMapSize().setHeight(1024); double d = 300; //DirectionalLight return use OrthographicCamera; light.getShadow().getCamera().gwtCastOrthographicCamera().setLeft(-d); light.getShadow().getCamera().gwtCastOrthographicCamera().setRight(d); light.getShadow().getCamera().gwtCastOrthographicCamera().setTop(d); light.getShadow().getCamera().gwtCastOrthographicCamera().setBottom(-d); light.getShadow().getCamera().gwtCastOrthographicCamera().setFar(1000); scene.add( light ); // cloth material Texture clothTexture = THREE.TextureLoader().load( "textures/patterns/circuit_pattern.png" ); clothTexture.setWrapS(THREE.RepeatWrapping); clothTexture.setWrapT(THREE.RepeatWrapping); clothTexture.setAnisotropy(16); MeshPhongMaterial clothMaterial = THREE.MeshPhongMaterial( GWTParamUtils.MeshPhongMaterial().alphaTest(0.5).color(0xffffff).specular(0x030303).emissive(0x111111).shininess(10) .map(clothTexture).side(THREE.DoubleSide).wireframe(true)); // cloth geometry clothGeometry = THREE.ParametricGeometry( cloth.clothFunction, cloth.w, cloth.h ); clothGeometry.setDynamic(true); clothGeometry.computeFaceNormals(); BoxGeometry clothBox=THREE.BoxGeometry(cloth.w*cloth.restDistance, cloth.restDistance*cloth.h, 40); clothBox.applyMatrix( THREE.Matrix4().makeTranslation( 0,250,0 ) ); clothBox.setBones(createBone(cloth)); MeshPhongMaterial boxhMaterial = THREE.MeshPhongMaterial( GWTParamUtils.MeshPhongMaterial().alphaTest(0.5).color(0xffffff).specular(0x030303).emissive(0x111111).shininess(10) .map(clothTexture).skinning(true)); clothBoxMesh = THREE.SkinnedMesh(clothBox,boxhMaterial); mixer=THREE.AnimationMixer(clothBoxMesh); scene.add(clothBoxMesh); clothBoxMesh.getPosition().set(0, 0, 0); //LogUtils.log(clothBoxMesh.getSkeleton()); helper = THREE.SkeletonHelper(clothBoxMesh); scene.add(helper); //shadow Uniforms uniforms=Uniforms.create().setTypeAndValue("texture", clothTexture); String vertexShader=Bundles.INSTANCE.vertexShaderDepth().getText(); String fragmentShader=Bundles.INSTANCE.fragmentShaderDepth().getText(); // cloth mesh Mesh object = THREE.Mesh( clothGeometry, clothMaterial ); object.getPosition().set( 0, 0, 0 ); object.setCastShadow(true); object.setReceiveShadow(true); scene.add( object ); object.setCustomDepthMaterial(THREE.ShaderMaterial(GWTParamUtils.ShaderMaterial().uniforms(uniforms).vertexShader(vertexShader).fragmentShader(fragmentShader))); // sphere SphereGeometry ballGeo = THREE.SphereGeometry( cloth.ballSize, 20, 20 );//var ballGeo = new THREE.SphereGeometry( ballSize, 20, 20 ); MeshPhongMaterial ballMaterial = THREE.MeshPhongMaterial( GWTParamUtils.MeshPhongMaterial().color(0xffffff));// var ballMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff } ); sphere = THREE.Mesh( ballGeo, ballMaterial );// sphere = new THREE.Mesh( ballGeo, ballMaterial ); sphere.setCastShadow(true);// sphere.castShadow = true; sphere.setReceiveShadow(true);// sphere.receiveShadow = true; scene.add( sphere ); // arrow arrow = THREE.ArrowHelper( THREE.Vector3( 0, 1, 0 ), THREE.Vector3( 0, 0, 0 ), 50, 0xff0000 );//arrow = new THREE.ArrowHelper( new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 0, 0 ), 50, 0xff0000 ); arrow.getPosition().set( -200, 0, -200 );//arrow.position.set( -200, 0, -200 ); // scene.add( arrow ); // ground Texture groundTexture = THREE.TextureLoader().load( "textures/terrain/grasslight-big.jpg" );//var groundTexture = THREE.ImageUtils.loadTexture( "textures/terrain/grasslight-big.jpg" ); groundTexture.setWrapS(THREE.RepeatWrapping);//groundTexture.wrapS = groundTexture.wrapT = THREE.RepeatWrapping; groundTexture.setWrapT(THREE.RepeatWrapping); groundTexture.getRepeat().set( 25, 25 );//groundTexture.repeat.set( 25, 25 ); groundTexture.setAnisotropy(16);//groundTexture.anisotropy = 16; MeshPhongMaterial groundMaterial = THREE.MeshPhongMaterial( GWTParamUtils.MeshPhongMaterial().color(0xffffff).specular(0x111111).map(groundTexture) );//var groundMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff, specular: 0x111111, map: groundTexture } ); Mesh mesh = THREE.Mesh( THREE.PlaneBufferGeometry( 20000, 20000 ), groundMaterial );//var mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry( 20000, 20000 ), groundMaterial ); mesh.getPosition().setY(-250);//mesh.position.y = -250; mesh.getRotation().setX(- Math.PI / 2);//mesh.rotation.x = - Math.PI / 2; mesh.setReceiveShadow(true);//mesh.receiveShadow = true; scene.add( mesh ); // poles BoxGeometry poleGeo = THREE.BoxGeometry( 5, 375, 5 );//var poleGeo = new THREE.BoxGeometry( 5, 375, 5 ); MeshPhongMaterial poleMat = THREE.MeshPhongMaterial( GWTParamUtils.MeshPhongMaterial().color(0xffffff).specular(0x111111).shininess(100) );//var poleMat = new THREE.MeshPhongMaterial( { color: 0xffffff, specular: 0x111111, shiness: 100 } ); mesh = THREE.Mesh( poleGeo, poleMat );//var mesh = new THREE.Mesh( poleGeo, poleMat ); mesh.getPosition().setX(-125);//mesh.position.x = -125; mesh.getPosition().setY(-62);//mesh.position.y = -62; mesh.setReceiveShadow(true);//mesh.receiveShadow = true; mesh.setCastShadow(true);//mesh.castShadow = true; scene.add( mesh ); mesh = THREE.Mesh( poleGeo, poleMat );//var mesh = new THREE.Mesh( poleGeo, poleMat ); mesh.getPosition().setX(125);//mesh.position.x = 125; mesh.getPosition().setY(-62);//mesh.position.y = -62; mesh.setReceiveShadow(true);//mesh.receiveShadow = true; mesh.setCastShadow(true);//mesh.castShadow = true; scene.add( mesh ); mesh = THREE.Mesh( THREE.BoxGeometry( 255, 5, 5 ), poleMat );//var mesh = new THREE.Mesh( new THREE.BoxGeometry( 255, 5, 5 ), poleMat ); mesh.getPosition().setY(-250 + 750/2);//mesh.position.y = -250 + 750/2; mesh.getPosition().setX(0);//mesh.position.x = 0; mesh.setReceiveShadow(true);//mesh.receiveShadow = true; mesh.setCastShadow(true);//mesh.castShadow = true; scene.add( mesh ); BoxGeometry gg = THREE.BoxGeometry( 10, 10, 10 );//var gg = new THREE.BoxGeometry( 10, 10, 10 ); mesh = THREE.Mesh( gg, poleMat );//var mesh = new THREE.Mesh( gg, poleMat ); mesh.getPosition().setY(-250);//mesh.position.y = -250; mesh.getPosition().setX(125);//mesh.position.x = 125; mesh.setReceiveShadow(true);//mesh.receiveShadow = true; mesh.setCastShadow(true);//mesh.castShadow = true; scene.add( mesh ); mesh = THREE.Mesh( gg, poleMat );//var mesh = new THREE.Mesh( gg, poleMat ); mesh.getPosition().setY(-250);//mesh.position.y = -250; mesh.getPosition().setX(-125);//mesh.position.x = -125; mesh.setReceiveShadow(true);//mesh.receiveShadow = true; mesh.setCastShadow(true);//mesh.castShadow = true; scene.add( mesh ); // renderer = THREE.WebGLRenderer( GWTParamUtils.WebGLRenderer().antialias(true) );//renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setPixelRatio( GWTThreeUtils.getWindowDevicePixelRatio() ); renderer.setSize((int)getWindowInnerWidth() , (int)getWindowInnerHeight()); renderer.setClearColor( scene.getFog().getColor() );//renderer.setClearColor( scene.fog.color ); container.getElement().appendChild(renderer.getDomElement()); renderer.setGammaInput(true);//renderer.gammaInput = true; renderer.setGammaOutput(true);//renderer.gammaOutput = true; //renderer.setShadowMapEnabled(true);//renderer.shadowMapEnabled = true; renderer.getShadowMap().setEnabled(true); // stats = Stats.create(); stats.setPosition(0, 0); container.getElement().appendChild(stats.domElement()); container.add(createAbsoluteHTML("Simple Cloth Simulation<br>Verlet integration with Constrains relaxation" ,100,10)); //window.addEventListener( 'resize', onWindowResize, false ); sphere.setVisible(false); //TODO create-gui setDebugAnimateOneTimeOnly(true); createGUI(); } public void createGUI(){ VerticalPanel gui=addResizeHandlerAndCreateGUIPanel();//need for window-resize CheckBox lockCamera=new CheckBox("Lock Camera"); gui.add(lockCamera); lockCamera.addValueChangeHandler(new ValueChangeHandler<Boolean>() { @Override public void onValueChange(ValueChangeEvent<Boolean> event) { rotate=!event.getValue(); } }); lockCamera.setValue(!rotate); CheckBox wind=new CheckBox("Wind"); gui.add(wind); wind.addValueChangeHandler(new ValueChangeHandler<Boolean>() { @Override public void onValueChange(ValueChangeEvent<Boolean> event) { cloth.wind=event.getValue(); } }); wind.setValue(true); CheckBox showSphere=new CheckBox("Show Ball"); gui.add(showSphere); showSphere.addValueChangeHandler(new ValueChangeHandler<Boolean>() { @Override public void onValueChange(ValueChangeEvent<Boolean> event) { sphere.setVisible(event.getValue()); } }); final ListBox pinsBox=new ListBox(); pinsBox.addItem("Center only pin"); pinsBox.addItem("Top all pins"); pinsBox.addItem("First only pin"); pinsBox.addItem("No pin"); pinsBox.addItem("2 pins"); pinsBox.setSelectedIndex(1);//default pinsBox.addChangeHandler(new ChangeHandler() { @Override public void onChange(ChangeEvent event) { int selection=pinsBox.getSelectedIndex(); cloth.pins=cloth.pinsFormation.get(selection); } }); gui.add(pinsBox); //setDebugAnimateOneTimeOnly(true); Button bt=new Button("test",new ClickHandler() { @Override public void onClick(ClickEvent event) { animate(System.currentTimeMillis()); } }); gui.add(bt); } //do something cloth public void render(double time){ double timer = time * 0.0002; List<Particle> p = cloth.particles; for ( int i = 0, il = p.size(); i < il; i ++ ) { clothGeometry.getVertices().get(i).copy( p.get(i).getPosition()); } clothGeometry.computeFaceNormals(); clothGeometry.computeVertexNormals(); clothGeometry.setNormalsNeedUpdate(true);//clothGeometry.normalsNeedUpdate = true; clothGeometry.setVerticesNeedUpdate(true);//clothGeometry.verticesNeedUpdate = true; sphere.getPosition().copy( cloth.ballPosition );//sphere.position.copy( ballPosition ); if ( rotate ) { camera.getPosition().setX(Math.cos( timer ) * 1500);//camera.position.x = Math.cos( timer ) * 1500; camera.getPosition().setZ(Math.sin( timer ) * 1500);//camera.position.z = Math.sin( timer ) * 1500; } camera.lookAt( scene.getPosition() ); renderer.render( scene, camera ); } @Override public String getName() { return "animation/skinning-cloth"; } @Override public void animate(double time) { cloth.windStrength = Math.cos( time / 7000 ) * 20 + 40; cloth.windForce.set( Math.sin( time / 2000 ), Math.cos( time / 3000 ), Math.sin( time / 1000 ) ).normalize().multiplyScalar( cloth.windStrength ); arrow.setLength( cloth.windStrength ); arrow.setDirection( cloth.windForce ); cloth.simulate(time,clothGeometry,sphere);//set otherwhere? syncBones(); if(mixer!=null){ mixer.update(1.0/60); } helper.update(); render(time); stats.update();//really deprecate?many still use this } public void syncBones(){ JsArray<Bone> bones=clothBoxMesh.getSkeleton().getBones(); Map<Bone,Bone> childAndParent=childAndParentMap(bones); Map<String,Vector3> origins=getOriginalPosition(clothBoxMesh.getGeometry()); Map<Bone,Integer> boneIndex=getBoneIndex(bones); JsArray<KeyframeTrack> allTracks=JavaScriptObject.createArray().cast(); //no need [0] root for(int i=1;i<bones.length();i++){ int childIndex=i; Bone child=bones.get(i); Bone parent=childAndParent.get(child); if(parent==null || parent.getName().equals("root")){ continue; }else{ //seems ok //LogUtils.log("modified"); } int parentIndex=boneIndex.get(parent); Vector3 childPosition=getPositionByBoneName(child.getName()); Vector3 parentPosition=getPositionByBoneName(parent.getName()); Vector3 origin=origins.get(child.getName());//origin position already relative parent Vector3 point2=childPosition.clone().sub(parentPosition); /* * if(!origin.equals(point2)){ LogUtils.log("somehow invalid:"+child.getName()); ThreeLog.log(origins.get(child.getName())); ThreeLog.log(origins.get(parent.getName())); ThreeLog.log(childPosition); ThreeLog.log(parentPosition); } */ Vector3 point1=THREE.Vector3(); double length=point2.distanceTo(point1); //pos for child Vector3 modified=origin.clone().normalize().multiplyScalar(length); //angle for parent Quaternion quaternion = THREE.Quaternion().setFromUnitVectors( origin.clone().normalize() ,point2.clone().normalize() ); mergeTracks(allTracks,makeTracks(childIndex, parentIndex, modified, quaternion)); } //LogUtils.log("tracks:"+allTracks.length());//220 tracks AnimationClip clip=THREE.AnimationClip("bones", -1, allTracks); //LogUtils.log(track.validate()); mixer.stopAllAction(); mixer.uncacheClip(clip); mixer.clipAction(clip).play(); } public void mergeTracks(JsArray<KeyframeTrack> allTracks,JsArray<KeyframeTrack> tracks){ for(int i=0;i<tracks.length();i++){ allTracks.push(tracks.get(i)); } } public JsArray<KeyframeTrack> makeTracks(int childIndex,int parentIndex,Vector3 pos,Quaternion q){ JsArray<KeyframeTrack> tracks=JavaScriptObject.createArray().cast(); JsArrayNumber times=JavaScriptObject.createArray().cast(); times.push(0); //I'm not sure is there reset-pose has value JsArrayNumber values=JsArray.createArray().cast(); concat(values,q.toArray()); QuaternionKeyframeTrack track=THREE.QuaternionKeyframeTrack(".bones["+parentIndex+"].quaternion", times, values); tracks.push(track); //position changed if(pos!=null){ JsArrayNumber times2=JavaScriptObject.createArray().cast(); times2.push(0); //I'm not sure is there reset-pose has value JsArrayNumber values2=JsArray.createArray().cast(); concat(values2,pos.toArray()); VectorKeyframeTrack track2=THREE.VectorKeyframeTrack(".bones["+(childIndex)+"].position", times2, values2); //tracks.push(track2); } return tracks; } public void concat(JsArrayNumber target,JsArrayNumber values){ for(int i=0;i<values.length();i++){ target.push(values.get(i)); } } public Vector3 getPositionByBoneName(String name){ String[] xy=name.split(","); int x=Integer.parseInt(xy[0]); int y=Integer.parseInt(xy[1]); Particle particle=cloth.particles.get((cloth.w+1)*y+x); return particle.getPosition().clone(); } //TODO make method public Map<Bone,Bone> childAndParentMap(JsArray<Bone> bones){ Map<Bone,Bone> map=Maps.newHashMap(); for(int i=1;i<bones.length();i++){ Bone parent=bones.get(i); for(int j=0;j<parent.getChildrenBones().length();j++){ Bone child=parent.getChildrenBones().get(j); map.put(child, parent); } } return map; } public Map<Bone,Integer> getBoneIndex(JsArray<Bone> bones){ Map<Bone,Integer> map=Maps.newHashMap(); for(int i=0;i<bones.length();i++){ map.put(bones.get(i),i); } return map; } public Map<String,Vector3> getOriginalPosition(Geometry geometry){ Map<String,Vector3> map=Maps.newHashMap(); for(int i=0;i<geometry.getBones().length();i++){ //Bone parent=bones.get(i); AnimationBone ab=geometry.getBones().get(i); map.put(ab.getName(), THREE.Vector3().fromArray(ab.getPos())); LogUtils.log(ab); } return map; } @Override public void onWindowResize() { camera.setAspect(getWindowInnerWidth() / getWindowInnerHeight()); camera.updateProjectionMatrix(); renderer.setSize( (int)getWindowInnerWidth() , (int)getWindowInnerHeight() ); } @Override public String getTokenKey() { return "cloth3"; } }