package com.akjava.gwt.threejsexamples.client.examples.original.hair; 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.examples.js.THREEExp; import com.akjava.gwt.three.client.examples.js.controls.OrbitControls; import com.akjava.gwt.three.client.gwt.GWTParamUtils; import com.akjava.gwt.three.client.gwt.core.BoundingBox; import com.akjava.gwt.three.client.gwt.core.Intersect; import com.akjava.gwt.three.client.gwt.ui.LabeledInputRangeWidget2; import com.akjava.gwt.three.client.java.ThreeLog; import com.akjava.gwt.three.client.java.ui.example.AbstractExample; import com.akjava.gwt.three.client.java.utils.GWTThreeUtils; import com.akjava.gwt.three.client.js.THREE; import com.akjava.gwt.three.client.js.cameras.PerspectiveCamera; import com.akjava.gwt.three.client.js.core.BufferAttribute; import com.akjava.gwt.three.client.js.core.BufferGeometry; import com.akjava.gwt.three.client.js.core.Clock; import com.akjava.gwt.three.client.js.core.Face3; import com.akjava.gwt.three.client.js.core.Geometry; import com.akjava.gwt.three.client.js.core.Raycaster; import com.akjava.gwt.three.client.js.extras.geometries.CircleGeometry; import com.akjava.gwt.three.client.js.extras.geometries.SphereGeometry; import com.akjava.gwt.three.client.js.extras.helpers.VertexNormalsHelper; import com.akjava.gwt.three.client.js.lights.AmbientLight; import com.akjava.gwt.three.client.js.lights.DirectionalLight; import com.akjava.gwt.three.client.js.loaders.JSONLoader.JSONLoadHandler; import com.akjava.gwt.three.client.js.materials.Material; import com.akjava.gwt.three.client.js.materials.MeshPhongMaterial; import com.akjava.gwt.three.client.js.materials.MultiMaterial; import com.akjava.gwt.three.client.js.math.Matrix3; import com.akjava.gwt.three.client.js.math.THREEMath; import com.akjava.gwt.three.client.js.math.Vector3; import com.akjava.gwt.three.client.js.objects.LineSegments; 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.threejsexamples.client.examples.animation.cloth.ClothControler; import com.akjava.gwt.threejsexamples.client.examples.animation.cloth.ClothData; import com.akjava.gwt.threejsexamples.client.examples.original.Mblb3dExpression; import com.akjava.gwt.threejsexamples.client.examples.vr.VrStereoExample; import com.google.gwt.core.client.JsArray; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.MouseDownEvent; import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.dom.client.MouseMoveEvent; import com.google.gwt.event.dom.client.MouseMoveHandler; 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.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.ValueListBox; import com.google.gwt.user.client.ui.VerticalPanel; public class HairExample extends AbstractExample{ @Override public String getName() { return "Hair Example"; } @Override public void animate(double timestamp) { render(timestamp); stats.update();//really deprecate?many still use this } private WebGLRenderer renderer; private Scene scene; private PerspectiveCamera camera; private Stats stats; double SCREEN_WIDTH; double SCREEN_HEIGHT; private int windowHalfX,windowHalfY; //private int mouseX,mouseY; Clock clock; private ValueListBox<Mblb3dExpression> expressionsListBox; private HorizontalPanel downloadPanel; private SkinnedMesh mesh; private Map<String,LabeledInputRangeWidget2> ranges; private List<Mblb3dExpression> expressionList; private OrbitControls controls; private Mesh sphere; private VertexNormalsHelper vertexHelper; private int ballSize; @Override public void init() { clock=THREE.Clock(); SCREEN_WIDTH = getWindowInnerWidth(); SCREEN_HEIGHT = getWindowInnerHeight(); windowHalfX= (int)(SCREEN_WIDTH/2); windowHalfY= (int)(SCREEN_HEIGHT/2); FocusPanel container = createContainerPanel(); container.addMouseMoveHandler(new MouseMoveHandler() { @Override public void onMouseMove(MouseMoveEvent event) { onDocumentMouseMove(event); } }); container.addMouseDownHandler(new MouseDownHandler() { @Override public void onMouseDown(MouseDownEvent event) { doMouseDown(event); } }); // renderer renderer = THREE.WebGLRenderer(GWTParamUtils.WebGLRenderer().preserveDrawingBuffer(true)); renderer.setPixelRatio( GWTThreeUtils.getWindowDevicePixelRatio() ); renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT ); renderer.setClearColor( THREE.Color( 0xffffff ) ); container.getElement().appendChild( renderer.getDomElement() ); // scene scene = THREE.Scene(); // camera double cameraY=700; camera = THREE.PerspectiveCamera(45, getWindowInnerWidth()/getWindowInnerHeight(), .1, 10000); camera.getPosition().set(0, cameraY, 500); controls = THREEExp.OrbitControls(camera,container.getElement() ); controls.setTarget(THREE.Vector3( 0, cameraY, 0 )); controls.update(); AmbientLight ambient = THREE.AmbientLight( 0xeeeeee );//var ambient = new THREE.AmbientLight( 0xffffff ); scene.add( ambient ); DirectionalLight directionalLight = THREE.DirectionalLight( 0x333333 );//var directionalLight = new THREE.DirectionalLight( 0x444444 ); directionalLight.getPosition().set( -1, 1, 1 ).normalize();//directionalLight.position.set( -1, 1, 1 ).normalize(); scene.add( directionalLight ); //String url= "models/mbl3d/morph.json";//var url= "morph.json"; String url= "models/mbl3d/nomorph.json";//var url= "morph.json"; THREE.JSONLoader().load(url,new JSONLoadHandler() { @Override public void loaded(Geometry geometry,JsArray<Material> materials) { geometry.computeBoundingBox(); BoundingBox bb = geometry.getBoundingBox(); //LogUtils.log(bb); //double x=-20, y=-1270,z= -300,s= 800; double x=-0, y=-0,z= -0,s= 1000; y=-bb.getMax().getY()/2*s; MultiMaterial mat=THREE.MultiMaterial(materials );//var mat=THREE.MultiMaterial( materials);//MultiMaterial mat=THREE.MultiMaterial( materials);//var mat=new THREE.MultiMaterial( materials); mesh = THREE.SkinnedMesh( geometry, mat );//mesh = THREE.SkinnedMesh( geometry, mat );//mesh = THREE.SkinnedMesh( geometry, mat );//mesh = new THREE.SkinnedMesh( geometry, mat ); mesh.setName("model");//mesh.setName("model");//mesh.setName("model");//mesh.name = "model"; //mesh.getPosition().set( x, y - bb.getMin().getY() * s, z );//mesh.getPosition().set( x, y - bb.getMin().y * s, z );//mesh.getPosition().set( x, y - bb.getMin().y * s, z );//mesh.position.set( x, y - bb.min.y * s, z ); mesh.getPosition().set(x, y, z); mesh.getScale().set( s, s, s );//mesh.getScale().set( s, s, s );//mesh.getScale().set( s, s, s );//mesh.scale.set( s, s, s ); scene.add( mesh ); vertexHelper = THREE.VertexNormalsHelper(mesh, 0.8, 0x008800, 2); scene.add(vertexHelper); //scene.add(THREE.VertexNormalsHelper(mesh, 0.2, 0x0000ff, 3));//can overwrite ballSize = 100; SphereGeometry ballGeo = THREE.SphereGeometry( ballSize, 20, 20 );//var ballGeo = new THREE.SphereGeometry( ballSize, 20, 20 ); MeshPhongMaterial ballMaterial = THREE.MeshPhongMaterial( GWTParamUtils.MeshPhongMaterial().color(0x888888).side(THREE.DoubleSide));// var ballMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff } ); sphere = THREE.Mesh( ballGeo, ballMaterial );// sphere = new THREE.Mesh( ballGeo, ballMaterial ); scene.add( sphere ); sphere.getPosition().setY(bb.getMax().getY()/2*s-100); createControler(); clothControls=new ClothControler(sphere); clothControls.setWind(true); } }); //stats stats = Stats.create(); stats.setPosition(0, 0); container.getElement().appendChild(stats.domElement()); //add html info container.add(createAbsoluteHTML( "<div style='text:white'>" + "<a href='http://threejs.org' target='_blank'>three.js</a>" + " - equirectangular video panorama demo" + "</div>" ,100,10)); //handle resize & gui gui=initResizeHandlerAndGUI(); //setDebugAnimateOneTimeOnly(true); } ClothControler clothControls; protected Vector3 matrixedPoint(Vector3 vec){ return vec.clone().applyMatrix4(mesh.getMatrixWorld()); } protected void doMouseDown(MouseDownEvent event) { double mx=event.getX(); double my=event.getY(); /* * conver -1-1 cordinate */ Vector3 screenPosition=THREE.Vector3(( mx / SCREEN_WIDTH ) * 2 - 1, - ( my / SCREEN_HEIGHT ) * 2 + 1,1 );//no idea why 0.5 screenPosition.unproject(camera); Raycaster ray=THREE.Raycaster(camera.getPosition(), screenPosition.sub( camera.getPosition() ).normalize()); JsArray<Intersect> intersects=ray.intersectObject(mesh); //find nearlist vertex if(intersects.length()>0){ //LogUtils.log("intersects:"+intersects.length()); Face3 face=intersects.get(0).getFace(); Vector3 point=intersects.get(0).getPoint(); //ThreeLog.log("point:",point); int index=0; Vector3 selection=mesh.getGeometry().getVertices().get(face.getA()); //ThreeLog.log("vertex1:",mesh.getGeometry().getVertices().get(face.getA())); double distance=point.distanceTo(matrixedPoint(mesh.getGeometry().getVertices().get(face.getA()))); //ThreeLog.log("vertex2:",mesh.getGeometry().getVertices().get(face.getB())); double distance2=point.distanceTo(matrixedPoint(mesh.getGeometry().getVertices().get(face.getB()))); if(distance2<distance){ index=1; distance=distance2; selection=mesh.getGeometry().getVertices().get(face.getB()); } //ThreeLog.log("vertex3:",mesh.getGeometry().getVertices().get(face.getC())); double distance3=point.distanceTo(matrixedPoint(mesh.getGeometry().getVertices().get(face.getC()))); if(distance3<distance){ index=2; distance=distance3; selection=mesh.getGeometry().getVertices().get(face.getC()); } //make lines double size=1.6; Matrix3 normalMatrix=THREE.Matrix3(); Vector3 v1 = THREE.Vector3(); Vector3 v2 = THREE.Vector3(); normalMatrix.getNormalMatrix( mesh.getMatrixWorld()); Vector3 normal = face.getVertexNormals().get(index); v1.copy( selection ).applyMatrix4( mesh.getMatrixWorld() ); v2.copy( normal ).applyMatrix3( normalMatrix ).normalize().multiplyScalar( size ).add( v1 ); BufferGeometry geometry = THREE.BufferGeometry(); BufferAttribute positions = THREE.Float32Attribute( 2*3, 3 ); geometry.addAttribute( "position", positions ); positions.setXYZ( 0, v1.getX(), v1.getY(), v1.getZ() ); positions.setXYZ( 1, v2.getX(), v2.getY(), v2.getZ() ); if(currentSelection==null){ currentSelection=new VertexAndNormal(selection.clone(), normal.clone()); }else{ currentSelection=new VertexAndNormal(selection.clone(), normal.clone()); } if(selectedLine!=null){ scene.remove(selectedLine); } selectedLine=THREE.LineSegments(geometry.gwtCastGeometry(), THREE.LineBasicMaterial(GWTParamUtils.LineBasicMaterial().color(0xff0000).linewidth(2))); scene.add(selectedLine); } } LineSegments selectedLine; private VerticalPanel initResizeHandlerAndGUI() { VerticalPanel gui=addResizeHandlerAndCreateGUIPanel(); gui.setWidth("200px");//some widget broke,like checkbox without parent size gui.setSpacing(2); //TODO add other ui or after return; return gui; } /** * must call after sphere initialized; */ private void createControler() { //vertex gui.add(new Label("Vertex")); HorizontalPanel h0=new HorizontalPanel(); CheckBox visibleVertexCheck=new CheckBox("visible"); visibleVertexCheck.setValue(true); visibleVertexCheck.addValueChangeHandler(new ValueChangeHandler<Boolean>() { @Override public void onValueChange(ValueChangeEvent<Boolean> event) { vertexHelper.setVisible(event.getValue()); } }); h0.add(visibleVertexCheck); gui.add(h0); //sphere gui.add(new Label("Sphere")); HorizontalPanel h1=new HorizontalPanel(); CheckBox visibleCheck=new CheckBox("visible"); visibleCheck.setValue(true); visibleCheck.addValueChangeHandler(new ValueChangeHandler<Boolean>() { @Override public void onValueChange(ValueChangeEvent<Boolean> event) { sphere.setVisible(event.getValue()); } }); h1.add(visibleCheck); gui.add(h1); final LabeledInputRangeWidget2 xRange=new LabeledInputRangeWidget2("x", -100, 100, 1); xRange.setValue(sphere.getPosition().getX()); gui.add(xRange); xRange.addtRangeListener(new ValueChangeHandler<Number>() { @Override public void onValueChange(ValueChangeEvent<Number> event) { sphere.getPosition().setX(event.getValue().doubleValue()); } }); final double resetY=sphere.getPosition().getY(); final LabeledInputRangeWidget2 yRange=new LabeledInputRangeWidget2("y", resetY-100, resetY+100, 1); yRange.setValue(sphere.getPosition().getY()); gui.add(yRange); yRange.addtRangeListener(new ValueChangeHandler<Number>() { @Override public void onValueChange(ValueChangeEvent<Number> event) { sphere.getPosition().setY(event.getValue().doubleValue()); } }); final LabeledInputRangeWidget2 zRange=new LabeledInputRangeWidget2("z", -200, 200, 1); zRange.setValue(sphere.getPosition().getZ()); gui.add(zRange); zRange.addtRangeListener(new ValueChangeHandler<Number>() { @Override public void onValueChange(ValueChangeEvent<Number> event) { sphere.getPosition().setZ(event.getValue().doubleValue()); } }); Button reset=new Button("reset xyz",new ClickHandler() { @Override public void onClick(ClickEvent event) { xRange.setValue(0,true); yRange.setValue(resetY,true); zRange.setValue(0,true); } }); h1.add(reset); createClothControl(); } private VertexAndNormal currentSelection; private VertexAndNormal firstSelection; private VertexAndNormal secondSelection; private void createClothControl(){ gui.add(new Label("Cloth")); //tmp HorizontalPanel h=new HorizontalPanel(); gui.add(h); Button first=new Button("first",new ClickHandler() { @Override public void onClick(ClickEvent event) { firstSelection=currentSelection; } }); h.add(first); Button second=new Button("second",new ClickHandler() { @Override public void onClick(ClickEvent event) { secondSelection=currentSelection; } }); h.add(second); Button addCloth=new Button("add cloth",new ClickHandler() { @Override public void onClick(ClickEvent event) { addCloth(); } }); h.add(addCloth); } protected void addCloth() { if(firstSelection==null || secondSelection==null){ LogUtils.log("need first & second"); return; } Vector3 v1=firstSelection.getVertex().clone().applyMatrix4(mesh.getMatrixWorld()); Vector3 v2=secondSelection.getVertex().clone().applyMatrix4(mesh.getMatrixWorld()); double distance=v1.distanceTo(v2); if(distance==0){ LogUtils.log("invalidly first & second same:"+ThreeLog.get(v1)+","+ThreeLog.get(v1)); return; } LogUtils.log("distance:"+distance); int cw=8; ClothData data=new ClothData(cw, 8, distance, distance); clothControls.addClothData(data); data.getCloth().setPinAll(); data.getCloth().ballSize=ballSize; MeshPhongMaterial material=THREE.MeshPhongMaterial(GWTParamUtils. MeshPhongMaterial().color(0x553817).side(THREE.DoubleSide).specular(0xffffff).shininess(15).transparent(true).opacity(0.5) //.map(THREE.TextureLoader().load("models/mbl3d/hair1.png")) ) ; Mesh object = THREE.Mesh( data.getClothGeometry(), material ); //object.getPosition().set( 0, 0, 0 ); scene.add( object ); data.getCloth().particles.get(0).getOriginal().copy(v1); data.getCloth().particles.get(cw).getOriginal().copy(v2); Vector3 sub=v2.clone().sub(v1).divideScalar(cw+1); for(int i=1;i<cw;i++){ Vector3 v=sub.clone().multiplyScalar(i).add(v1); data.getCloth().particles.get(i).getOriginal().copy(v); } } private VerticalPanel gui; int mouseX,mouseY; protected void onDocumentMouseMove(MouseMoveEvent event) { mouseX = ( event.getClientX() - windowHalfX ); mouseY = ( event.getClientY() - windowHalfY )*2; } public void onWindowResize() { SCREEN_WIDTH = getWindowInnerWidth(); SCREEN_HEIGHT = getWindowInnerHeight(); //re read because of double camera.setAspect(SCREEN_WIDTH / SCREEN_HEIGHT); camera.updateProjectionMatrix(); renderer.setSize( SCREEN_WIDTH , SCREEN_HEIGHT ); } public void render(double now) { //double delta=clock.getDelta(); //do something if(clothControls!=null){ clothControls.update(now); } //not good //camera.getPosition().gwtIncrementX(( - mouseY - camera.getPosition().getX()) * .01);//camera.position.y += ( - mouseY - camera.position.y ) * .01; //camera.lookAt(scene.getPosition()); renderer.render(scene, camera); } @Override public String getTokenKey() { return "hair"; } }