Java tutorial
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package org.shaman.terrain.sketch; import Jama.Matrix; import com.jme3.collision.CollisionResult; import com.jme3.collision.CollisionResults; import com.jme3.input.KeyInput; import com.jme3.input.MouseInput; import com.jme3.input.controls.*; import com.jme3.light.DirectionalLight; import com.jme3.material.Material; import com.jme3.material.RenderState; import com.jme3.math.*; import com.jme3.renderer.Camera; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.renderer.queue.RenderQueue; import com.jme3.scene.*; import com.jme3.scene.control.AbstractControl; import com.jme3.scene.shape.Cylinder; import com.jme3.scene.shape.Quad; import com.jme3.scene.shape.Sphere; import com.jme3.shadow.DirectionalLightShadowRenderer; import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image; import com.jme3.util.BufferUtils; import de.lessvoid.nifty.Nifty; import java.awt.Graphics; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.image.*; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.shaman.terrain.AbstractTerrainStep; import org.shaman.terrain.TerrainHeighmapCreator; import org.shaman.terrain.Heightmap; import org.shaman.terrain.erosion.WaterErosionSimulation; /** * * @author Sebastian Weiss */ public class SketchTerrain extends AbstractTerrainStep implements ActionListener, AnalogListener { private static final Logger LOG = Logger.getLogger(SketchTerrain.class.getName()); private static final Class<? extends AbstractTerrainStep> NEXT_STEP = WaterErosionSimulation.class; private static final float PLANE_QUAD_SIZE = 200 * TerrainHeighmapCreator.TERRAIN_SCALE; private static final float INITIAL_PLANE_DISTANCE = 150f * TerrainHeighmapCreator.TERRAIN_SCALE; private static final float PLANE_MOVE_SPEED = 0.002f * TerrainHeighmapCreator.TERRAIN_SCALE; private static final float CURVE_SIZE = 0.5f * TerrainHeighmapCreator.TERRAIN_SCALE; private static final int CURVE_RESOLUTION = 8; private static final int CURVE_SAMPLES = 128; private static final boolean DEBUG_DIFFUSION_SOLVER = false; private static final boolean SAVE_TEXTURES = false; private static final int DIFFUSION_SOLVER_ITERATIONS = 100; private Heightmap map; private Heightmap originalMap; private Spatial waterPlane; private float planeDistance = INITIAL_PLANE_DISTANCE; private Spatial sketchPlane; private SketchTerrainScreenController screenController; private Node curveNode; private final ArrayList<ControlCurve> featureCurves; private final ArrayList<Node> featureCurveNodes; private final ArrayList<ControlCurveMesh> featureCurveMesh; private boolean addNewCurves = true; private ControlPoint oldSelectedPoint; private WeakHashMap<ControlPoint, Geometry> controlPointMap; private int selectedCurveIndex; private int selectedPointIndex; private Material controlPointMaterial; private Material controlPointSelectedMaterial; private ControlCurve newCurve; private Node newCurveNode; private ControlCurveMesh newCurveMesh; private long lastTime; private final CurvePreset[] presets; private int selectedPreset; private DiffusionSolver solver; private Matrix solverMatrix; private int step; private long lastUpdateTime; public SketchTerrain() { this.featureCurves = new ArrayList<>(); this.featureCurveNodes = new ArrayList<>(); this.featureCurveMesh = new ArrayList<>(); this.presets = DefaultCurvePresets.DEFAULT_PRESETS; this.controlPointMap = new WeakHashMap<>(); } @Override protected void enable() { this.map = (Heightmap) properties.get(AbstractTerrainStep.KEY_HEIGHTMAP); if (this.map == null) { throw new IllegalStateException("SketchTerrain requires a heightmap"); } this.originalMap = this.map.clone(); selectedPreset = 0; controlPointMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md"); controlPointMaterial.setBoolean("UseMaterialColors", true); controlPointMaterial.setColor("Diffuse", ColorRGBA.Gray); controlPointMaterial.setColor("Ambient", ColorRGBA.White); controlPointSelectedMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md"); controlPointSelectedMaterial.setBoolean("UseMaterialColors", true); controlPointSelectedMaterial.setColor("Diffuse", ColorRGBA.Black); controlPointSelectedMaterial.setColor("Ambient", ColorRGBA.White); init(); } @Override protected void disable() { app.getInputManager().removeListener(this); } private void init() { //init nodes guiNode.detachAllChildren(); sceneNode.detachAllChildren(); curveNode = new Node("curves"); sceneNode.attachChild(curveNode); //initial terrain app.setTerrain(originalMap); //init water plane initWaterPlane(); //init sketch plane initSketchPlane(); //init actions // app.getInputManager().addMapping("SolveDiffusion", new KeyTrigger(KeyInput.KEY_RETURN)); app.getInputManager().addMapping("MouseClicked", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); app.getInputManager().addListener(this, "SolveDiffusion", "MouseClicked"); //init light for shadow DirectionalLight light = new DirectionalLight(new Vector3f(0, -1, 0)); light.setColor(ColorRGBA.White); sceneNode.addLight(light); DirectionalLightShadowRenderer shadowRenderer = new DirectionalLightShadowRenderer(app.getAssetManager(), 512, 1); shadowRenderer.setLight(light); app.getHeightmapSpatial().setShadowMode(RenderQueue.ShadowMode.Receive); app.getViewPort().addProcessor(shadowRenderer); //TODO: add pseudo water at height 0 initNifty(); } private void initNifty() { Nifty nifty = app.getNifty(); screenController = new SketchTerrainScreenController(this); nifty.registerScreenController(screenController); nifty.addXml("org/shaman/terrain/sketch/SketchTerrainScreen.xml"); nifty.gotoScreen("SketchTerrain"); sendAvailablePresets(); // nifty.setDebugOptionPanelColors(true); } private void initWaterPlane() { float size = map.getSize() * TerrainHeighmapCreator.TERRAIN_SCALE; Quad quad = new Quad(size, size); Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); mat.setColor("Color", new ColorRGBA(0, 0, 0.5f, 0.5f)); mat.setTransparent(true); mat.getAdditionalRenderState().setAlphaTest(true); mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off); mat.getAdditionalRenderState().setDepthWrite(false); Geometry geom = new Geometry("water", quad); geom.setMaterial(mat); geom.setQueueBucket(RenderQueue.Bucket.Transparent); geom.rotate(FastMath.HALF_PI, 0, 0); geom.move(-size / 2, 0, -size / 2); waterPlane = geom; sceneNode.attachChild(geom); } private void initSketchPlane() { Quad quad = new Quad(PLANE_QUAD_SIZE * 2, PLANE_QUAD_SIZE * 2); Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); mat.setColor("Color", new ColorRGBA(0.5f, 0, 0, 0.5f)); mat.setTransparent(true); mat.getAdditionalRenderState().setAlphaTest(true); mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); Geometry geom = new Geometry("SketchPlaneQuad", quad); geom.setMaterial(mat); geom.setQueueBucket(RenderQueue.Bucket.Translucent); geom.setLocalTranslation(-PLANE_QUAD_SIZE, -PLANE_QUAD_SIZE, 0); sketchPlane = new Node("SketchPlane"); ((Node) sketchPlane).attachChild(geom); sceneNode.attachChild(sketchPlane); sketchPlane.addControl(new AbstractControl() { @Override protected void controlUpdate(float tpf) { //place the plane at the given distance from the camera Vector3f pos = app.getCamera().getLocation().clone(); pos.addLocal(app.getCamera().getDirection().mult(planeDistance)); sketchPlane.setLocalTranslation(pos); //face the camera Quaternion q = sketchPlane.getLocalRotation(); q.lookAt(app.getCamera().getDirection().negate(), Vector3f.UNIT_Y); sketchPlane.setLocalRotation(q); } @Override protected void controlRender(RenderManager rm, ViewPort vp) { } }); app.getInputManager().addMapping("SketchPlaneDist-", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)); app.getInputManager().addMapping("SketchPlaneDist+", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)); app.getInputManager().addListener(this, "SketchPlaneDist-", "SketchPlaneDist+"); } private void addFeatureCurve(ControlCurve curve) { int index = featureCurveNodes.size(); Node node = new Node("feature" + index); featureCurves.add(curve); addControlPointsToNode(curve.getPoints(), node, index); ControlCurveMesh mesh = new ControlCurveMesh(curve, "Curve" + index, app); node.attachChild(mesh.getTubeGeometry()); node.attachChild(mesh.getSlopeGeometry()); node.attachChild(mesh.getSmoothGeometry()); node.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); mesh.getSlopeGeometry().setShadowMode(RenderQueue.ShadowMode.Off); mesh.getSmoothGeometry().setShadowMode(RenderQueue.ShadowMode.Off); featureCurveNodes.add(node); featureCurveMesh.add(mesh); curveNode.attachChild(node); } private void addControlPointsToNode(ControlPoint[] points, Node node, int index) { for (int i = 0; i < points.length; ++i) { Sphere s = new Sphere(CURVE_RESOLUTION, CURVE_RESOLUTION, CURVE_SIZE * 3f); Geometry g = new Geometry(index < 0 ? "dummy" : ("ControlPoint" + index + ":" + i), s); g.setMaterial(controlPointMaterial); g.setLocalTranslation(app.mapHeightmapToWorld(points[i].x, points[i].y, points[i].height)); controlPointMap.put(points[i], g); node.attachChild(g); } } private void startSolving() { LOG.info("Solve diffusion"); //create solver solver = new DiffusionSolver(map.getSize(), featureCurves.toArray(new ControlCurve[featureCurves.size()])); solverMatrix = new Matrix(map.getSize(), map.getSize()); //Save if (DEBUG_DIFFUSION_SOLVER) { solver.saveFloatMatrix(solverMatrix, "diffusion/Iter0.png", 1); } step = 1; screenController.startSolving(); lastUpdateTime = System.currentTimeMillis(); app.setCameraEnabled(false); } private void runSolving() { long maxTime = 50; int minIterations = 5; //run iterations long time = System.currentTimeMillis() + maxTime; for (int i = 0; (System.currentTimeMillis() < time) || (i < minIterations); ++i) { solverMatrix = solver.oneIteration(solverMatrix, step); step++; } screenController.setSolvingIteration(step); //update terrain occasionally time = System.currentTimeMillis(); if (time > lastUpdateTime + 500) { lastUpdateTime = time; for (int x = 0; x < map.getSize(); ++x) { for (int y = 0; y < map.getSize(); ++y) { map.setHeightAt(x, y, (float) solverMatrix.get(x, y) + originalMap.getHeightAt(x, y)); } } app.setTerrain(map); } } private void solvingFinished() { LOG.info("solved"); //fill heighmap for (int x = 0; x < map.getSize(); ++x) { for (int y = 0; y < map.getSize(); ++y) { map.setHeightAt(x, y, (float) solverMatrix.get(x, y) + originalMap.getHeightAt(x, y)); } } app.setTerrain(map); LOG.info("terrain updated"); solver = null; screenController.stopSolving(); app.setCameraEnabled(true); } @Override public void update(float tpf) { if (solver != null) { runSolving(); } } @Override public void onAction(String name, boolean isPressed, float tpf) { if ("SolveDiffusion".equals(name) && isPressed) { startSolving(); } else if ("MouseClicked".equals(name) && isPressed) { Vector3f dir = app.getCamera().getWorldCoordinates(app.getInputManager().getCursorPosition(), 1); dir.subtractLocal(app.getCamera().getLocation()); dir.normalizeLocal(); Ray ray = new Ray(app.getCamera().getLocation(), dir); if (addNewCurves) { addNewPoint(ray); } else { pickCurve(ray); } } } @Override public void onAnalog(String name, float value, float tpf) { switch (name) { case "SketchPlaneDist-": planeDistance *= 1 + PLANE_MOVE_SPEED; break; case "SketchPlaneDist+": planeDistance /= 1 + PLANE_MOVE_SPEED; break; } } //Edit private void addNewPoint(Ray ray) { long time = System.currentTimeMillis(); if (time < lastTime + 200) { //finish current curve if (newCurve == null) { return; } newCurveMesh = null; curveNode.detachChild(newCurveNode); newCurveNode = null; if (newCurve.getPoints().length >= 2) { addFeatureCurve(newCurve); } newCurve = null; LOG.info("new feature added"); screenController.setMessage(""); return; } lastTime = time; //create new point CollisionResults results = new CollisionResults(); sketchPlane.collideWith(ray, results); if (results.size() == 0) { return; } Vector3f point = results.getClosestCollision().getContactPoint(); point = app.mapWorldToHeightmap(point); ControlPoint p = createNewControlPoint(point.x, point.y, point.z); //add to curve if (newCurve == null) { LOG.info("start a new feature"); newCurve = new ControlCurve(new ControlPoint[] { p }); newCurveNode = new Node(); addControlPointsToNode(newCurve.getPoints(), newCurveNode, -1); newCurveMesh = new ControlCurveMesh(newCurve, "dummy", app); newCurveNode.attachChild(newCurveMesh.getSlopeGeometry()); newCurveNode.attachChild(newCurveMesh.getSmoothGeometry()); newCurveNode.attachChild(newCurveMesh.getTubeGeometry()); curveNode.attachChild(newCurveNode); screenController.setMessage("ADDING A NEW FEATURE"); } else { newCurve.setPoints(ArrayUtils.add(newCurve.getPoints(), p)); newCurveNode.detachAllChildren(); addControlPointsToNode(newCurve.getPoints(), newCurveNode, -1); newCurveMesh.updateMesh(); newCurveNode.attachChild(newCurveMesh.getSlopeGeometry()); newCurveNode.attachChild(newCurveMesh.getSmoothGeometry()); newCurveNode.attachChild(newCurveMesh.getTubeGeometry()); } newCurveNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); newCurveMesh.getSlopeGeometry().setShadowMode(RenderQueue.ShadowMode.Off); newCurveMesh.getSmoothGeometry().setShadowMode(RenderQueue.ShadowMode.Off); } private ControlPoint createNewControlPoint(float x, float y, float h) { ControlPoint[] points = newCurve == null ? new ControlPoint[0] : newCurve.getPoints(); return presets[selectedPreset].createControlPoint(x, y, h, points, map); } private void pickCurve(Ray ray) { CollisionResults results = new CollisionResults(); curveNode.collideWith(ray, results); if (results.size() == 0) { selectedCurveIndex = -1; selectedPointIndex = -1; selectCurve(-1, null); } else { CollisionResult result = results.getClosestCollision(); Geometry geom = result.getGeometry(); if (geom.getName().startsWith("Curve")) { int index = Integer.parseInt(geom.getName().substring("Curve".length())); selectedCurveIndex = index; selectedPointIndex = -1; selectCurve(index, null); } else if (geom.getName().startsWith("ControlPoint")) { String n = geom.getName().substring("ControlPoint".length()); String[] parts = n.split(":"); selectedCurveIndex = Integer.parseInt(parts[0]); selectedPointIndex = Integer.parseInt(parts[1]); selectCurve(selectedCurveIndex, featureCurves.get(selectedCurveIndex).getPoints()[selectedPointIndex]); } else { selectedCurveIndex = -1; selectedPointIndex = -1; selectCurve(-1, null); } } } //GUI-Interface public void guiAddCurves() { addNewCurves = true; } public void guiEditCurves() { addNewCurves = false; if (newCurve != null) { newCurveMesh = null; curveNode.detachChild(newCurveNode); newCurveNode = null; newCurve = null; screenController.setMessage(""); } } public void guiShowCurves(boolean show) { curveNode.setCullHint(show ? Spatial.CullHint.Inherit : Spatial.CullHint.Always); } private void selectCurve(int curveIndex, ControlPoint point) { screenController.selectCurve(curveIndex, point); if (oldSelectedPoint != null) { Geometry geom = controlPointMap.get(oldSelectedPoint); if (geom != null) { geom.setMaterial(controlPointMaterial); } } if (point != null) { Geometry geom = controlPointMap.get(point); if (geom != null) { geom.setMaterial(controlPointSelectedMaterial); } } oldSelectedPoint = point; } public void guiDeleteCurve() { if (selectedCurveIndex == -1) { return; } Node n = featureCurveNodes.get(selectedCurveIndex); featureCurveNodes.set(selectedCurveIndex, null); featureCurves.set(selectedCurveIndex, null); curveNode.detachChild(n); selectCurve(-1, null); selectedCurveIndex = -1; } public void guiDeleteControlPoint() { if (selectedCurveIndex == -1 || selectedPointIndex == -1) { return; } ControlCurve c = featureCurves.get(selectedCurveIndex); if (c.getPoints().length <= 2) { LOG.warning("Cannot delete control point, at least 2 points are required"); return; } featureCurves.remove(selectedCurveIndex); Node n = featureCurveNodes.remove(selectedCurveIndex); curveNode.detachChild(n); c = new ControlCurve(ArrayUtils.remove(c.getPoints(), selectedPointIndex)); addFeatureCurve(c); } public void guiControlPointChanged() { if (selectedCurveIndex == -1 || selectedPointIndex == -1) { return; } System.out.println( "control point changed: " + featureCurves.get(selectedCurveIndex).getPoints()[selectedPointIndex]); ControlCurveMesh mesh = featureCurveMesh.get(selectedCurveIndex); mesh.updateMesh(); } private void sendAvailablePresets() { String[] names = new String[presets.length]; for (int i = 0; i < presets.length; ++i) { names[i] = presets[i].getName(); } screenController.setAvailablePresets(names); } public void guiPresetChanged(int index) { selectedPreset = index; } public void guiSolve() { startSolving(); } public void guiStopSolve() { solvingFinished(); } public void guiNextStep() { Map<Object, Object> props = new HashMap<>(properties); props.put(KEY_HEIGHTMAP, map.clone()); //replace heightmap with new one nextStep(NEXT_STEP, props); } private class DiffusionSolver { //settings private final double BETA_SCALE = 1.0; private final double ALPHA_SCALE = 0.7; private final double GRADIENT_SCALE = 1.3 / TerrainHeighmapCreator.HEIGHMAP_HEIGHT_SCALE; private final float SLOPE_ALPHA_FACTOR = 0f; private final boolean EVALUATE_SLOPE_RELATIVE_TO_ORIGINAL = false; private final boolean LIMIT_SMOOTHING = true; //input private final int size; private final ControlCurve[] curves; //matrices private Matrix elevation; //target elevation private Matrix alpha, beta, gamma; //factors specifying the influence of the gradient, smootheness and elevation private Matrix gradX, gradY; //the normalized direction of the gradient at this point private Matrix gradH; //the target gradient / height difference from the reference point specified by gradX, gradY private DiffusionSolver(int size, ControlCurve[] curves) { this.size = size; this.curves = curves; rasterize(); } /** * Rasterizes the control curves in the matrices */ private void rasterize() { elevation = new Matrix(size, size); alpha = new Matrix(size, size); beta = new Matrix(size, size); gamma = new Matrix(size, size); gradX = new Matrix(size, size); gradY = new Matrix(size, size); gradH = new Matrix(size, size); //render meshes Material vertexColorMat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); vertexColorMat.setBoolean("VertexColor", true); vertexColorMat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off); vertexColorMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); vertexColorMat.getAdditionalRenderState().setDepthTest(true); vertexColorMat.getAdditionalRenderState().setDepthWrite(true); Material grayMat1 = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); //grayMat1.setColor("Color", new ColorRGBA(1-(float) ALPHA_SCALE, 1-(float) ALPHA_SCALE, 1-(float) ALPHA_SCALE, 1)); grayMat1.setColor("Color", ColorRGBA.White); grayMat1.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off); grayMat1.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); grayMat1.getAdditionalRenderState().setDepthTest(true); grayMat1.getAdditionalRenderState().setDepthWrite(true); Material grayMat2 = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); // grayMat2.setColor("Color", new ColorRGBA(1-(float) BETA_SCALE, 1-(float) BETA_SCALE, 1-(float) BETA_SCALE, 1)); grayMat2.setColor("Color", ColorRGBA.White); grayMat2.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off); grayMat2.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); grayMat2.getAdditionalRenderState().setDepthTest(true); grayMat2.getAdditionalRenderState().setDepthWrite(true); Node elevationNode = new Node(); Node gradientNode = new Node(); Node smoothingNode = new Node(); for (ControlCurve curve : curves) { if (curve == null) continue; //sample curve int samples = 32; ControlPoint[] points = new ControlPoint[samples + 1]; for (int i = 0; i <= samples; ++i) { points[i] = curve.interpolate(i / (float) samples); } Mesh lineMesh = createLineMesh(points); Geometry lineGeom = new Geometry("line", lineMesh); lineGeom.setMaterial(vertexColorMat); lineGeom.setQueueBucket(RenderQueue.Bucket.Gui); Mesh plateauMesh = createPlateauMesh(points); Geometry plateauGeom = new Geometry("plateau", plateauMesh); plateauGeom.setMaterial(vertexColorMat); plateauGeom.setQueueBucket(RenderQueue.Bucket.Gui); elevationNode.attachChild(lineGeom); elevationNode.attachChild(plateauGeom); Mesh slopeMesh = createSlopeMesh(points); Geometry slopeGeom = new Geometry("slope", slopeMesh); slopeGeom.setMaterial(vertexColorMat); slopeGeom.setQueueBucket(RenderQueue.Bucket.Gui); gradientNode.attachChild(slopeGeom); Geometry smoothGeom = new Geometry("smooth", createSmoothingMesh(points)); smoothGeom.setMaterial(vertexColorMat); smoothGeom.setQueueBucket(RenderQueue.Bucket.Gui); smoothingNode.attachChild(smoothGeom); Geometry smoothGeom2 = new Geometry("smooth2", slopeMesh); smoothGeom2.setMaterial(grayMat1); smoothGeom2.setQueueBucket(RenderQueue.Bucket.Gui); smoothingNode.attachChild(smoothGeom2); Geometry smoothGeom3 = new Geometry("smooth3", lineMesh); smoothGeom3.setMaterial(grayMat2); smoothGeom3.setQueueBucket(RenderQueue.Bucket.Gui); smoothingNode.attachChild(smoothGeom3); Geometry smoothGeom4 = new Geometry("smooth4", plateauMesh); smoothGeom4.setMaterial(grayMat2); smoothGeom4.setQueueBucket(RenderQueue.Bucket.Gui); smoothingNode.attachChild(smoothGeom4); } elevationNode.setCullHint(Spatial.CullHint.Never); gradientNode.setCullHint(Spatial.CullHint.Never); smoothingNode.setCullHint(Spatial.CullHint.Never); for (Spatial s : elevationNode.getChildren()) { fillMatrix(elevation, s, true); } fillSlopeMatrix(gradientNode); fillMatrix(gamma, smoothingNode, true); vertexColorMat.setBoolean("VertexColor", false); vertexColorMat.setColor("Color", ColorRGBA.White); fillMatrix(beta, elevationNode, false); //copy existing heightmap data for (int x = 0; x < map.getSize(); ++x) { for (int y = 0; y < map.getSize(); ++y) { elevation.set(x, y, elevation.get(x, y) - originalMap.getHeightAt(x, y)); } } alpha.timesEquals(ALPHA_SCALE); beta.timesEquals(BETA_SCALE); gradH.timesEquals(GRADIENT_SCALE); //save for debugging if (DEBUG_DIFFUSION_SOLVER || SAVE_TEXTURES) { saveFloatMatrix(elevation, "diffusion/Elevation.png", 0.5); saveMatrix(beta, "diffusion/Beta.png"); saveMatrix(alpha, "diffusion/Alpha.png"); saveMatrix(gamma, "diffusion/Gamma.png"); saveFloatMatrix(gradX, "diffusion/GradX.png", 1); saveFloatMatrix(gradY, "diffusion/GradY.png", 1); saveFloatMatrix(gradH, "diffusion/GradH.png", 50); } LOG.info("curves rasterized"); } public Matrix oneIteration(Matrix last, int iteration) { Matrix mat = new Matrix(size, size); //add elevation constraint mat.plusEquals(elevation.arrayTimes(beta)); //add laplace constraint Matrix laplace = new Matrix(size, size); // Matrix gamma1 = new Matrix(size, size); // Matrix gamma2 = new Matrix(size, size); for (int x = 0; x < size; ++x) { for (int y = 0; y < size; ++y) { double v = 0; v += last.get(Math.max(0, x - 1), y); v += last.get(Math.min(size - 1, x + 1), y); v += last.get(x, Math.max(0, y - 1)); v += last.get(x, Math.min(size - 1, y + 1)); v /= 4.0; v *= 1 - alpha.get(x, y) - beta.get(x, y); if (LIMIT_SMOOTHING) { v *= gamma.get(x, y); } // gamma2.set(x, y, 1 - alpha.get(x, y) - beta.get(x, y)); // gamma1.set(x, y, (1 - alpha.get(x, y) - beta.get(x, y)) * gamma.get(x, y)); laplace.set(x, y, v); } } // if (iteration==1) { // saveFloatMatrix(gamma1, "diffusion/Gamma1.png", 1); // saveFloatMatrix(gamma2, "diffusion/Gamma2.png", 1); // saveFloatMatrix(gamma1.minus(gamma2), "diffusion/GammaDiff.png", 1); // } // saveFloatMatrix(laplace, "diffusion/Laplace"+iteration+".png",1); mat.plusEquals(laplace); //add gradient constraint Matrix gradient = new Matrix(size, size); for (int x = 0; x < size; ++x) { for (int y = 0; y < size; ++y) { double v = 0; //v = gradH.get(x, y); double gx = gradX.get(x, y); double gy = gradY.get(x, y); if (gx == 0 && gy == 0) { v = 0; //no gradient } else { double h1, h2; if (EVALUATE_SLOPE_RELATIVE_TO_ORIGINAL) { h1 = last.get(clamp(x - (int) Math.signum(gx)), y) + originalMap.getHeightAt(clamp(x - (int) Math.signum(gx)), y) - originalMap.getHeightAt(x, y); h2 = last.get(x, clamp(y - (int) Math.signum(gy))) + originalMap.getHeightAt(x, clamp(y - (int) Math.signum(gy))) - originalMap.getHeightAt(x, y); } else { h1 = last.get(clamp(x - (int) Math.signum(gx)), y); h2 = last.get(x, clamp(y - (int) Math.signum(gy))); } v += gx * gx * h1; v += gy * gy * h2; } gradient.set(x, y, v); } } Matrix oldGradient; if (DEBUG_DIFFUSION_SOLVER) { oldGradient = gradient.copy(); //Test } if (DEBUG_DIFFUSION_SOLVER) { saveFloatMatrix(gradient, "diffusion/Gradient" + iteration + ".png", 1); } Matrix gradChange = gradH.plus(gradient); gradChange.arrayTimesEquals(alpha); if (DEBUG_DIFFUSION_SOLVER) { saveFloatMatrix(gradChange, "diffusion/GradChange" + iteration + ".png", 1); } mat.plusEquals(gradChange); //Test if (DEBUG_DIFFUSION_SOLVER) { Matrix newGradient = new Matrix(size, size); for (int x = 0; x < size; ++x) { for (int y = 0; y < size; ++y) { double v = 0; v += gradX.get(x, y) * gradX.get(x, y) * mat.get(clamp(x - (int) Math.signum(gradX.get(x, y))), y); v += gradY.get(x, y) * gradY.get(x, y) * mat.get(x, clamp(y - (int) Math.signum(gradY.get(x, y)))); v -= mat.get(x, y); newGradient.set(x, y, -v); } } Matrix diff = oldGradient.minus(newGradient); saveFloatMatrix(diff, "diffusion/Diff" + iteration + ".png", 1); } return mat; } private int clamp(int i) { return Math.max(0, Math.min(size - 1, i)); } private Mesh createLineMesh(ControlPoint[] points) { Vector3f[] pos = new Vector3f[points.length]; ColorRGBA[] col = new ColorRGBA[points.length]; for (int i = 0; i < points.length; ++i) { pos[i] = new Vector3f(points[i].x, points[i].y, 1 - points[i].height); float height = points[i].hasElevation ? points[i].height : 0; col[i] = new ColorRGBA(height, height, height, 1); } Mesh mesh = new Mesh(); mesh.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(pos)); mesh.setBuffer(VertexBuffer.Type.Color, 4, BufferUtils.createFloatBuffer(col)); mesh.setMode(Mesh.Mode.LineStrip); mesh.setLineWidth(1); return mesh; } private Mesh createPlateauMesh(ControlPoint[] points) { Vector3f[] pos = new Vector3f[points.length * 3]; ColorRGBA[] col = new ColorRGBA[points.length * 3]; for (int i = 0; i < points.length; ++i) { pos[3 * i] = new Vector3f(points[i].x, points[i].y, points[i].height * 100 - 100); float dx, dy; if (i == 0) { dx = points[i + 1].x - points[i].x; dy = points[i + 1].y - points[i].y; } else if (i == points.length - 1) { dx = points[i].x - points[i - 1].x; dy = points[i].y - points[i - 1].y; } else { dx = (points[i + 1].x - points[i - 1].x) / 2f; dy = (points[i + 1].y - points[i - 1].y) / 2f; } float sum = (float) Math.sqrt(dx * dx + dy * dy); dx /= sum; dy /= sum; pos[3 * i + 1] = pos[3 * i].add(points[i].plateau * -dy, points[i].plateau * dx, points[i].height * 100 - 100); pos[3 * i + 2] = pos[3 * i].add(points[i].plateau * dy, points[i].plateau * -dx, points[i].height * 100 - 100); float height = points[i].hasElevation ? points[i].height : 0; col[3 * i] = new ColorRGBA(height, height, height, 1); col[3 * i + 1] = col[3 * i]; col[3 * i + 2] = col[3 * i]; } int[] index = new int[(points.length - 1) * 12]; for (int i = 0; i < points.length - 1; ++i) { index[12 * i] = 3 * i; index[12 * i + 1] = 3 * i + 3; index[12 * i + 2] = 3 * i + 1; index[12 * i + 3] = 3 * i + 3; index[12 * i + 4] = 3 * i + 4; index[12 * i + 5] = 3 * i + 1; index[12 * i + 6] = 3 * i; index[12 * i + 7] = 3 * i + 2; index[12 * i + 8] = 3 * i + 3; index[12 * i + 9] = 3 * i + 3; index[12 * i + 10] = 3 * i + 2; index[12 * i + 11] = 3 * i + 5; } Mesh m = new Mesh(); m.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(pos)); m.setBuffer(VertexBuffer.Type.Color, 4, BufferUtils.createFloatBuffer(col)); m.setBuffer(VertexBuffer.Type.Index, 1, index); m.setMode(Mesh.Mode.Triangles); return m; } private Mesh createSlopeMesh(ControlPoint[] points) { Vector3f[] pos = new Vector3f[points.length * 4]; ColorRGBA[] col = new ColorRGBA[points.length * 4]; for (int i = 0; i < points.length; ++i) { Vector3f p = new Vector3f(points[i].x, points[i].y, 1 - points[i].height); float dx, dy; if (i == 0) { dx = points[i + 1].x - points[i].x; dy = points[i + 1].y - points[i].y; } else if (i == points.length - 1) { dx = points[i].x - points[i - 1].x; dy = points[i].y - points[i - 1].y; } else { dx = (points[i + 1].x - points[i - 1].x) / 2f; dy = (points[i + 1].y - points[i - 1].y) / 2f; } float sum = (float) Math.sqrt(dx * dx + dy * dy); dx /= sum; dy /= sum; pos[4 * i + 0] = p.add(points[i].plateau * -dy, points[i].plateau * dx, 0); pos[4 * i + 1] = p.add( (points[i].plateau + FastMath.cos(points[i].angle1) * points[i].extend1) * -dy, (points[i].plateau + FastMath.cos(points[i].angle1) * points[i].extend1) * dx, 0); pos[4 * i + 2] = p.add(points[i].plateau * dy, points[i].plateau * -dx, 0); pos[4 * i + 3] = p.add( (points[i].plateau + FastMath.cos(points[i].angle2) * points[i].extend2) * dy, (points[i].plateau + FastMath.cos(points[i].angle2) * points[i].extend2) * -dx, 0); ColorRGBA c1, c2, c3, c4; c1 = new ColorRGBA(-dy / 2 + 0.5f, dx / 2 + 0.5f, -FastMath.sin(points[i].angle1) / 2 + 0.5f, 1); c2 = new ColorRGBA(c1.r, c1.g, c1.b, 0.5f); c3 = new ColorRGBA(dy / 2 + 0.5f, -dx / 2 + 0.5f, -FastMath.sin(points[i].angle2) / 2 + 0.5f, 1); c4 = new ColorRGBA(c3.r, c3.g, c3.b, 0.5f); col[4 * i + 0] = c1; col[4 * i + 1] = c2; col[4 * i + 2] = c3; col[4 * i + 3] = c4; } int[] index = new int[(points.length - 1) * 12]; for (int i = 0; i < points.length - 1; ++i) { index[12 * i] = 4 * i; index[12 * i + 1] = 4 * i + 4; index[12 * i + 2] = 4 * i + 1; index[12 * i + 3] = 4 * i + 4; index[12 * i + 4] = 4 * i + 5; index[12 * i + 5] = 4 * i + 1; index[12 * i + 6] = 4 * i + 2; index[12 * i + 7] = 4 * i + 3; index[12 * i + 8] = 4 * i + 6; index[12 * i + 9] = 4 * i + 6; index[12 * i + 10] = 4 * i + 3; index[12 * i + 11] = 4 * i + 7; } Mesh m = new Mesh(); m.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(pos)); m.setBuffer(VertexBuffer.Type.Color, 4, BufferUtils.createFloatBuffer(col)); m.setBuffer(VertexBuffer.Type.Index, 1, index); m.setMode(Mesh.Mode.Triangles); return m; } private Mesh createSmoothingMesh(ControlPoint[] points) { Vector3f[] pos = new Vector3f[points.length * 4 + 4]; ColorRGBA[] col = new ColorRGBA[points.length * 4 + 4]; ColorRGBA c1 = new ColorRGBA(1, 1, 1, 1); ColorRGBA c2 = new ColorRGBA(1, 1, 1, 1); for (int i = 0; i < points.length; ++i) { Vector3f p = new Vector3f(points[i].x, points[i].y, 1 - points[i].height); float dx, dy; if (i == 0) { dx = points[i + 1].x - points[i].x; dy = points[i + 1].y - points[i].y; } else if (i == points.length - 1) { dx = points[i].x - points[i - 1].x; dy = points[i].y - points[i - 1].y; } else { dx = (points[i + 1].x - points[i - 1].x) / 2f; dy = (points[i + 1].y - points[i - 1].y) / 2f; } float sum = (float) Math.sqrt(dx * dx + dy * dy); dx /= sum; dy /= sum; pos[4 * i + 0] = p.add( (points[i].plateau + FastMath.cos(points[i].angle1) * points[i].extend1) * -dy, (points[i].plateau + FastMath.cos(points[i].angle1) * points[i].extend1) * dx, 0); pos[4 * i + 1] = p.add( (points[i].plateau + (FastMath.cos(points[i].angle1) * points[i].extend1) + points[i].smooth1) * -dy, (points[i].plateau + (FastMath.cos(points[i].angle1) * points[i].extend1) + points[i].smooth1) * dx, 0); pos[4 * i + 2] = p.add( (points[i].plateau + FastMath.cos(points[i].angle2) * points[i].extend2) * dy, (points[i].plateau + FastMath.cos(points[i].angle2) * points[i].extend2) * -dx, 0); pos[4 * i + 3] = p.add( (points[i].plateau + (FastMath.cos(points[i].angle2) * points[i].extend2) + points[i].smooth2) * dy, (points[i].plateau + (FastMath.cos(points[i].angle2) * points[i].extend2) + points[i].smooth2) * -dx, 0); col[4 * i + 0] = c1; col[4 * i + 1] = c2; col[4 * i + 2] = c1; col[4 * i + 3] = c2; } //add first and last smoothing Vector3f p = new Vector3f(points[0].x, points[0].y, 1 - points[0].height); float dx, dy; dx = points[1].x - points[0].x; dy = points[1].y - points[0].y; float sum = (float) Math.sqrt(dx * dx + dy * dy); dx /= sum; dy /= sum; float smooth = (points[0].smooth1 + points[0].smooth2) / 2; pos[pos.length - 4] = p.add(points[0].plateau * -dy - smooth * dx, points[0].plateau * dx - smooth * dy, 0); pos[pos.length - 3] = p.add(points[0].plateau * +dy - smooth * dx, points[0].plateau * -dx - smooth * dy, 0); col[pos.length - 4] = c2; col[pos.length - 3] = c2; p = new Vector3f(points[points.length - 1].x, points[points.length - 1].y, 1 - points[points.length - 1].height); dx = points[points.length - 1].x - points[points.length - 2].x; dy = points[points.length - 1].y - points[points.length - 2].y; sum = (float) Math.sqrt(dx * dx + dy * dy); dx /= sum; dy /= sum; smooth = (points[points.length - 1].smooth1 + points[points.length - 1].smooth2) / 2; pos[pos.length - 2] = p.add(points[points.length - 1].plateau * -dy + smooth * dx, points[points.length - 1].plateau * dx + smooth * dy, 0); pos[pos.length - 1] = p.add(points[points.length - 1].plateau * +dy + smooth * dx, points[points.length - 1].plateau * -dx + smooth * dy, 0); col[pos.length - 2] = c2; col[pos.length - 1] = c2; //indices int[] index = new int[(points.length - 1) * 12 + 24]; for (int i = 0; i < points.length - 1; ++i) { index[12 * i] = 4 * i; index[12 * i + 1] = 4 * i + 4; index[12 * i + 2] = 4 * i + 1; index[12 * i + 3] = 4 * i + 4; index[12 * i + 4] = 4 * i + 5; index[12 * i + 5] = 4 * i + 1; index[12 * i + 6] = 4 * i + 2; index[12 * i + 7] = 4 * i + 3; index[12 * i + 8] = 4 * i + 6; index[12 * i + 9] = 4 * i + 6; index[12 * i + 10] = 4 * i + 3; index[12 * i + 11] = 4 * i + 7; } int offset = 12 * (points.length - 1); index[offset] = 0; index[offset + 1] = pos.length - 4; index[offset + 2] = 1; index[offset + 3] = 0; index[offset + 4] = pos.length - 4; index[offset + 5] = pos.length - 3; index[offset + 6] = 0; index[offset + 7] = pos.length - 3; index[offset + 8] = 2; index[offset + 9] = 2; index[offset + 10] = pos.length - 3; index[offset + 11] = 3; index[offset + 12] = pos.length - 8; index[offset + 13] = pos.length - 2; index[offset + 14] = pos.length - 7; index[offset + 15] = pos.length - 8; index[offset + 16] = pos.length - 2; index[offset + 17] = pos.length - 1; index[offset + 18] = pos.length - 8; index[offset + 19] = pos.length - 1; index[offset + 20] = pos.length - 6; index[offset + 21] = pos.length - 6; index[offset + 22] = pos.length - 1; index[offset + 23] = pos.length - 5; Mesh m = new Mesh(); m.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(pos)); m.setBuffer(VertexBuffer.Type.Color, 4, BufferUtils.createFloatBuffer(col)); m.setBuffer(VertexBuffer.Type.Index, 1, index); m.setMode(Mesh.Mode.Triangles); return m; } @Deprecated private Mesh createSlopeAlphaMesh(ControlPoint[] points) { Vector3f[] pos = new Vector3f[points.length * 6]; ColorRGBA[] col = new ColorRGBA[points.length * 6]; for (int i = 0; i < points.length; ++i) { Vector3f p = new Vector3f(points[i].x, points[i].y, 1 - points[i].height); float dx, dy; if (i == 0) { dx = points[i + 1].x - points[i].x; dy = points[i + 1].y - points[i].y; } else if (i == points.length - 1) { dx = points[i].x - points[i - 1].x; dy = points[i].y - points[i - 1].y; } else { dx = (points[i + 1].x - points[i - 1].x) / 2f; dy = (points[i + 1].y - points[i - 1].y) / 2f; } float sum = (float) Math.sqrt(dx * dx + dy * dy); dx /= sum; dy /= sum; float factor = SLOPE_ALPHA_FACTOR; pos[6 * i + 0] = p.add(points[i].plateau * -dy, points[i].plateau * dx, 0); pos[6 * i + 1] = p.add( (points[i].plateau + FastMath.cos(points[i].angle1) * points[i].extend1 * factor) * -dy, (points[i].plateau + FastMath.cos(points[i].angle1) * points[i].extend1 * factor) * dx, 0); pos[6 * i + 2] = p.add( (points[i].plateau + FastMath.cos(points[i].angle1) * points[i].extend1) * -dy, (points[i].plateau + FastMath.cos(points[i].angle1) * points[i].extend1) * dx, 0); pos[6 * i + 3] = p.add(points[i].plateau * dy, points[i].plateau * -dx, 0); pos[6 * i + 4] = p.add( (points[i].plateau + FastMath.cos(points[i].angle2) * points[i].extend2 * factor) * dy, (points[i].plateau + FastMath.cos(points[i].angle2) * points[i].extend2 * factor) * -dx, 0); pos[6 * i + 5] = p.add( (points[i].plateau + FastMath.cos(points[i].angle2) * points[i].extend2) * dy, (points[i].plateau + FastMath.cos(points[i].angle2) * points[i].extend2) * -dx, 0); ColorRGBA c1 = new ColorRGBA(0, 0, 0, 1); ColorRGBA c2 = new ColorRGBA(1, 1, 1, 1); col[6 * i + 0] = c1; col[6 * i + 1] = c2; col[6 * i + 2] = c1; col[6 * i + 3] = c1; col[6 * i + 4] = c2; col[6 * i + 5] = c1; } int[] index = new int[(points.length - 1) * 24]; for (int i = 0; i < points.length - 1; ++i) { index[24 * i + 0] = 6 * i; index[24 * i + 1] = 6 * i + 6; index[24 * i + 2] = 6 * i + 1; index[24 * i + 3] = 6 * i + 6; index[24 * i + 4] = 6 * i + 7; index[24 * i + 5] = 6 * i + 1; index[24 * i + 6] = 6 * i + 1; index[24 * i + 7] = 6 * i + 7; index[24 * i + 8] = 6 * i + 2; index[24 * i + 9] = 6 * i + 7; index[24 * i + 10] = 6 * i + 8; index[24 * i + 11] = 6 * i + 2; index[24 * i + 12] = 6 * i + 3; index[24 * i + 13] = 6 * i + 9; index[24 * i + 14] = 6 * i + 4; index[24 * i + 15] = 6 * i + 9; index[24 * i + 16] = 6 * i + 10; index[24 * i + 17] = 6 * i + 4; index[24 * i + 18] = 6 * i + 4; index[24 * i + 19] = 6 * i + 10; index[24 * i + 20] = 6 * i + 5; index[24 * i + 21] = 6 * i + 10; index[24 * i + 22] = 6 * i + 11; index[24 * i + 23] = 6 * i + 5; } Mesh m = new Mesh(); m.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(pos)); m.setBuffer(VertexBuffer.Type.Color, 4, BufferUtils.createFloatBuffer(col)); m.setBuffer(VertexBuffer.Type.Index, 1, index); m.setMode(Mesh.Mode.Triangles); return m; } /** * Renders the given scene in a top-down manner in the given matrix * @param matrix * @param scene */ private void fillMatrix(Matrix matrix, Spatial scene, boolean max) { //init Camera cam = new Camera(size, size); cam.setParallelProjection(true); ViewPort view = new ViewPort("Off", cam); view.setClearFlags(true, true, true); FrameBuffer buffer = new FrameBuffer(size, size, 1); buffer.setDepthBuffer(Image.Format.Depth); buffer.setColorBuffer(Image.Format.RGBA32F); view.setOutputFrameBuffer(buffer); view.attachScene(scene); //render scene.updateGeometricState(); view.setEnabled(true); app.getRenderManager().renderViewPort(view, 0); //retrive data ByteBuffer data = BufferUtils.createByteBuffer(size * size * 4 * 4); app.getRenderer().readFrameBufferWithFormat(buffer, data, Image.Format.RGBA32F); data.rewind(); for (int y = 0; y < size; ++y) { for (int x = 0; x < size; ++x) { // byte d = data.get(); // matrix.set(x, y, (d & 0xff) / 255.0); // data.get(); data.get(); data.get(); double v = data.getFloat(); double old = matrix.get(x, y); if (max) { v = Math.max(v, old); } else { v += old; } matrix.set(x, y, v); data.getFloat(); data.getFloat(); data.getFloat(); } } } /** * Renders the given scene in a top-down manner in the given matrix * @param matrix * @param scene */ private void fillSlopeMatrix(Spatial scene) { //init Camera cam = new Camera(size, size); cam.setParallelProjection(true); ViewPort view = new ViewPort("Off", cam); view.setClearFlags(true, true, true); view.setBackgroundColor(new ColorRGBA(0.5f, 0.5f, 0.5f, 0f)); FrameBuffer buffer = new FrameBuffer(size, size, 1); buffer.setDepthBuffer(Image.Format.Depth); buffer.setColorBuffer(Image.Format.RGBA32F); view.setOutputFrameBuffer(buffer); view.attachScene(scene); //render scene.updateGeometricState(); view.setEnabled(true); app.getRenderManager().renderViewPort(view, 0); //retrive data ByteBuffer data = BufferUtils.createByteBuffer(size * size * 4 * 4); app.getRenderer().readFrameBufferWithFormat(buffer, data, Image.Format.RGBA32F); data.rewind(); for (int y = 0; y < size; ++y) { for (int x = 0; x < size; ++x) { // double gx = (((data.get() & 0xff) / 256.0) - 0.5) * 2; // double gy = (((data.get() & 0xff) / 256.0) - 0.5) * 2; double gx = (data.getFloat() - 0.5) * 2; double gy = (data.getFloat() - 0.5) * 2; double s = Math.sqrt(gx * gx + gy * gy); if (s == 0) { gx = 0; gy = 0; s = 1; } gradX.set(x, y, (gx / s) + gradX.get(x, y)); gradY.set(x, y, (gy / s) + gradY.get(x, y)); // double v = (((data.get() & 0xff) / 255.0) - 0.5); double v = (data.getFloat() - 0.5); if (Math.abs(v) < 0.002) { v = 0; } gradH.set(x, y, v * 2 + gradH.get(x, y)); // data.get(); double a = data.getFloat(); alpha.set(x, y, a); } } } private void saveMatrix(Matrix matrix, String filename) { byte[] buffer = new byte[size * size]; int i = 0; for (int x = 0; x < size; ++x) { for (int y = 0; y < size; ++y) { buffer[i] = (byte) (matrix.get(x, y) * 255); i++; } } ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); int[] nBits = { 8 }; ColorModel cm = new ComponentColorModel(cs, nBits, false, true, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); SampleModel sm = cm.createCompatibleSampleModel(size, size); DataBufferByte db = new DataBufferByte(buffer, size * size); WritableRaster raster = Raster.createWritableRaster(sm, db, null); BufferedImage result = new BufferedImage(cm, raster, false, null); try { ImageIO.write(result, "png", new File(filename)); } catch (IOException ex) { Logger.getLogger(SketchTerrain.class.getName()).log(Level.SEVERE, null, ex); } } private void saveFloatMatrix(Matrix matrix, String filename, double scale) { byte[] buffer = new byte[size * size]; int i = 0; for (int x = 0; x < size; ++x) { for (int y = 0; y < size; ++y) { buffer[i] = (byte) ((matrix.get(x, y) * scale / 2 + 0.5) * 255); i++; } } ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); int[] nBits = { 8 }; ColorModel cm = new ComponentColorModel(cs, nBits, false, true, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); SampleModel sm = cm.createCompatibleSampleModel(size, size); DataBufferByte db = new DataBufferByte(buffer, size * size); WritableRaster raster = Raster.createWritableRaster(sm, db, null); BufferedImage result = new BufferedImage(cm, raster, false, null); try { ImageIO.write(result, "png", new File(filename)); } catch (IOException ex) { Logger.getLogger(SketchTerrain.class.getName()).log(Level.SEVERE, null, ex); } } } }