diff --git a/GlaciersFeatures.ipynb b/GlaciersFeatures.ipynb new file mode 100644 index 0000000..2d49705 --- /dev/null +++ b/GlaciersFeatures.ipynb @@ -0,0 +1,2024 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib as mpl\n", + "import time\n", + "import queue\n", + "import png\n", + "import cv2\n", + "from datetime import datetime\n", + "from skimage.morphology import skeletonize\n", + "\n", + "%matplotlib inline\n", + "mpl.rcParams['image.cmap'] = 'bone'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Input: DEM, ice thickness, ELA mask" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "demPath = 'data/maps/aiguestortes/dem.png'\n", + "icePath = 'data/maps/aiguestortes/ice.png'\n", + "elaPath = 'data/maps/aiguestortes/ela.png'\n", + "\n", + "# IMPORTANT: cell sizes must match those in simulation\n", + "dx = 20\n", + "dy = 20\n", + "\n", + "outPath = 'data/maps/aiguestortes/'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load DEM\n", + "Bed = cv2.imread(demPath, cv2.IMREAD_ANYDEPTH).astype(np.double)*0.1\n", + "nx, ny = Bed.shape\n", + "\n", + "img = cv2.imread(icePath).astype(np.double)\n", + "Ice = img[:,:,0]/255 + img[:,:,1] + img[:,:,2]*255;\n", + "Ice = np.flipud(Ice)\n", + "\n", + "elaMap = np.flipud(cv2.imread(elaPath).astype(np.int)[:,:,0])\n", + "\n", + "Surf = Bed + Ice\n", + "mapShape = Bed.shape\n", + "\n", + "ice_above_ELA = np.logical_and(Ice > 0, elaMap > 0)\n", + "ice_below_ELA = np.logical_and(Ice > 0, elaMap == 0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = plt.figure(figsize=(20, 10))\n", + "ax = fig.add_subplot(131)\n", + "ax.imshow(Bed, cmap='terrain')\n", + "ax = fig.add_subplot(132)\n", + "ax.imshow(np.sqrt(Ice))\n", + "ax = fig.add_subplot(133)\n", + "ax.imshow(ice_above_ELA)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Definitions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dxy = np.sqrt(dx*dx+dy*dy)\n", + "isq2 = 1.0/np.sqrt(2.0)\n", + "CELL_NEIGHS = [ (-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)]\n", + "DIST_NEIGHS = [ dxy, dx, dxy, dy, dy, dxy, dx, dxy]\n", + "DIR_NEIGHS = [(-isq2,-isq2), (-1,0), (-isq2,isq2), (0,-1), (0,1), (isq2,-isq2), (1,0), (isq2,isq2)]\n", + "\n", + "CELL_NEIGHS_4 = [(-1,0), (0,-1), (0,1), (1,0)]\n", + "DIST_NEIGHS_4 = [dy, dx, dx, dy]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "di = np.arange(nx)\n", + "dj = np.arange(ny)\n", + "dip = np.hstack([np.arange(1,nx), nx-1])\n", + "dim = np.hstack([0, np.arange(0, nx-1)])\n", + "djp = np.hstack([np.arange(1,ny), ny-1])\n", + "djm = np.hstack([0, np.arange(0, ny-1)])\n", + "\n", + "cellIdx = (np.meshgrid(di,dj)[1], np.meshgrid(di,dj)[0])\n", + "\n", + "neighs8idx = [\n", + " (np.meshgrid(dim,djm)[1], np.meshgrid(dim,djm)[0]),\n", + " (np.meshgrid(dim,dj)[1], np.meshgrid(dim,dj)[0]),\n", + " (np.meshgrid(dim,djp)[1], np.meshgrid(dim,djp)[0]),\n", + " (np.meshgrid(di,djm)[1], np.meshgrid(di,djm)[0]),\n", + " (np.meshgrid(di,djp)[1], np.meshgrid(di,djp)[0]),\n", + " (np.meshgrid(dip,djm)[1], np.meshgrid(dip,djm)[0]),\n", + " (np.meshgrid(dip,dj)[1], np.meshgrid(dip,dj)[0]),\n", + " (np.meshgrid(dip,djp)[1], np.meshgrid(dip,djp)[0])\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def gradient(field):\n", + " grad_x = (field[dip,:] - field[dim,:])/(2*dx)\n", + " grad_y = (field[:,djp] - field[:,djm])/(2*dy)\n", + " return grad_x, grad_y" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def lineSign(A, B, X):\n", + " return np.sign((B[0] - A[0])*(X[1] - A[1]) - (B[1] - A[1])*(X[0] - A[0]))\n", + "\n", + "def linearstep(x, mi, mx):\n", + " return (lambda t: np.where(t < 0, 0, np.where(t <= 1, t, 1)))( (x-mi)/(mx-mi) )\n", + "\n", + "def smoothstep(x, mi, mx): \n", + " return (lambda t: np.where(t < 0, 0, np.where(t <= 1, 3*t**2-2*t**3, 1)))( (x-mi)/(mx-mi) )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def encodeFieldAsRGB(values):\n", + " c = (np.maximum(0, (values/65536.0)) * (256.0 * 256.0 * 256.0 - 1.0)).astype(int);\n", + " cr = np.bitwise_and(np.right_shift(c, 16), 255).astype(np.uint8);\n", + " cg = np.bitwise_and(np.right_shift(c, 8), 255).astype(np.uint8);\n", + " cb = np.bitwise_and(c, 255).astype(np.uint8);\n", + " c = np.dstack([cb, cg, cr])\n", + " return c" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 1. Glacier properties" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Grad_B_x, Grad_B_y = gradient(Bed)\n", + "Grad_S_x, Grad_S_y = gradient(Surf)\n", + "\n", + "Gradient = np.sqrt(Grad_S_x*Grad_S_x + Grad_S_y*Grad_S_y)\n", + "\n", + "FlowDir = np.dstack([-Grad_S_x, -Grad_S_y])\n", + "FlowDir[Gradient > 0] = FlowDir[Gradient > 0]/Gradient[Gradient > 0, np.newaxis]\n", + "\n", + "SurfaceNormal = np.dstack([-Grad_S_x, -Grad_S_y, np.ones(mapShape)])\n", + "SurfaceNormalMag = np.linalg.norm(SurfaceNormal, axis=2)\n", + "SurfaceNormal[SurfaceNormalMag > 0] = SurfaceNormal[SurfaceNormalMag > 0]/SurfaceNormalMag[SurfaceNormalMag > 0, np.newaxis]\n", + "\n", + "IceSlopeAngle = np.zeros(mapShape) \n", + "IceSlopeAngle[SurfaceNormalMag > 0] = 0.5*np.pi - np.arccos(Gradient[SurfaceNormalMag > 0]\n", + " /SurfaceNormalMag[SurfaceNormalMag > 0])\n", + "\n", + "Tau = 910*9.81*Ice*Gradient\n", + "\n", + "IceFlowing = Tau > 50000 # we will consider 50 kPa the stress regime when ice begins to flow plastically\n", + "\n", + "Gamma_d = 7.26e-5\n", + "Gamma_s = 3.27\n", + "Uslide = Gamma_s * pow(Ice, 2.0) * pow(Gradient, 2.0)\n", + "Udeform = Gamma_d * pow(Ice, 4.0) * pow(Gradient, 2.0)\n", + "Utotal = Uslide + Udeform" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = plt.figure(figsize=(20, 12))\n", + "\n", + "ax = fig.add_subplot(231)\n", + "ax.imshow(Gradient, cmap='gray')\n", + "ax = fig.add_subplot(232)\n", + "ax.imshow(IceSlopeAngle, cmap='gray')\n", + "ax = fig.add_subplot(233)\n", + "ax.imshow(linearstep(Tau, 0, 150000))\n", + "ax = fig.add_subplot(234)\n", + "ax.imshow(IceFlowing)\n", + "ax = fig.add_subplot(235)\n", + "ax.imshow(linearstep(Uslide, 0, 500))\n", + "ax = fig.add_subplot(236)\n", + "ax.imshow(linearstep(Udeform, 0, 500))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "IceNeighbors = np.zeros(mapShape)\n", + "for n in neighs8idx:\n", + " IceNeighbors = IceNeighbors + (Ice[n] > 0).astype(np.int)\n", + "\n", + "RockIceBorder = np.logical_and(Ice <= 0, IceNeighbors > 0)\n", + "ValleyWallDF = cv2.distanceTransform((Ice > 0).astype(np.uint8), cv2.DIST_L2, cv2.DIST_MASK_PRECISE)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cv2.imwrite(outPath + 'valleydist.png', np.flipud(encodeFieldAsRGB(max(dx,dy)*ValleyWallDF).astype(np.uint8)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 2. Glacier segmentation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.1 Upflow segmentation of glacial basins" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# reachable cells downflow from a set of source nodes, returns also distances and source mask\n", + "def downflowCells(surface, mask, sources):\n", + " \n", + " downflow = np.full(mask.shape, False)\n", + " distance = np.full(mask.shape, 0.0)\n", + " isSource = np.full(mask.shape, False)\n", + " \n", + " Q = queue.PriorityQueue()\n", + " for s in sources:\n", + " Q.put((0, tuple(s)))\n", + " isSource[tuple(s)] = True\n", + " \n", + " while not Q.empty():\n", + " d,p = Q.get()\n", + " \n", + " if downflow[p]: # already visited\n", + " continue\n", + " \n", + " downflow[p] = True\n", + " distance[p] = d\n", + " for n,dn in zip(CELL_NEIGHS, DIST_NEIGHS):\n", + " pn = (p[0] + n[0], p[1] + n[1])\n", + " if 0 <= pn[0] < nx and 0 <= pn[1] < ny: \n", + " if not downflow[pn] and mask[pn] and surface[pn] <= surface[p]:\n", + " Q.put((d+dn, pn))\n", + " \n", + " return downflow, distance, isSource\n", + "\n", + "\n", + "# reachable cells upflow from a set of source nodes, returns also distances and source mask\n", + "def upflowCells(surface, mask, sources):\n", + " \n", + " upflow = np.full(mask.shape, False)\n", + " distance = np.full(mask.shape, 0.0)\n", + " isSource = np.full(mask.shape, False)\n", + " \n", + " Q = queue.PriorityQueue()\n", + " for s in sources:\n", + " Q.put((0, tuple(s)))\n", + " isSource[tuple(s)] = True\n", + " \n", + " while not Q.empty():\n", + " d,p = Q.get()\n", + " \n", + " if upflow[p]: # already visited\n", + " continue\n", + " \n", + " upflow[p] = True\n", + " distance[p] = d\n", + " for n,dn in zip(CELL_NEIGHS, DIST_NEIGHS):\n", + " pn = (p[0] + n[0], p[1] + n[1])\n", + " if 0 <= pn[0] < nx and 0 <= pn[1] < ny: \n", + " if not upflow[pn] and mask[pn] and surface[pn] >= surface[p]:\n", + " Q.put((d+dn, pn))\n", + " \n", + " return upflow, distance, isSource" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# segment all glaciers by propagating upflow from lowest points and keeping those big enough\n", + "def upflowSegmentation(surface, mask, minGlacierCells):\n", + " \n", + " GlacierIds = np.zeros(mask.shape, dtype=np.int)\n", + " GlacierMin = np.zeros((1,2), dtype=np.int)\n", + "\n", + " remaining = mask.copy()\n", + " idGlacier = 1\n", + "\n", + " while remaining.any():\n", + "\n", + " iceIdx = np.where(remaining.flatten())[0]\n", + " pmin = np.unravel_index(iceIdx[surface.flatten()[iceIdx].argmin()], surface.shape)\n", + " \n", + " glacierSeg,_,_ = upflowCells(surface, remaining, [pmin])\n", + " remaining[glacierSeg] = False\n", + "\n", + " if (glacierSeg.sum() >= minGlacierCells):\n", + " print('Glacier area', idGlacier, glacierSeg.sum()*dx*dy/1e6, 'km2')\n", + " GlacierIds[glacierSeg] = idGlacier\n", + " GlacierMin = np.vstack([GlacierMin, pmin])\n", + " idGlacier += 1\n", + " \n", + " return GlacierIds, GlacierMin, idGlacier" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "minGlacierArea = 1e6 # 1 km2\n", + "GlacierIdMap, GlacierMinPoints, numGlaciers = upflowSegmentation(Surf, Ice > 0, minGlacierArea/(dx*dy))\n", + "print('Found', numGlaciers, 'relevant glacier areas')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Visualization\n", + "_ = plt.figure(figsize=(15,15))\n", + "plt.imshow(GlacierIdMap, cmap='terrain')\n", + "plt.plot(GlacierMinPoints[1:,1], GlacierMinPoints[1:,0], 'ro', markersize=4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2.2 Subglaciers segmentation using skeleton" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class GlacierSkeleton:\n", + " \n", + " def __init__(self, mask):\n", + " \n", + " self.mask = mask\n", + " self.numPoints = mask.sum()\n", + " \n", + " self.idMap = np.full(self.mask.shape, -1) \n", + " self.points = np.zeros((self.numPoints,2), dtype=np.int)\n", + " skeletonX, skeletonY = self.mask.nonzero()\n", + " for i in range(self.numPoints):\n", + " sx = skeletonX[i]\n", + " sy = skeletonY[i]\n", + " self.points[i] = (sx, sy)\n", + " self.idMap[(sx,sy)] = i\n", + " \n", + " self.property = {}\n", + " \n", + " \n", + " def avgProperty(self, name, values, smoothingRadius):\n", + " \n", + " if len(values.shape) > 2:\n", + " propVals = np.zeros((self.numPoints, values.shape[2]))\n", + " else: \n", + " propVals = np.zeros((self.numPoints, 1))\n", + " \n", + " smoothingRadiusX = int(np.round(smoothingRadius/dx))\n", + " smoothingRadiusY = int(np.round(smoothingRadius/dy))\n", + " \n", + " for i in range(self.numPoints):\n", + " nsum = 0\n", + " sx,sy = self.points[i]\n", + " for di in range(-smoothingRadiusX, smoothingRadiusX+1):\n", + " for dj in range(-smoothingRadiusY, smoothingRadiusY+1):\n", + " px = sx+di\n", + " py = sy+dj\n", + " if self.mask[px, py]:\n", + " propVals[i] += values[px,py]\n", + " nsum += 1\n", + " propVals[i] /= nsum\n", + " \n", + " self.property[name] = propVals\n", + "\n", + " \n", + " def assignFlowDirection(self):\n", + " \n", + " # in order to simplify the graph into a tree, assume each node only flows to another one\n", + " self.flowTo = np.full((self.numPoints, ), -1)\n", + " \n", + " # the lowest point on the skeleton will be the endpoint, not flowing anywhere\n", + " self.minId = self.property['S'].argmin()\n", + " minPoint = self.points[self.minId]\n", + " \n", + " # we want to compute also how far from the endpoint we are\n", + " self.distToMin = np.full((self.numPoints, ), -1.0)\n", + " \n", + " # dijkstra search, based on some cost\n", + " PQ = queue.PriorityQueue()\n", + " PQ.put((0, 0, (minPoint[0], minPoint[1]), -1))\n", + " while not PQ.empty():\n", + " \n", + " _,d,p,pfrom = PQ.get()\n", + " pid = self.idMap[p]\n", + "\n", + " # if we have already visited this node, skip\n", + " if self.flowTo[pid] >= 0:\n", + " continue\n", + "\n", + " self.flowTo[pid] = pfrom\n", + " self.distToMin[pid] = d\n", + " \n", + " for i,n in enumerate(CELL_NEIGHS): \n", + "\n", + " pn = (p[0] + n[0], p[1] + n[1])\n", + " nid = self.idMap[pn]\n", + " dn = d + DIST_NEIGHS[i]\n", + "\n", + " # if skeleton neighbor\n", + " if self.mask[pn] and nid != self.minId:\n", + " # cost based only on distance\n", + " #PQ.put((dn, dn, pn, pid)) \n", + " # cost based on dist^2 and inversely proportional to stress (to better follow flows)\n", + " PQ.put((dn*dn/self.property['Tau'][nid], dn, pn, pid)) \n", + " \n", + " \n", + " def findSubflows(self, minSubglacierLength):\n", + " \n", + " # we want to assign a subglacier id to each skeleton node\n", + " self.subglacier = np.zeros((self.numPoints, ), dtype=np.int)\n", + " \n", + " # keep list of source nodes and where they merge to another glacier\n", + " self.subglacierSources = [-1]\n", + " self.subglacierUnions = [-1]\n", + " self.subglacierLengths = [0]\n", + " \n", + " # first, compute how many nodes flow to each one\n", + " self.inflows = np.full((self.numPoints,), 0)\n", + " for i in range(self.numPoints):\n", + " self.inflows[i] = np.sum(self.flowTo == i)\n", + " \n", + " # sources are nodes with no inflow\n", + " sources = (self.inflows == 0).nonzero()[0]\n", + " sourceDistToGlacier = self.distToMin[sources]\n", + " \n", + " subglacierId = 0\n", + " \n", + " # while not visited sources\n", + " while sourceDistToGlacier.max() > minSubglacierLength:\n", + " \n", + " # new subglacier\n", + " subglacierId += 1\n", + " \n", + " # get farthest source\n", + " maxId = sourceDistToGlacier.argmax()\n", + " farthestSource = sources[maxId]\n", + " sourceDistToGlacier[maxId] = 0\n", + " \n", + " # propagate \"downflow\" until we either reach an endpoint or an already visited subglacier\n", + " currNode = farthestSource\n", + " while self.flowTo[currNode] >= 0 and self.subglacier[currNode] == 0:\n", + " self.subglacier[currNode] = subglacierId\n", + " currNode = self.flowTo[currNode]\n", + " if self.subglacier[currNode] == 0:\n", + " self.subglacier[currNode] = subglacierId\n", + " \n", + " # save source and union nodes\n", + " self.subglacierSources.append(farthestSource)\n", + " self.subglacierUnions .append(currNode)\n", + " self.subglacierLengths.append(sourceDistToGlacier.max())\n", + " \n", + " # now update all distances for remaining sources\n", + " for i in range(sourceDistToGlacier.size):\n", + " \n", + " sid = sources[i]\n", + "\n", + " # already part of a glacier?\n", + " if self.subglacier[sid] > 0:\n", + " continue\n", + "\n", + " # find distance to some already seen glacier\n", + " curr = sid\n", + " while curr >= 0 and self.subglacier[curr] == 0:\n", + " curr = self.flowTo[curr]\n", + " sourceDistToGlacier[i] = self.distToMin[sid] - (self.distToMin[curr] if curr >= 0 else 0)\n", + " \n", + " self.numSubglaciers = subglacierId \n", + " \n", + " \n", + " def subflowsMap(self):\n", + " smap = np.zeros(self.mask.shape)\n", + " for i in range(self.numPoints):\n", + " smap[tuple(self.points[i])] = self.subglacier[i]\n", + " return smap\n", + " \n", + " \n", + " def downflowPath(self, startNode, endNode):\n", + " nodes = []\n", + " curr = startNode\n", + " while self.flowTo[curr] >= 0 and curr != endNode:\n", + " nodes.append(curr)\n", + " curr = self.flowTo[curr]\n", + " return nodes\n", + " \n", + " \n", + " # find the closest non-skeleton cell that is reachable from both branchA and branchB without intersecting the skeleton\n", + " def findClosestInbetweenPoint(self, unionNode, nodeBranchA, nodeBranchB, surface):\n", + "\n", + " pointU = self.points[unionNode]\n", + " pointA = self.points[nodeBranchA]\n", + " pointB = self.points[nodeBranchB]\n", + " \n", + " sharedPoint = pointU\n", + " minDist = 10\n", + " maxSurf = surface[tuple(pointU)]\n", + " for di in range(-3,4):\n", + " for dj in range(-3,4): \n", + " pn = [pointU[0] + di, pointU[1] + dj]\n", + " if self.idMap[tuple(pn)] >= 0:\n", + " continue\n", + " distA = np.linalg.norm(pointA - pn)\n", + " distB = np.linalg.norm(pointB - pn)\n", + " dist = distA + distB\n", + " if dist < minDist or (dist == minDist and surface[tuple(pn)] > maxSurf):\n", + " if lineSign(pointA, pointU, pn) * lineSign(pointB, pointU, pn) < 0:\n", + " minDist = dist\n", + " sharedPoint = pn\n", + " maxSurf = surface[tuple(pn)]\n", + " \n", + " return sharedPoint\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# find the closest cell in goalsMap using a BFS from startPoint and a navmap\n", + "def findClosestPoint(startPoint, navmap, goalsMap, neighSchema=CELL_NEIGHS):\n", + "\n", + " visited = np.full(navmap.shape, False)\n", + " PQ = queue.PriorityQueue()\n", + " PQ.put((0, startPoint))\n", + " \n", + " while not PQ.empty():\n", + " d,p = PQ.get()\n", + " if goalsMap[p]:\n", + " return p,d\n", + " if visited[p]:\n", + " continue \n", + " visited[p] = True\n", + " for n in neighSchema:\n", + " pn = (p[0] + n[0], p[1] + n[1])\n", + " if navmap[pn]:\n", + " dn = np.linalg.norm(np.array(pn) - np.array(startPoint))\n", + " PQ.put((dn, pn))\n", + " \n", + " return None, -1\n", + "\n", + "\n", + "# find the cell with minimum valuefield using a BFS from startPoint and a navmap\n", + "def findLocalMinimum(valuefield, navmap, startPoint):\n", + " pcurr = startPoint\n", + " dist = valuefield[startPoint]\n", + " while True:\n", + " minDist = dist\n", + " minNeigh = pcurr\n", + " for n in CELL_NEIGHS:\n", + " pn = (pcurr[0] + n[0], pcurr[1] + n[1])\n", + " if navmap[pn]: \n", + " d = valuefield[pn]\n", + " if d < minDist:\n", + " minDist = d\n", + " minNeigh = pn\n", + " if minDist < dist:\n", + " dist = minDist\n", + " pcurr = minNeigh\n", + " else:\n", + " break\n", + " \n", + " return pcurr, dist" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# subdivides the contour into A and B, and fills the glacier using a BFS starting with cost 0 at the contours\n", + "def separateSubglaciers(intersectArea, surface, pointA, pointB, weightA, weightB, idA, idB, proportionalContour):\n", + " \n", + " # output\n", + " subflowmap = np.zeros(intersectArea.shape)\n", + " \n", + " # get contour\n", + " _,contours,_ = cv2.findContours(intersectArea.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)\n", + " sortedContours = sorted(contours, key=cv2.contourArea, reverse=True)\n", + " contour = [(x[0][1], x[0][0]) for x in sortedContours[0]]\n", + " \n", + " # lowest point on contour\n", + " contourMin = 0\n", + " for i,c in enumerate(contour):\n", + " if surface[c] < surface[contour[contourMin]]:\n", + " contourMin = i\n", + " \n", + " # contour on left and right sides of glacier flow\n", + " contourClosestA = np.linalg.norm(np.array(contour - pointA), axis=1).argmin()\n", + " contourClosestB = np.linalg.norm(np.array(contour - pointB), axis=1).argmin()\n", + " nodesRight = []\n", + " for i in range(contourMin+1, contourMin + len(contour)):\n", + " cid = i%len(contour)\n", + " nodesRight.append(contour[cid]) \n", + " if cid == contourClosestA:\n", + " contourA = nodesRight\n", + " break\n", + " elif cid == contourClosestB:\n", + " contourB = nodesRight\n", + " break\n", + " \n", + " nodesLeft = []\n", + " for i in range(contourMin-1, contourMin - len(contour), -1):\n", + " cid = (i + len(contour))%len(contour)\n", + " nodesLeft.append(contour[cid])\n", + " if cid == contourClosestA:\n", + " contourA = nodesLeft\n", + " break\n", + " elif cid == contourClosestB:\n", + " contourB = nodesLeft\n", + " break\n", + " \n", + " # contourA and contourB start at glacier endpoint and follow the glacier shape up until the skeleton. \n", + " # if proportional, split contours such that the subflow with largest cost dies before reaching endpoint\n", + " if weightA > weightB:\n", + " num = int((weightB/weightA)*len(contourA)) if proportionalContour else len(contourA)-1\n", + " contourA = contourA[len(contourA)-1 : len(contourA)-num-1 : -1]\n", + " contourB = contourB[::-1]\n", + " else:\n", + " num = int((weightA/weightB)*len(contourB)) if proportionalContour else len(contourB)-1\n", + " contourA = contourA[::-1]\n", + " contourB = contourB[len(contourB)-1 : len(contourB)-num-1 : -1]\n", + "\n", + " # initialize expansion from contours\n", + " PQ = queue.PriorityQueue()\n", + " for c in contourA:\n", + " dA = np.linalg.norm(np.array([(c[0] - pointA[0])*dx, (c[1] - pointA[1])*dy]))\n", + " PQ.put((0, tuple(c), idA, weightA))\n", + " for c in contourB:\n", + " dB = np.linalg.norm(np.array([(c[0] - pointB[0])*dx, (c[1] - pointB[1])*dy]))\n", + " PQ.put((0, tuple(c), idB, weightB))\n", + " \n", + " # each glacier expands from contours to neighbors, using a weighted expansion\n", + " while not PQ.empty():\n", + " d,p,g,w = PQ.get()\n", + " if subflowmap[p] > 0:\n", + " continue\n", + " subflowmap[p] = g\n", + " for n,dn in zip(CELL_NEIGHS, DIST_NEIGHS):\n", + " pn = (p[0] + n[0], p[1] + n[1])\n", + " if intersectArea[pn] and subflowmap[pn] <= 0:\n", + " PQ.put((d + dn*w, pn, g, w))\n", + " \n", + " return subflowmap, contourA, contourB" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def computeSubglaciers(glacierMask, glacierFlowMask, glacierSurface, skeleton, \n", + " maxSubglaciers, minSubflowWidth, proportionalContour):\n", + "\n", + " # output\n", + " subglaciersMap = np.full(mapShape, -1)\n", + " subglaciersMap[glacierMask] = 0\n", + " subglaciersCount = 0\n", + " \n", + " # debug\n", + " debugInfo = []\n", + "\n", + " # which union nodes will create a subflow?\n", + " unionsQueue = queue.PriorityQueue()\n", + " for ui,unionNode in enumerate(skeleton.subglacierUnions):\n", + " \n", + " # skip main glacier\n", + " if unionNode < 0:\n", + " continue\n", + " \n", + " # merging nodes info\n", + " unionPoint = skeleton.points[unionNode]\n", + " flowingNodes = [i for i in (skeleton.flowTo == unionNode).nonzero()[0] if i > 0]\n", + " flowingGlaciers = skeleton.subglacier[flowingNodes]\n", + " \n", + " # we are interested in \"flat\" valley glaciers merging\n", + " if skeleton.property['Gradient'][unionNode] > 0.5:\n", + " continue\n", + " \n", + " # we need at least two nodes\n", + " if len(flowingNodes) < 2: \n", + " continue\n", + " \n", + " # more than two subglaciers? keep two longest\n", + " elif len(flowingNodes) > 2:\n", + " lengthSort = [(skeleton.subglacierLengths[gid], nid) for nid,gid in zip(flowingNodes,flowingGlaciers)]\n", + " lengthSort = sorted(lengthSort) \n", + " flowingNodes = [lengthSort[-1][1], lengthSort[-2][1]]\n", + " flowingGlaciers = skeleton.subglacier[flowingNodes]\n", + " \n", + " # we now have two glaciers, choose shortest (by skeleton construction, smallest id)\n", + " glacierA = flowingGlaciers.max()\n", + " unionA = flowingNodes[flowingGlaciers.argmax()]\n", + " sourceA = skeleton.subglacierSources[glacierA]\n", + " \n", + " # add union to queue based on surface elevation \n", + " unionsQueue.put((float(skeleton.property['S'][unionNode]), unionNode, flowingNodes))\n", + " \n", + " \n", + " # let's compute subflows\n", + " inglacierDF = cv2.distanceTransform(glacierMask.astype(np.uint8), cv2.DIST_L2, cv2.DIST_MASK_PRECISE)\n", + " skelSubflowsMap = skeleton.subflowsMap()\n", + " currGlacierFlows = [skeleton.subglacier[skeleton.minId]]\n", + "\n", + " while not unionsQueue.empty() and subglaciersCount < maxSubglaciers:\n", + "\n", + " _,unionNode,flowingNodes = unionsQueue.get()\n", + " flowingGlaciers = skeleton.subglacier[flowingNodes]\n", + " unionPoint = skeleton.points[unionNode]\n", + "\n", + " # lower ids are longer glaciers, take it as main body B with affluent A\n", + " glacierA = flowingGlaciers.max()\n", + " glacierB = flowingGlaciers.min()\n", + " unionA = flowingNodes[flowingGlaciers.argmax()]\n", + " unionB = flowingNodes[flowingGlaciers.argmin()]\n", + "\n", + " # sources\n", + " sourceA = skeleton.subglacierSources[glacierA]\n", + " sourceB = skeleton.subglacierSources[glacierB]\n", + "\n", + " # we will always merge into already processed glaciers\n", + " if glacierB in currGlacierFlows:\n", + "\n", + " # get the path of each branch from its source to union\n", + " nodesA = skeleton.downflowPath(sourceA, unionNode)\n", + " nodesB = skeleton.downflowPath(sourceB, unionNode)\n", + "\n", + " # find the starting point for the rock wall search\n", + " pInbetween = skeleton.findClosestInbetweenPoint(unionNode, unionA, unionB, glacierSurface)\n", + " rockSearchMap = np.logical_and(skelSubflowsMap != glacierA, skelSubflowsMap != glacierB)\n", + " pRock, _ = findClosestPoint(tuple(pInbetween), rockSearchMap, RockIceBorder, CELL_NEIGHS_4)\n", + " if not pRock:\n", + " print(' Skipping union at node', unionNode, unionPoint)\n", + " continue\n", + "\n", + " # skeleton distances\n", + " skeldistA = cv2.distanceTransform((skelSubflowsMap != glacierA).astype(np.uint8), \n", + " cv2.DIST_L2, cv2.DIST_MASK_PRECISE)\n", + " skeldistB = cv2.distanceTransform((skelSubflowsMap != glacierB).astype(np.uint8), \n", + " cv2.DIST_L2, cv2.DIST_MASK_PRECISE)\n", + " skelsdist = np.minimum(skeldistA, skeldistB)\n", + "\n", + " # find closest rock points to each skeleton A and skeleton B\n", + " prockA, _ = findLocalMinimum(skeldistA, RockIceBorder, pRock)\n", + " prockB, _ = findLocalMinimum(skeldistB, RockIceBorder, pRock)\n", + "\n", + " # find closest skeleton point from each rock\n", + " drocksSkelA = np.linalg.norm(skeleton.points[nodesA] - prockA, axis=1)\n", + " idxskelA = drocksSkelA.argmin()\n", + " pskelA = skeleton.points[nodesA[idxskelA]]\n", + "\n", + " drocksSkelB = np.linalg.norm(skeleton.points[nodesB] - prockB, axis=1)\n", + " idxskelB = drocksSkelB.argmin()\n", + " pskelB = skeleton.points[nodesB[idxskelB]]\n", + "\n", + " # refine closest skeleton point upflow until we find a width minima (as approximated by DF)\n", + " while idxskelA > 0 and inglacierDF[tuple(skeleton.points[nodesA[idxskelA-1]])] <= \\\n", + " inglacierDF[tuple(skeleton.points[nodesA[idxskelA]])]:\n", + " idxskelA -= 1\n", + " while idxskelB > 0 and inglacierDF[tuple(skeleton.points[nodesB[idxskelB-1]])] <= \\\n", + " inglacierDF[tuple(skeleton.points[nodesB[idxskelB]])]:\n", + " idxskelB -= 1\n", + " pskelA = skeleton.points[nodesA[idxskelA]]\n", + " pskelB = skeleton.points[nodesB[idxskelB]]\n", + " \n", + " flowWidthA = skeleton.property['ValleyWidth'][nodesA[idxskelA]]#drocksSkelA.min() \n", + " flowWidthB = skeleton.property['ValleyWidth'][nodesB[idxskelB]]#drocksSkelB.min()\n", + " \n", + " # skip if very small union\n", + " if np.minimum(flowWidthA, flowWidthB)*dx < minSubflowWidth:\n", + " continue\n", + "\n", + " # find each subglacier mask\n", + " maskAvailable = np.logical_or(subglaciersMap == 0, subglaciersMap == glacierB)\n", + " maskA, _, _ = downflowCells(glacierSurface, maskAvailable, skeleton.points[nodesA[idxskelA:]])\n", + " maskB, _, _ = downflowCells(glacierSurface, maskAvailable, skeleton.points[nodesB[idxskelB:]])\n", + "\n", + " # area we are interested in separating\n", + " maskUnion = np.logical_or(maskA, maskB)\n", + " maskIntersect = np.logical_and(maskA, maskB)\n", + "\n", + " # assign weight to each glacier proportional to valley width at this point\n", + " wA = 1.0/flowWidthA\n", + " wB = 1.0/flowWidthB\n", + " \n", + "\n", + " # separate subglaciers\n", + " try:\n", + " separatedFlows, contourA, contourB = separateSubglaciers(maskUnion, glacierSurface, pskelA, pskelB, \n", + " wA, wB, glacierA, glacierB, proportionalContour)\n", + " except Exception as e:\n", + " print(' Failed at', unionNode)\n", + " print(' ', e)\n", + " continue\n", + "\n", + " # cells of a glacier: the ones from the intersection and the ones outside the intersection\n", + " subglacierA = np.logical_and(maskA, np.logical_not(maskIntersect))\n", + " subglacierA = np.logical_or(subglacierA, separatedFlows == glacierA)\n", + " subglacierB = np.logical_and(maskB, np.logical_not(maskIntersect))\n", + " subglacierB = np.logical_or(subglacierB, separatedFlows == glacierB)\n", + "\n", + " # update map\n", + " subglaciersMap[subglacierA] = glacierA\n", + " subglaciersMap[subglacierB] = glacierB\n", + " subglaciersCount += 1\n", + "\n", + " # add glacier A as a new potential receiver of tributaries\n", + " currGlacierFlows.append(glacierA)\n", + "\n", + " # debug\n", + " debugInfo.append({\n", + " 'nodesA': nodesA,\n", + " 'nodesB': nodesB,\n", + " 'maskA': maskA,\n", + " 'maskB': maskB,\n", + " 'glacierA': glacierA,\n", + " 'glacierB': glacierB,\n", + " 'separatedFlows': separatedFlows,\n", + " 'rockSearchMap': rockSearchMap,\n", + " 'intersect': maskIntersect,\n", + " 'union': maskUnion,\n", + " 'pUnion': unionPoint,\n", + " 'pInbetween': pInbetween,\n", + " 'pRock': pRock,\n", + " 'prockA': prockA,\n", + " 'prockB': prockB,\n", + " 'pskelA': pskelA,\n", + " 'pskelB': pskelB,\n", + " 'wA': wA,\n", + " 'wB': wB,\n", + " 'contourA': contourA,\n", + " 'contourB': contourB\n", + " })\n", + " \n", + " # propagate to extend segmented glacier flows\n", + " # important to do this at the end, otherwise we interfere with proper subflows segmentation\n", + " subflowIds = np.unique(subglaciersMap)\n", + " freeNodes = np.logical_and(subglaciersMap == 0, glacierFlowMask) \n", + "\n", + " PQ = queue.PriorityQueue()\n", + " for i in range(skeleton.numPoints):\n", + " p = tuple(skeleton.points[i])\n", + " g = skeleton.subglacier[i]\n", + " if freeNodes[p] and g > 0 and g in subflowIds:\n", + " PQ.put((0, p, g)) \n", + " while not PQ.empty():\n", + " d,p,g = PQ.get()\n", + " if not freeNodes[p]:\n", + " continue\n", + " freeNodes[p] = False\n", + " subglaciersMap[p] = g\n", + " for n,dn in zip(CELL_NEIGHS, DIST_NEIGHS):\n", + " pn = (p[0] + n[0], p[1] + n[1])\n", + " if freeNodes[pn] and glacierSurface[pn] < glacierSurface[p] + 1:\n", + " PQ.put((d+dn, pn, g))\n", + "\n", + " \n", + " return subglaciersMap, debugInfo" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# add first empty node to match indices\n", + "glaciersSubflowMaps = [None]\n", + "debugInfos = [None]\n", + "glacierSkeletons = [None]\n", + "\n", + "for gid in range(1, numGlaciers):\n", + " \n", + " # masks of this glacier\n", + " g_mask = GlacierIdMap == gid\n", + " g_minPoint = GlacierMinPoints[gid,:]\n", + " g_flowing = np.logical_and(g_mask, Tau > 50000)\n", + " g_belowELA = np.logical_and(g_mask, ice_below_ELA)\n", + " \n", + " # skeletonize\n", + " g_skeletonMask = skeletonize(np.logical_or(g_flowing, g_belowELA))\n", + " \n", + " # compute skeleton information and convert it into a tree (assign a unique outflow cell to each node)\n", + " g_skeleton = GlacierSkeleton(g_skeletonMask)\n", + " g_skeleton.avgProperty('S', Surf, 100.0)\n", + " g_skeleton.avgProperty('Tau', Tau, 100.0)\n", + " g_skeleton.avgProperty('Gradient', Gradient, 100.0)\n", + " g_skeleton.avgProperty('ValleyWidth', ValleyWallDF, 100.0)\n", + " g_skeleton.assignFlowDirection()\n", + " \n", + " # find subglaciers\n", + " numMaxTributaries = 10\n", + " minTributaryLength = 3000 # aran 3km, ecrins 5km\n", + " minTributaryWidth = 80 # m\n", + " proportionalContours = False # if False, all tributaries reach the same endpoint at glacier tongue\n", + " \n", + " g_skeleton.findSubflows(minTributaryLength) \n", + " subglaciers, debugInfo = computeSubglaciers(g_mask, g_belowELA, Surf, g_skeleton, \n", + " numMaxTributaries, minTributaryWidth, proportionalContours)\n", + " glaciersSubflowMaps.append(subglaciers)\n", + " \n", + " # save skeleton for later reuse\n", + " debugInfos.append(debugInfo)\n", + " glacierSkeletons.append(g_skeleton)\n", + " \n", + " print('Glacier %d: %d subglaciers' % (gid, np.unique(subglaciers,return_counts=True)[0].size-1))\n", + " \n", + "print('done!')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this map contains a unique identifier for each subflow starting from id=2, and id=1 on other ice mass\n", + "SubflowIdMap = np.zeros(mapShape, dtype=np.int)\n", + "SubflowIdMap[Ice > 0] = 1\n", + "idSubflow = 2\n", + "\n", + "# this map identifies the subglacier ids within a glacier, repeated ids possible in different basins\n", + "SubglacierIdMap = np.zeros(mapShape, dtype=np.int)\n", + "\n", + "\n", + "for gid in range(1, numGlaciers):\n", + " idSubglacier = 1 \n", + " subflowIds = np.unique(glaciersSubflowMaps[gid])\n", + " \n", + " for s in subflowIds:\n", + " if s <= 0:\n", + " continue\n", + " \n", + " SubflowIdMap[glaciersSubflowMaps[gid] == s] = idSubflow \n", + " idSubflow += 1\n", + " \n", + " SubglacierIdMap[glaciersSubflowMaps[gid] == s] = idSubglacier\n", + " idSubglacier += 1 \n", + " \n", + "numGlacierSubflows = idSubflow" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "_ = plt.figure(figsize=(15,15))\n", + "plt.imshow(SubglacierIdMap + (Ice > 0) + (ice_below_ELA), cmap='terrain')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# save\n", + "OutAreasMap = np.zeros((mapShape[0], mapShape[1], 3), dtype=np.uint8)\n", + "OutAreasMap[:,:,0] = GlacierIdMap.astype(np.uint8)\n", + "OutAreasMap[:,:,1] = SubglacierIdMap.astype(np.uint8)\n", + "\n", + "cv2.imwrite(outPath + 'segmentation.png', np.flipud(OutAreasMap)) # note OpenCV uses BGR" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Debug visualization" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Debug code left here as it might help understand the tributary flows segmentation algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "debugGlacierId = 1\n", + "\n", + "d_subglaciers = glaciersSubflowMaps[debugGlacierId]\n", + "\n", + "i, j = np.where(d_subglaciers >= 0)\n", + "bmini = i.min()-1\n", + "bmaxi = i.max()+1\n", + "bminj = j.min()-1\n", + "bmaxj = j.max()+1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# glacier skeleton\n", + "d_skel = glacierSkeletons[debugGlacierId]\n", + "\n", + "skelmap = np.zeros(mapShape)\n", + "for i in range(d_skel.numPoints):\n", + " skelmap[tuple(d_skel.points[i])] = d_skel.subglacier[i]+50 if d_skel.subglacier[i] > 0 else 5\n", + "skelmap[Ice == 0] = 2\n", + "\n", + "_ = plt.figure(figsize=(20,20))\n", + "plt.imshow(skelmap[bmini:bmaxi, bminj:bmaxj], cmap='terrain')\n", + "\n", + "for dinfo in debugInfos[debugGlacierId]:\n", + " plt.plot(dinfo['pUnion'][1]-bminj, dinfo['pUnion'][0]-bmini, 'ro', markersize=8)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# contour segmentation\n", + "seeOff = 100\n", + "subflowId = 1\n", + "\n", + "debug = debugInfos[debugGlacierId][subflowId]\n", + "punion = debug['pUnion']\n", + "pshared = debug['pInbetween']\n", + "pRock = debug['pRock']\n", + "prockA = debug['prockA']\n", + "prockB = debug['prockB']\n", + "pskelA = debug['pskelA']\n", + "pskelB = debug['pskelB']\n", + "nodesA = debug['nodesA']\n", + "nodesB = debug['nodesB']\n", + "\n", + "bmini = pshared[0] - seeOff\n", + "bmaxi = pshared[0] + seeOff*3\n", + "bminj = pshared[1] - seeOff - 20\n", + "bmaxj = pshared[1] + seeOff\n", + "\n", + "_ = plt.figure(figsize=(10,10))\n", + "plt.imshow(debug['separatedFlows'][bmini:bmaxi, bminj:bmaxj] + 1*(Ice[bmini:bmaxi, bminj:bmaxj] > 0), cmap='terrain')\n", + "\n", + "for ni in nodesA:\n", + " n = d_skel.points[ni]\n", + " if bmini <= n[0] < bmaxi and bminj <= n[1] < bmaxj:\n", + " plt.plot(n[1] - bminj, n[0] - bmini, 'co', markersize=8)\n", + "for ni in nodesB:\n", + " n = d_skel.points[ni]\n", + " if bmini <= n[0] < bmaxi and bminj <= n[1] < bmaxj:\n", + " plt.plot(n[1] - bminj, n[0] - bmini, 'bo', markersize=8)\n", + " \n", + "\n", + "plt.plot(punion[1] - bminj, punion[0] - bmini, 'r^', markersize=10)\n", + "plt.plot(pshared[1] - bminj, pshared[0] - bmini, 'ro', markersize=12)\n", + "plt.plot(pRock[1] - bminj, pRock[0] - bmini, 'go', markersize=12)\n", + "plt.plot(prockA[1] - bminj, prockA[0] - bmini, 'yo', markersize=10)\n", + "plt.plot(prockB[1] - bminj, prockB[0] - bmini, 'yo', markersize=10)\n", + "plt.plot(pskelA[1] - bminj, pskelA[0] - bmini, 'mo', markersize=12)\n", + "plt.plot(pskelB[1] - bminj, pskelB[0] - bmini, 'mo', markersize=12)\n", + "\n", + "for c in debug['contourA']:\n", + " if bmini <= c[0] < bmaxi and bminj <= c[1] < bmaxj:\n", + " plt.plot(c[1] - bminj, c[0] - bmini, 'co', markersize=4)\n", + "for c in debug['contourB']: \n", + " if bmini <= c[0] < bmaxi and bminj <= c[1] < bmaxj:\n", + " plt.plot(c[1] - bminj, c[0] - bmini, 'bo', markersize=4)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 3. Glacier Feature Maps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Seracs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Caused by sudden discontinuities in moving ice surface. We will define a discontinuity between a node and its neighbor if the surface of the neighbor is lower than the node's bedrock." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "BrokenSurfaceNeighs = np.zeros(mapShape, dtype=np.int)\n", + "for n in neighs8idx:\n", + " alignedToFlow = np.einsum('ijk,kij->ij', FlowDir, np.array(n) - np.array(cellIdx)) > 0\n", + " discontinuity = np.logical_and(Ice[n] > 0, Bed > Surf[n])\n", + " BrokenSurfaceNeighs = BrokenSurfaceNeighs + np.logical_and(discontinuity, alignedToFlow).astype(np.int)\n", + "BrokenSurfaceNeighs[Ice <= 0] = 0\n", + "\n", + "# Seracs: discontinuity + moving ice\n", + "Seracs = np.minimum(BrokenSurfaceNeighs/4.0, 1.0) * IceFlowing" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "bmini = 500\n", + "bmaxi = 800\n", + "bminj = 400\n", + "bmaxj = 700\n", + "\n", + "_ = plt.figure(figsize=(10,10))\n", + "plt.imshow((Seracs*3.0 + (Ice > 0))[bmini:bmaxi,bminj:bmaxj])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cv2.imwrite(outPath + 'seracs.png', np.flipud((Seracs*255).astype(np.uint8)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Rimayes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Appear at the bottom of steep ice walls, at the interface between this steep and frozen stagnant ice and moving body of the glacier." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "NumIcewallNeighs = np.zeros(mapShape, dtype=np.int)\n", + "NumFlowingNeighs = np.zeros(mapShape, dtype=np.int)\n", + "NumFlatNeighs = np.zeros(mapShape, dtype=np.int)\n", + "NumSteepNeighs = np.zeros(mapShape, dtype=np.int)\n", + "\n", + "flowAngle = np.cos(np.radians(90.0))\n", + "steepIce = IceSlopeAngle > np.radians(30.0)\n", + "flatIce = np.logical_not(steepIce)\n", + "\n", + "for n in neighs8idx:\n", + " flowAlignedNeigh = np.einsum('ijk,kij->ij', FlowDir, np.array(n) - np.array(cellIdx)) < flowAngle\n", + " \n", + " NumIcewallNeighs = NumIcewallNeighs + np.logical_and.reduce([steepIce[n], flowAlignedNeigh]).astype(np.int) \n", + " NumFlowingNeighs = NumFlowingNeighs + np.logical_and(np.logical_not(steepIce[n]), IceFlowing[n]).astype(np.int)\n", + " \n", + " NumFlatNeighs = NumFlatNeighs + flatIce[n].astype(np.int)\n", + " NumSteepNeighs = NumSteepNeighs + steepIce[n].astype(np.int)\n", + "\n", + "Rimayes = np.logical_and.reduce([steepIce,\n", + " Tau > 30000,\n", + " Tau < 60000,\n", + " ice_above_ELA, \n", + " NumIcewallNeighs >= 3, \n", + " NumFlatNeighs >= 2])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "bmini = 500\n", + "bmaxi = 800\n", + "bminj = 450\n", + "bmaxj = 750\n", + "\n", + "_ = plt.figure(figsize=(10,10))\n", + "plt.imshow((Rimayes*5.0 + (Ice > 0))[bmini:bmaxi,bminj:bmaxj])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cv2.imwrite(outPath + 'rimayes.png', np.flipud((Rimayes*255).astype(np.uint8)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Icefalls" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Icefalls form when ice is flowing under high pressure, and at steep angles. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# from the pixel-based segmentation of icefalls, creates individual falls and improves their shape by extending them\n", + "# to the rock walls, as well as omitting small segmented areas not large enough to be interesting\n", + "def icefallSegmentation(icefalls, glacierMask, surface, wallDist, minIcefallSize, maxExtendDist):\n", + " \n", + " FallIds = np.zeros(icefalls.shape, dtype=np.int)\n", + " FallIds[icefalls] = -1\n", + " idIcefall = 1\n", + " \n", + " while (FallIds == -1).any():\n", + " \n", + " # segment icefall and extend toward valley borders if close enough\n", + " Q = queue.Queue()\n", + " Q.put((0, tuple(np.argwhere(FallIds == -1)[0])))\n", + " while not Q.empty():\n", + " d,p = Q.get()\n", + " if FallIds[p] > 0 or d > maxExtendDist:\n", + " continue\n", + " FallIds[p] = idIcefall\n", + " for n,dn in zip(CELL_NEIGHS, DIST_NEIGHS):\n", + " pn = (p[0] + n[0], p[1] + n[1])\n", + " if FallIds[pn] < 0:\n", + " Q.put((0,pn))\n", + " elif FallIds[pn] == 0:\n", + " if wallDist[pn]*dx <= maxExtendDist and glacierMask[pn] and surface[pn] > surface[p]:\n", + " Q.put((d+dn,pn))\n", + " \n", + " \n", + " # not large enough? do not count this icefall\n", + " if np.logical_and(FallIds == idIcefall, icefalls).sum()*dx*dy < minIcefallSize:\n", + " FallIds[FallIds == idIcefall] = 0\n", + " else:\n", + " idIcefall += 1\n", + " \n", + " return FallIds" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "icefallFlow = 100000 # Pa\n", + "icefallAngle = np.radians(15)\n", + "\n", + "# icefall locations\n", + "IcefallCandidates = np.logical_and.reduce([Tau > icefallFlow, IceSlopeAngle > icefallAngle])\n", + "\n", + "# clean segmentation\n", + "minIcefallSize = 0.05e6 # km2\n", + "maxExtendDist = 100.0 # m\n", + "IcefallIds = icefallSegmentation(IcefallCandidates, Ice > 0, Surf, ValleyWallDF, minIcefallSize, maxExtendDist)\n", + "Icefalls = IcefallIds > 0" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# show the comparison before and after cleanup of icefalls\n", + "fig = plt.figure(figsize=(20,10))\n", + "ax = fig.add_subplot(121)\n", + "ax.imshow(Icefalls*5.0 + 1.0*(Ice > 0) + 1.0*ice_below_ELA)\n", + "ax = fig.add_subplot(122)\n", + "ax.imshow(IcefallCandidates*5.0 + 1.0*(Ice > 0) + 1.0*ice_below_ELA)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cv2.imwrite(outPath + 'icefalls.png', np.flipud((Icefalls*255).astype(np.uint8)))\n", + "cv2.imwrite(outPath + 'icefallsRaw.png', np.flipud((IcefallCandidates*255).astype(np.uint8)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Ogives" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ogives may form at the base of an icefall and generate an ondulation pattern." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "flowAngle = np.cos(np.radians(45.0))\n", + "flatAngle = np.radians(15.0)\n", + "\n", + "NumIcefallNeighs = np.zeros(mapShape, dtype=np.int)\n", + "for n in neighs8idx:\n", + " flowAligned = np.einsum('ijk,ijk->ij', FlowDir, FlowDir[n]) > flowAngle\n", + " flowFromNeigh = np.einsum('ijk,kij->ij', FlowDir, np.array(n) - np.array(cellIdx)) < -flowAngle\n", + " NumIcefallNeighs = NumIcefallNeighs + np.logical_and.reduce([Icefalls[n], flowAligned, flowFromNeigh]).astype(np.int)\n", + "\n", + "OgivesBorder = np.logical_and.reduce([np.logical_not(Icefalls), \n", + " SubglacierIdMap > 0,\n", + " IceFlowing,\n", + " IceSlopeAngle < flatAngle,\n", + " ice_below_ELA, \n", + " np.logical_and(NumIcefallNeighs >= 2, NumIcefallNeighs <= 4)])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def createOgivesMap(icefalls, ogivesBorder, surface, flowDir, subflowsMap):\n", + " \n", + " # parameters\n", + " minOgiveArea = 0.1e6\n", + " minOgiveNodes = 6 # 100m at 20m/pix\n", + " maxFlowDev = np.radians(25)\n", + " numSubflows = subflowsMap.max() + 1\n", + " \n", + " # result\n", + " OgiveMap = np.full(mapShape, -1, dtype=np.int)\n", + " OgiveMap[ogivesBorder] = 0\n", + " OgiveSources = [[]]\n", + " AllSources = []\n", + " OgiveDist = np.full(mapShape, -1.0)\n", + " \n", + " idOgive = 1\n", + " while (OgiveMap == 0).any():\n", + " \n", + " # minimum point\n", + " omapIdx = np.where(OgiveMap.flatten() == 0)[0]\n", + " pmin = np.unravel_index(omapIdx[surface.flatten()[omapIdx].argmin()], surface.shape)\n", + "\n", + " # 1) find connected ogive source nodes\n", + " ogiveNodes = []\n", + " sumDprod = 0.0\n", + " ogiveSubflow = np.zeros((numSubflows,), dtype=np.int)\n", + " Q = queue.Queue()\n", + " Q.put((pmin, 0))\n", + " while not Q.empty():\n", + " p,d = Q.get()\n", + " if OgiveMap[p] > 0:\n", + " continue\n", + " OgiveMap[p] = idOgive\n", + " ogiveNodes.append(p)\n", + " sumDprod += d\n", + " if subflowsMap[p] >= 0:\n", + " ogiveSubflow[subflowsMap[p]] += 1\n", + " for n,d in zip(CELL_NEIGHS,DIR_NEIGHS):\n", + " pn = (p[0] + n[0], p[1] + n[1])\n", + " if OgiveMap[pn] == 0:\n", + " dprod = flowDir[pn[0],pn[1],0]*d[0] + flowDir[pn[0],pn[1],1]*d[1]\n", + " Q.put((pn, np.abs(dprod)))\n", + " \n", + " AllSources.append(ogiveNodes) \n", + " \n", + " # if ogive too small or too unaligned with flow\n", + " if len(ogiveNodes) < minOgiveNodes or sumDprod/len(ogiveNodes) > np.cos(0.5*np.pi - maxFlowDev):\n", + " OgiveMap[OgiveMap == idOgive] = -1\n", + " continue\n", + "\n", + " \n", + " # 2) subflows affected by this ogive\n", + " flowIds = np.where(ogiveSubflow > 0)[0]\n", + " if len(flowIds) == 0:\n", + " OgiveMap[OgiveMap == idOgive] = -1\n", + " continue\n", + "\n", + " \n", + " # 3) downflow from ogive nodes, stopping if another icefall is found\n", + " distMap = np.full(OgiveDist.shape, -1.0)\n", + " PQ = queue.PriorityQueue()\n", + " for n in ogiveNodes:\n", + " if subflowsMap[n] in flowIds:\n", + " PQ.put((0, n))\n", + " while not PQ.empty():\n", + " d,p = PQ.get()\n", + " if distMap[p] >= 0:\n", + " continue \n", + " distMap[p] = d\n", + " for n,dn in zip(CELL_NEIGHS, DIST_NEIGHS):\n", + " pn = (p[0] + n[0], p[1] + n[1])\n", + " if subflowsMap[pn] in flowIds and not icefalls[pn] and distMap[pn] < 0 and surface[pn] <= surface[p]:\n", + " PQ.put((d+dn, pn))\n", + " \n", + " # filter out small sections\n", + " for fid in flowIds:\n", + " fidOgive = np.logical_and(subflowsMap == fid, distMap >= 0)\n", + " if fidOgive.sum()*dx*dy < minOgiveArea:\n", + " distMap[fidOgive] = -1\n", + " \n", + " if (distMap >= 0).sum()*dx*dy < minOgiveArea:\n", + " OgiveMap[OgiveMap == idOgive] = -1\n", + " continue\n", + " \n", + " \n", + " # copy result\n", + " OgiveMap [distMap >= 0] = idOgive\n", + " OgiveDist[distMap >= 0] = distMap[distMap >= 0]\n", + " OgiveSources.append(ogiveNodes)\n", + "\n", + " # go for the next one\n", + " idOgive += 1\n", + " \n", + " \n", + " return OgiveMap, OgiveDist, OgiveSources, AllSources\n", + " \n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "OgivesMap, OgivesDist, OgivesSources, AllSources = createOgivesMap(Icefalls, OgivesBorder, Surf, FlowDir, SubflowIdMap)\n", + "\n", + "print('Found %d ogive areas'%OgivesMap.max())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "OgivesCtrDist = np.zeros(OgivesMap.shape)\n", + "for i in range(1, OgivesMap.max()+1):\n", + " ogiveSkeletonMask = np.logical_not(skeletonize(OgivesMap == i))\n", + " dist = cv2.distanceTransform(ogiveSkeletonMask.astype(np.uint8), cv2.DIST_L2, cv2.DIST_MASK_PRECISE)\n", + " dist[OgivesMap != i] = 0\n", + " OgivesCtrDist += dist\n", + "OgivesCtrDist *= np.maximum(dx, dy)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "bmini = 550\n", + "bmaxi = 850\n", + "bminj = 250\n", + "bmaxj = 550\n", + "\n", + "fig = plt.figure(figsize=(20,10))\n", + "\n", + "ax = fig.add_subplot(121)\n", + "ax.imshow((OgivesMap + 300*(OgivesMap > 0) + 3.0*SubglacierIdMap + 100*Icefalls)[bmini:bmaxi,bminj:bmaxj], cmap='terrain')\n", + "for os in AllSources:\n", + " for s in os:\n", + " if bmini <= s[0] <= bmaxi and bminj <= s[1] <= bmaxj:\n", + " ax.plot(s[1]-bminj, s[0]-bmini, 'mo', markersize=3)\n", + "for os in OgivesSources:\n", + " for s in os:\n", + " if bmini <= s[0] <= bmaxi and bminj <= s[1] <= bmaxj:\n", + " ax.plot(s[1]-bminj, s[0]-bmini, 'ro', markersize=4)\n", + " \n", + "\n", + "ax = fig.add_subplot(122)\n", + "ax.imshow(np.cos((OgivesDist + OgivesCtrDist)/30.0)[bmini:bmaxi,bminj:bmaxj])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cv2.imwrite(outPath + 'ogivesId.png', np.flipud(np.maximum(0, OgivesMap)).astype(np.uint8))\n", + "cv2.imwrite(outPath + 'ogivesDistSrc.png', np.flipud(encodeFieldAsRGB(OgivesDist).astype(np.uint8)))\n", + "cv2.imwrite(outPath + 'ogivesDistCtr.png', np.flipud(encodeFieldAsRGB(OgivesCtrDist).astype(np.uint8)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Crevasses" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Crevasses appear due to sudden steep changes in glacier slope, different velocities, irregularities on the bedrock or the valley widening/narrowing." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, let's compute some properties that will be useful to place crevasses" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# gradient of bedrock in the direction of flow\n", + "Dflow_bed = (FlowDir[:,:,0]*Grad_B_x + FlowDir[:,:,1]*Grad_B_y)*(Ice > 0)\n", + "\n", + "# derivative of stress in perpendicular direction to flow\n", + "Grad_tau_x, Grad_tau_y = gradient(Tau)\n", + "Dperp_tau = (FlowDir[:,:,1]*Grad_tau_x - FlowDir[:,:,0]*Grad_tau_y)*(ValleyWallDF > 1)\n", + "\n", + "# speed derivatives along flow and perpendicular to flow\n", + "Grad_u_x, Grad_u_y = gradient(Utotal)\n", + "Dflow_speed = (FlowDir[:,:,0]*Grad_u_x + FlowDir[:,:,1]*Grad_u_y)*(ValleyWallDF > 1)\n", + "Dperp_speed = (FlowDir[:,:,1]*Grad_u_x - FlowDir[:,:,0]*Grad_u_y)*(ValleyWallDF > 1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The distance from the glacier terminus will be used to determine some crevasse types" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# distance map to end of glaciers\n", + "GlacierEndDist = np.full(mapShape, -1)\n", + "GlacierMaxDist = np.full(mapShape, -1)\n", + "for i in range(1, numGlaciers):\n", + " print('Computing distmap of glacier %d'%i, end='\\r')\n", + " gmask = GlacierIdMap == i\n", + " _,gdist,_ = upflowCells(Surf, gmask, [(GlacierMinPoints[i,0], GlacierMinPoints[i,1])])\n", + " GlacierEndDist[gmask] = gdist[gmask]\n", + " GlacierMaxDist[gmask] = gdist[gmask].max()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We want to compute the width of the glacier over its surface. To do so, we will use the value at the glacier skeleton as extracted from the distance field, and propagate it to each skeleton node closest cells." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sk_valleyW = np.zeros(mapShape)\n", + "\n", + "for gid in range(1, numGlaciers):\n", + " print('Computing valley width of glacier %d'%gid, end='\\r')\n", + " \n", + " # mask and skeleton\n", + " gmask = GlacierIdMap == gid\n", + " skel = glacierSkeletons[gid]\n", + "\n", + " # closest skeleton node map\n", + " skelNodeMap = np.full(mapShape, -1)\n", + " PQ = queue.PriorityQueue()\n", + " for i in range(skel.numPoints):\n", + " if skel.subglacier[i] > 0:\n", + " PQ.put((0, (skel.points[i][0], skel.points[i][1]), i))\n", + " while not PQ.empty():\n", + " d,p,i = PQ.get()\n", + " if skelNodeMap[p] >= 0:\n", + " continue\n", + " skelNodeMap[p] = i\n", + " for n,dn in zip(CELL_NEIGHS, DIST_NEIGHS):\n", + " pn = (p[0] + n[0], p[1] + n[1])\n", + " if gmask[pn] and skelNodeMap[pn] < 0:\n", + " PQ.put((d+dn, pn, i))\n", + " \n", + " # valley width from skeleton\n", + " sk_valleyW[gmask] = (skel.property['ValleyWidth'][skelNodeMap].squeeze())[gmask]\n", + " \n", + " # compute some derivatives along the skeleton\n", + " Dskel_valleyW = np.zeros((skel.numPoints,))\n", + " for i in range(skel.numPoints):\n", + " n = skel.flowTo[i]\n", + " d = np.linalg.norm(skel.points[i] - skel.points[n])\n", + " if n >= 0:\n", + " Dskel_valleyW[i] = (skel.property['ValleyWidth'][n] - skel.property['ValleyWidth'][i])/d\n", + " \n", + "# some blurring, helps to remove the discontinuities of the skeleton closest node field\n", + "sk_valleyW = cv2.GaussianBlur(sk_valleyW, (9,9), 0)\n", + "\n", + "# set to 0 outside iced areas\n", + "sk_valleyW[Ice <= 0] = 0\n", + "\n", + "print('\\nDone')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# valley with derivative along flow direction\n", + "Grad_skValleyW_x, Grad_skValleyW_y = gradient(sk_valleyW)\n", + "Dflow_skValleyW = (FlowDir[:,:,0]*Grad_skValleyW_x + FlowDir[:,:,1]*Grad_skValleyW_y)*(ValleyWallDF > 1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cv2.imwrite(outPath + 'valleywidth.png', np.flipud(encodeFieldAsRGB(max(dx,dy)*sk_valleyW).astype(np.uint8)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**MARGINAL CREVASSES** form due to stress on ice close to glacier margins that moves much lower than ice on glacier center.\n", + "\n", + "We will place them below ELA, on relatively flat areas and where the stress is lower than deformation threshold (50 kPa).\n", + "The probability will decrease towards the center of the glacier, and will be proportional to the variation of stress towards the wall" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# where to find them\n", + "MarginalCrevasses = np.logical_and.reduce([ice_below_ELA,\n", + " IceSlopeAngle < np.radians(10),\n", + " Tau < 70000]).astype(np.float)\n", + "\n", + "# intensity\n", + "MarginalCrevasses = MarginalCrevasses * np.abs(Dperp_tau)\n", + "MarginalCrevasses = (MarginalCrevasses - MarginalCrevasses.min())/(MarginalCrevasses.max() - MarginalCrevasses.min())\n", + "MarginalCrevasses = np.sqrt(MarginalCrevasses * (1 - smoothstep(ValleyWallDF, 3, 10)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "i, j = np.where(GlacierIdMap == 1)\n", + "bmini = i.min()-1\n", + "bmaxi = i.max()+1\n", + "bminj = j.min()-1\n", + "bmaxj = j.max()+1\n", + "\n", + "fig = plt.figure(figsize=(10,10))\n", + "plt.imshow(MarginalCrevasses[bmini:bmaxi,bminj:bmaxj])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cv2.imwrite(outPath + 'crevassesMargin.png', np.flipud((MarginalCrevasses*255).astype(np.uint8)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**LONGITUDINAL CREVASSES** form when the valley widens or bends, and there is a compressive stress in the flow direction (i.e. speed slows down) and expansive in direction perpendicular to flow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# compressive forces in the direction of flow -> speed slows down\n", + "loncrev_slowing = smoothstep(Dflow_speed, 0, -1)\n", + "\n", + "# expansive forces in direction perpendicular to flow -> longitudinal openings (acceleration perpendicular to flow)\n", + "loncrev_expanse = smoothstep(np.abs(Dperp_speed), 0, 10)\n", + "\n", + "# they are more usual towards the end of a glacier\n", + "loncrev_closeToEnd = smoothstep(GlacierEndDist/GlacierMaxDist, 0.4, 0.1)\n", + "\n", + "# they occur due to valley opening -> Dflow_skValleyW > 0, \n", + "# we use step from a negative derivative to avoid sharp transitions\n", + "loncrev_vOpening = smoothstep(Dflow_skValleyW, -0.01, 0)\n", + "\n", + "\n", + "# probability\n", + "LongitudinalCrevasses = (loncrev_slowing + loncrev_expanse) * loncrev_closeToEnd * loncrev_vOpening * ice_below_ELA\n", + "LongitudinalCrevasses = (LongitudinalCrevasses - LongitudinalCrevasses.min())/\\\n", + " (LongitudinalCrevasses.max() - LongitudinalCrevasses.min())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = plt.figure(figsize=(10,10))\n", + "plt.imshow(LongitudinalCrevasses[bmini:bmaxi, bminj:bmaxj])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cv2.imwrite(outPath + 'crevassesLong.png', np.flipud((LongitudinalCrevasses*255).astype(np.uint8)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TRANSVERSE CREVASSES** are the most common type. They form when the valley steepens and the flow speeds up, due to tensile stresses on the ice. They form across the glacier, and the margin is typically crevasse-free." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# second order directional derivative of bedrock to detect bumps on the terrain\n", + "Dflow_bed_gx, Dflow_bed_gy = gradient(Dflow_bed)\n", + "D2flow_bed = FlowDir[:,:,0]*Dflow_bed_gx + FlowDir[:,:,1]*Dflow_bed_gy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# ice accelerating: D speed / d flow > 0\n", + "transcrev_accel = smoothstep(Dflow_speed, 0, 1)\n", + "\n", + "# bedrock irregularities\n", + "transcrev_bedbumps = np.sqrt(smoothstep(D2flow_bed, 0, -0.005))\n", + "\n", + "# valley steepens\n", + "transcrev_steep = linearstep(IceSlopeAngle, np.radians(10), np.radians(30))\n", + "\n", + "# distance to margin\n", + "transcrev_center = smoothstep(ValleyWallDF, 3, 10)\n", + "\n", + "# map\n", + "TransverseCrevasses = np.logical_and.reduce([Tau > 50000,\n", + " np.logical_not(Icefalls)])\n", + "TransverseCrevasses = TransverseCrevasses * (transcrev_accel + transcrev_bedbumps + transcrev_steep)/3.0 * transcrev_center" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "_ = plt.figure(figsize=(10,10))\n", + "plt.imshow(TransverseCrevasses)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cv2.imwrite(outPath + 'crevassesTrans.png', np.flipud((TransverseCrevasses*255).astype(np.uint8)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/GlaciersRender.pro b/GlaciersRender.pro new file mode 100644 index 0000000..05c7d60 --- /dev/null +++ b/GlaciersRender.pro @@ -0,0 +1,43 @@ +QT += core gui opengl +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++11 + +INCLUDEPATH += code +INCLUDEPATH += code/core +INCLUDEPATH += code/appRender +INCLUDEPATH += $$(GLEW_DIR) + +VPATH += code + +SOURCES += \ + glacierterrain.cpp \ + appRender/main.cpp \ + appRender/mainwindow.cpp \ + appRender/glacierswidget.cpp \ + appRender/featurespainter.cpp \ + core/core.cpp \ + core/scalarfield2.cpp \ + core/vectorfield2.cpp \ + core/shader-utils.cpp + +HEADERS += \ + glacierterrain.h \ + appRender/mainwindow.h \ + appRender/glacierswidget.h \ + appRender/featurespainter.h \ + core/core.h \ + core/scalarfield2.h \ + core/vectorfield2.h \ + core/shader-utils.h + +FORMS += \ + appRender/mainwindow.ui + +LIBS += -L$$(GLEW_DIR) -lglew32 +LIBS += -lopengl32 -lglu32 + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/GlaciersSim.pro b/GlaciersSim.pro new file mode 100644 index 0000000..ebb174c --- /dev/null +++ b/GlaciersSim.pro @@ -0,0 +1,41 @@ +QT += core gui opengl +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++11 + +INCLUDEPATH += code +INCLUDEPATH += code/core +INCLUDEPATH += code/appRender +INCLUDEPATH += $$(GLEW_DIR) + +VPATH += code + +SOURCES += \ + glacierterrain.cpp \ + appSimulation/main.cpp \ + appSimulation/mainwindow.cpp \ + appSimulation/glacierswidget.cpp \ + core/core.cpp \ + core/scalarfield2.cpp \ + core/vectorfield2.cpp \ + core/shader-utils.cpp + +HEADERS += \ + glacierterrain.h \ + appSimulation/mainwindow.h \ + appSimulation/glacierswidget.h \ + core/core.h \ + core/scalarfield2.h \ + core/vectorfield2.h \ + core/shader-utils.h + +FORMS += \ + appSimulation/mainwindow.ui + +LIBS += -L$$(GLEW_DIR) -lglew32 +LIBS += -lopengl32 -lglu32 + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/README.md b/README.md index eae7d7c..efae7f2 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,102 @@ Interactive simulation of glaciers on a terrain and procedural modeling of several glacier features. -Coming soon...! + +## Repository contents + +This repository contains our implementation of the paper, needed data, and examples. + +| Directory | Description | +| ---: | :--- | +| **[code](./code)** | Source code of the two OpenGL applications: simulation and feature placement. | +| **[data](./data)** | Input terrains, application data, intermediate results and maps. | +| **[img](./img)** | Teaser image and other beautiful pictures of our glaciers rendered using E-On software's VUE. | +| **[shaders](./shaders)** | Shaders for rendering and ice simulation. | + + +## Requirements + +We have tested this implementation on a Windows 10 machine equipped with an NVIDIA GeForce GTX 1060. + +For the simulation and rendering applications, we used Qt Creator and tested the projects using both the ``MinGW`` and the ``MSVC2019`` kits (64 bit). Importing the .pro files into Visual Studio 2019 with Qt Visual Studio Tools 2.6.0 also created working solutions directly. + +C++ libraries: +``` +Qt 5.15, OpenGL 4.3, GLEW 2.1.0 +``` +You will need to define a ``GLEW_DIR`` environment variable pointing to the location of the headers and library files. + +For the features notebook, we used a miniconda Python 3.7 environment installing the following packages: +``` +numpy, matplotlib, jupyter, pypng, opencv, scikit-image +``` + + +## Running the code + +The workflow consists of three sequential steps. + +### 1. Glacier simulation + +Compile and run [GlaciersSim.pro](./GlaciersSim.pro). + +The ice simulation is executed in an interactive terrain editor using OpenGL and a Compute Shader. + +The input is an elevation map, we assume height to be encoded in a 16 bit png as decimeters (0.1m). Additionally, a solar radiance map, a precipitation map or an initial ice layer can also be provided. See [data/terrains](./data/terrains) for examples. We included preset scenes configurations to replicate some of the results shown in the paper. + +The core of the glacier simulation implementation is the SIA compute shader [sia.glsl](./shaders/sia.glsl), which is used from the [GlacierTerrain class](./code/glacierterrain.h). + + +### 2. Feature placement + +Run the [GlaciersFeatures.ipynb](/GlaciersFeatures.ipynb) jupyter notebook. It contains all the functions, instructions and comments explaining the main ideas, so you only need to follow it in order. + +This notebook will read the elevation map of the bedrock, the simulated ice thickness, and the mask of ice cells above ELA, and output all the necessary extra maps that will later be used to prepare the final rendering. + + +### 3. Render + +Compile and run [GlaciersRender.pro](./GlaciersRender.pro). + +We include a simple Qt OpenGL app that we used to test the feature placement and generate the necessary maps that would be streamed to the offline renders using ``E-On software's VUE``. Note that the goal of this application is to demonstrate the placement, not to be photorealistic. + + +## Remarks and to-do + +The code we provide here is a cleaned version of our research code for demonstration purposes and is provided *as is*. + +The C++ application relied on many classes from the team's internal libraries. We have extracted and refactored the relevant functions for this code release, so don't expect these classes to be fully reusable out of this context, commonly needed functionality might be missing. + +It would also be interesting to integrate the features computation into the OpenGL app editor directly. This part was faster to experiment using Python and we haven't ported the final code into the apps. + + +### Known issues + +GlacierSim: brush edits on a map added for demonstration purposes. They do not propagate to other levels of the multiresolution, but this should be easy to fix. + + +## Article + +The article is published in [ACM Transactions on Graphics](https://doi.org/10.1145/3414685.3417855). The authors' version can also be read [here](https://hal.archives-ouvertes.fr/hal-02929531/document). + +If you use this code for your research, please cite our paper: +``` +@article{Argudo2020glaciers, + title = {Simulation, Modeling and Authoring of Glaciers}, + author = {Argudo,Oscar and Galin,Eric and Peytavie,Adrien and Paris,Axel and Gu\'{e}rin,Eric}, + journal = {ACM Transactions on Graphics (SIGGRAPH Asia 2020)}, + year = {2020}, + volume = {39}, + number = {6} +} +``` + + +## Acknowledgements + +* Alex Jarosch for sharing a Python implementation of their paper "Restoring mass conservation to shallow ice flow models over complex terrain", which was used as a starting point in our experiments. Check out [his repository](https://github.com/alexjarosch/sia-fluxlim) for their code and a link to their paper. + +* [Jordi Camins](http://www.gelicehielo.com/) for his advice on modeling the glacier features. + +* Jürg Alean and Michael Hambrey from Glaciers online. Their [photoglossary](https://www.swisseduc.ch/glaciers/glossary/index-en.html) was very useful for understanding and modeling the glacier features. + \ No newline at end of file diff --git a/code/appRender/featurespainter.cpp b/code/appRender/featurespainter.cpp new file mode 100644 index 0000000..f9383ba --- /dev/null +++ b/code/appRender/featurespainter.cpp @@ -0,0 +1,629 @@ +#include "featurespainter.h" +#include +#include +#include + +void remapTexCoordinates(const Vector2& p, const Box2& domain, const QSize& texSize, int& texi, int& texj) +{ + Vector2 q = p - domain.getMin(); + Vector2 d = domain.getMax() - domain.getMin(); + double u = q[0] / d[0]; + double v = q[1] / d[1]; + texi = int(u * (texSize.width() - 1)); + texj = int(v * (texSize.height() - 1)); +} + +double FeaturesPainter::randUniform(double a, double b) +{ + return a + (b-a)*uniformDist(rne); +} + +QImage FeaturesPainter::baseTexture(const ScalarField2& hf, const ScalarField2& ice, const ScalarField2& ela) const +{ + double a, b; + hf.getRange(a, b); + Vector3 lightDir = Normalized(Vector3(-1.0, -1.0, 2.5)); + ColorPalette reliefPalette = ColorPalette::Relief(); + + QImage shading(hf.getSizeX(), hf.getSizeY(), QImage::Format_ARGB32); + for (int i = 0; i < hf.getSizeX(); i++) { + for (int j = 0; j < hf.getSizeY(); j++) { + + Vector3 pos = hf.vertex(i, j); + Vector2 p(pos[0], pos[1]); + + // Height shading: color is a linear interpolation of height colors + double t = 300.0 * Math::LinearStep(hf.at(i, j), -b, 1.5 * b); + Vector3 cz = reliefPalette.getColor(t); + cz = 0.25 * cz; + cz = Vector3(1 * cz[0], 0.7 * cz[1], 0.5 * cz[2]); + + // Ice shading + Vector3 ci; + if (ice.at(i, j) > 0.0) { + ci = Vector3(148 / 255.0, 164 / 255.0, 164 / 255.0) * (1 - ela.value(p)) + (ela.value(p)) * Vector3(1, 1, 1); + + // blue-ish ramp + double tb = (1 - Math::LinearStep(hf.at(i, j), 1000, 2500.0)); + Vector3 cb = Vector3(ci[0] * 0.2, ci[1], ci[2]); + ci = (1 - tb) * ci + tb * cb; + + // thin ice: interpolate towards rock texture + double ti = std::min(ice.at(i, j) / 2.0, 1.0); + if (ela.value(p) < 1) ti = 1; + cz = (1 - ti) * cz + ti * ci; + } + + // light + double light = dot(hf.normal(i, j), lightDir); + light = 0.5 * (1.0 + light); + double cs = 0.9 * light; + cs *= cs; // cosine like + + // Normal shading: color is a combination of cool and cold colors according to the orientation + Vector3 ambient = 0.25 * Vector3(1.0, 1.0, 1.0); + Vector3 c1 = 0.25 * cs * cz; + Vector3 c2 = 0.5 * cs * Vector3(1.0, 1.0, 1.0); + Vector3 c = ambient + c1 + c2; + + shading.setPixelColor(i, j, c.toQColor().rgb()); + } + } + + int texw = int(hf.getDomain().width() / TEXTURE_RES); + int texh = int(hf.getDomain().height() / TEXTURE_RES); + + std::cerr << "Creating base texture of size " << texw << "x" << texh << std::endl; + + QImage tex(texw, texh, QImage::Format_RGBA8888); + QPainter painter(&tex); + painter.drawImage(QPoint(0, 0), shading.scaled(texw, texh, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + return tex; +} + + +QImage FeaturesPainter::crevasses(const ScalarField2& crevasseField, int crevasseType, double scale, const ScalarField2& alphaCrevasses, QImage& texture) +{ + QImage crevasseMap(texture.size(), QImage::Format_Grayscale16); + crevasseMap.fill(quint16(65535)); + QPainter mapPainter(&crevasseMap); + + // Stamps (we might be interested in having different crevasse patterns above/below ELA) + unsigned int CREVASSE_STAMP_SIZE = 256; + std::vector > stamps = { + { // stamps under ELA + QImage("data/stamps/crevasses_1_mask.png").scaledToWidth(CREVASSE_STAMP_SIZE, Qt::TransformationMode::SmoothTransformation), + QImage("data/stamps/crevasses_2_mask.png").scaledToWidth(CREVASSE_STAMP_SIZE, Qt::TransformationMode::SmoothTransformation), + QImage("data/stamps/crevasses_3_mask.png").scaledToWidth(CREVASSE_STAMP_SIZE, Qt::TransformationMode::SmoothTransformation), + QImage("data/stamps/crevasses_4_mask.png").scaledToWidth(CREVASSE_STAMP_SIZE, Qt::TransformationMode::SmoothTransformation), + QImage("data/stamps/crevasses_5_mask.png").scaledToWidth(CREVASSE_STAMP_SIZE, Qt::TransformationMode::SmoothTransformation), + QImage("data/stamps/crevasses_6_mask.png").scaledToWidth(CREVASSE_STAMP_SIZE, Qt::TransformationMode::SmoothTransformation), + QImage("data/stamps/crevasses_7_mask.png").scaledToWidth(CREVASSE_STAMP_SIZE, Qt::TransformationMode::SmoothTransformation) + }, + { // stamps above ELA + QImage("data/stamps/crevasses_2_mask.png").scaledToWidth(CREVASSE_STAMP_SIZE, Qt::TransformationMode::SmoothTransformation), + QImage("data/stamps/crevasses_5_mask.png").scaledToWidth(CREVASSE_STAMP_SIZE, Qt::TransformationMode::SmoothTransformation), + QImage("data/stamps/crevasses_7_mask.png").scaledToWidth(CREVASSE_STAMP_SIZE, Qt::TransformationMode::SmoothTransformation), + QImage("data/stamps/crevasses_8_mask.png").scaledToWidth(CREVASSE_STAMP_SIZE, Qt::TransformationMode::SmoothTransformation), + QImage("data/stamps/crevasses_9_mask.png").scaledToWidth(CREVASSE_STAMP_SIZE, Qt::TransformationMode::SmoothTransformation) + } + }; + + // Bombing + for (int i = 0; i < crevasseField.getSizeX(); i++) { + for (int j = 0; j < crevasseField.getSizeY(); j++) { + + double crevProb = crevasseField.at(i, j); + double r = randUniform(); + + if (r < crevProb) + { + // World space point + Vector2 p = crevasseField.domainCoords(i, j); + + Vector2 flowDir = flowDirField.at(i, j); + double flowDirAngle = Math::RadianToDegree(std::atan2(flowDir[1], flowDir[0])); + bool aboveELA = accumArea.value(p) > 0.5 ? 1 : 0; + + // angle and size of crevasse + double crevAngle, crevSize; + switch (crevasseType) { + + case 0: // transverse + crevAngle = flowDirAngle + 90; + crevSize = std::min(aboveELA ? 2.5 : 3.5, valleyDist.at(i, j) / 15.0); + crevSize /= TEXTURE_RES; + break; + + case 1: // longitudinal + crevAngle = flowDirAngle; + crevSize = 3.0 / TEXTURE_RES; + break; + + case 2: { // marginal + // are we on left or right side of glacier? + Vector2 gradDist = valleyDist.gradient(i, j); + if (Norm(gradDist) < 1e-3) continue; + Vector2 glacierCenterDir = Normalized(gradDist); + Vector2 ctrPoint = p + Norm(valleyDist.getCellSize()) * glacierCenterDir * valleyDist.value(p); + Vector2 ctrDown = ctrPoint + flowDirField.value(p); + Vector2 ctrFlowDir = ctrDown - ctrPoint; + double side = (p[0] - ctrPoint[0]) * ctrFlowDir[1] - (p[1] - ctrPoint[1]) * ctrFlowDir[0]; + double sign = side < 0 ? -1 : 1; + crevAngle = Math::RadianToDegree(std::atan2(glacierCenterDir[1], glacierCenterDir[0])) + sign * 45.0; + crevSize = 3.0 / TEXTURE_RES; + } break; + + case 3: // icefall + crevAngle = flowDirAngle + randUniform(-20, 20) + 90.0 * (randUniform() > 0.5); + crevSize = 2.0 * randUniform(0.75, 1.5) / TEXTURE_RES; + break; + + default: + crevAngle = 0; + crevSize = 0; + break; + } + + // Texture coordinates (high res) + int ii, jj; + remapTexCoordinates(p, crevasseField.getDomain(), texture.size(), ii, jj); + + int stampIdx = int(randUniform() * (aboveELA ? stamps[1].size() : stamps[0].size())); + + // Draw map stamp + mapPainter.setCompositionMode(QPainter::CompositionMode::CompositionMode_Multiply); + mapPainter.translate(QPoint(ii, texture.height() - 1 - jj)); + mapPainter.rotate(-crevAngle); + + const QImage& stampToPlace = stamps[aboveELA ? 1 : 0][stampIdx]; + double cscale = randUniform(0.8, 3.0) * crevSize * 18.0 / CREVASSE_STAMP_SIZE * scale; + mapPainter.drawImage(QPoint(0, 0), stampToPlace.scaled(stampToPlace.width() * cscale, stampToPlace.height() * cscale)); + + mapPainter.rotate(crevAngle); + mapPainter.translate(-QPoint(ii, texture.height() - 1 - jj)); + } + } + } + + // draw crevasses to texture and at the same time remove crevasses outside ice in map + quint16* cmap = reinterpret_cast(crevasseMap.bits()); + for (int i = 0; i < crevasseMap.width(); i++) { + for (int j = 0; j < crevasseMap.height(); j++) { + double crevValue = cmap[j * crevasseMap.width() + i] / 65535.0; + double alpha = alphaCrevasses.at(i, alphaCrevasses.getSizeY() - 1 - j); + crevValue = alpha * crevValue + (1 - alpha) * 1.0; + cmap[j * crevasseMap.width() + i] = quint16(65535 * crevValue); + + Vector3 texColor = Vector3::fromQColor(texture.pixel(i, j)) * crevValue; + texture.setPixelColor(i, j, texColor.toQColor()); + } + } + + return crevasseMap; +} + + +QImage FeaturesPainter::rimayes(const ScalarField2& rimayeField, QImage& texture) +{ + const QPoint next[8] = { QPoint(1, 0), QPoint(1, 1), QPoint(0, 1), QPoint(-1, 1), QPoint(-1, 0), QPoint(-1, -1), QPoint(0, -1), QPoint(1, -1) }; + + QImage rimayesMap(texture.size(), QImage::Format_Grayscale16); + rimayesMap.fill(quint16(65535)); + QPainter mapPainter(&rimayesMap); + QPainter painter(&texture); + + double penSize = 3.0 / TEXTURE_RES; + QPen rimayePenBlack(QColor(48, 48, 48), penSize, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin); + + // Drawing rimayes + int rimayeCount = 0; + std::vector visited(rimayeField.getSizeX() * rimayeField.getSizeY(), false); + for (int i = 0; i < rimayeField.getSizeX(); i++) { + for (int j = 0; j < rimayeField.getSizeY(); j++) { + if (rimayeField.at(i, j) > 0.5 && !visited[rimayeField.vertexIndex(i, j)]) + { + // count neighbors + int numNeighs = 0; + for (int n = 0; n < 8; n++) { + QPoint pn = QPoint(i, j) + next[n]; + if (rimayeField.validIndex(pn.x(), pn.y()) && rimayeField.at(pn.x(), pn.y()) > 0.5 + && !visited[rimayeField.vertexIndex(pn.x(), pn.y())]) + { + numNeighs++; + } + } + + // if more than one neighbor, this is an intermediate rimaye node and we will + // visit it eventually starting from another point + // this simplifies the logic below + if (numNeighs > 1) continue; + + // starting at the node, visit neighbors until reaching the other rimaye endpoint + std::vector rimayeNodes; + QPoint pcurr(i, j); + bool foundNext = true; + while (foundNext) { + rimayeNodes.push_back(pcurr); + visited[rimayeField.vertexIndex(pcurr.x(), pcurr.y())] = true; + foundNext = false; + for (int n = 0; n < 8; n++) { + QPoint pnext = pcurr + next[n]; + if (rimayeField.validIndex(pnext.x(), pnext.y()) && rimayeField.at(pnext.x(), pnext.y()) > 0.5 + && !visited[rimayeField.vertexIndex(pnext.x(), pnext.y())]) { + pcurr = pnext; + foundNext = true; + break; + } + } + } + + if (rimayeNodes.size() < 2) continue; + + // draw rimaye + bool first = true; + QPainterPath path; + for (QPoint pn : rimayeNodes) { + // World space point + Vector2 p = rimayeField.domainCoords(pn.x(), pn.y()) + 0.25 * randUniform() * rimayeField.getCellSize(); + + // Texture coordinates (high res) + int ii, jj; + remapTexCoordinates(p, rimayeField.getDomain(), texture.size(), ii, jj); + if (first) { + path.moveTo(ii, texture.height() - 1 - jj); + first = false; + } + else { + path.lineTo(ii, texture.height() - 1 - jj); + } + } + + painter.setPen(rimayePenBlack); + painter.drawPath(path); + + mapPainter.setPen(rimayePenBlack); + mapPainter.drawPath(path); + + rimayeCount++; + } + } + } + + return rimayesMap; +} + + +QImage FeaturesPainter::ogives(const ScalarField2& ogiveDistSrc, const ScalarField2& ogiveDistCtr, const QImage& ogiveId, double ogiveLenScale, double ogiveFreq, + QImage& texture, ScalarField2& ogiveStrength) +{ + QImage ogiveMap(texture.size(), QImage::Format_Grayscale16); + ogiveMap.fill(32767); + quint16* ogiveMapData = (quint16*)ogiveMap.bits(); + + std::vector ogiveLength(255, -1); + std::vector ogiveScale(255, -1); + + for (int i = 0; i < texture.width(); i++) { + for (int j = 0; j < texture.height(); j++) { + + // world coords + double di = i / double(texture.width()); + double dj = j / double(texture.height()); + double x = di * ogiveDistSrc.getDomain().width(); + double y = dj * ogiveDistSrc.getDomain().height(); + Vector2 p = Vector2(x + ogiveDistSrc.getDomain().getMin()[0], + ogiveDistSrc.getDomain().getMax()[1] - y); + + // small texture coords + int ii = int(di * ogiveId.width()); + int jj = int(dj * ogiveId.height()); + int oid = qRed(ogiveId.pixel(ii, ogiveId.height() - 1 - jj)); + if (oid > 0) { + + if (ogiveLength[oid] < 0) { + ogiveLength[oid] = randUniform(2000, 5000) * ogiveLenScale; + ogiveScale[oid] = randUniform(0.3, 1.0); + } + + // create waves pattern based on dist + double distSrc = ogiveDistSrc.value(p); + double distCtr = ogiveDistCtr.value(p); + double dist = distSrc + distCtr; + double wave = std::cos(dist*ogiveFreq / 15.0); + double damp = std::max(0.0, 1.0 - std::max(0.0, dist - 0.2 * ogiveLength[oid]) / ogiveLength[oid]); + + double hOff = ogiveScale[oid] * wave * damp; + int cOff = int(255 * 0.05 * damp * wave); + + QColor c = texture.pixelColor(i, j); + c = QColor(std::min(c.red() + cOff, 255), + std::min(c.green() + cOff, 255), + std::min(c.blue() + cOff, 255), + c.alpha()); + texture.setPixelColor(i, j, c); + + ogiveMapData[i + j * texture.width()] = quint16(std::min(std::max(0.0, (0.5 * hOff + 0.5) * 65535), 65535.0)); + + ogiveStrength(i, ogiveStrength.getSizeY() - 1 - j) = damp; + } + } + } + + return ogiveMap; +} + + +QImage FeaturesPainter::moraines(const Vector3& moraineColor, double moraineScale, QImage& texture) +{ + const unsigned int minPathNodes = 100; + + // upscale map using NN to avoid one-pixel segmentations + QImage glaciersIdUp = glacierIds.scaled(QSize(2 * glacierIds.width(), 2 * glacierIds.height()), Qt::KeepAspectRatio, Qt::FastTransformation); + + // 1: identify all of the nodes of a moraine based on segmentation map + std::map, std::vector > moraineNodes; + for (int i = 1; i < glaciersIdUp.width() - 1; i++) { + for (int j = 1; j < glaciersIdUp.height() - 1; j++) { + // fetch ids + int glacierId = qBlue(glaciersIdUp.pixel(i, glaciersIdUp.height() - 1 - j)); + int subflowId = qGreen(glaciersIdUp.pixel(i, glaciersIdUp.height() - 1 - j)); + int gid = glacierId * 256 + subflowId; + if (glacierId == 0 || subflowId == 0) continue; + + // check neighbors + for (int di = 0; di <= 1; di++) { + for (int dj = 0; dj <= 1; dj++) { + if (di * dj > 0) continue; + int neighGlacier = qBlue(glaciersIdUp.pixel(i + di, glaciersIdUp.height() - 1 - j - dj)); + int neighSubflow = qGreen(glaciersIdUp.pixel(i + di, glaciersIdUp.height() - 1 - j - dj)); + int neighId = neighGlacier * 256 + neighSubflow; + + // two glacier flows touching + if (neighGlacier == glacierId && neighSubflow != subflowId && neighSubflow > 0) { + std::pair idPair(std::min(neighId, gid), std::max(neighId, gid)); + if (moraineNodes.find(idPair) == moraineNodes.end()) { + moraineNodes[idPair] = std::vector(); + } + moraineNodes[idPair].push_back(Vector2(i + 0.5 + 0.5 * di, j + 0.5 + 0.5 * dj)); + } + } + } + } + } + + // 2: create moraines paths + std::vector > morainePaths; + std::vector > partialPaths; + for (auto it = moraineNodes.begin(); it != moraineNodes.end(); it++) { + + // find all node neighbors pairs + std::vector nodes = it->second; + std::vector > nodeNeighs(nodes.size()); + for (int i = 0; i < nodes.size(); i++) { + for (int j = i + 1; j < nodes.size(); j++) { + if (SquaredNorm(nodes[i] - nodes[j]) < 1.01) { + nodeNeighs[i].push_back(j); + nodeNeighs[j].push_back(i); + } + } + } + + // starting from a node with only 1 neighbor, propagate until end + std::vector nodeSeen(nodes.size(), false); + for (int i = 0; i < nodes.size(); i++) { + if (nodeSeen[i]) continue; + if (nodeNeighs[i].size() == 1) { + + // build path + std::vector path; + int prev = -1; + int curr = i; + while (curr >= 0) { + path.push_back(nodes[curr]); + nodeSeen[curr] = true; + int next = -1; + for (int j : nodeNeighs[curr]) { + if (j != prev && !nodeSeen[j]) { + next = j; + break; + } + } + prev = curr; + curr = next; + } + + if (path.size() > minPathNodes) { + morainePaths.push_back(path); + } + else if (path.size() > 4) { + partialPaths.push_back(path); + } + } + } + } + + // complete paths with partial segments + int addedPaths = 1; + std::vector partialPathAdded(partialPaths.size(), false); + while (addedPaths > 0) { + addedPaths = 0; + for (int pi = 0; pi < partialPaths.size(); pi++) { + if (partialPathAdded[pi]) continue; + const std::vector& pp = partialPaths[pi]; + Vector2 partialFront = pp.front(); + Vector2 partialBack = pp.back(); + for (int mj = 0; mj < morainePaths.size(); mj++) { + std::vector& path = morainePaths[mj]; + Vector2 pathFront = path.front(); + Vector2 pathBack = path.back(); + + const int maxDist = 10;//3; + if (SquaredNorm(partialFront - pathFront) < maxDist) { + for (int i = 0; i < pp.size(); i++) { + path.insert(path.begin(), pp[i]); + } + addedPaths++; + partialPathAdded[pi] = true; + break; + } + if (SquaredNorm(partialFront - pathBack) < maxDist) { + for (int i = 0; i < pp.size(); i++) { + path.insert(path.end(), pp[i]); + } + addedPaths++; + partialPathAdded[pi] = true; + break; + } + if (SquaredNorm(partialBack - pathFront) < maxDist) { + for (int i = int(pp.size()) - 1; i >= 0; i--) { + path.insert(path.begin(), pp[i]); + } + addedPaths++; + partialPathAdded[pi] = true; + break; + } + if (SquaredNorm(partialBack - pathBack) < maxDist) { + for (int i = int(pp.size()) - 1; i >= 0; i--) { + path.insert(path.end(), pp[i]); + } + addedPaths++; + partialPathAdded[pi] = true; + break; + } + } + } + } + + // smoothing of positions + const int SMOOTH_ITERS = 10; + for (int s = 0; s < SMOOTH_ITERS; s++) { + for (int m = 0; m < morainePaths.size(); m++) { + std::vector newPositions = morainePaths[m]; + for (int i = 1; i < newPositions.size() - 1; i++) { + newPositions[i] = 0.5 * (morainePaths[m][i - 1] + morainePaths[m][i + 1]); + } + morainePaths[m] = newPositions; + } + } + + // 3: draw moraines + const int moraineMapDownscale = 2; + QImage moraineMap(QSize(texture.width() / moraineMapDownscale, texture.height() / moraineMapDownscale), QImage::Format_Grayscale16); + moraineMap.fill(0); + QPen morainePen(QColor(255, 255, 255), 1, Qt::SolidLine, Qt::PenCapStyle::FlatCap, Qt::PenJoinStyle::MiterJoin); + QPainter painterMap(&moraineMap); + + for (int m = 0; m < morainePaths.size(); m++) { + const auto moraine = morainePaths[m]; + + // go to tex coords + std::vector path; + for (const Vector2& p : moraine) { + double di = p[0] / double(glaciersIdUp.width()); + double dj = p[1] / double(glaciersIdUp.height()); + int ii = int(di * texture.width() / moraineMapDownscale + 0.5); + int jj = int(dj * texture.height() / moraineMapDownscale + 0.5); + path.push_back(QPoint(ii, jj)); + } + + QPainterPath ppath; + ppath.moveTo(path[0]); + for (int p = 0; p < path.size(); p++) { + ppath.lineTo(path[p]); + } + morainePen.setWidth(randUniform(5.0, 50.0) / TEXTURE_RES * moraineScale); + painterMap.setPen(morainePen); + painterMap.drawPath(ppath); + } + + // 4: round shape and smooth fading + ScalarField2 morainesField = ScalarField2(terrainBox, moraineMap, 0.0, 1.0, true); + for (int blurPass = 0; blurPass < 4; blurPass++) + morainesField.gaussianBlur(); + //morainesField.Normalize(); + morainesField = morainesField.setResolution(texture.width(), texture.height()); + + for (int i = 0; i < morainesField.getSizeX(); i++) { + for (int j = 0; j < morainesField.getSizeY(); j++) { + + // world coords + double di = i / double(texture.width()); + double dj = j / double(texture.height()); + double x = di * terrainBox.width(); + double y = dj * terrainBox.height(); + Vector2 p = Vector2(x + terrainBox.getMin()[0], terrainBox.getMax()[1] - y); + + // fetch ids + //int ii = int(di * glaciersIdUp.width()); + //int jj = int(dj * glaciersIdUp.height()); + //int glacierId = qBlue(glaciersIdUp.pixel(ii, glaciersIdUp.height() - 1 - jj)); + //int subflowId = qGreen(glaciersIdUp.pixel(ii, glaciersIdUp.height() - 1 - jj)); + + double moraine = morainesField.at(i, j); + moraine = std::sqrt(moraine); + // remove moraines above ELA + moraine *= 1.0 - accumArea.value(p); + // fade out moraines on very steep areas + moraine *= 1.0 - std::min(1.0, std::max(0.0, slopeField.value(p) - 0.2) / 0.3); + //moraine *= subflowId > 0; + morainesField(i, j) = moraine; + + // final color + Vector3 c = Vector3::fromQColor(texture.pixelColor(i, j)); + Vector3 cfinal = moraine * moraineColor + (1 - moraine) * c; + texture.setPixelColor(i, j, cfinal.toQColor()); + } + } + + QImage resultMap(QSize(texture.width(), texture.height()), QImage::Format_Grayscale16); + quint16* resData = (quint16*)resultMap.bits(); + for (int i = 0; i < resultMap.width(); i++) { + for (int j = 0; j < resultMap.height(); j++) { + resData[i + j * resultMap.width()] = quint16(65535 * morainesField.at(i, j)); + } + } + + return resultMap; +} + + +ScalarField2 FeaturesPainter::seracs(const ScalarField2& seracs, double displacement) +{ + ScalarField2 seracIce(iceHi); + + SimplexNoise simplex; + + int nx = iceLow.getSizeX(); + int ny = iceLow.getSizeY(); + int hx = iceHi.getSizeX(); + int hy = iceHi.getSizeY(); + + ScalarField2 downIce2 = iceLow.setResolution(nx / 2, ny / 2); + ScalarField2 downIce4 = iceLow.setResolution(nx / 4, ny / 4); + ScalarField2 downIce8 = iceLow.setResolution(nx / 8, ny / 8); + + for (int i = 0; i < hx; i++) { + for (int j = 0; j < hy; j++) { + // low coordinates + double x = double(i) / hx; + double y = double(j) / hy; + int ii = int(x * nx); + int jj = int(y * ny); + + double hnoise = -2.5 + 5.0 * Math::Ridge(simplex.at(30.0 * i / 250.0, 30.0 * j / 250.0), 0.5); + double loIce2 = downIce2.at(int(x * nx / 2), int(y * ny / 2)) - iceLow.at(ii, jj); + double loIce4 = downIce4.at(int(x * nx / 4), int(y * ny / 4)) - iceLow.at(ii, jj); + double loIce8 = downIce8.at(int(x * nx / 8), int(y * ny / 8)) - iceLow.at(ii, jj); + + double iceDisp = (1.5 * hnoise + 1.0 * loIce8 + 0.5 * loIce4 + 0.25 * loIce2); + double iceL = std::min(iceLow.at(ii,jj) + iceDisp*displacement, iceHi.at(i, j)); + + double t = seracs.at(ii, jj); + double ice = iceL * t + (1 - t) * iceHi.at(i, j); + seracIce(i, j) = ice; + } + } + + return seracIce; +} diff --git a/code/appRender/featurespainter.h b/code/appRender/featurespainter.h new file mode 100644 index 0000000..dcbc610 --- /dev/null +++ b/code/appRender/featurespainter.h @@ -0,0 +1,46 @@ +#ifndef FEATURESPAINTER_H +#define FEATURESPAINTER_H + +#include +#include +#include "core.h" +#include "scalarfield2.h" +#include "vectorfield2.h" + + +class FeaturesPainter { + +public: + QImage baseTexture(const ScalarField2& hf, const ScalarField2& ice, const ScalarField2& ela) const; + + QImage crevasses(const ScalarField2& crevasseField, int crevasseType, double scale, const ScalarField2& alphaCrevasses, QImage& texture); + QImage rimayes(const ScalarField2& rimayeField, QImage& texture); + QImage ogives(const ScalarField2& ogiveDistSrc, const ScalarField2& ogiveDistCtr, const QImage& ogiveId, double ogiveLenScale, double ogiveFreq, + QImage& texture, ScalarField2& ogiveStrength); + QImage moraines(const Vector3& moraineColor, double moraineScale, QImage& texture); + ScalarField2 seracs(const ScalarField2& seracs, double displacement); + +public: + Box2 terrainBox; + + ScalarField2 bedLow, iceLow, hfLow; + ScalarField2 bedHi, iceHi, hfHi; + ScalarField2 accumArea; + + ScalarField2 slopeField; + VectorField2 flowDirField; + ScalarField2 valleyDist; + QImage glacierIds; + + QImage imgBase; + double TEXTURE_RES = 1.25; // m + + std::minstd_rand rne; + std::uniform_real_distribution uniformDist = std::uniform_real_distribution(0.0, 1.0); + +protected: + double randUniform(double a = 0.0, double b = 1.0); + +}; + +#endif // FEATURESPAINTER_H diff --git a/code/appRender/glacierswidget.cpp b/code/appRender/glacierswidget.cpp new file mode 100644 index 0000000..2dad559 --- /dev/null +++ b/code/appRender/glacierswidget.cpp @@ -0,0 +1,266 @@ +#include "glacierswidget.h" +#include +#include +#include +#include +#include "shader-utils.h" + +void checkGLError() +{ + GLenum err; + while ((err = glGetError()) != GL_NO_ERROR) { + std::cerr << err << std::endl; + } +} + +GlaciersWidget::GlaciersWidget(QWidget* parent) : QOpenGLWidget(parent) +{ + setMouseTracking(true); + setFocusPolicy(Qt::StrongFocus); +} + +GlaciersWidget::~GlaciersWidget() +{ +} + + +void GlaciersWidget::initializeGL() +{ + glewExperimental = true; + GLenum err = glewInit(); + if (err != GLEW_OK) { + std::cout << "Error : " << glewGetErrorString(err) << std::endl; + exit(-1); + } + + glEnable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + shader = read_program("./shaders/tex-mesh.glsl"); + skyboxShader = read_program("./shaders/skybox.glsl"); + + glGenVertexArrays(1, &skyboxVAO); + + unsigned char foo = 255; + glGenTextures(1, &texId); + glBindTexture(GL_TEXTURE_2D, texId); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, (void*)(&foo)); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, 0); +} + +void GlaciersWidget::resizeGL(int w, int h) +{ + glViewport(0, 0, (GLint)w, (GLint)h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(Math::RadianToDegree(camera.getAngleOfViewV(w, h)), (GLdouble)w / (GLdouble)h, camera.getNearPlane(), camera.getFarPlane()); +} + +void GlaciersWidget::paintGL() +{ + double camDist = Norm(camera.getEye() - terrainBBox.center()); + double tradius = terrainBBox.radius(); + if (camDist < terrainBBox.radius()) + camera.setPlanes(100, 2*tradius); + else + camera.setPlanes(camDist - tradius, 2 * tradius + camDist); + + // Clear + glClearColor(0.62f, 0.74f, 0.85f, 1.f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(Math::RadianToDegree(camera.getAngleOfViewV(width(), height())), (GLdouble)width() / (GLdouble)height(), camera.getNearPlane(), camera.getFarPlane()); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + // Draw + gluLookAt(camera.getEye()[0], camera.getEye()[1], camera.getEye()[2], + camera.getAt()[0], camera.getAt()[1], camera.getAt()[2], + camera.getUp()[0], camera.getUp()[1], camera.getUp()[2]); + + // Sky + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glUseProgram(skyboxShader); + glBindVertexArray(skyboxVAO); + glUniform3f(0, camera.getEye()[0], camera.getEye()[1], camera.getEye()[2]); + glUniform3f(1, camera.getAt()[0], camera.getAt()[1], camera.getAt()[2]); + glUniform3f(2, camera.getUp()[0], camera.getUp()[1], camera.getUp()[2]); + glUniform2f(3, width(), height()); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // Terrain + if (meshVAO > 0) { + glUseProgram(shader); + glUniform2f(glGetUniformLocation(shader, "u_worldMin"), terrainBBox.getMin()[0], terrainBBox.getMin()[1]); + glUniform2f(glGetUniformLocation(shader, "u_worldSize"), terrainBBox.width(), terrainBBox.height()); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texId); + glUniform1i(glGetUniformLocation(shader, "u_texture"), 0); + + glBindVertexArray(meshVAO); + glDrawElements(GL_TRIANGLES, numTriangles*3, GL_UNSIGNED_INT, 0); + glBindVertexArray(0); + + glUseProgram(0); + } + + // Schedule next draw + //update(); +} + + +void GlaciersWidget::setHeightfield(const ScalarField2& hf, bool centerCamera) +{ + makeCurrent(); + + int nx = hf.getSizeX(); + int ny = hf.getSizeY(); + + // bbox + Box2 bbox = hf.getDomain(); + double a, b; + hf.getRange(a, b); + terrainBBox = Box3( + Vector3(bbox.getMin()[0], bbox.getMin()[1], a), + Vector3(bbox.getMax()[0], bbox.getMax()[1], b)); + + // camera + if (centerCamera) camera = Camera::View(terrainBBox); + + // verts + int idx = 0; + std::vector verts(3*nx*ny); + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + Vector3 p = hf.vertex(i, j); + verts[idx++] = GLfloat(p[0]); + verts[idx++] = GLfloat(p[1]); + verts[idx++] = GLfloat(p[2]); + } + } + + // tris + numTriangles = (nx - 1) * (ny - 1) * 2; + idx = 0; + std::vector indices(numTriangles * 3); + for (int i = 1; i < nx; i++) { + for (int j = 1; j < ny; j++) { + GLuint v00 = (i - 1) * ny + j - 1; + GLuint v01 = (i - 1) * ny + j; + GLuint v10 = i * ny + j - 1; + GLuint v11 = i * ny + j; + + indices[idx++] = v00; + indices[idx++] = v01; + indices[idx++] = v10; + + indices[idx++] = v10; + indices[idx++] = v01; + indices[idx++] = v11; + } + } + + // update buffers + if (bufferVerts > 0) glDeleteBuffers(1, &bufferVerts); + if (bufferIndices > 0) glDeleteBuffers(1, &bufferIndices); + if (meshVAO > 0) glDeleteVertexArrays(1, &meshVAO); + + glGenVertexArrays(1, &meshVAO); + glBindVertexArray(meshVAO); + + glUseProgram(shader); + GLuint attribVertexLoc = glGetAttribLocation(shader, "a_position"); + + glGenBuffers(1, &bufferVerts); + glBindBuffer(GL_ARRAY_BUFFER, bufferVerts); + glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * verts.size(), &verts[0], GL_STATIC_DRAW); + glVertexAttribPointer(attribVertexLoc, 3, GL_FLOAT, GL_FALSE, 0, 0); + glEnableVertexAttribArray(attribVertexLoc); + + glGenBuffers(1, &bufferIndices); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferIndices); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * indices.size(), &indices[0], GL_STATIC_DRAW); + + glBindVertexArray(0); + glUseProgram(0); +} + + +void GlaciersWidget::setTexture(const QImage& img) +{ + makeCurrent(); + + glBindTexture(GL_TEXTURE_2D, texId); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.width(), img.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, img.bits()); + glGenerateMipmap(GL_TEXTURE_2D); +} + + +void GlaciersWidget::mousePressEvent(QMouseEvent * e) +{ + x0 = e->globalX(); + y0 = e->globalY(); + + update(); +} + +void GlaciersWidget::mouseMoveEvent(QMouseEvent * e) +{ + int x = e->globalX(); + int y = e->globalY(); + + if (e->buttons() & Qt::LeftButton) { + camera.leftRightRound((x0 - x) * 0.01); + camera.upDownRound((y0 - y) * 0.005); + } + else if (e->buttons() & Qt::RightButton) { + Vector3 previousAt = camera.getAt(); + Vector3 direction = camera.getAt() - camera.getEye(); + double currentDist = 0.002f * Norm(direction); + camera.backForth((y - y0) * currentDist); + camera.setAt(previousAt); + } + else if (e->buttons() & Qt::MidButton) { + camera.leftRightPlane((x - x0) * 1.0); + camera.upDownPlane((y - y0) * 1.0); + } + + x0 = e->globalX(); + y0 = e->globalY(); + + update(); +} + +void GlaciersWidget::mouseReleaseEvent(QMouseEvent*) +{ + update(); +} + +void GlaciersWidget::wheelEvent(QWheelEvent* e) +{ + int numDegrees = e->angleDelta().y() / 8; + int numSteps = numDegrees / 15; + + if (!(e->modifiers()&Qt::ShiftModifier)) { + Vector3 direction = camera.getAt() - camera.getEye(); + double currentDist = Norm(direction); + direction = Normalized(direction); + double speed = 0.1*currentDist; + + camera.setEye(camera.getEye() + direction * speed * numSteps); + } + + update(); +} diff --git a/code/appRender/glacierswidget.h b/code/appRender/glacierswidget.h new file mode 100644 index 0000000..0b1c43c --- /dev/null +++ b/code/appRender/glacierswidget.h @@ -0,0 +1,63 @@ +#ifndef GLACIERSWIDGET_H +#define GLACIERSWIDGET_H + +#include "glew.h" +#include +#include +#include +#include "scalarfield2.h" +#include "glacierterrain.h" + + +class GlaciersWidget : public QOpenGLWidget +{ + Q_OBJECT + +public: + GlaciersWidget(QWidget* = nullptr); + ~GlaciersWidget(); + + void SetCamera(const Camera& cam) { camera = cam; }; + Camera GetCamera() { return camera; } + + void setHeightfield(const ScalarField2& hf, bool centerCamera = true); + void setTexture(const QImage& img); + +public slots: + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void wheelEvent(QWheelEvent* event); + +signals: + void _signalEditSceneLeft(const Vector3&); + void _signalEditSceneRight(const Vector3&); + +protected: + virtual void initializeGL(); + virtual void resizeGL(int, int); + virtual void paintGL(); + +protected: + + // OpenGL render + Box3 terrainBBox; + GLuint shader = 0; + GLuint meshVAO = 0; + GLuint bufferVerts = 0, bufferIndices = 0; + GLuint numTriangles = 0; + GLuint texId = 0; + GLuint skyboxShader = 0; + GLuint skyboxVAO = 0; + + // Camera + Camera camera; + bool MoveAt = false; + int x0 = 0, y0 = 0; + Vector3 currentAt = Vector3(0); + Vector3 toAt = Vector3(0); + int stepAt = 0; + +}; + +#endif // GLACIERSWIDGET_H diff --git a/code/appRender/main.cpp b/code/appRender/main.cpp new file mode 100644 index 0000000..fd3e533 --- /dev/null +++ b/code/appRender/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/code/appRender/mainwindow.cpp b/code/appRender/mainwindow.cpp new file mode 100644 index 0000000..f46431a --- /dev/null +++ b/code/appRender/mainwindow.cpp @@ -0,0 +1,293 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "vectorfield2.h" +#include +#include + + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + glaciersWidget = new GlaciersWidget(); + QGridLayout* GLlayout = new QGridLayout; + GLlayout->addWidget(glaciersWidget, 0, 0); + GLlayout->setContentsMargins(0, 0, 0, 0); + ui->glWidget->setLayout(GLlayout); + + glaciersWidget->SetCamera(Camera(Vector3(-10.0, -10.0, 10.0), Vector3(0.0, 0.0, 0.0))); + features = nullptr; + + createActions(); +} + +MainWindow::~MainWindow() +{ + if (features) delete features; + delete ui; +} + +void MainWindow::createActions() +{ + connect(ui->btnOpenFolder, SIGNAL(clicked()), this, SLOT(openFolder())); + connect(ui->btnLoadTerrain, SIGNAL(clicked()), this, SLOT(loadTerrain())); + connect(ui->btnUpdateFeatures, SIGNAL(clicked()), this, SLOT(updateFeatures())); + + connect(&featuresThread, SIGNAL(textureUpdate(const QImage&)), this, SLOT(updateTexture(const QImage&))); + connect(&featuresThread, SIGNAL(statusUpdate(const QString&)), this, SLOT(setStatusText(const QString&))); + connect(&featuresThread, SIGNAL(finished()), this, SLOT(finishedFeatures())); +} + +void MainWindow::openFolder() +{ + QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"), + "", QFileDialog::ShowDirsOnly); + if (!dir.isEmpty()) { + ui->mapsFolder->setText(dir); + } +} + +void MainWindow::loadTerrain() +{ + QString dir = QDir::cleanPath(ui->mapsFolder->text()) + QDir::separator(); + QString demFile = dir + "dem.png"; + QString hiFile = dir + "dem-hires.png"; + QString iceFile = dir + "ice.png"; + QString elaFile = dir + "ela.png"; + + if (QFile::exists(demFile) && QFile::exists(iceFile) && QFile::exists(elaFile)) { + + statusBar()->showMessage("Loading terrain..."); + + if (features) delete features; + features = new FeaturesPainter(); + features->TEXTURE_RES = ui->sbTexResolution->value(); + + double tw = ui->sbTerrainWidth->value(); + double th = ui->sbTerrainHeight->value(); + features->terrainBox = Box2(Vector2(0), Vector2(tw*1000.0, th*1000.0)); + + QImage imgDemLow(demFile); + QImage imgDemHi; + if (QFile::exists(hiFile)) + imgDemHi.load(hiFile); + else + imgDemHi.load(demFile); + + // low res + features->bedLow = ScalarField2(features->terrainBox, imgDemLow.mirrored(false, true), 0, 0.1*(256*256-1), true); + features->iceLow = ScalarField2(features->terrainBox, QImage(iceFile), 0.0, (256 * 256) - 1.0, false); + GlacierTerrain terrainLow(features->bedLow, features->iceLow); + features->hfLow = features->bedLow + features->iceLow; + + // hi res + features->bedHi = ScalarField2(features->terrainBox, imgDemHi.mirrored(false, true), 0, 0.1 * (256 * 256 - 1), true); + features->iceHi = terrainLow.remapIceSurface(features->bedHi); + features->hfHi = features->bedHi + features->iceHi; + + // above ELA texture + features->accumArea = ScalarField2(features->terrainBox, QImage(elaFile), 0.0, 1.0, true); + features->accumArea.smooth(1); + features->accumArea = features->accumArea.setResolution(features->hfHi.getSizeX(), features->hfHi.getSizeY()); + + // create base texture + features->imgBase = features->baseTexture(features->hfHi, features->iceHi, features->accumArea); + + // update viewer + glaciersWidget->setHeightfield(features->hfHi); + glaciersWidget->setTexture(features->imgBase); + glaciersWidget->update(); + + statusBar()->showMessage("Terrain loaded"); + } + else { + statusBar()->showMessage("ERROR: Incorrect folder or missing map files"); + } +} + +void MainWindow::updateFeatures() +{ + ui->btnUpdateFeatures->setEnabled(false); + ui->btnUpdateFeatures->setText("Placing features..."); + statusBar()->showMessage("Placing features..."); + + featuresThread.setUI(ui); + featuresThread.setPainter(features); + featuresThread.start(); +} + +void MainWindow::finishedFeatures() +{ + ui->btnUpdateFeatures->setText("Update features"); + ui->btnUpdateFeatures->setEnabled(true); + statusBar()->showMessage("Done!", 5000); +} + +void MainWindow::updateTexture(const QImage &img) +{ + // update viewer + glaciersWidget->setTexture(img.mirrored(false, true)); + glaciersWidget->update(); +} + +void MainWindow::setStatusText(const QString& s) +{ + statusBar()->showMessage(s); +} + +void FeaturesWorker::run() +{ + emit statusUpdate("Placing features..."); + + // Final texture + QImage imgFeatures(features->imgBase.mirrored(false, true)); + + // declarations + QString dir = QDir::cleanPath(ui->mapsFolder->text()) + QDir::separator(); + + // replicability + features->rne = std::minstd_rand(133742); + + // maps derived from heightfield, only need to compute once unless terrain reloaded + if (features->flowDirField.getSizeX()*features->flowDirField.getSizeY() <= 0) { + // load and compute additional maps that will be used during feature placement + features->flowDirField = features->hfLow.gradientField(); + features->slopeField = ScalarField2(features->terrainBox, features->flowDirField.getSizeX(), features->flowDirField.getSizeY(), 0); + for (int i = 0; i < features->flowDirField.getSizeX(); i++) { + for (int j = 0; j < features->flowDirField.getSizeY(); j++) { + double n = Norm(features->flowDirField.at(i, j)); + features->slopeField(i, j) = n; + if (n > 1e-6) features->flowDirField(i, j) = -features->flowDirField.at(i, j) / n; + else features->flowDirField(i, j) = Vector2(0, 0); + } + } + } + features->valleyDist = ScalarField2(features->terrainBox, QImage(dir + "valleydist.png"), 0.0, 256 * 256 - 1.0, false); + features->glacierIds = QImage(dir + "segmentation.png"); + + // alpha map for crevasses + ScalarField2 alphaCrevasses(features->iceHi); + alphaCrevasses.step(0, 2.0); + alphaCrevasses = alphaCrevasses.setResolution(imgFeatures.width(), imgFeatures.height()); + + // SERACS AND ICEFALLS: perturb heightfield + if (ui->renderSeracs->isChecked()) { + emit statusUpdate("Seracs..."); + + ScalarField2 seracsField(features->terrainBox, QImage(dir + "seracs.png"), 0.0, 256 * 256 - 1.0, true); + ScalarField2 icefalls = ScalarField2(features->terrainBox, QImage(dir + "icefallsRaw.png"), 0.0, 256 * 256 - 1.0, true); + double seracDisp = ui->sbSeracDisp->value(); + + for (int i = 0; i < seracsField.getSizeX() * seracsField.getSizeY(); i++) { + seracsField[i] = std::min(1.0, seracsField[i] + icefalls[i]); + } + ScalarField2 iceSeracs = features->seracs(seracsField, seracDisp); + + ScalarField2 hfSeracs = features->bedHi + iceSeracs; + //glaciersWidget->setHeightfield(hfSeracs, false); + imgFeatures = features->baseTexture(hfSeracs, features->iceHi, features->accumArea).mirrored(false, true); + emit textureUpdate(imgFeatures); + } + + // OGIVES: ondulations, need to draw before moraines as they will cover its margins (which have interpolation artifacts on distance field) + if (ui->renderOgives->isChecked()) { + emit statusUpdate("Ogives..."); + + ScalarField2 ogiveDistSrc = ScalarField2(features->terrainBox, QImage(dir + "ogivesDistSrc.png"), 0.0, (256. * 256.) - 1.0, false); + ScalarField2 ogiveDistCtr = ScalarField2(features->terrainBox, QImage(dir + "ogivesDistCtr.png"), 0.0, (256. * 256.) - 1.0, false); + QImage ogiveId = QImage(dir + "ogivesId.png"); + double scaleLen = ui->sbOgiveLength->value(); + double scaleFreq = ui->sbOgiveFreq->value(); + + ScalarField2 ogiveStrength(features->terrainBox, imgFeatures.width(), imgFeatures.height(), 0); + QImage ogiveMap = features->ogives(ogiveDistSrc, ogiveDistCtr, ogiveId, scaleLen, scaleFreq, imgFeatures, ogiveStrength); + emit textureUpdate(imgFeatures); + + // prevent crevasses from forming on top of ogives (for better illustration) + ogiveStrength = ogiveStrength.setResolution(features->iceLow.getSizeX(), features->iceLow.getSizeY()); + ogiveStrength.smooth(1); + ogiveStrength.step(0.1, 0.4); + for (int i = 0; i < alphaCrevasses.getSizeX(); i++) { + for (int j = 0; j < alphaCrevasses.getSizeY(); j++) { + Vector2 p = alphaCrevasses.domainCoords(i, j); + alphaCrevasses(i, j) = std::max(0.0, std::min(1.0, alphaCrevasses.at(i, j) - ogiveStrength.value(p))); + } + } + } + + // MORAINES: draw moraine paths + if (ui->renderMoraines->isChecked()) { + emit statusUpdate("Moraines..."); + + //const Vector3 moraineColor(164 / 255.0, 151 / 255.0, 125 / 255.0); + const Vector3 moraineColor(0.9 * 171 / 255.0, 0.9 * 167 / 255.0, 0.9 * 164 / 255.0); + double scale = ui->sbMoraineScale->value(); + QImage moraineMap = features->moraines(moraineColor, scale, imgFeatures); + emit textureUpdate(imgFeatures); + + // prevent crevasses from forming on top of moraines (for better illustration) + for (int i = 0; i < alphaCrevasses.getSizeX(); i++) { + for (int j = 0; j < alphaCrevasses.getSizeY(); j++) { + alphaCrevasses(i, j) = std::max(0.0, std::min(1.0, alphaCrevasses.at(i, j) + - double(moraineMap.pixelColor(i, moraineMap.height() - 1 - j).red()) / 255.0)); + } + } + } + + // CREVASSES: texture bombing + if (ui->renderCrevasses->isChecked()) { + double modDensityCT = ui->crevDensityT->value() / 10.0; + double modDensityCL = ui->crevDensityL->value() / 10.0; + double modDensityCM = ui->crevDensityT->value() / 10.0; + double crevasseScale = ui->sbCrevScale->value(); + ScalarField2 crevasseField; + + emit statusUpdate("Transverse crevasses..."); + crevasseField = ScalarField2(features->terrainBox, QImage(dir + "crevassesTrans.png"), 0.0, 256 * 256 - 1.0, true); + crevasseField.normalize(); + crevasseField *= modDensityCT; + for (int i = 0; i < crevasseField.getSizeX() * crevasseField.getSizeY(); i++) { + // fewer crevasses where no much ice above ELA + crevasseField[i] *= std::min(1.0, features->iceLow[i] / 10.0) * features->accumArea[i] + (1 - features->accumArea[i]); + } + QImage crevTransMap = features->crevasses(crevasseField, 0, crevasseScale, alphaCrevasses, imgFeatures); + emit textureUpdate(imgFeatures); + + emit statusUpdate("Longitudinal crevasses..."); + crevasseField = ScalarField2(features->terrainBox, QImage(dir + "crevassesLong.png"), 0.0, 256 * 256 - 1.0, true); + crevasseField.normalize(); + crevasseField *= modDensityCL; + QImage crevLongMap = features->crevasses(crevasseField, 1, crevasseScale, alphaCrevasses, imgFeatures); + emit textureUpdate(imgFeatures); + + emit statusUpdate("Marginal crevasses..."); + crevasseField = ScalarField2(features->terrainBox, QImage(dir + "crevassesMargin.png"), 0.0, 256 * 256 - 1.0, true); + crevasseField.normalize(); + crevasseField *= modDensityCM; + QImage crevMarginMap = features->crevasses(crevasseField, 2, crevasseScale, alphaCrevasses, imgFeatures); + emit textureUpdate(imgFeatures); + } + + // ICEFALLS: as crevasses + if (ui->renderIcefalls->isChecked()) { + emit statusUpdate("Icefalls..."); + double modDensityCI = ui->crevDensityI->value() / 10.0; + ScalarField2 icefalls = ScalarField2(features->terrainBox, QImage(dir + "icefallsRaw.png"), 0.0, 256 * 256 - 1.0, true); + icefalls.normalize(); + icefalls *= modDensityCI; + QImage crevIcefallsMap = features->crevasses(icefalls, 3, 1.0, alphaCrevasses, imgFeatures); + emit textureUpdate(imgFeatures); + } + + // RIMAYES: fixed positions + if (ui->renderRimayes->isChecked()) { + emit statusUpdate("Rimayes..."); + ScalarField2 rimayeField = ScalarField2(features->terrainBox, QImage(dir + "rimayes.png"), 0.0, 256 * 256 - 1.0, true); + QImage rimayeMap = features->rimayes(rimayeField, imgFeatures); + emit textureUpdate(imgFeatures); + } + + emit finished(); +} diff --git a/code/appRender/mainwindow.h b/code/appRender/mainwindow.h new file mode 100644 index 0000000..b092526 --- /dev/null +++ b/code/appRender/mainwindow.h @@ -0,0 +1,59 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include "glacierswidget.h" +#include "glacierterrain.h" +#include "featurespainter.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + + +class FeaturesWorker : public QThread +{ + Q_OBJECT + void run() override; +signals: + void textureUpdate(const QImage& img); + void statusUpdate(const QString& s); + void finished(); +public: + void setUI(Ui::MainWindow* ui) { this->ui = ui; } + void setPainter(FeaturesPainter* painter) { features = painter; } +private: + Ui::MainWindow* ui; + FeaturesPainter* features; +}; + + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +public slots: + void openFolder(); + void loadTerrain(); + void updateFeatures(); + void finishedFeatures(); + + void updateTexture(const QImage& img); + void setStatusText(const QString& s); + +private: + void createActions(); + +private: + Ui::MainWindow *ui; + GlaciersWidget *glaciersWidget; + FeaturesWorker featuresThread; + + FeaturesPainter *features; +}; +#endif // MAINWINDOW_H diff --git a/code/appRender/mainwindow.ui b/code/appRender/mainwindow.ui new file mode 100644 index 0000000..50a1b7e --- /dev/null +++ b/code/appRender/mainwindow.ui @@ -0,0 +1,573 @@ + + + MainWindow + + + + 0 + 0 + 1618 + 1137 + + + + Glacier features demo + + + + + + + + 425 + 16777215 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 0 + 0 + + + + + 400 + 0 + + + + + 400 + 16777215 + + + + Input maps + + + + + + + + data/maps/aiguestortesCrop + + + true + + + + + + + + 28 + 28 + + + + ... + + + + + + + + + + + DEM size (km) + + + + + + + 1000.000000000000000 + + + 12.000000000000000 + + + + + + + + 0 + 0 + + + + x + + + + + + + 1000.000000000000000 + + + 12.000000000000000 + + + + + + + + + + 0 + 32 + + + + Load + + + + + + + + + + Output + + + + + + Texture resolution (m/pixel) + + + + + + + 20.000000000000000 + + + 0.250000000000000 + + + 1.250000000000000 + + + + + + + + + + Features + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Render features + + + + + + + + + Rimayes + + + true + + + + + + + Crevasses + + + true + + + + + + + Ogives + + + true + + + + + + + Moraines + + + true + + + + + + + Icefalls + + + true + + + + + + + Seracs + + + true + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 15 + + + + + + + + Crevasse scale + + + + + + + 10.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + - transverse density + + + + + + + 20 + + + 6 + + + Qt::Horizontal + + + QSlider::NoTicks + + + + + + + - longitudinal density + + + + + + + 20 + + + 10 + + + Qt::Horizontal + + + QSlider::NoTicks + + + + + + + - marginal density + + + + + + + 20 + + + 14 + + + Qt::Horizontal + + + QSlider::NoTicks + + + + + + + - icefalls + + + + + + + 20 + + + 8 + + + Qt::Horizontal + + + QSlider::NoTicks + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 15 + + + + + + + + Serac displacement + + + + + + + 10.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 15 + + + + + + + + Moraines width + + + + + + + 4.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 15 + + + + + + + + Ogives length + + + + + + + 10.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + Ogives frequency + + + + + + + 4.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + + + + + 0 + 32 + + + + Update features + + + + + + + Qt::Vertical + + + + 20 + 622 + + + + + + + + + + + + + + + + 0 + 0 + 1618 + 26 + + + + + + + + diff --git a/code/appSimulation/glacierswidget.cpp b/code/appSimulation/glacierswidget.cpp new file mode 100644 index 0000000..3ede233 --- /dev/null +++ b/code/appSimulation/glacierswidget.cpp @@ -0,0 +1,797 @@ +#include "glacierswidget.h" +#include +#include +#include +#include +#include "shader-utils.h" + + +GlaciersWidget::GlaciersWidget(QWidget* parent) : QOpenGLWidget(parent) +{ + setMouseTracking(true); + setFocusPolicy(Qt::StrongFocus); + start = std::chrono::high_resolution_clock::now(); + nbframes = 0; + shaderCubes = 0; +} + +GlaciersWidget::~GlaciersWidget() +{ +} + + +void GlaciersWidget::setGlacierTerrain(GlacierTerrain* glacier) +{ + makeCurrent(); + + glacierTerrain = glacier; + nx = glacier->numCellsX(); + ny = glacier->numCellsY(); + + glacierTerrain->initSimulationGL(); + + Box2 domain = glacierTerrain->GetBedrock().getDomain(); + terrainBBox = glacierTerrain->getBoundingBox(); + + cubeInstancesBedrock.resize(nx * ny); + cubeInstancesIce.resize(nx * ny); + + fillCubeInstances(ScalarField2(domain, nx, ny, 0), + glacierTerrain->GetBedrock(), + cubeInstancesBedrock); + fillCubeInstances(glacierTerrain->GetBedrock(), + glacierTerrain->GetIce(), + cubeInstancesIce); + + icePrev = glacierTerrain->GetIce(); + texValues = ScalarField2(domain, nx, ny, 0); + texImg = QImage(nx, ny, QImage::Format::Format_RGBA8888); + + datatexBedVals.resize(nx * ny); + datatexIceVals.resize(nx * ny); + + updateBedrockTexture(); + updateTexture(); +} + + +void GlaciersWidget::initializeGL() +{ + glewExperimental = true; + GLenum err = glewInit(); + if (err != GLEW_OK) { + std::cout << "Error : " << glewGetErrorString(err) << std::endl; + exit(-1); + } + + glEnable(GL_LINE_SMOOTH); + glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE); + + glEnable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + shaderCubes = read_program("./shaders/instanced-cube.glsl"); + skyboxShader = read_program("./shaders/skybox.glsl"); + + glGenVertexArrays(1, &skyboxVAO); + initializeCubeVAO(); + + unsigned char foo = 255; + glGenTextures(1, &texId); + glBindTexture(GL_TEXTURE_2D, texId); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, (void*)(&foo)); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, 0); + + glGenTextures(1, &texBed); + glBindTexture(GL_TEXTURE_2D, texBed); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, (void*)(&foo)); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); + + QImage arrowImg; + arrowImg.load("./data/arrow.png"); + glGenTextures(1, &texArrow); + glBindTexture(GL_TEXTURE_2D, texArrow); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, arrowImg.width(), arrowImg.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, arrowImg.bits()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glGenerateMipmap(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); + + float ffoo = 1.0f; + glGenTextures(1, &datatexBedrock); + glBindTexture(GL_TEXTURE_2D, datatexBedrock); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, 1, 1, 0, GL_RED, GL_FLOAT, (void*)(&ffoo)); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, 0); + + glGenTextures(1, &datatexIce); + glBindTexture(GL_TEXTURE_2D, datatexIce); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, 1, 1, 0, GL_RED, GL_FLOAT, (void*)(&ffoo)); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, 0); +} + +void GlaciersWidget::initializeCubeVAO() +{ + const int faceVerts[24] = { 0, 4, 5, 1, // plane ymin + 0, 1, 3, 2, // plane xmin + 2, 3, 7, 6, // plane ymax + 4, 6, 7, 5, // plane xmax + 0, 2, 6, 4, // plane zmin + 1, 5, 7, 3 }; // plane zmax + const float vertNormal[18] = { + 0, -1, 0, + -1, 0, 0, + 0, 1, 0, + 1, 0, 0, + 0, 0, -1, + 0, 0, 1 + }; + + std::vector bufferV(6 * 4 * 3, 0); + std::vector bufferN(6 * 4 * 3, 0); + for (int k = 0; k < 24; k++) { + bufferV[k*3 ] = faceVerts[k] & 0x04 ? 1.f : 0.f; + bufferV[k*3 + 1] = faceVerts[k] & 0x02 ? 1.f : 0.f; + bufferV[k*3 + 2] = faceVerts[k] & 0x01 ? 1.f : 0.f; + bufferN[k*3 ] = vertNormal[int(k / 4) * 3]; + bufferN[k*3 + 1] = vertNormal[int(k / 4) * 3 + 1]; + bufferN[k*3 + 2] = vertNormal[int(k / 4) * 3 + 2]; + } + + glUseProgram(shaderCubes); + GLuint attribVertexLoc = glGetAttribLocation(shaderCubes, "a_position"); + GLuint attribNormalLoc = glGetAttribLocation(shaderCubes, "a_normal"); + GLuint instanceTranslation = glGetAttribLocation(shaderCubes, "i_translation"); + GLuint instanceHeight = glGetAttribLocation(shaderCubes, "i_height"); + + glGenVertexArrays(1, &vaoCube); + glBindVertexArray(vaoCube); + + GLuint vboVertex; + glGenBuffers(1, &vboVertex); + glBindBuffer(GL_ARRAY_BUFFER, vboVertex); + glBufferData(GL_ARRAY_BUFFER, sizeof(float)*bufferV.size(), &bufferV[0], GL_STATIC_DRAW); + glVertexAttribPointer(attribVertexLoc, 3, GL_FLOAT, GL_FALSE, 0, 0); + glEnableVertexAttribArray(attribVertexLoc); + + GLuint vboNormal; + glGenBuffers(1, &vboNormal); + glBindBuffer(GL_ARRAY_BUFFER, vboNormal); + glBufferData(GL_ARRAY_BUFFER, sizeof(float)*bufferN.size(), &bufferN[0], GL_STATIC_DRAW); + glVertexAttribPointer(attribNormalLoc, 3, GL_FLOAT, GL_FALSE, 0, 0); + glEnableVertexAttribArray(attribNormalLoc); + + glGenBuffers(1, &vboInstanceData); + glBindBuffer(GL_ARRAY_BUFFER, vboInstanceData); + glVertexAttribPointer(instanceTranslation, 3, GL_FLOAT, GL_FALSE, sizeof(CubeInstanceData), 0); + glVertexAttribPointer(instanceHeight, 1, GL_FLOAT, GL_FALSE, sizeof(CubeInstanceData), (GLvoid*)(3*sizeof(float))); + glEnableVertexAttribArray(instanceTranslation); + glEnableVertexAttribArray(instanceHeight); + glVertexAttribDivisor(instanceTranslation, 1); + glVertexAttribDivisor(instanceHeight, 1); + + glBindVertexArray(0); + glUseProgram(0); +} + + +void GlaciersWidget::updateGeometry() +{ + fillCubeInstances(ScalarField2(glacierTerrain->GetBedrock().getDomain(), nx, ny, 0), + glacierTerrain->GetBedrock(), + cubeInstancesBedrock); + fillCubeInstances(glacierTerrain->GetBedrock(), + glacierTerrain->GetIce(), + cubeInstancesIce); + updateTexture(); +} + +void GlaciersWidget::reloadShader() +{ + if (shaderCubes) glDeleteProgram(shaderCubes); + shaderCubes = read_program("./shaders/instanced-cube.glsl"); +} + +void GlaciersWidget::resizeGL(int w, int h) +{ + glViewport(0, 0, (GLint)w, (GLint)h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(Math::RadianToDegree(camera.getAngleOfViewV(w, h)), (GLdouble)w / (GLdouble)h, camera.getNearPlane(), camera.getFarPlane()); +} + +void GlaciersWidget::paintGL() +{ + double camDist = Norm(camera.getEye() - terrainBBox.center()); + double tradius = terrainBBox.radius(); + if (camDist < terrainBBox.radius()) + camera.setPlanes(100, 2*tradius); + else + camera.setPlanes(camDist - tradius, 2 * tradius + camDist); + + // Clear + glClearColor(0.62f, 0.74f, 0.85f, 1.f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(Math::RadianToDegree(camera.getAngleOfViewV(width(), height())), (GLdouble)width() / (GLdouble)height(), camera.getNearPlane(), camera.getFarPlane()); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + // Draw + gluLookAt(camera.getEye()[0], camera.getEye()[1], camera.getEye()[2], + camera.getAt()[0], camera.getAt()[1], camera.getAt()[2], + camera.getUp()[0], camera.getUp()[1], camera.getUp()[2]); + + // Sky + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + glUseProgram(skyboxShader); + glBindVertexArray(skyboxVAO); + glUniform3f(0, camera.getEye()[0], camera.getEye()[1], camera.getEye()[2]); + glUniform3f(1, camera.getAt()[0], camera.getAt()[1], camera.getAt()[2]); + glUniform3f(2, camera.getUp()[0], camera.getUp()[1], camera.getUp()[2]); + glUniform2f(3, width(), height()); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // Terrain + if (glacierTerrain) { + drawVoxels(); + } + + // FPS Computation + nbframes++; + auto microseconds = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); + double seconds = double(microseconds) / 1000000.0; + if (seconds >= 1.0) + { + fps = nbframes / seconds; + nbframes = 0; + start = std::chrono::high_resolution_clock::now(); + } + + // Draw text box overlay + // We need to unbind VAO and program for now because it causes problem with commands below + glUseProgram(0); + glBindVertexArray(0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + QPainter painter; + painter.begin(this); + + painter.setRenderHint(QPainter::Antialiasing); + QPen penLineGrey(QColor(50, 50, 50)); + QPen penLineWhite(QColor(250, 250, 250)); + + const int bX = 10; + const int bY = 10; + const int sizeX = 210; + const int sizeY = 130; + + // Background + painter.setPen(penLineGrey); + painter.fillRect(QRect(bX, bY, sizeX, sizeY), QColor(0, 0, 255, 25)); + painter.drawRect(bX, bY, sizeX, sizeY); + + // Text + QFont f1, f2; + f1.setBold(true); + f1.setPointSize(18); + f2.setPointSize(10); + painter.setFont(f1); + painter.setPen(penLineWhite); + painter.setFont(f2); + painter.drawText(10 + 5, bY + 10 + 10, "Year:\t" + QString::number(simulYear, 'f', 1) + (steadyState ? " (steady)" : "")); + painter.drawText(10 + 5, bY + 10 + 30, "Time:\t" + QString::number(simulComputeTime, 'f', 1) + " s"); + painter.drawText(10 + 5, bY + 10 + 50, "Perf:\t" + QString::number(simulPerf, 'f', 2) + " years/s"); + painter.drawText(10 + 5, bY + 10 + 70, "Avg dIce:\t" + QString::number(simuldIce, 'f', 4) + " m/y"); + painter.drawText(10 + 5, bY + 10 + 90, "Ice Max:\t" + QString::number(simulCurrMaxIce, 'f', 1) + " m"); + painter.drawText(10 + 5, bY + 10 + 110, "Ice Vol:\t" + QString::number(lastIceVol/1e9, 'f', 1) + " km3"); + painter.end(); + + // Reset GL depth test + glEnable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Schedule next draw + update(); +} + + +void GlaciersWidget::drawVoxels() +{ + float colorBedrock[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; + float colorIce[4] = { 0.6f, 0.8f, 0.8f, 1.0f }; + + glUseProgram(shaderCubes); + glBindVertexArray(vaoCube); + + // common uniforms + glUniform2f(glGetUniformLocation(shaderCubes, "u_worldSize"), terrainBBox.width(), terrainBBox.height()); + glUniform2f(glGetUniformLocation(shaderCubes, "u_cellSize"), glacierTerrain->cellWidth(), glacierTerrain->cellHeight()); + glUniform2i(glGetUniformLocation(shaderCubes, "u_gridCells"), nx, ny); + glUniform3f(glGetUniformLocation(shaderCubes, "u_lightPos"), terrainBBox.center()[0], + terrainBBox.center()[1], + 100000); + glUniform4f(glGetUniformLocation(shaderCubes, "u_materialSpecular"), 0.f, 0.f, 0.f, 0.0f); + glUniform1f(glGetUniformLocation(shaderCubes, "u_shininess"), 20.0f); + glUniform1i(glGetUniformLocation(shaderCubes, "u_shadeCube"), 1); + + glUniform1i(glGetUniformLocation(shaderCubes, "u_drawCursor"), validAnchor); + glUniform3f(glGetUniformLocation(shaderCubes, "u_cursorPoint"), anchor[0], anchor[1], anchor[2]); + glUniform1f(glGetUniformLocation(shaderCubes, "u_cursorRadius"), brushRadius); + + // draw bedrock + if (showBedrock) { + glUniform4fv(glGetUniformLocation(shaderCubes, "u_materialAmbient"), 1, colorBedrock); + glUniform4f(glGetUniformLocation(shaderCubes, "u_materialDiffuse"), 0.08f, 0.08f, 0.08f, 1.0f); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texBed); + glUniform1i(glGetUniformLocation(shaderCubes, "u_texture"), 0); + glUniform1i(glGetUniformLocation(shaderCubes, "u_useTexture"), 2); + + glBindBuffer(GL_ARRAY_BUFFER, vboInstanceData); + glBufferData(GL_ARRAY_BUFFER, sizeof(CubeInstanceData) * cubeInstancesBedrock.size(), &cubeInstancesBedrock[0], GL_DYNAMIC_DRAW); + glDrawArraysInstanced(GL_QUADS, 0, 24, GLsizei(cubeInstancesBedrock.size())); + } + + // draw ice + if (showIce) { + glUniform4fv(glGetUniformLocation(shaderCubes, "u_materialAmbient"), 1, colorIce); + glUniform4f(glGetUniformLocation(shaderCubes, "u_materialDiffuse"), 0.15f, 0.15f, 0.15f, 1.0f); + + // compute directly the texture using the shader + if (activeTexType == GlacierTextureType::FEAT_SHADER) { + glUniform1i(glGetUniformLocation(shaderCubes, "u_useTexture"), 3); + + glUniform3f(glGetUniformLocation(shaderCubes, "u_bedRange"), float(bedMin), float(bedMax), float(bedMax - bedMin)); + glUniform3f(glGetUniformLocation(shaderCubes, "u_iceRange"), float(iceMin), float(iceMax), float(iceMax - iceMin)); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, texArrow); + glUniform1i(glGetUniformLocation(shaderCubes, "u_arrowTexture"), 1); + + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, datatexBedrock); + glUniform1i(glGetUniformLocation(shaderCubes, "u_datatexBed"), 2); + + glActiveTexture(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_2D, datatexIce); + glUniform1i(glGetUniformLocation(shaderCubes, "u_datatexIce"), 3); + } + else { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texId); + glUniform1i(glGetUniformLocation(shaderCubes, "u_texture"), 0); + glUniform1i(glGetUniformLocation(shaderCubes, "u_useTexture"), 1); + } + + glBindBuffer(GL_ARRAY_BUFFER, vboInstanceData); + glBufferData(GL_ARRAY_BUFFER, sizeof(CubeInstanceData) * cubeInstancesIce.size(), &cubeInstancesIce[0], GL_DYNAMIC_DRAW); + glDrawArraysInstanced(GL_QUADS, 0, 24, GLsizei(cubeInstancesIce.size())); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + + +void GlaciersWidget::fillCubeInstances(const ScalarField2& fieldBase, const ScalarField2& fieldHeight, std::vector& cubeInstances) +{ + double dx = glacierTerrain->cellWidth(); + double dy = glacierTerrain->cellHeight(); + Vector3 pminBox = glacierTerrain->getBoundingBox().getMin(); + + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + cubeInstances[i * ny + j].x = pminBox[0] + i*dx; + cubeInstances[i * ny + j].y = pminBox[1] + j*dy; + cubeInstances[i * ny + j].z = fieldBase.at(i, j); + cubeInstances[i * ny + j].height = fieldHeight.at(i, j); + } + } +} + +void GlaciersWidget::runSimulation(bool pauseSteady) +{ + simulationRunning = true; + steadyState = false; + pauseWhenSteady = pauseSteady; + yearlyIce = glacierTerrain->yearlyIce(false); + + if (!simulQtimer) { + simulQtimer = new QTimer(this); + connect(simulQtimer, SIGNAL(timeout()), this, SLOT(simulationTask())); + } + simulQtimer->start(0); +} + +void GlaciersWidget::pauseSimulation() +{ + simulationRunning = false; +} + +void GlaciersWidget::resetSimulation() +{ + simulYear = 0; + steadyState = false; + simulComputeTime = 0; + simulSteps = 0; + yearlyIce = glacierTerrain->yearlyIce(false); + + fillCubeInstances(glacierTerrain->GetBedrock(), glacierTerrain->GetIce(), cubeInstancesIce); +} + + +void GlaciersWidget::simulationTask() +{ + if (simulationRunning && (!steadyState || !pauseWhenSteady)) { + + yearlyIce = glacierTerrain->yearlyIce(true); + icePrev = glacierTerrain->GetIce(); + + // simulation + auto start = std::chrono::high_resolution_clock::now(); + + double dt = glacierTerrain->runSimulationSteps(SIMUL_STEPS_BATCH, 1e3); + simulYear += dt; + simulSteps += SIMUL_STEPS_BATCH; + minSimulYears -= dt; + + auto finish = std::chrono::high_resolution_clock::now(); + std::chrono::duration duration = finish - start; + simulComputeTime += duration.count(); + + double currIceVol = glacierTerrain->iceVolume(); + double dIce = currIceVol - lastIceVol; + double dIce_dt = dIce / dt; + double dIce_dt_avg = dIce_dt / glacierTerrain->terrainArea(); + + simulPerf = dt / duration.count(); + double minIce; + glacierTerrain->GetIce().getRange(minIce, simulCurrMaxIce); + + /*steadyState = minSimulYears < 0 && (std::abs(dIceRate) < 0.01 * yearlyIce * steadyRatio + || std::abs(dIceDensityRate) < 0.001*steadyRatio);*/ + steadyState = minSimulYears < 0 && std::abs(dIce_dt_avg) < dIceSteadyCondition; + lastIceVol = currIceVol; + simuldIce = dIce_dt_avg; + + // graphics + fillCubeInstances(glacierTerrain->GetBedrock(), glacierTerrain->GetIce(), cubeInstancesIce); + updateTexture(); + + if (!steadyState) { + simulQtimer->start(0); + } + update(); + } +} + +QImage GlaciersWidget::shadedBedrock() +{ + QImage shading(nx, ny, QImage::Format_RGBA8888); + + const ScalarField2& hf = glacierTerrain->GetBedrock(); + double a, b; + hf.getRange(a, b); + + Vector3 lightDir = Normalized(Vector3(-1.0, -1.0, 2.5)); + ColorPalette reliefPalette = ColorPalette::Relief(); + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + // Height shading: color is a linear interpolation of height colors + double t = 300.0 * Math::LinearStep(hf.at(i, j), -b, 1.5*b); + Vector3 cz = reliefPalette.getColor(t); + + double light = dot(hf.normal(i, j), lightDir); + light = 0.5 * (1.0 + light); + double cs = 0.9 * light; + + // Cosine like + cs *= cs; + + // Normal shading: color is a combination of cool and cold colors according to the orientation + Vector3 ambient = 0.25 * Vector3(1.0, 1.0, 1.0); + Vector3 c1 = 0.25 * cs * cz; + Vector3 c2 = 0.5 * cs * Vector3(1.0, 1.0, 1.0); + Vector3 c = ambient + c1 + c2; + c = c - Vector3(0.05); + + shading.setPixelColor(i, j, c.toQColor().rgb()); + } + } + + return shading; +} + +void GlaciersWidget::updateBedrockTexture() +{ + QImage shading = shadedBedrock(); + glBindTexture(GL_TEXTURE_2D, texBed); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nx, ny, 0, GL_RGBA, GL_UNSIGNED_BYTE, shading.bits()); + glGenerateMipmap(GL_TEXTURE_2D); +} + +void GlaciersWidget::updateTexture() +{ + double maxAbsVal = 0; + + switch (activeTexType) { + + case GlacierTextureType::NONE: + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + texImg.setPixelColor(i, j, QColor(178, 188, 188, 255)); + } + } + break; + + case GlacierTextureType::ELA: + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + if (glacierTerrain->aboveELA(i, j)) + texImg.setPixelColor(i, j, QColor(200, 200, 200, 255)); + else + texImg.setPixelColor(i, j, QColor(178, 188, 188, 255)); + } + } + break; + + case GlacierTextureType::THICKNESS: + texImg = glacierTerrain->GetIce().CreateImage(ColorPalette::CoolWarm()).convertToFormat(QImage::Format_RGBA8888); + break; + + case GlacierTextureType::GRADIENT: + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + texValues(i, j) = Norm(glacierTerrain->iceGradient(i, j)); + } + } + texImg = texValues.CreateImage(ColorPalette::CoolWarm()).convertToFormat(QImage::Format_RGBA8888); + break; + + case GlacierTextureType::STRESS: + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + texValues(i, j) = glacierTerrain->iceStress(i, j); + } + } + texImg = texValues.CreateImage(ColorPalette::CoolWarm()).convertToFormat(QImage::Format_RGBA8888); + break; + + case GlacierTextureType::DIFFUSIVITY: + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + texValues(i, j) = glacierTerrain->diffusionTerm(i, j); + } + } + texImg = texValues.CreateImage(ColorPalette::CoolWarm()).convertToFormat(QImage::Format_RGBA8888); + break; + + case GlacierTextureType::DIFFUSIVITY_RAW: + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + texValues(i, j) = glacierTerrain->diffusionTermRaw(i, j); + } + } + texImg = texValues.CreateImage(ColorPalette::CoolWarm()).convertToFormat(QImage::Format_RGBA8888); + break; + + case GlacierTextureType::SPEED_DEFORM: + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + texValues(i, j) = glacierTerrain->iceSpeedDeform(i, j); + } + } + texImg = texValues.CreateImage(ColorPalette::CoolWarm()).convertToFormat(QImage::Format_RGBA8888); + break; + + case GlacierTextureType::SPEED_SLIP: + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + texValues(i, j) = glacierTerrain->iceSpeedSlide(i, j); + } + } + texImg = texValues.CreateImage(ColorPalette::CoolWarm()).convertToFormat(QImage::Format_RGBA8888); + break; + + case GlacierTextureType::SPEED_DOMINANT_TYPE: + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + if (glacierTerrain->iceThickness(i, j) <= 0) { + texImg.setPixelColor(i, j, QColor(50, 50, 50, 255)); + continue; + } + double ud = glacierTerrain->iceSpeedDeform(i, j); + double us = glacierTerrain->iceSpeedSlide(i, j); + if (ud > us) texImg.setPixelColor(i, j, QColor(50, 50, 200, 255)); + else if (ud < us) texImg.setPixelColor(i, j, QColor(200, 50, 50, 255)); + else texImg.setPixelColor(i, j, QColor(200, 200, 200, 255)); + } + } + break; + + case GlacierTextureType::VELOCITY: + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + Vector2 v = glacierTerrain->iceGradient(i, j) / Norm(glacierTerrain->iceGradient(i, j)); + texImg.setPixelColor(i, j, QColor((unsigned char)(v[0] * 128 + 128), (unsigned char)(v[1] * 128 + 128), 128, 255)); + } + } + break; + + case GlacierTextureType::ICE_DIFFERENCE: + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + texValues(i, j) = -(glacierTerrain->Ice(i, j) - icePrev.at(i, j)); + maxAbsVal = std::max(std::abs(texValues.at(i,j)), maxAbsVal); + } + } + maxAbsVal = std::max(maxAbsVal, 1e-6); + texImg = texValues.CreateImage(-maxAbsVal, maxAbsVal, ColorPalette::CoolWarm()).convertToFormat(QImage::Format_RGBA8888); + break; + + case GlacierTextureType::FEAT_SHADER: + { + glacierTerrain->GetBedrock().getRange(bedMin, bedMax); + glacierTerrain->GetIce().getRange(iceMin, iceMax); + + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + datatexBedVals[glacierTerrain->vertexIndex(i, j)] = float((glacierTerrain->Bedrock(i, j) - bedMin) / (bedMax - bedMin)); + datatexIceVals[glacierTerrain->vertexIndex(i, j)] = float((glacierTerrain->iceThickness(i, j) - iceMin) / (iceMax - iceMin)); + } + } + + glBindTexture(GL_TEXTURE_2D, datatexBedrock); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, nx, ny, 0, GL_RED, GL_FLOAT, (void*)(&datatexBedVals[0])); + glGenerateMipmap(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, datatexIce); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, nx, ny, 0, GL_RED, GL_FLOAT, (void*)(&datatexIceVals[0])); + glGenerateMipmap(GL_TEXTURE_2D); + + return; + } + break; + default: + break; + } + + setTexture(texImg); +} + +void GlaciersWidget::setTexture(const QImage& img) +{ + glBindTexture(GL_TEXTURE_2D, texId); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nx, ny, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.bits()); + glGenerateMipmap(GL_TEXTURE_2D); +} + +void GlaciersWidget::setRenderType(bool voxels, bool bedrock, bool ice) +{ + renderAsVoxels = voxels; + showBedrock = bedrock; + showIce = ice; +} + + +void GlaciersWidget::mousePressEvent(QMouseEvent * e) +{ + x0 = e->globalX(); + y0 = e->globalY(); + + if (e->modifiers() & Qt::ControlModifier) { + Ray ray = camera.pixelToRay(e->pos().x(), e->pos().y() - 1, width(), height()); + double t; + validAnchor = glacierTerrain->Intersect(ray, t, anchor); + + if (validAnchor) { + if (e->buttons() & Qt::LeftButton) { + emit _signalEditSceneLeft(anchor); + } + if (e->buttons() & Qt::RightButton) { + emit _signalEditSceneRight(anchor); + } + } + } + else { + validAnchor = false; + } + + update(); +} + +void GlaciersWidget::mouseMoveEvent(QMouseEvent * e) +{ + int x = e->globalX(); + int y = e->globalY(); + + if (e->modifiers() & Qt::ControlModifier) { + const QPoint& pixel = e->pos(); + Ray ray = camera.pixelToRay(pixel.x(), pixel.y() - 1, width(), height()); + double t; + validAnchor = glacierTerrain->Intersect(ray, t, anchor); + + if (validAnchor) { + if (e->buttons() & Qt::LeftButton) { + emit _signalEditSceneLeft(anchor); + } + if (e->buttons() & Qt::RightButton) { + emit _signalEditSceneRight(anchor); + } + } + } + else { + if (e->buttons() & Qt::LeftButton) { + camera.leftRightRound((x0 - x) * 0.01); + camera.upDownRound((y0 - y) * 0.005); + } + else if (e->buttons() & Qt::RightButton) { + Vector3 previousAt = camera.getAt(); + Vector3 direction = camera.getAt() - camera.getEye(); + double currentDist = 0.002f * Norm(direction); + camera.backForth((y - y0) * currentDist); + camera.setAt(previousAt); + } + else if (e->buttons() & Qt::MidButton) { + camera.leftRightPlane((x - x0) * 1.0); + camera.upDownPlane((y - y0) * 1.0); + } + + x0 = e->globalX(); + y0 = e->globalY(); + + validAnchor = false; + } + + update(); +} + +void GlaciersWidget::mouseReleaseEvent(QMouseEvent*) +{ + validAnchor = false; + updateBedrockTexture(); + update(); +} + +void GlaciersWidget::wheelEvent(QWheelEvent* e) +{ + int numDegrees = e->angleDelta().y() / 8; + int numSteps = numDegrees / 15; + + if (!(e->modifiers()&Qt::ShiftModifier)) { + Vector3 direction = camera.getAt() - camera.getEye(); + double currentDist = Norm(direction); + direction = Normalized(direction); + double speed = 0.1*currentDist; + + camera.setEye(camera.getEye() + direction * speed * numSteps); + } + + update(); +} diff --git a/code/appSimulation/glacierswidget.h b/code/appSimulation/glacierswidget.h new file mode 100644 index 0000000..5d102c5 --- /dev/null +++ b/code/appSimulation/glacierswidget.h @@ -0,0 +1,166 @@ +#ifndef GLACIERSWIDGET_H +#define GLACIERSWIDGET_H + +#include "glew.h" +#include +#include +#include +#include "scalarfield2.h" +#include "glacierterrain.h" + + +class GlaciersWidget : public QOpenGLWidget +{ + Q_OBJECT + +public: + + enum class GlacierTextureType { + NONE, + ELA, + THICKNESS, + GRADIENT, + STRESS, + DIFFUSIVITY, + DIFFUSIVITY_RAW, + SPEED_DEFORM, + SPEED_SLIP, + SPEED_DOMINANT_TYPE, + VELOCITY, + ICE_DIFFERENCE, + FEAT_SHADER + }; + + + GlaciersWidget(QWidget* = nullptr); + ~GlaciersWidget(); + + void setGlacierTerrain(GlacierTerrain* glacier); + + void SetCamera(const Camera& cam) { camera = cam; }; + Camera GetCamera() { return camera; } + + void setRenderType(bool voxels, bool bedrock, bool ice); + void setTextureType(GlacierTextureType texType) { activeTexType = texType; updateTexture(); } + void updateBedrockTexture(); + void updateTexture(); + const QImage& getTexture() { return texImg; }; + void setTexture(const QImage& img); + + void setSteadyCondition(double d) { dIceSteadyCondition = d; } + double getLastdIce() { return simuldIce; } + void setMinimumSimulationYears(double y) { minSimulYears = y; } + + void runSimulation(bool pauseStationary); // remember to config parameters before this call, directly on the glacier heightfield + void pauseSimulation(); + void resetSimulation(); + + QImage shadedBedrock(); + + +public slots: + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void wheelEvent(QWheelEvent* event); + + void simulationTask(); + + void updateGeometry(); + void reloadShader(); + void updateBrushRadius(double d) { brushRadius = d; }; + +signals: + void _signalEditSceneLeft(const Vector3&); + void _signalEditSceneRight(const Vector3&); + +protected: + + struct CubeInstanceData { + float x, y, z; + float height; + }; + + virtual void initializeGL(); + virtual void resizeGL(int, int); + virtual void paintGL(); + + void initializeCubeVAO(); + + void fillCubeInstances(const ScalarField2& fieldBottom, const ScalarField2& fieldTop, std::vector& cubeInstances); + + void drawVoxels(); + + +protected: + + // Glacier simulator + GlacierTerrain* glacierTerrain = nullptr; + Box3 terrainBBox; + int nx = 0, ny = 0; + ScalarField2 icePrev; + const unsigned int SIMUL_STEPS_BATCH = 16; + QTimer* simulQtimer = nullptr; + + // OpenGL render + GLuint shaderCubes = 0; + GLuint vaoCube = 0; + GLuint vboInstanceData = 0; + GLuint texBed = 0; + GLuint texId = 0; + GLuint texArrow = 0; + GLuint skyboxShader = 0; + GLuint skyboxVAO = 0; + std::vector cubeInstancesBedrock; + std::vector cubeInstancesIce; + + bool renderAsVoxels, showBedrock, showIce; + ScalarField2 texValues; + QImage texImg; + GlacierTextureType activeTexType; + + // Custom shader data + std::vector datatexBedVals; + std::vector datatexIceVals; + GLuint datatexBedrock = 0; + GLuint datatexIce = 0; + double bedMin, bedMax; + double iceMin, iceMax; + + + // Camera + Camera camera; + bool MoveAt = false; + int x0 = 0, y0 = 0; + Vector3 currentAt = Vector3(0); + Vector3 toAt = Vector3(0); + int stepAt = 0; + + // Timer + std::chrono::time_point start; + int nbframes; + int fps = 0; + + // Simul + double maxSimulYears = 1e5; + double simulYear = 0; + double simulComputeTime = 0; + double simulPerf = 0; + double simuldIce = 0; + double simulCurrMaxIce = 0; + int simulSteps = 0; + bool steadyState = false; + bool simulationRunning = true; + bool pauseWhenSteady = true; + double dIceSteadyCondition = 0.001; + double minSimulYears = 0; + double lastIceVol = 0; + double yearlyIce = 0; + + // Editor + bool validAnchor; + Vector3 anchor; + double brushRadius = 750.0; +}; + +#endif // GLACIERSWIDGET_H diff --git a/code/appSimulation/main.cpp b/code/appSimulation/main.cpp new file mode 100644 index 0000000..fd3e533 --- /dev/null +++ b/code/appSimulation/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/code/appSimulation/mainwindow.cpp b/code/appSimulation/mainwindow.cpp new file mode 100644 index 0000000..45a5a05 --- /dev/null +++ b/code/appSimulation/mainwindow.cpp @@ -0,0 +1,596 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + glaciersWidget = new GlaciersWidget(); + QGridLayout* GLlayout = new QGridLayout; + GLlayout->addWidget(glaciersWidget, 0, 0); + GLlayout->setContentsMargins(0, 0, 0, 0); + ui->glWidget->setLayout(GLlayout); + glaciersWidget->SetCamera(Camera(Vector3(-10.0, -10.0, 10.0), Vector3(0.0, 0.0, 0.0))); + + createActions(); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::createActions() +{ + connect(glaciersWidget, SIGNAL(_signalEditSceneLeft(const Vector3&)), this, SLOT(editingSceneLeft(const Vector3&))); + connect(glaciersWidget, SIGNAL(_signalEditSceneRight(const Vector3&)), this, SLOT(editingSceneRight(const Vector3&))); + + connect(ui->btnLoadScene1, SIGNAL(clicked()), this, SLOT(presetScene1())); + connect(ui->btnLoadScene2, SIGNAL(clicked()), this, SLOT(presetScene2())); + connect(ui->btnLoadScene3, SIGNAL(clicked()), this, SLOT(presetScene3())); + connect(ui->btnLoadScene4, SIGNAL(clicked()), this, SLOT(presetScene4())); + connect(ui->btnLoadScene5, SIGNAL(clicked()), this, SLOT(presetScene5())); + connect(ui->btnLoadScene6, SIGNAL(clicked()), this, SLOT(presetScene6())); + connect(ui->btnLoadScene7, SIGNAL(clicked()), this, SLOT(presetScene7())); + connect(ui->btnLoadScene8, SIGNAL(clicked()), this, SLOT(presetScene8())); + connect(ui->btnLoadScene9, SIGNAL(clicked()), this, SLOT(presetScene9())); + + connect(ui->btnSelectDEM, SIGNAL(clicked()), this, SLOT(selectDEM())); + connect(ui->btnLoadDEM, SIGNAL(clicked()), this, SLOT(loadDEM())); + connect(ui->btnLoadELA, SIGNAL(clicked()), this, SLOT(loadELA())); + connect(ui->btnLoadPrec, SIGNAL(clicked()), this, SLOT(loadPrecipitation())); + connect(ui->btnLoadInitialIce, SIGNAL(clicked()), this, SLOT(loadInitialIce())); + + connect(ui->btnGlacierSimRun, SIGNAL(clicked()), this, SLOT(runGlacierSimulation())); + connect(ui->btnGlacierSimPause, SIGNAL(clicked()), this, SLOT(pauseGlacierSimulation())); + connect(ui->btnGlacierSimReset, SIGNAL(clicked()), this, SLOT(resetGlacierSimulation())); + connect(ui->btnGlacierUpsample, SIGNAL(clicked()), this, SLOT(upscaleGlacier())); + connect(ui->sb_dIce, SIGNAL(valueChanged(double)), this, SLOT(updateSteadyCondition(double))); + + connect(ui->sb_brushRadius, SIGNAL(valueChanged(double)), this, SLOT(updateBrushRadius(double))); + + connect(ui->btnSaveScene, SIGNAL(clicked()), this, SLOT(saveScene())); + + connect(ui->radioButton_Voxels, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + connect(ui->radioButton_Mesh, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + + connect(ui->checkBox_RenderBedrock, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + connect(ui->checkBox_RenderIce, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + + connect(ui->radioButton_ShadingIceNeutral, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + connect(ui->radioButton_ShadingIceELA, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + connect(ui->radioButton_ShadingIceThick, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + connect(ui->radioButton_ShadingIceGradient, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + connect(ui->radioButton_ShadingIceStress, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + connect(ui->radioButton_ShadingIceDiffusivity, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + connect(ui->radioButton_ShadingIceDiffusivityRaw, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + connect(ui->radioButton_ShadingIceUDeform, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + connect(ui->radioButton_ShadingIceUSlip, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + connect(ui->radioButton_ShadingIceUDominant, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + connect(ui->radioButton_ShadingIceDifference, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + connect(ui->radioButton_ShadingFeatShader, SIGNAL(toggled(bool)), this, SLOT(RenderUpdate())); + + connect(ui->btnReloadShader, SIGNAL(clicked()), glaciersWidget, SLOT(reloadShader())); +} + +void MainWindow::presetScene1() +{ + loadBedrocks("data/terrains/aiguestortes_20m.png", { 1, 2, 3, 6 }, 30); + loadMapsELA("data/terrains/aiguestortes_sun.png"); + //loadMapsPrecipitation("data/terrains/aiguestortes_precipitation.png"); + ui->checkUseELAMap->setChecked(true); + ui->checkUsePrecMap->setChecked(false); + ui->sb_ELA->setValue(2500); + ui->sb_ELAdev->setValue(200); + ui->sb_AccumRate->setValue(0.002); + ui->sb_AblateRate->setValue(0.001); +} + +void MainWindow::presetScene2() +{ + loadBedrocks(QString("data/terrains/ecrins_30m.png"), { 1, 2, 4, 8 }, 84); + loadMapsELA("data/terrains/ecrins_sun.png"); + factorBetaMaps.clear(); + ui->checkUseELAMap->setChecked(true); + ui->checkUsePrecMap->setChecked(false); + ui->sb_ELA->setValue(2600.0); + ui->sb_ELAdev->setValue(200.0); + ui->sb_AccumRate->setValue(0.001); + ui->sb_AblateRate->setValue(0.001); +} + +void MainWindow::presetScene3() +{ + loadBedrocks(QString("data/terrains/canyon_10m.png"), { 1, 2, 4, 6, 12 }, 30); + loadMapsELA("data/terrains/canyon_sun.png"); + loadMapsPrecipitation("data/terrains/canyon_precipitation.png"); + ui->checkUseELAMap->setChecked(true); + ui->checkUsePrecMap->setChecked(true); + ui->sb_ELA->setValue(2000.0); + ui->sb_ELAdev->setValue(300.0); + ui->sb_AccumRate->setValue(0.002); + ui->sb_AblateRate->setValue(0.001); +} + +void MainWindow::presetScene4() +{ + loadBedrocks("data/terrains/mtperdu_20m.png", { 1, 2, 3, 6 }, 30); + loadMapsELA("data/terrains/mtperdu_sun.png"); + factorBetaMaps.clear(); + ui->checkUseELAMap->setChecked(true); + ui->checkUsePrecMap->setChecked(false); + ui->sb_ELA->setValue(2500); + ui->sb_ELAdev->setValue(300); + ui->sb_AccumRate->setValue(0.002); + ui->sb_AblateRate->setValue(0.001); +} + +void MainWindow::presetScene5() +{ + loadBedrocks("data/terrains/sthelens_10m.png", { 2, 4, 8 }, 20); + loadMapsELA("data/terrains/sthelens_sun.png"); + factorBetaMaps.clear(); + ui->checkUseELAMap->setChecked(true); + ui->checkUsePrecMap->setChecked(false); + ui->sb_ELA->setValue(2000); + ui->sb_ELAdev->setValue(200); + ui->sb_AccumRate->setValue(0.005); + ui->sb_AblateRate->setValue(0.001); +} + +void MainWindow::presetScene6() +{ + loadBedrocks(QString("data/terrains/ruapehu_25m.png"), { 1, 2, 3, 6 }, 30); + loadMapsELA("data/terrains/ruapehu_sun.png"); + factorBetaMaps.clear(); + ui->checkUseELAMap->setChecked(true); + ui->checkUsePrecMap->setChecked(false); + ui->sb_ELA->setValue(2000); + ui->sb_ELAdev->setValue(300); + ui->sb_AccumRate->setValue(0.003); + ui->sb_AblateRate->setValue(0.001); +} + +void MainWindow::presetScene7() +{ + loadBedrocks(QString("data/terrains/smokies_10m.png"), { 1, 2, 4, 6, 12 }, 30); + loadMapsELA("data/terrains/smokies_sun.png"); + factorBetaMaps.clear(); + ui->checkUseELAMap->setChecked(true); + ui->checkUsePrecMap->setChecked(false); + ui->sb_ELA->setValue(1500); + ui->sb_ELAdev->setValue(0); + ui->sb_AccumRate->setValue(0.001); + ui->sb_AblateRate->setValue(0.001); +} + +void MainWindow::presetScene8() +{ + loadBedrocks(QString("data/terrains/kungsleden_25m.png"), { 1, 3, 6, 12 }, 60); + loadMapsELA("data/terrains/kungsleden_sun.png"); + factorBetaMaps.clear(); + ui->checkUseELAMap->setChecked(true); + ui->checkUsePrecMap->setChecked(false); + ui->sb_ELA->setValue(1400); + ui->sb_ELAdev->setValue(0); + ui->sb_AccumRate->setValue(0.005); + ui->sb_AblateRate->setValue(0.001); +} + +void MainWindow::presetScene9() +{ + loadBedrocks(QString("data/terrains/chartreuse_edit_20m.png"), { 1, 2, 5, 10 }, 50); + loadMapsELA("data/terrains/chartreuse_sun.png"); + factorBetaMaps.clear(); + ui->checkUseELAMap->setChecked(true); + ui->checkUsePrecMap->setChecked(false); + ui->sb_ELA->setValue(1400); + ui->sb_ELAdev->setValue(100); + ui->sb_AccumRate->setValue(0.003); + ui->sb_AblateRate->setValue(0.001); +} + +void MainWindow::selectDEM() +{ + QString filename = QFileDialog::getOpenFileName(nullptr, + "Choose a filename to load", "", "png file (*.png)"); + if (!filename.isNull()) { + demFilename = filename; + ui->btnSelectDEM->setText(QFile(filename).fileName()); + } +} + +void MainWindow::loadDEM() +{ + if (demFilename.length() < 1) return; + + double demKM = ui->sb_DEM_km->value(); + double hmin = ui->sb_DEM_hmin->value(); + double hmax = ui->sb_DEM_hmax->value(); + std::vector scales; + QStringList ds = ui->le_DEM_scales->text().split(","); + for (QString ss : ds) { + bool ok = false; + int s = ss.toInt(&ok); + if (!ok) s = 1; + scales.push_back(s); + } + + loadBedrocks(demFilename, scales, demKM, { hmin, hmax }); +} + +void MainWindow::loadELA() +{ + QString filename = QFileDialog::getOpenFileName(nullptr, + "Choose a filename to load", "", "png file (*.png)"); + if (!filename.isNull()) { + loadMapsELA(filename); + } +} + +void MainWindow::loadPrecipitation() +{ + QString filename = QFileDialog::getOpenFileName(nullptr, + "Choose a filename to load", "", "png file (*.png)"); + if (!filename.isNull()) { + loadMapsPrecipitation(filename); + } +} + +void MainWindow::loadInitialIce() +{ + QString filename = QFileDialog::getOpenFileName(nullptr, + "Choose a filename to load", "", "png file (*.png)"); + if (!filename.isNull()) { + loadMapsIce(filename); + } +} + +void MainWindow::Render(bool resetCamera) +{ + if (glacierTerrain.isEmpty()) + return; + + if (resetCamera) { + Box3 tbox = glacierTerrain.getBoundingBox(); + Camera cc = Camera::View(tbox); + glaciersWidget->SetCamera(cc); + } + + // Geometry + bool b = ui->checkBox_RenderBedrock->isChecked(); + bool i = ui->checkBox_RenderIce->isChecked(); + if (ui->radioButton_Voxels->isChecked()) { + glaciersWidget->setRenderType(true, b, i); + } + else { + glaciersWidget->setRenderType(false, b, i); + } + + // Texture + if (ui->radioButton_ShadingIceNeutral->isChecked()) glaciersWidget->setTextureType(GlaciersWidget::GlacierTextureType::NONE); + if (ui->radioButton_ShadingIceELA->isChecked()) glaciersWidget->setTextureType(GlaciersWidget::GlacierTextureType::ELA); + if (ui->radioButton_ShadingIceThick->isChecked()) glaciersWidget->setTextureType(GlaciersWidget::GlacierTextureType::THICKNESS); + if (ui->radioButton_ShadingIceGradient->isChecked()) glaciersWidget->setTextureType(GlaciersWidget::GlacierTextureType::GRADIENT); + if (ui->radioButton_ShadingIceStress->isChecked()) glaciersWidget->setTextureType(GlaciersWidget::GlacierTextureType::STRESS); + if (ui->radioButton_ShadingIceDiffusivity->isChecked()) glaciersWidget->setTextureType(GlaciersWidget::GlacierTextureType::DIFFUSIVITY); + if (ui->radioButton_ShadingIceDiffusivityRaw->isChecked()) glaciersWidget->setTextureType(GlaciersWidget::GlacierTextureType::DIFFUSIVITY_RAW); + if (ui->radioButton_ShadingIceUDeform->isChecked()) glaciersWidget->setTextureType(GlaciersWidget::GlacierTextureType::SPEED_DEFORM); + if (ui->radioButton_ShadingIceUSlip->isChecked()) glaciersWidget->setTextureType(GlaciersWidget::GlacierTextureType::SPEED_SLIP); + if (ui->radioButton_ShadingIceUDominant->isChecked()) glaciersWidget->setTextureType(GlaciersWidget::GlacierTextureType::SPEED_DOMINANT_TYPE); + if (ui->radioButton_ShadingIceDifference->isChecked()) glaciersWidget->setTextureType(GlaciersWidget::GlacierTextureType::ICE_DIFFERENCE); + if (ui->radioButton_ShadingFeatShader->isChecked()) glaciersWidget->setTextureType(GlaciersWidget::GlacierTextureType::FEAT_SHADER); + + glaciersWidget->update(); +} + +void MainWindow::RenderUpdate() +{ + Render(false); +} + +void MainWindow::editingSceneLeft(const Vector3& anchor) +{ + Vector2 ctr = Vector2(anchor[0], anchor[1]); + if (ui->radioButton_brushBed->isChecked()) { + glacierTerrain.GetBedrock().addGaussian(ctr, brushRadius, rockStrength); + glacierTerrain.updateBedrockGPU(); + glaciersWidget->updateGeometry(); + } + else if (ui->radioButton_brushIce->isChecked()) { + glacierTerrain.GetIce().addGaussian(ctr, brushRadius, iceStrength); + glacierTerrain.updateIceGPU(); + glaciersWidget->updateGeometry(); + } + else if (ui->radioButton_brushELA->isChecked()) { + glacierTerrain.GetELA().addGaussian(ctr, brushRadius, elaStrength); + glacierTerrain.updateELAGPU(); + updatePreviewELA(glacierTerrain.GetELA()); + } + else if (ui->radioButton_brushPrec->isChecked()) { + glacierTerrain.GetAccumRate().addGaussian(ctr, brushRadius, precStrenth); + glacierTerrain.updateAccumRateGPU(); + updatePreviewAccum(glacierTerrain.GetAccumRate()); + } + + Render(false); +} + +void MainWindow::editingSceneRight(const Vector3& anchor) +{ + Vector2 ctr = Vector2(anchor[0], anchor[1]); + if (ui->radioButton_brushBed->isChecked()) { + glacierTerrain.GetBedrock().addGaussian(ctr, brushRadius, -rockStrength); + glacierTerrain.updateBedrockGPU(); + glaciersWidget->updateGeometry(); + } + else if (ui->radioButton_brushIce->isChecked()) { + glacierTerrain.GetIce().addGaussian(ctr, brushRadius, -iceStrength); + glacierTerrain.updateIceGPU(); + glaciersWidget->updateGeometry(); + } + else if (ui->radioButton_brushELA->isChecked()) { + glacierTerrain.GetELA().addGaussian(ctr, brushRadius, -elaStrength); + glacierTerrain.updateELAGPU(); + updatePreviewELA(glacierTerrain.GetELA()); + } + else if (ui->radioButton_brushPrec->isChecked()) { + glacierTerrain.GetAccumRate().addGaussian(ctr, brushRadius, -precStrenth); + glacierTerrain.updateAccumRateGPU(); + updatePreviewAccum(glacierTerrain.GetAccumRate()); + } + + Render(false); +} + +void MainWindow::updateBrushRadius(double d) +{ + brushRadius = d; + glaciersWidget->updateBrushRadius(d); +} + +void MainWindow::configSimulation() +{ + double factorUdeform = std::min(2.0 - 0.1 * double(ui->sliderFactorU->value()), 1.0); + double factorUslide = std::min(0.1 * double(ui->sliderFactorU->value()), 1.0); + + ScalarField2 configELA = ui->checkUseELAMap->isChecked() ? + getVariableELA(ui->sb_ELA->value(), ui->sb_ELAdev->value()) : + ScalarField2(glacierTerrain.getDomain(), glacierTerrain.numCellsX(), glacierTerrain.numCellsY(), ui->sb_ELA->value()); + + ScalarField2 configBeta = ui->checkUsePrecMap->isChecked() ? + factorBetaMaps[multiresIndex] : + ScalarField2(glacierTerrain.getDomain(), glacierTerrain.numCellsX(), glacierTerrain.numCellsY(), 1.0); + + glacierTerrain.configSimulation(configELA, configBeta, + ui->sb_AccumRate->value(), ui->sb_AblateRate->value(), + factorUdeform, factorUslide); + + double dIceSteady = ui->sb_dIce->value(); + glaciersWidget->setSteadyCondition(dIceSteady); + glaciersWidget->setMinimumSimulationYears(1); +} + +void MainWindow::runGlacierSimulation() +{ + configSimulation(); + glaciersWidget->runSimulation(ui->checkPauseOnSteady->isChecked()); +} + +void MainWindow::pauseGlacierSimulation() +{ + glaciersWidget->pauseSimulation(); +} + +void MainWindow::resetGlacierSimulation() +{ + glacierTerrain.resetSimulation(); + if (ui->checkUseInitialIce->isChecked() && int(initialIceMaps.size()) > multiresIndex) { + glacierTerrain.setIceMap(initialIceMaps[multiresIndex]); + } + glaciersWidget->pauseSimulation(); + glaciersWidget->resetSimulation(); + glaciersWidget->update(); +} + +void MainWindow::upscaleGlacier() +{ + if (multiresIndex <= 0) { + return; + } + + multiresIndex--; + ScalarField2 hiresIce = glacierTerrain.remapIceSurface(bedrocksMultires[multiresIndex]); + glacierTerrain = GlacierTerrain(bedrocksMultires[multiresIndex], hiresIce); + + double dIce = glaciersWidget->getLastdIce(); + if (bedrocksMultires.size() > 0) { + double r = double(bedrocksMultires[multiresIndex].getSizeX()) / double(bedrocksMultires[multiresIndex+1].getSizeX()); + dIce *= r; + } + ui->sb_dIce->setValue(dIce); + + glaciersWidget->setGlacierTerrain(&glacierTerrain); + configSimulation(); + + ui->labelResolution->setText(QString::number(hiresIce.getSizeX()) + "x" + QString::number(hiresIce.getSizeY()) + (multiresIndex > 0 ? "" : " (max)")); + + Render(false); +} + +void MainWindow::updateSteadyCondition(double d) +{ + glaciersWidget->setSteadyCondition(d); +} + +void MainWindow::saveScene() +{ + QString filename = QFileDialog::getSaveFileName(nullptr, + "Choose a filename to save", + "", + "png file (*.png)"); + + if (!filename.isNull()) { + + double iceMin, iceMax; + glacierTerrain.GetIce().getRange(iceMin, iceMax); + QImage imageIce = glacierTerrain.GetIce().CreateImage(0.0, 256.0 * 256.0, false); + imageIce.save(QString(filename).replace(".png", "_ice_" + QString::number(iceMax) + ".png")); + + double hmin, hmax; + ScalarField2 hf = glacierTerrain.GetHeightfield(); + hf.getRange(hmin, hmax); + hf.CreateImage(0.0, hmax, false).mirrored(false, true).save(QString(filename).replace(".png", "_heights_" + QString::number(hmax) + ".png")); + + QImage aboveELA(imageIce.width(), imageIce.height(), QImage::Format::Format_ARGB32); + for (int i = 0; i < aboveELA.width(); i++) { + for (int j = 0; j < aboveELA.height(); j++) { + aboveELA.setPixel(i, j, glacierTerrain.aboveELA(i,j) ? QColor(255,255,255).rgba() : QColor(0,0,0,255).rgba() ); + } + } + aboveELA.save(QString(filename).replace(".png", "_ela.png")); + } +} + +void MainWindow::loadBedrocks(const QString& filename, const std::vector& downscales, double terrainKM, const std::vector& elevRange) +{ + QImage bedImg = QImage(filename).mirrored(false, true); + + int w = bedImg.width(); + int h = bedImg.height(); + double terrainWidth = w >= h ? terrainKM : terrainKM * (double(w) / double(h)); + double terrainHeight = h >= w ? terrainKM : terrainKM * (double(h) / double(w)); + Box2 terrainSize(Vector2(0), Vector2(terrainWidth*1000.0, terrainHeight*1000.0)); + + ScalarField2 bedrock(terrainSize, bedImg, 0, 0.1 * (256 * 256 - 1), true); + double hmin, hmax; + bedrock.getRange(hmin, hmax); + if (elevRange.size() == 1) { + bedrock *= elevRange[0]; + } + else if (elevRange.size() == 2) { + for (int i = 0; i < bedrock.getSizeX() * bedrock.getSizeY(); i++) { + bedrock[i] = elevRange[0] + (elevRange[1] - elevRange[0]) * (bedrock[i] - hmin) / (hmax - hmin); + } + hmin = elevRange[0]; + hmax = elevRange[1]; + } + + bedrocksMultires.clear(); + for (int s : downscales) { + bedrocksMultires.push_back(bedrock.setResolution(bedrock.getSizeX() / s, bedrock.getSizeY() / s)); + } + multiresIndex = int(bedrocksMultires.size()) - 1; + + ui->sb_dIce->setValue(0.001); + + glacierTerrain = GlacierTerrain(bedrocksMultires.back()); + glaciersWidget->setGlacierTerrain(&glacierTerrain); + + Render(true); + + demFilename = filename; + ui->btnSelectDEM->setText(QFileInfo(filename).fileName()); + ui->labelResolution->setText(QString::number(bedrocksMultires.back().getSizeX()) + + "x" + QString::number(bedrocksMultires.back().getSizeY())); + ui->sb_DEM_km->setValue(terrainKM); + ui->sb_DEM_hmin->setValue(hmin); + ui->sb_DEM_hmax->setValue(hmax); + QString ss = QString::number(downscales[0]); + for (int i = 1; i < downscales.size(); i++) ss += "," + QString::number(downscales[i]); + ui->le_DEM_scales->setText(ss); + + int s = std::min(ui->imglabelDEM->width(), ui->imglabelDEM->height()); + ui->imglabelDEM->setPixmap(QPixmap::fromImage(bedImg.mirrored(false, true)).scaled(s, s, Qt::KeepAspectRatio)); +} + +void MainWindow::loadMapsELA(const QString& filename) +{ + QImage sunImg = QImage(filename).mirrored(false, true); + double sunavg = 0.0; + for (int i = 0; i < sunImg.width(); i++) { + for (int j = 0; j < sunImg.height(); j++) { + sunavg += double(qRed(sunImg.pixel(i, j))) / 255.0; + } + } + sunavg /= sunImg.width() * sunImg.height(); + + for (unsigned int i = 0; i < bedrocksMultires.size(); i++) { + const ScalarField2& bedrock = bedrocksMultires[i]; + int nx = bedrock.getSizeX(); + int ny = bedrock.getSizeY(); + QImage sunlight = sunImg.scaled(QSize(nx, ny), Qt::KeepAspectRatio, Qt::SmoothTransformation); + ScalarField2 elaFactor(bedrock.getDomain(), nx, ny, 1.0); + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + elaFactor(i, j) = double(qRed(sunlight.pixel(i, j))) / 255.0 - sunavg; + } + } + factorElaDeviation.push_back(elaFactor); + } + + updatePreviewELA(getVariableELA(ui->sb_ELA->value(), ui->sb_ELAdev->value())); +} + +void MainWindow::loadMapsPrecipitation(const QString& filename) +{ + QImage precImg = QImage(filename).mirrored(false, true); + + for (unsigned int i = 0; i < bedrocksMultires.size(); i++) { + const ScalarField2& bedrock = bedrocksMultires[i]; + int nx = bedrock.getSizeX(); + int ny = bedrock.getSizeY(); + QImage precipitation = precImg.scaled(QSize(nx, ny), Qt::KeepAspectRatio, Qt::SmoothTransformation); + ScalarField2 precFactor(bedrock.getDomain(), ny, ny, 1.0); + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + precFactor(i, j) = double(qRed(precipitation.pixel(i, j))) / 255.0; + } + } + factorBetaMaps.push_back(precFactor); + } + + updatePreviewAccum(factorBetaMaps.back()); +} + +void MainWindow::loadMapsIce(const QString& filename) +{ + QImage iceImg = QImage(filename); // .mirrored(false, true); + + for (unsigned int i = 0; i < bedrocksMultires.size(); i++) { + const ScalarField2& bedrock = bedrocksMultires[i]; + int nx = bedrock.getSizeX(); + int ny = bedrock.getSizeY(); + QImage ice = iceImg.scaled(QSize(nx, ny), Qt::KeepAspectRatio, Qt::SmoothTransformation); + ScalarField2 iceMap(bedrock.getDomain(), ice, 0, 256.0 * 256.0 - 1, false); + initialIceMaps.push_back(iceMap); + } + + ui->imglabelInitialIce->setPixmap(QPixmap::fromImage(iceImg.mirrored(false, true)) + .scaled(ui->imglabelELA->width(), ui->imglabelELA->height(), Qt::KeepAspectRatio)); +} + +ScalarField2 MainWindow::getVariableELA(double avgELA, double devELA) +{ + ScalarField2 elamap(factorElaDeviation[multiresIndex]); + elamap *= devELA; + elamap += avgELA; + return elamap; +} + +void MainWindow::updatePreviewELA(const ScalarField2& ela) +{ + ui->imglabelELA->setPixmap(QPixmap::fromImage(ela.CreateImage(ColorPalette::CoolWarm()).mirrored(false, true)) + .scaled(ui->imglabelELA->width(), ui->imglabelELA->height(), Qt::KeepAspectRatio)); +} + +void MainWindow::updatePreviewAccum(const ScalarField2& accum) +{ + ui->imglabelPrec->setPixmap(QPixmap::fromImage(accum.CreateImage(ColorPalette::CoolWarm()).mirrored(false, true)) + .scaled(ui->imglabelPrec->width(), ui->imglabelPrec->height(), Qt::KeepAspectRatio)); +} diff --git a/code/appSimulation/mainwindow.h b/code/appSimulation/mainwindow.h new file mode 100644 index 0000000..1f14dab --- /dev/null +++ b/code/appSimulation/mainwindow.h @@ -0,0 +1,89 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include "glacierswidget.h" +#include "glacierterrain.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +public slots: + + void presetScene1(); + void presetScene2(); + void presetScene3(); + void presetScene4(); + void presetScene5(); + void presetScene6(); + void presetScene7(); + void presetScene8(); + void presetScene9(); + + void selectDEM(); + void loadDEM(); + void loadELA(); + void loadPrecipitation(); + void loadInitialIce(); + + void Render(bool resetCamera = true); + void RenderUpdate(); + + void editingSceneLeft(const Vector3& p); + void editingSceneRight(const Vector3& p); + void updateBrushRadius(double d); + + void runGlacierSimulation(); + void pauseGlacierSimulation(); + void resetGlacierSimulation(); + void upscaleGlacier(); + void updateSteadyCondition(double); + + void saveScene(); + +private: + void createActions(); + + void configSimulation(); + + void loadBedrocks(const QString& filename, const std::vector& downscales, double terrainKM, const std::vector& elevRange = {}); + void loadMapsELA(const QString& filename); + void loadMapsPrecipitation(const QString& filename); + void loadMapsIce(const QString& filename); + + ScalarField2 getVariableELA(double avgELA, double devELA); + void updatePreviewELA(const ScalarField2& ela); + void updatePreviewAccum(const ScalarField2& accum); + +private: + Ui::MainWindow *ui; + + GlaciersWidget *glaciersWidget; + GlacierTerrain glacierTerrain; + + // multiresolution bedrocks and control maps + QString demFilename = ""; + std::vector bedrocksMultires; + std::vector factorElaDeviation; + std::vector factorBetaMaps; + std::vector initialIceMaps; + int multiresIndex; + + // brushes + double brushRadius = 750; + const double rockStrength = 20; + const double iceStrength = 10; + const double elaStrength = 20; + const double precStrenth = 0.1; + +}; +#endif // MAINWINDOW_H diff --git a/code/appSimulation/mainwindow.ui b/code/appSimulation/mainwindow.ui new file mode 100644 index 0000000..ab1e922 --- /dev/null +++ b/code/appSimulation/mainwindow.ui @@ -0,0 +1,1148 @@ + + + MainWindow + + + + 0 + 0 + 1618 + 1137 + + + + Glaciers Simulation + + + + + + + + 340 + 16777215 + + + + 0 + + + + Scene + + + + + + Scene presets + + + + + + Aiguestortes + + + false + + + + + + + Ecrins + + + false + + + + + + + Canyon + + + false + + + + + + + Pyrenees + + + false + + + + + + + St Helens + + + false + + + + + + + Ruapehu + + + false + + + + + + + Smokies + + + false + + + + + + + Sweden + + + false + + + + + + + Chartreuse + + + false + + + + + + + + + + + 0 + 250 + + + + + 16777215 + 300 + + + + 0 + + + + Terrain + + + + + + + 0 + 0 + + + + + 128 + 128 + + + + + 200 + 200 + + + + DEM + + + Qt::AlignCenter + + + + + + + + + Select file... + + + false + + + + + + + + + Side km + + + + + + + 1 + + + 10000.000000000000000 + + + 1.000000000000000 + + + 30.000000000000000 + + + + + + + Elevation + + + + + + + 1 + + + 10000.000000000000000 + + + 100.000000000000000 + + + 1500.000000000000000 + + + + + + + 1 + + + 10000.000000000000000 + + + 100.000000000000000 + + + 3500.000000000000000 + + + + + + + Multires + + + + + + + 1,2,4,8 + + + + + + + + + Load / Update + + + false + + + + + + + + + + Sunlight + + + + + + + 0 + 0 + + + + + 128 + 128 + + + + + 200 + 200 + + + + Sunlight + + + Qt::AlignCenter + + + + + + + + + Use + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Load + + + false + + + + + + + + + + Precip. + + + + + + + 0 + 0 + + + + + 128 + 128 + + + + + 200 + 200 + + + + Precipitation + + + Qt::AlignCenter + + + + + + + + + Use + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Load + + + false + + + + + + + + + + Ice ini + + + + + + + 0 + 0 + + + + + 128 + 128 + + + + + 200 + 200 + + + + Initial ice + + + Qt::AlignCenter + + + + + + + + + Use + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Load + + + false + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + Glacier parameters + + + + + + + + ELA + + + + + + + 10000.000000000000000 + + + 100.000000000000000 + + + 2500.000000000000000 + + + + + + + + + + + ELA sunlight deviation + + + + + + + 10000.000000000000000 + + + 100.000000000000000 + + + 200.000000000000000 + + + + + + + + + + + Accumulation rate + + + + + + + 3 + + + 1.000000000000000 + + + 0.001000000000000 + + + 0.001000000000000 + + + + + + + + + + + Ablation rate + + + + + + + 3 + + + 1.000000000000000 + + + 0.001000000000000 + + + 0.001000000000000 + + + + + + + + + 20 + + + 10 + + + Qt::Horizontal + + + QSlider::NoTicks + + + + + + + + + Only deformation + + + + + + + ^ + + + Qt::AlignCenter + + + + + + + Qt::LeftToRight + + + Only sliding + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + Simulation + + + + + + + + Run + + + false + + + + + + + Pause + + + false + + + + + + + Reset + + + false + + + + + + + + + + + Pause on steady state + + + true + + + + + + + 4 + + + 10000.000000000000000 + + + 0.000100000000000 + + + 0.001000000000000 + + + + + + + m/y + + + + + + + + + + + Upsample + + + false + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + Brushes (ctrl + left/right) + + + + + + + + Bedrock (add/rem) + + + true + + + + + + + ELA (higher/lower) + + + + + + + Ice (add/rem) + + + + + + + Precip. (more/less) + + + + + + + + + + + 5000.000000000000000 + + + 50.000000000000000 + + + 750.000000000000000 + + + + + + + Brush width (m) + + + + + + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + Save scene... + + + + + + + + Render + + + + + + false + + + Model type + + + + + + Mesh + + + true + + + false + + + + + + + Voxel + + + true + + + + + + + + + + Layers + + + + + + Bedrock + + + true + + + + + + + Ice + + + true + + + + + + + + + + Texture + + + + + + Neutral + + + false + + + + + + + Ablation / Accumulation zones + + + true + + + + + + + Ice thickness + + + false + + + + + + + Gradient mag + + + true + + + false + + + + + + + Basal stress + + + false + + + + + + + Speed Deform + + + false + + + + + + + Speed Slip + + + false + + + + + + + Dominant speed + + + false + + + + + + + Diffusivity (muscl) + + + false + + + + + + + Diffusivity (raw) + + + false + + + + + + + Ice difference + + + false + + + + + + + + + Custom shader + + + false + + + + + + + + 16777215 + 21 + + + + Reload + + + false + + + + + + + + + + + + Qt::Vertical + + + + 20 + 418 + + + + + + + + + + + + + + + + + 0 + 0 + 1618 + 26 + + + + + + + + diff --git a/code/core/core.cpp b/code/core/core.cpp new file mode 100644 index 0000000..ddf7226 --- /dev/null +++ b/code/core/core.cpp @@ -0,0 +1,345 @@ +#include "core.h" + +const int SimplexNoise::perm[512] = { + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, + 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, + 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, + 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, + 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, + 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, + 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, + 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, + 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, + 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, + 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, + 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180, + + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, + 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, + 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, + 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, + 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, + 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, + 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, + 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, + 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, + 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, + 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, + 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 +}; +const int SimplexNoise::grad2[8][2] = { + { 1, 1 }, { -1, 1 }, { 1, -1 }, { -1, -1 }, + { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } +}; +const double SimplexNoise::F2 = 0.5 * (sqrt(3.0) - 1.0); +const double SimplexNoise::G2 = (3.0 - sqrt(3.0)) / 6.0; + +double SimplexNoise::at(double x, double y) const +{ + // Noise contributions from the three corners + double n[3] = { 0.0, 0.0, 0.0 }; + + // Skew the input space to determine which simplex cell we are in + + // Hairy factor for 2D + double s = (x + y) * F2; + + int i = Math::Integer(x + s); + int j = Math::Integer(y + s); + + double t = (i + j) * G2; + + // Unskew the cell origin back to (x,y) space + double X0 = i - t; + double Y0 = j - t; + + // The x,y distances from the cell origin + double x0 = x - X0; + double y0 = y - Y0; + + // For the 2D case, the simplex shape is an equilateral triangle. + // Determine which simplex we are in. + + int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords + + if (x0 > y0) { i1 = 1; j1 = 0; } // lower triangle, XY order: (0,0)->(1,0)->(1,1) + else { i1 = 0; j1 = 1; } // upper triangle, YX order: (0,0)->(0,1)->(1,1) + + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where c = (3-sqrt(3))/6 + + double x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords + double y1 = y0 - j1 + G2; + double x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords + double y2 = y0 - 1.0 + 2.0 * G2; + + // Work out the hashed gradient indices of the three simplex corners + int ii = i & 255; + int jj = j & 255; + + int gi0 = perm[ii + perm[jj]] % 8; + int gi1 = perm[ii + i1 + perm[jj + j1]] % 8; + int gi2 = perm[ii + 1 + perm[jj + 1]] % 8; + + // Calculate the contribution from the three corners + + double t0 = 0.5 - x0 * x0 - y0 * y0; + if (t0 >= 0) + { + t0 *= t0; + n[0] = t0 * t0 * dot(grad2[gi0], x0, y0); + } + + double t1 = 0.5 - x1 * x1 - y1 * y1; + if (t1 >= 0) + { + t1 *= t1; + n[1] = t1 * t1 * dot(grad2[gi1], x1, y1); + } + + double t2 = 0.5 - x2 * x2 - y2 * y2; + if (t2 >= 0) + { + t2 *= t2; + n[2] = t2 * t2 * dot(grad2[gi2], x2, y2); + } + + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + return 70.0 * (n[0] + n[1] + n[2]); +} + + +bool Box2::Intersect(const Vector2 &s0, const Vector2 &s1, double &tmin, double &tmax) +{ + const double epsilon = 1.0e-5; + + tmin = -1e16; + tmax = 1e16; + + const Vector2& a = bmin; + const Vector2& b = bmax; + Vector2 p = s0; + Vector2 d = s1 - s0; + + double t; + // Ox + if (d[0] < -epsilon) { + t = (a[0] - p[0]) / d[0]; + if (t < tmin) + return false; + if (t <= tmax) + tmax = t; + t = (b[0] - p[0]) / d[0]; + if (t >= tmin) { + if (t > tmax) + return false; + tmin = t; + } + } + else if (d[0] > epsilon) { + t = (b[0] - p[0]) / d[0]; + if (t < tmin) + return false; + if (t <= tmax) + tmax = t; + t = (a[0] - p[0]) / d[0]; + if (t >= tmin) { + if (t > tmax) + return false; + tmin = t; + } + } + else if (p[0]b[0]) + return false; + + // Oy + if (d[1] < -epsilon) { + t = (a[1] - p[1]) / d[1]; + if (t < tmin) + return false; + if (t <= tmax) + tmax = t; + t = (b[1] - p[1]) / d[1]; + if (t >= tmin) { + if (t > tmax) + return false; + tmin = t; + } + } + else if (d[1] > epsilon) { + t = (b[1] - p[1]) / d[1]; + if (t < tmin) + return false; + if (t <= tmax) + tmax = t; + t = (a[1] - p[1]) / d[1]; + if (t >= tmin) { + if (t > tmax) + return false; + tmin = t; + } + } + else if (p[1]b[1]) + return false; + + return true; +} + +Camera::Camera() +{ + Camera::eye = Vector3(0.0); + Camera::at = Vector3(0.0, 1.0, 0.0); + Camera::up = Vector3(0.0, 0.0, 1.0); + + // Near and far planes + Camera::nearplane = 1.0; + Camera::farplane = 1000.0; + + // Aperture + Camera::cah = 0.980; + Camera::cav = 0.735; + Camera::fl = 35.0; +} + +Camera::Camera(const Vector3& eye, const Vector3& at, const Vector3& up, double near, double far) +{ + Camera::eye = eye; + Camera::at = at; + Camera::up = up; + + // Near and far planes + Camera::nearplane = near; + Camera::farplane = far; + + // Aperture + Camera::cah = 0.980; + Camera::cav = 0.735; + Camera::fl = 35.0; +} + +double Camera::getAngleOfViewH(double, double) const +{ + return 2.0 * atan(cah * 25.4 * 0.5 / fl); +} + +double Camera::getAngleOfViewV(double w, double h) const +{ + double avh = getAngleOfViewH(w, h); + return 2.0 * atan(tan(avh / 2.0) * double(h) / double(w)); +} + +void Camera::upDownRound(double a) +{ + Vector3 z = at - eye; + double length = Norm(z); + z = z/length; + Vector3 left = Normalized(cross(up, z)); + + // Rotate + z = z * cos(a) + up * sin(a); + + // Update Vector + up = cross(z, left); + eye = at - z * length; +} + +void Camera::leftRightRound(double a) +{ + Vector3 e = eye - at; + Vector3 left = cross(up, e); + e = Vector3(e[0] * cos(a) - e[1] * sin(a), e[0] * sin(a) + e[1] * cos(a), e[2]); + left = Vector3(left[0] * cos(a) - left[1] * sin(a), left[0] * sin(a) + left[1] * cos(a), 0.0); + up = Normalized(cross(left, -e)); + eye = at + e; +} + +void Camera::backForth(double a, bool moveAt) +{ + Vector3 z = at - eye; + double length = Norm(z); + z = z/length; + eye = eye + a * z; + if (moveAt) { + at = at + a * z; + } +} + +void Camera::upDownPlane(double a) +{ + Vector3 z = at - eye; + double length = Norm(z); + z = z/length; + Vector3 left = Normalized(cross(Vector3(0, 0, 1), z)); + + eye = eye + a * cross(z, left); + at = at + a * cross(z, left); +} + +void Camera::leftRightPlane(double a) +{ + Vector3 z = at - eye; + z[2] = 0.0; + double length = Norm(z); + z = z/length; + Vector3 left = Normalized(cross(Vector3(0, 0, 1), z)); + + eye = eye + a * left; + at = at + a * left; +} + +Ray Camera::pixelToRay(int px, int py, int w, int h) const +{ + // Get coordinates + Vector3 view = getViewDir(); + Vector3 horizontal = Normalized(cross(view, up)); + Vector3 vertical = Normalized(cross(horizontal, view)); + + double length = 1.0; + + // Convert to radians + double rad = getAngleOfViewV(w, h); // fov + + double vLength = tan(rad / 2.0) * length; + double hLength = vLength * (double(w) / double(h)); + vertical = vertical*vLength; + horizontal = horizontal*hLength; + + // Translate mouse coordinates so that the origin lies in the center of the view port + double x = px - w / 2.0; + double y = h / 2.0 - py; + + // Scale mouse coordinates so that half the view port width and height becomes 1.0 + x /= w / 2.0; + y /= h / 2.0; + + // Direction is a linear combination to compute intersection of picking ray with view port plane + return Ray(eye, Normalized(view * length + horizontal * x + vertical * y)); +} + +Camera Camera::View(const Box3& box) +{ + Vector3 v = 0.5*(box.getMax() - box.getMin()); + v[2] = 0; + double r = Norm(v); + v = 2*v; + v[2] = -r; + return Camera(box.center() - v, box.center(), Vector3(0.0, 0.0, 1.0), r, 3*r); +} + + +Vector3 ColorPalette::getColor(double t) const { + if (colors.size() == 0) return Vector3(1); + if (colors.size() == 1) return colors[0]; + + if (t < anchors.front()) return colors.front(); + if (t > anchors.back()) return colors.back(); + for (int i = 0; i < int(colors.size() - 1); i++) { + if (t < anchors[i+1]) { + double s = Math::LinearStep(t, anchors[i], anchors[i+1]); + return (1-s)*colors[i] + s*colors[i+1]; + } + } + + return Vector3(1); +} diff --git a/code/core/core.h b/code/core/core.h new file mode 100644 index 0000000..2f67d97 --- /dev/null +++ b/code/core/core.h @@ -0,0 +1,289 @@ +#ifndef _CORE_H_ +#define _CORE_H_ + +#include +#include +#include + +namespace Math { + const double Pi = 3.14159265358979323846; + + inline double min(double a, double b) { + return (a < b ? a : b); + } + + inline double max(double a, double b) { + return (a > b ? a : b); + } + + inline double DegreeToRadian(double a) { + return a * Math::Pi / 180.0; + } + + inline double RadianToDegree(double a) { + return a * 180.0 / Math::Pi; + } + + inline double LinearStep(double x, double a, double b) { + if (x < a) return 0; + if (x > b) return 1; + return (x - a) / (b - a); + } + + inline double Clamp(double x, double a = 0, double b = 1) { + return (x < a ? a : (x > b ? b : x)); + } + + inline double Lerp(double a, double b, double t) { + return a + t * (b - a); + } + + inline double Bilinear(double a00, double a10, double a11, double a01, double u, double v) { + return (1 - u)*(1 - v)*a00 + (1 - u)*(v)*a01 + (u)*(1 - v)*a10 + (u)*(v)*a11; + } + + inline double CubicSmooth(double x, double r) { + return (1.0 - x / r)*(1.0 - x / r)*(1.0 - x / r); + } + + inline int Integer(double x) { + return x > 0.0 ? int(x) : int(x) - 1; + } + + inline double Ridge(const double& z, const double& r) { + if (z < r) return z; + else return 2.0 * r - z; + } +} + +class SimplexNoise { +public: + SimplexNoise() {} + ~SimplexNoise() {} + double at(double x, double y) const; +protected: + double dot(const int* g, const double& x, const double& y) const { + return g[0] * x + g[1] * y; + } + static const int perm[512]; //!< Permutation table, 256 entries duplicated once to avoid modulo computations. + static const int grad2[8][2]; //!< Array of gradients for 2D noise. + static const double F2, G2; //!< Unskew factors for 2D case. +}; + + +class Vector2 { +protected: + double c[2]; + +public: + Vector2() { + c[0] = c[1] = 0; + } + explicit Vector2(double d) { + c[0] = c[1] = d; + } + explicit Vector2(double d0, double d1) { + c[0] = d0; + c[1] = d1; + } + + double& operator[] (int i) { return c[i]; }; + double operator[] (int i) const { return c[i]; }; + + Vector2 operator- () const { return Vector2(-c[0], -c[1]); }; + friend Vector2 operator+(const Vector2& u, const Vector2& v) { return Vector2(u[0]+v[0], u[1]+v[1]); }; + friend Vector2 operator-(const Vector2& u, const Vector2& v) { return Vector2(u[0]-v[0], u[1]-v[1]); }; + friend Vector2 operator*(const Vector2& u, double a) { return Vector2(u[0]*a, u[1]*a); } + friend Vector2 operator*(double a, const Vector2& v) { return v * a; } + friend Vector2 operator/(const Vector2& u, double a) { return Vector2(u[0]/a, u[1]/a); } + + friend double Norm(const Vector2& u) { return sqrt(u[0]*u[0] + u[1]*u[1]); } + friend double SquaredNorm(const Vector2& u) { return u[0]*u[0] + u[1]*u[1]; } + friend Vector2 Normalized(const Vector2& u) { return u/Norm(u); } +}; + + +class Vector3 { +protected: + double c[3]; + +public: + Vector3() { + c[0] = c[1] = c[2] = 0; + } + explicit Vector3(double d) { + c[0] = c[1] = c[2] = d; + }; + explicit Vector3(double d0, double d1, double d2) { + c[0] = d0; + c[1] = d1; + c[2] = d2; + }; + explicit Vector3(const Vector2& u) { + c[0] = u[0]; + c[1] = u[1]; + c[2] = 0; + } + + double& operator[] (int i) { return c[i]; }; + double operator[] (int i) const { return c[i]; }; + Vector3 operator-() const { return Vector3(-c[0], -c[1], -c[2]); } + + friend Vector3 operator+(const Vector3& u, const Vector3& v) { return Vector3(u[0]+v[0], u[1]+v[1], u[2]+v[2]); }; + friend Vector3 operator-(const Vector3& u, const Vector3& v) { return Vector3(u[0]-v[0], u[1]-v[1], u[2]-v[2]); }; + friend Vector3 operator*(const Vector3& u, double a) { return Vector3(u[0]*a, u[1]*a, u[2]*a); } + friend Vector3 operator*(double a, const Vector3& v) { return v * a; } + friend Vector3 operator/(const Vector3& u, double a) { return Vector3(u[0]/a, u[1]/a, u[2]/a); } + + friend double Norm(const Vector3& u) { return sqrt(u[0]*u[0] + u[1]*u[1] + u[2]*u[2]); } + friend double SquaredNorm(const Vector3& u) { return u[0]*u[0] + u[1]*u[1] + u[2]*u[2]; } + friend Vector3 Normalized(const Vector3& u) { return u/Norm(u); } + + friend double dot(const Vector3& u, const Vector3& v) { return u[0]*v[0] + u[1]*v[1] + u[2]*v[2]; } + friend Vector3 cross(const Vector3& u, const Vector3& v) { + return Vector3(u[1]*v[2] - u[2]*v[1], u[2]*v[0] - u[0]*v[2], u[0]*v[1] - u[1]*v[0]); + } + + static Vector3 fromQColor(const QColor& c) { + return Vector3(c.red() / 255.0, c.green() / 255.0, c.blue() / 255.0); + } + QColor toQColor() const { + return QColor(int(255.0 * Math::Clamp(c[0])), + int(255.0 * Math::Clamp(c[1])), + int(255.0 * Math::Clamp(c[2])), + 255); + } +}; + + +class Box2 { +protected: + Vector2 bmin; // min + Vector2 bmax; // max + +public: + Box2() : bmin(0), bmax(0) {}; + Box2(const Vector2& pmin, const Vector2& pmax) : bmin(pmin), bmax(pmax) {} + Box2(const Vector2& c, double r) : bmin(c - Vector2(r)), bmax(c + Vector2(r)) {} + + Vector2 getMin() const { return bmin; } + Vector2 getMax() const { return bmax; } + Vector2 center() const { return 0.5*(bmin + bmax); } + double radius() const { return 0.5 * Norm(bmax - bmin); } + double width() const { return bmax[0] - bmin[0]; } + double height() const { return bmax[1] - bmin[1]; } + + bool Intersect(const Vector2& s0, const Vector2& s1, double& tmin, double& tmax); +}; + + +class Box3 { +protected: + Vector3 bmin; // min + Vector3 bmax; // max + +public: + Box3() : bmin(0), bmax(0) {}; + Box3(const Vector3& pmin, const Vector3& pmax) : bmin(pmin), bmax(pmax) {} + + Vector3 getMin() const { return bmin; } + Vector3 getMax() const { return bmax; } + Vector3 center() const { return 0.5*(bmin + bmax); } + double radius() const { return 0.5 * Norm(bmax - bmin); } + double width() const { return bmax[0] - bmin[0]; } + double height() const { return bmax[1] - bmin[1]; } + double depth() const { return bmax[2] - bmin[2]; } +}; + + +class Ray +{ +protected: + Vector3 p; // Origin of the ray. + Vector3 d; // Direction. + +public: + Ray() {} + explicit Ray(const Vector3& p, const Vector3& d) : p(p), d(d) {} + + Vector3 origin() const { return p; } + Vector3 direction() const { return d; } + + Vector3 operator()(double t) const { return p + t * d; } + + Ray reflect(const Vector3& p, const Vector3& n) { return Ray(p, n - 2 * n * dot(d, n)); } +}; + + +class Camera { +protected: + Vector3 eye; // Eye + Vector3 at; // Look at point + Vector3 up; // Up vector + double cah; // Camera aperture horizontal + double cav; // Camera aperture vertical + double nearplane; // Near plane + double farplane; // Far plane + double fl; // Focal length + +public: + Camera(); + Camera(const Vector3& eye, const Vector3& at, const Vector3& up = Vector3(0,0,1), double near = 1.0, double far = 100000.0); + + Vector3 getEye() const { return eye; } + Vector3 getAt() const { return at; } + Vector3 getUp() const { return up; } + Vector3 getViewDir() const { return Normalized(at - eye); } + double getNearPlane() const { return nearplane; } + double getFarPlane() const { return farplane; } + double getAngleOfViewH(double, double) const; + double getAngleOfViewV(double, double) const; + + void setAt(const Vector3& p) { at = p; up = Vector3(0,0,1); } + void setEye(const Vector3& p) { eye = p; } + void setPlanes(double n, double f) { nearplane = n; farplane = f;} + + // Move camera around eye + void upDownRound(double a); + void leftRightRound(double a); + void backForth(double a, bool moveAt = false); + + // Move camera in a plane + void upDownPlane(double); + void leftRightPlane(double); + + Ray pixelToRay(int px, int py, int w, int h) const; + + static Camera View(const Box3& box); +}; + + +class ColorPalette { + +protected: + std::vector colors; + std::vector anchors; + +public: + ColorPalette() : colors({Vector3(1)}), anchors({0}) {} + ColorPalette(const std::vector& c, const std::vector& a) : colors(c), anchors(a) {} + + Vector3 getColor(double u) const; + + static ColorPalette CoolWarm() { + const Vector3 Cool = Vector3(97, 130, 234) / 255.0; + const Vector3 White = Vector3(221, 220, 219) / 255.0; + const Vector3 Warm = Vector3(220, 94, 75) / 255.0; + return ColorPalette({Cool, White, Warm}, {0, 0.5, 1}); + } + static ColorPalette Relief() { + const std::vector c = { Vector3(160, 220, 105) / 255.0, + Vector3(1.0, 0.9, 0.45), + Vector3(168/ 255.0, 155 / 255.0, 138 / 255.0), + Vector3(0.95, 0.95, 0.95) }; + return ColorPalette(c, {0, 150, 250, 400}); + } +}; + + +#endif diff --git a/code/core/scalarfield2.cpp b/code/core/scalarfield2.cpp new file mode 100644 index 0000000..9cbe636 --- /dev/null +++ b/code/core/scalarfield2.cpp @@ -0,0 +1,466 @@ +#include "scalarfield2.h" +#include + +ScalarField2::ScalarField2() : nx(0), ny(0), domain(Box2(Vector2(0), Vector2(1))) +{ +} + +ScalarField2::ScalarField2(const Box2 &domain, int nx, int ny, double v) : nx(nx), ny(ny), domain(domain) +{ + field.fill(v, nx*ny); + cellSize = Vector2(domain.width()/(nx-1), domain.height()/(ny-1)); +} + +ScalarField2::ScalarField2(const Box2& box, const QImage& image, const double& a, const double& b, bool grayscale) +{ + domain = box; + nx = image.width(); + ny = image.height(); + field.resize(nx*ny); + cellSize = Vector2(domain.width()/(nx - 1), domain.height()/(ny - 1)); + + // Write Heightmap + for (int i = 0; i < image.width(); i++) { + for (int j = 0; j < image.height(); j++) { + double t = 0.0; + + // Grayscale + if (grayscale) { + // Grayscale 16 bits + if (image.format() == QImage::Format_Grayscale16) { + QColor thecolor = image.pixelColor(i, j); + t = thecolor.blueF(); + } + // Grayscale 8 bits + else { + QRgb color = image.pixel(i, j); + t = double(qGray(color)) / 255.0; + } + } + // Color + else { + QRgb color = image.pixel(i, j); + // Maximum value is 256^3-1 + t = double(qRed(color) << 16 | qGreen(color) << 8 | qBlue(color)) / (16777216.0 - 1.0); + } + + field[vertexIndex(i, j)] = Math::Lerp(a, b, t); + } + } +} + +void ScalarField2::getRange(double &vmin, double &vmax) const +{ + vmin = vmax = field.at(0); + for (int i = 1; i < field.size(); i++) { + double x = field.at(i); + if (x < vmin) vmin = x; + if (x > vmax) vmax = x; + } +} + +void ScalarField2::cellCoords(const Vector2& p, int& i, int& j, double& u, double& v) const +{ + Vector2 q = p - domain.getMin(); + u = q[0]/cellSize[0]; + v = q[1]/cellSize[1]; + + // Integer coordinates + i = int(u); + j = int(v); + + // Local coordinates within cell + u -= i; + v -= j; +} + +void ScalarField2::cellIntegerCoords(const Vector2 &p, int &i, int &j) const +{ + Vector2 q = p - domain.getMin(); + i = int(q[0]/cellSize[0]); + j = int(q[1]/cellSize[1]); +} + +double ScalarField2::value(const Vector2& p) const +{ + double u, v; + int i, j; + cellCoords(p, i, j, u, v); + + if (!validIndex(i, j)) return 0.0; + return Math::Bilinear(at(i, j), at(i + 1, j), at(i + 1, j + 1), at(i, j + 1), u, v); +} + +Vector3 ScalarField2::vertex(int i, int j) const +{ + return Vector3(domain.getMin()[0] + i*cellSize[0], domain.getMin()[1] + j*cellSize[1], at(i, j)); +} + +inline Vector3 triangleAreaNormal(const Vector3& p0, const Vector3& p1, const Vector3& p2) { + return 0.5*cross(p1 - p0, p2 - p0); +} + +Vector3 ScalarField2::normal(int i, int j) const +{ + Vector3 n; + if (i == 0) { + if (j == 0) { + // Corner: 0/1 + n = triangleAreaNormal(vertex(i, j), vertex(i + 1, j), vertex(i + 1, j + 1)) + + triangleAreaNormal(vertex(i, j), vertex(i + 1, j + 1), vertex(i, j + 1)); + } + else if (j == ny - 1) { + // Corner: 5 + n = triangleAreaNormal(vertex(i, j), vertex(i, j - 1), vertex(i + 1, j)); + } + else { + // Edge: 0/1/5 + n = triangleAreaNormal(vertex(i, j), vertex(i + 1, j), vertex(i + 1, j + 1)) + + triangleAreaNormal(vertex(i, j), vertex(i + 1, j + 1), vertex(i, j + 1)) + + triangleAreaNormal(vertex(i, j), vertex(i, j - 1), vertex(i + 1, j)); + } + } + else if (i == nx - 1) { + if (j == 0) { + // Corner: 2 + n = triangleAreaNormal(vertex(i, j), vertex(i, j + 1), vertex(i - 1, j)); + } + else if (j == ny - 1) { + // Corner: 3/4 + n = triangleAreaNormal(vertex(i, j), vertex(i - 1, j - 1), vertex(i, j - 1)) + + triangleAreaNormal(vertex(i, j), vertex(i - 1, j), vertex(i - 1, j - 1)); + } else { + // Edge: 2/3/4 + n = triangleAreaNormal(vertex(i, j), vertex(i, j + 1), vertex(i - 1, j)) + + triangleAreaNormal(vertex(i, j), vertex(i - 1, j), vertex(i - 1, j - 1)) + + triangleAreaNormal(vertex(i, j), vertex(i - 1, j - 1), vertex(i, j - 1)); + } + } + else { + if (j == 0) { + // Edge: 0/1/2 + n = triangleAreaNormal(vertex(i, j), vertex(i + 1, j), vertex(i + 1, j + 1)) + + triangleAreaNormal(vertex(i, j), vertex(i + 1, j + 1), vertex(i, j + 1)) + + triangleAreaNormal(vertex(i, j), vertex(i, j + 1), vertex(i - 1, j)); + } + else if (j == ny - 1) { + // Edge: 3/4/5 + n = triangleAreaNormal(vertex(i, j), vertex(i - 1, j), vertex(i - 1, j - 1)) + + triangleAreaNormal(vertex(i, j), vertex(i - 1, j - 1), vertex(i, j - 1)) + + triangleAreaNormal(vertex(i, j), vertex(i, j - 1), vertex(i + 1, j)); + } + else { + // Face: 0/1/2/3/4/5 + n = triangleAreaNormal(vertex(i, j), vertex(i + 1, j), vertex(i + 1, j + 1)) + + triangleAreaNormal(vertex(i, j), vertex(i + 1, j + 1), vertex(i, j + 1)) + + triangleAreaNormal(vertex(i, j), vertex(i, j + 1), vertex(i - 1, j)) + + triangleAreaNormal(vertex(i, j), vertex(i - 1, j), vertex(i - 1, j - 1)) + + triangleAreaNormal(vertex(i, j), vertex(i - 1, j - 1), vertex(i, j - 1)) + + triangleAreaNormal(vertex(i, j), vertex(i, j - 1), vertex(i + 1, j)); + } + } + + return Normalized(n); +} + +Vector2 ScalarField2::gradient(int i, int j) const +{ + Vector2 n; + + // Gradient along x axis + if (i == 0) + n[0] = (at(i + 1, j) - at(i, j)) / cellSize[0]; + else if (i == nx - 1) + n[0] = (at(i, j) - at(i - 1, j)) / cellSize[0]; + else + n[0] = (at(i + 1, j) - at(i - 1, j)) / (2.0 * cellSize[0]); + + // Gradient along y axis + if (j == 0) + n[1] = (at(i, j + 1) - at(i, j)) / cellSize[1]; + else if (j == ny - 1) + n[1] = (at(i, j) - at(i, j - 1)) / cellSize[1]; + else + n[1] = (at(i, j + 1) - at(i, j - 1)) / (2.0 * cellSize[1]); + + return n; +} + +VectorField2 ScalarField2::gradientField() const +{ + VectorField2 v(getDomain(), nx, ny); + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + v(i, j) = gradient(i, j); + } + } + return v; +} + +void ScalarField2::fill(double d) +{ + field.fill(d, nx*ny); +} + +void ScalarField2::smooth(int n) +{ + // Smooth the scalar field using a discrete gaussian kernel. + // The function uses a 3^2 approximation of the Gaussian kernel. + QVector smoothed; + smoothed.resize(nx * ny); + int k; + + for (int iter = 0; iter < n; iter++) { + // Smooth center + for (int i = 1; i < nx - 1; i++) { + for (int j = 1; j < ny - 1; j++) { + k = vertexIndex(i, j); + smoothed[k] = (4.0*at(k) + 2.0*at(k - 1) + 2.0*at(k + 1) + 2.0*at(k - nx) + 2.0*at(k + nx) + at(k - 1 - nx) + at(k + 1 - nx) + at(k - 1 + nx) + at(k + 1 + nx)) / 16.0; + } + } + + // Smooth edges + for (int i = 1; i < nx - 1; i++) { + k = vertexIndex(i, 0); + smoothed[k] = (4.0*at(k) + 2.0*at(k - 1) + 2.0*at(k + 1) + 2.0*at(k + nx) + at(k - 1 + nx) + at(k + 1 + nx)) / 12.0; + k = vertexIndex(i, ny - 1); + smoothed[k] = (4.0*at(k) + 2.0*at(k - 1) + 2.0*at(k + 1) + 2.0*at(k - nx) + at(k - 1 - nx) + at(k + 1 - nx)) / 12.0; + } + for (int j = 1; j < ny - 1; j++) { + k = vertexIndex(0, j); + smoothed[k] = (2.0*at(k - nx) + 4.0*at(k) + 2.0*at(k + nx) + at(k + 1 - nx) + 2.0*at(k + 1) + at(k + 1 + nx)) / 12.0; + k = vertexIndex(nx - 1, j); + smoothed[k] = (2.0*at(k - nx) + 4.0*at(k) + 2.0*at(k + nx) + at(k - 1 - nx) + 2.0*at(k - 1) + at(k - 1 + nx)) / 12.0; + } + + // Corners + k = vertexIndex(0, 0); + smoothed[k] = (4.0*at(k) + 2.0*at(k + 1) + 2.0*at(k + nx) + 1.0*at(k + nx + 1)) / 9.0; + k = vertexIndex(nx - 1, 0); + smoothed[k] = (4.0*at(k) + 2.0*at(k - 1) + 2.0*at(k + nx) + 1.0*at(k + nx - 1)) / 9.0; + k = vertexIndex(0, ny - 1); + smoothed[k] = (4.0*at(k) + 2.0*at(k + 1) + 2.0*at(k - nx) + 1.0*at(k - nx + 1)) / 9.0; + k = vertexIndex(nx - 1, ny - 1); + smoothed[k] = (4.0*at(k) + 2.0*at(k - 1) + 2.0*at(k - nx) + 1.0*at(k - nx - 1)) / 9.0; + } + field = smoothed; +} + +void ScalarField2::gaussianBlur() +{ + const int kernelSize = 9; + double kernel[kernelSize] = { 1, 8, 28, 56, 70, 56, 28, 8, 1 }; + + ScalarField2 temp(*this); + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + double v = 0; + double w = 0; + for (int dj = -kernelSize / 2; dj <= kernelSize / 2; dj++) { + if (j + dj >= 0 && j + dj < ny - 1) { + v += kernel[dj + kernelSize / 2] * at(i, j + dj); + w += kernel[dj + kernelSize / 2]; + } + } + temp(i, j) = v / w; + } + } + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + double v = 0; + double w = 0; + for (int di = -kernelSize / 2; di <= kernelSize / 2; di++) { + if (i + di >= 0 && i + di < nx - 1) { + v += kernel[di + kernelSize / 2] * temp.at(i + di, j); + w += kernel[di + kernelSize / 2]; + } + } + (*this)(i, j) = v / w; + } + } +} + +void ScalarField2::step(const double& a, const double& b) +{ + for (int i = 0; i < field.size(); i++) { + field[i] = Math::LinearStep(field.at(i), a, b); + } +} + +void ScalarField2::normalize() +{ + double a, b; + getRange(a, b); + if (a == b) { + field.fill(1.0); + } + else { + for (int i = 0; i < field.size(); i++) { + field[i] = (field[i] - a) / (b - a); + } + } +} + +void ScalarField2::addGaussian(const Vector2& center, const double& radius, const double& height) +{ + + Box2 box (center, radius); + + int ia,ib,ja,jb; + cellIntegerCoords(box.getMin(), ia, ja); + cellIntegerCoords(box.getMax(), ib, jb); + + QPoint pa(ia, ja); + QPoint pb(ib, jb); + + // Rectangle + QRect area(pa.x(), pa.y(), pb.x() - pa.x() + 1, pb.y() - pa.y() + 1); + + // Limit to domain + QRect mask(0, 0, nx - 1, ny - 1); + area = area.intersected(mask); + + // Add to field + for (int y = area.y(); y <= area.y() + area.height(); y++) { + for (int x = area.x(); x <= area.x() + area.width(); x++) { + // Distance between central point and current point + double u = SquaredNorm(center - domainCoords(x, y)); + + if (u < radius * radius) { + field[vertexIndex(x, y)] += height * Math::CubicSmooth(u, radius * radius); + } + } + } +} + +ScalarField2 ScalarField2::setResolution(int x, int y) const +{ + // Sampled scalar field + ScalarField2 sampled(domain, x, y); + + // Corners + sampled(0, 0) = at(0, 0); + sampled(0, y - 1) = at(0, ny - 1); + sampled(x - 1, 0) = at(nx - 1, 0); + sampled(x - 1, y - 1) = at(nx - 1, ny - 1); + + // Borders (use linear interpolation) + for (int i = 1; i < x - 1; i++) { + double tx = (nx - 1) * (i / double(x - 1)); + int x0 = int(floor(tx)); + int x1 = int(ceil(tx)); + + sampled(i, 0) = Math::Lerp(at(x0, 0), at(x1, 0), tx - x0); + sampled(i, y - 1) = Math::Lerp(at(x0, ny - 1), at(x1, ny - 1), tx - x0); + } + for (int j = 1; j < y - 1; j++) { + double ty = (ny - 1) * (j / double(y - 1)); + int y0 = int(floor(ty)); + int y1 = int(ceil(ty)); + + sampled(0, j) = Math::Lerp(at(0, y0), at(0, y1), ty - y0); + sampled(x - 1, j) = Math::Lerp(at(nx - 1, y0), at(nx - 1, y1), ty - y0); + } + + // Interior + for (int i = 1; i < x - 1; i++) { + for (int j = 1; j < y - 1; j++) { + sampled(i, j) = value(sampled.domainCoords(i, j)); + } + } + + return sampled; +} + +QImage ScalarField2::CreateImage(bool grayscale) const +{ + double a, b; + this->getRange(a, b); + if (a == b) { + b = a + 1.0; + } + return CreateImage(a, b, grayscale); +} + +QImage ScalarField2::CreateImage(double a, double b, bool grayscale) const +{ + QImage image(nx, ny, QImage::Format_ARGB32); + for (int i = 0; i < image.width(); i++) { + for (int j = 0; j < image.height(); j++) { + double x = field.at(vertexIndex(i, j)); + double y = Math::LinearStep(x, a, b); + + QColor color; + if (grayscale) { + int c = int(y * 255.0); + color = QColor(c, c, c); + } + else { + int c = int(y * (256.0 * 256.0 * 256.0 - 1.0)); + int cr = (c >> 16) & 255; + int cv = (c >> 8) & 255; + int cb = c & 255; + color = QColor(cr, cv, cb); + } + image.setPixel(i, j, color.rgb()); + } + } + return image; +} + +QImage ScalarField2::CreateImage(const ColorPalette& palette) const +{ + double a, b; + getRange(a, b); + if (a == b) { + b = a + 1.0; + } + return CreateImage(a, b, palette); +} + +QImage ScalarField2::CreateImage(double a, double b, const ColorPalette& palette) const +{ + QImage image(nx, ny, QImage::Format_ARGB32); + for (int i = 0; i < image.width(); i++) { + for (int j = 0; j < image.height(); j++) { + double x = field.at(vertexIndex(i, j)); + double y = Math::LinearStep(x, a, b); + QColor color = palette.getColor(y).toQColor(); + image.setPixel(i, j, color.rgb()); + } + } + return image; +} + +ScalarField2& ScalarField2::operator+=(const ScalarField2& s) +{ + for (int i = 0; i < field.size(); i++) { + field[i] += s.at(i); + } + return *this; +} + +ScalarField2 &ScalarField2::operator+=(const double &d) +{ + for (int i = 0; i < field.size(); i++) { + field[i] += d; + } + return *this; +} + +ScalarField2& ScalarField2::operator*=(const double &d) +{ + for (int i = 0; i < field.size(); i++) { + field[i] *= d; + } + return *this; +} + +ScalarField2 operator+(const ScalarField2& s1, const ScalarField2& s2) +{ + ScalarField2 r(s1); + for (int i = 0; i < r.field.size(); i++) { + r.field[i] += s2.at(i); + } + return r; +} diff --git a/code/core/scalarfield2.h b/code/core/scalarfield2.h new file mode 100644 index 0000000..81bd1f9 --- /dev/null +++ b/code/core/scalarfield2.h @@ -0,0 +1,74 @@ +#ifndef SCALARFIELD2_H +#define SCALARFIELD2_H + +#include +#include +#include "core.h" +#include "vectorfield2.h" + + +class ScalarField2 +{ +public: + ScalarField2(); + ScalarField2(const Box2& domain, int nx, int ny, double v = 0.0); + ScalarField2(const Box2& domain, const QImage& image, const double& a, const double& b, bool grayscale); + + int getSizeX() const { return nx; } + int getSizeY() const { return ny; } + Vector2 getCellSize() const { return cellSize; } + + Box2 getDomain() const { return domain; }; + void getRange(double& vmin, double& vmax) const; + + int vertexIndex(int i, int j) const { return i + nx*j; } + bool validIndex(int i, int j) const { return (i >= 0) && (i < nx - 1) && (j >= 0) && (j < ny - 1); } + + void cellCoords(const Vector2& p, int& i, int& j, double& u, double& v) const; + void cellIntegerCoords(const Vector2& p, int& i, int& j) const; + Vector2 domainCoords(int i, int j) const { return domain.getMin() + Vector2(i * cellSize[0], j * cellSize[1]); } + + double at(int i, int j) const { return field.at(vertexIndex(i, j)); } + double& operator()(int i, int j) { return field[vertexIndex(i, j)]; } + double at(int i) const { return field.at(i); } + double& operator[](int i) { return field[i]; } + double value(const Vector2& p) const; + + Vector3 vertex(int i, int j) const; + Vector3 normal(int i, int j) const; + Vector2 gradient(int i, int j) const; + + VectorField2 gradientField() const; + + void fill(double d); + void smooth(int n); + void gaussianBlur(); + void step(const double& a, const double& b); + void normalize(); + void addGaussian(const Vector2& center, const double& radius, const double& height); + + ScalarField2 setResolution(int nx, int ny) const; + + + QImage CreateImage(bool grayscale) const; + QImage CreateImage(double a, double b, bool grayscale) const; + QImage CreateImage(const ColorPalette& palette) const; + QImage CreateImage(double a, double b, const ColorPalette& palette) const; + + ScalarField2& operator+=(const ScalarField2& s); + ScalarField2& operator+=(const double& d); + ScalarField2& operator*=(const double& d); + friend ScalarField2 operator+(const ScalarField2& s1, const ScalarField2& s2); + +protected: + + QVector field; + int nx, ny; + Box2 domain; + Vector2 cellSize; + +}; + + + +#endif // SCALARFIELD2_H diff --git a/code/core/shader-utils.cpp b/code/core/shader-utils.cpp new file mode 100644 index 0000000..2529a06 --- /dev/null +++ b/code/core/shader-utils.cpp @@ -0,0 +1,369 @@ +#include "shader-utils.h" +#include +#include +#include +#include +#include + + +static +std::string read(const char* filename) +{ + std::stringbuf source; + std::ifstream in(filename); + if (in.good() == false) + printf("[error] loading program '%s'...\n", filename); + else + printf("loading program '%s'...\n", filename); + + in.get(source, 0); + return source.str(); +} + +static +std::string prepare_source(std::string file, const std::string& definitions) +{ + if (file.empty()) + return std::string(); + + std::string source; + + std::string version; + size_t b = file.find("#version"); + if (b != std::string::npos) + { + size_t e = file.find('\n', b); + if (e != std::string::npos) + { + version = file.substr(0, e + 1); + file.erase(0, e + 1); + + if (file.find("#version") != std::string::npos) + { + printf("[error] found several #version directives. failed.\n"); + return std::string(); + } + } + } + else + { + printf("[error] no #version directive found. failed.\n"); + return std::string(); + } + + if (definitions.empty() == false) + { + source.append(version); + source.append(definitions).append("\n"); + source.append(file); + } + else + { + source.append(version); + source.assign(file); + } + + return source; +} + + +static +const char* shader_string(const GLenum type) +{ + switch (type) + { + case GL_VERTEX_SHADER: return "vertex shader"; + case GL_FRAGMENT_SHADER: return "fragment shader"; + case GL_GEOMETRY_SHADER: return "geometry shader"; +#ifdef GL_VERSION_4_0 + case GL_TESS_CONTROL_SHADER: return "control shader"; + case GL_TESS_EVALUATION_SHADER: return "evaluation shader"; +#endif +#ifdef GL_VERSION_4_3 + case GL_COMPUTE_SHADER: return "compute shader"; +#endif + default: return "shader"; + } +} + +static +const char* shader_keys[] = +{ + "VERTEX_SHADER", + "FRAGMENT_SHADER", + "GEOMETRY_SHADER", + "TESSELATION_CONTROL", + "EVALUATION_CONTROL", + "COMPUTE_SHADER" +}; +const int shader_keys_max = 6; + +static +GLenum shader_types[] = +{ + GL_VERTEX_SHADER, + GL_FRAGMENT_SHADER, + GL_GEOMETRY_SHADER, +#ifdef GL_VERSION_4_0 + GL_TESS_CONTROL_SHADER, + GL_TESS_EVALUATION_SHADER, +#else + 0, + 0, +#endif +#ifdef GL_VERSION_4_3 + GL_COMPUTE_SHADER +#else + 0 +#endif +}; + +static +GLuint compile_shader(const GLuint program, const GLenum shader_type, const std::string& source) +{ + if (source.size() == 0 || shader_type == 0) + return 0; + + GLuint shader = glCreateShader(shader_type); + glAttachShader(program, shader); + + const char* sources = source.c_str(); + glShaderSource(shader, 1, &sources, NULL); + glCompileShader(shader); + + GLint status; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + return (status == GL_TRUE) ? shader : 0; +} + + +int reload_program(GLuint program, const char* filename, const char* definitions) +{ + if (program == 0) + return -1; + + int shaders_max = 0; + glGetProgramiv(program, GL_ATTACHED_SHADERS, &shaders_max); + if (shaders_max > 0) + { + std::vector shaders(shaders_max, 0); + glGetAttachedShaders(program, shaders_max, NULL, &shaders.front()); + for (int i = 0; i < shaders_max; i++) + { + glDetachShader(program, shaders[i]); + glDeleteShader(shaders[i]); + } + } + +#ifdef GL_VERSION_4_3 + glObjectLabel(GL_PROGRAM, program, -1, filename); +#endif + + std::string common_source = read(filename); + for (int i = 0; i < shader_keys_max; i++) + { + if (common_source.find(shader_keys[i]) != std::string::npos) + { + std::string source = prepare_source(common_source, std::string(definitions).append("#define ").append(shader_keys[i]).append("\n")); + GLuint shader = compile_shader(program, shader_types[i], source); + if (shader == 0) + printf("[error] compiling %s...\n%s\n", shader_string(shader_types[i]), definitions); + } + } + + glLinkProgram(program); + + GLint status; + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (status == GL_FALSE) + { + printf("[error] linking program %u '%s'...\n", program, filename); + return -1; + } + + glUseProgram(program); + return 0; +} + +GLuint read_program(const char* filename, const char* definitions) +{ + GLuint program = glCreateProgram(); + reload_program(program, filename, definitions); + program_print_errors(program); + return program; +} + +int release_program(const GLuint program) +{ + if (program == 0) + return -1; + + int shaders_max = 0; + glGetProgramiv(program, GL_ATTACHED_SHADERS, &shaders_max); + + if (shaders_max > 0) + { + std::vector shaders(shaders_max, 0); + glGetAttachedShaders(program, shaders_max, NULL, &shaders.front()); + for (int i = 0; i < shaders_max; i++) + { + glDetachShader(program, shaders[i]); + glDeleteShader(shaders[i]); + } + } + + glDeleteProgram(program); + return 0; +} + + +static +void print_line(std::string& errors, const char* source, const int begin_id, const int line_id) +{ + int line = 0; + char last = '\n'; + for (unsigned int i = 0; source[i] != 0; i++) + { + if (line > line_id) + break; + + if (last == '\n') + { + line++; + if (line >= begin_id && line <= line_id) + { + errors.append(" "); + errors.push_back('0' + (line / 1000) % 10); + errors.push_back('0' + (line / 100) % 10); + errors.push_back('0' + (line / 10) % 10); + errors.push_back('0' + (line / 1) % 10); + errors.append(" "); + } + } + + if (line >= begin_id && line <= line_id) + { + if (source[i] == '\t') + errors.append(" "); + else + errors.push_back(source[i]); + } + last = source[i]; + } +} + +static +int print_errors(std::string& errors, const char* log, const char* source) +{ + printf("[error log]\n%s\n", log); + + int first_error = INT_MAX; + int last_string = -1; + int last_line = -1; + for (int i = 0; log[i] != 0; i++) + { + int string_id = 0, line_id = 0, position = 0; + if (sscanf_s(&log[i], "%d ( %d ) : %n", &string_id, &line_id, &position) == 2 // nvidia syntax + || sscanf_s(&log[i], "%d : %d (%*d) : %n", &string_id, &line_id, &position) == 2 // mesa syntax + || sscanf_s(&log[i], "ERROR : %d : %d : %n", &string_id, &line_id, &position) == 2 // ati syntax + || sscanf_s(&log[i], "WARNING : %d : %d : %n", &string_id, &line_id, &position) == 2) // ati syntax + { + if (string_id != last_string || line_id != last_line) + { + first_error = std::min(first_error, line_id); + + errors.append("\n"); + print_line(errors, source, last_line + 1, line_id); + errors.append("\n"); + } + } + for (i += position; log[i] != 0; i++) + { + errors.push_back(log[i]); + if (log[i] == '\n') + break; + } + + last_string = string_id; + last_line = line_id; + } + errors.append("\n"); + print_line(errors, source, last_line + 1, 1000); + errors.append("\n"); + + return first_error; +} + +int program_format_errors(const GLuint program, std::string& errors) +{ + errors.clear(); + + if (program == 0) + { + errors.append("[error] no program...\n"); + return -1; + } + + GLint status; + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (status == GL_TRUE) + return 0; + + int first_error = INT_MAX; + + int shaders_max = 0; + glGetProgramiv(program, GL_ATTACHED_SHADERS, &shaders_max); + if (shaders_max == 0) + { + errors.append("[error] no shaders...\n"); + return 0; + } + + std::vector shaders(shaders_max, 0); + glGetAttachedShaders(program, shaders_max, NULL, &shaders.front()); + for (int i = 0; i < shaders_max; i++) + { + GLint value; + glGetShaderiv(shaders[i], GL_COMPILE_STATUS, &value); + if (value == GL_FALSE) + { + glGetShaderiv(shaders[i], GL_INFO_LOG_LENGTH, &value); + std::vectorlog(value + 1, 0); + glGetShaderInfoLog(shaders[i], (GLsizei)log.size(), NULL, &log.front()); + + // recupere le source + glGetShaderiv(shaders[i], GL_SHADER_SOURCE_LENGTH, &value); + std::vector source(value + 1, 0); + glGetShaderSource(shaders[i], (GLsizei)source.size(), NULL, &source.front()); + + glGetShaderiv(shaders[i], GL_SHADER_TYPE, &value); + errors.append("[error] compiling ").append(shader_string(value)).append("...\n"); + + // formatte les erreurs + int last_error = print_errors(errors, &log.front(), &source.front()); + first_error = std::min(first_error, last_error); + } + } + + // recupere les erreurs de link du program + { + GLint value = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &value); + + std::vectorlog(value + 1, 0); + glGetProgramInfoLog(program, (GLsizei)log.size(), NULL, &log.front()); + + errors.append("[error] linking program...\n").append(log.begin(), log.end()); + } + + return first_error; +} + +int program_print_errors(const GLuint program) +{ + std::string errors; + int code = program_format_errors(program, errors); + if (errors.size() > 0) + printf("%s\n", errors.c_str()); + return code; +} diff --git a/code/core/shader-utils.h b/code/core/shader-utils.h new file mode 100644 index 0000000..3f4e7f1 --- /dev/null +++ b/code/core/shader-utils.h @@ -0,0 +1,14 @@ +#ifndef SHADER_UTILS_H +#define SHADER_UTILS_H + +#include "glew.h" +#include + +// Shader API +GLuint read_program(const char* filename, const char* definitions = ""); +int release_program(const GLuint program); +int reload_program(const GLuint program, const char* filename, const char* definitions = ""); +int program_format_errors(const GLuint program, std::string& errors); +int program_print_errors(const GLuint program); + +#endif diff --git a/code/core/vectorfield2.cpp b/code/core/vectorfield2.cpp new file mode 100644 index 0000000..8188be6 --- /dev/null +++ b/code/core/vectorfield2.cpp @@ -0,0 +1,57 @@ +#include "vectorfield2.h" +#include + +VectorField2::VectorField2() : nx(0), ny(0), domain(Box2(Vector2(0), Vector2(1))) +{ +} + +VectorField2::VectorField2(const Box2 &domain, int nx, int ny, const Vector2& v) : nx(nx), ny(ny), domain(domain) +{ + field.fill(v, nx*ny); + cellSize = Vector2(domain.width()/(nx-1), domain.height()/(ny-1)); +} + +void VectorField2::getRange(Vector2& vmin, Vector2& vmax) const +{ + vmin = vmax = field.at(0); + for (int i = 1; i < field.size(); i++) { + double x = field.at(i)[0]; + if (x < vmin[0]) vmin[0] = x; + if (x > vmax[0]) vmax[0] = x; + double y = field.at(i)[1]; + if (y < vmin[1]) vmin[1] = y; + if (y > vmax[1]) vmax[1] = y; + } +} + +void VectorField2::cellCoords(const Vector2& p, int& i, int& j, double& u, double& v) const +{ + Vector2 q = p - domain.getMin(); + u = q[0] / cellSize[0]; + v = q[1] / cellSize[1]; + + // Integer coordinates + i = int(u); + j = int(v); + + // Local coordinates within cell + u -= i; + v -= j; +} + +Vector2 VectorField2::value(const Vector2& p) const +{ + double u, v; + int i, j; + cellCoords(p, i, j, u, v); + + if (!validIndex(i, j)) return Vector2(0.0); + + Vector2 a00 = at(i, j); + Vector2 a01 = at(i, j + 1); + Vector2 a10 = at(i + 1, j); + Vector2 a11 = at(i + 1, j + 1); + double x = Math::Bilinear(a00[0], a10[0], a11[0], a01[0], u, v); + double y = Math::Bilinear(a00[1], a10[1], a11[1], a01[1], u, v); + return Vector2(x, y); +} diff --git a/code/core/vectorfield2.h b/code/core/vectorfield2.h new file mode 100644 index 0000000..60c7402 --- /dev/null +++ b/code/core/vectorfield2.h @@ -0,0 +1,43 @@ +#ifndef VECTORFIELD2_H +#define VECTORFIELD2_H + +#include +#include +#include "core.h" + + +class VectorField2 +{ +public: + VectorField2(); + VectorField2(const Box2& domain, int nx, int ny, const Vector2& v = Vector2(0)); + + int getSizeX() const { return nx; } + int getSizeY() const { return ny; } + Vector2 getCellSize() const { return cellSize; } + + Box2 getDomain() const { return domain; }; + void getRange(Vector2& vmin, Vector2& vmax) const; + + int vertexIndex(int i, int j) const { return i + nx*j; } + bool validIndex(int i, int j) const { return (i >= 0) && (i < nx - 1) && (j >= 0) && (j < ny - 1); } + + void cellCoords(const Vector2& p, int& i, int& j, double& u, double& v) const; + Vector2 domainCoords(int i, int j) const { return domain.getMin() + Vector2(i * cellSize[0], j * cellSize[1]); } + + Vector2 at(int i, int j) const { return field.at(vertexIndex(i, j)); } + Vector2& operator()(int i, int j) { return field[vertexIndex(i, j)]; } + Vector2 at(int i) const { return field.at(i); } + Vector2& operator[](int i) { return field[i]; } + Vector2 value(const Vector2& p) const; + +protected: + + QVector field; + int nx, ny; + Box2 domain; + Vector2 cellSize; + +}; + +#endif // VECTORFIELD2_H diff --git a/code/glacierterrain.cpp b/code/glacierterrain.cpp new file mode 100644 index 0000000..975ac65 --- /dev/null +++ b/code/glacierterrain.cpp @@ -0,0 +1,385 @@ +#include "glacierterrain.h" +#include "shader-utils.h" +#include +#include + + +GlacierTerrain::GlacierTerrain() +{ + initializedBuffers = false; + nx = ny = 0; +} + +GlacierTerrain::GlacierTerrain(int nx, int ny) : bedrock(Box2(Vector2(0), Vector2(1)), nx, ny), + ice(Box2(Vector2(0), Vector2(1)), nx, ny), + ela(Box2(Vector2(0), Vector2(1)), nx, ny), + beta(Box2(Vector2(0), Vector2(1)), nx, ny), + diffusivity(Box2(Vector2(0), Vector2(1)), nx, ny), + nx(nx), ny(ny) +{ + initializedBuffers = false; + terrainSize = Vector2(1, 1); +} + +GlacierTerrain::GlacierTerrain(const ScalarField2& b) : bedrock(b), nx(b.getSizeX()), ny(b.getSizeY()), + ice(b.getDomain(), b.getSizeX(), b.getSizeY()), + ela(b.getDomain(), b.getSizeX(), b.getSizeY()), + beta(b.getDomain(), b.getSizeX(), b.getSizeY(), 1.0), + diffusivity(b.getDomain(), b.getSizeX(), b.getSizeY()) + +{ + initializedBuffers = false; + terrainSize = Vector2(b.getDomain().width(), b.getDomain().height()); +} + +GlacierTerrain::GlacierTerrain(const ScalarField2& b, const ScalarField2& i) : bedrock(b), ice(i), nx(b.getSizeX()), ny(b.getSizeY()), + ela(b.getDomain(), b.getSizeX(), b.getSizeY()), + beta(b.getDomain(), b.getSizeX(), b.getSizeY(), 1.0), + diffusivity(b.getDomain(), b.getSizeX(), b.getSizeY()) +{ + initializedBuffers = false; + terrainSize = Vector2(b.getDomain().width(), b.getDomain().height()); +} + + + +GlacierTerrain::~GlacierTerrain() { + if (initializedBuffers) { + glDeleteBuffers(1, &glbufferBedrock); + glDeleteBuffers(1, &glbufferIceIn); + glDeleteBuffers(1, &glbufferIceOut); + glDeleteBuffers(1, &glbufferD); + glDeleteBuffers(1, &glbufferBeta); + glDeleteBuffers(1, &glbufferELA); + } +} + + +void GlacierTerrain::initSimulationGL() +{ + // load shader + std::string definitions = ""; + definitions += "#define WORK_GROUP_SIZE_X " + std::to_string(WORK_GROUP_SIZE_X) + "\n"; + definitions += "#define WORK_GROUP_SIZE_Y " + std::to_string(WORK_GROUP_SIZE_Y) + "\n"; + shaderSIAaxis = read_program("./Shaders/sia.glsl", (definitions + "#define SIA_DIR_AXES\n").c_str()); + shaderSIAdiag = read_program("./Shaders/sia.glsl", (definitions + "#define SIA_DIR_DIAGONALS\n").c_str()); + std::cout << "Compute shader loaded!" << std::endl; + + if (initializedBuffers) return; + + // create buffers + bufferElems = ice.getSizeX() * ice.getSizeY(); + + glGenBuffers(1, &glbufferBedrock); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferBedrock); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferElems * sizeof(double), (const void*)(&bedrock[0]), GL_STATIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + glGenBuffers(1, &glbufferIceIn); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferIceIn); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferElems * sizeof(double), (const void*)(&ice[0]), GL_DYNAMIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + glGenBuffers(1, &glbufferELA); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferELA); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferElems * sizeof(double), (const void*)(&ela[0]), GL_STATIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + glGenBuffers(1, &glbufferBeta); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferBeta); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferElems * sizeof(double), (const void*)(&beta[0]), GL_STATIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + glGenBuffers(1, &glbufferIceOut); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferIceOut); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferElems * sizeof(double), NULL, GL_DYNAMIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + glGenBuffers(1, &glbufferD); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferD); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferElems * sizeof(double), NULL, GL_DYNAMIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + // run one simulation step with dt=0, to initialize diffusivity buffer + initializedBuffers = true; + shaderSIA = shaderSIAaxis; + doPassthrough = true; +} + + +void GlacierTerrain::resetSimulation() +{ + ice.fill(0); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferIceIn); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferElems * sizeof(double), (const void*)(&ice[0]), GL_DYNAMIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + doPassthrough = true; +} + + +void GlacierTerrain::setIceMap(const ScalarField2& icemap) +{ + for (int i = 0; i < icemap.getSizeX(); i++) { + for (int j = 0; j < icemap.getSizeY(); j++) { + ice(i, j) = icemap.at(i, j); + } + } + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferIceIn); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferElems * sizeof(double), (const void*)(&ice[0]), GL_DYNAMIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + doPassthrough = true; +} + + +void GlacierTerrain::updateBedrockGPU() +{ + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferBedrock); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferElems * sizeof(double), (const void*)(&bedrock[0]), GL_STATIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + doPassthrough = true; +} + +void GlacierTerrain::updateIceGPU() +{ + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferIceIn); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferElems * sizeof(double), (const void*)(&ice[0]), GL_DYNAMIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + doPassthrough = true; +} + +void GlacierTerrain::updateELAGPU() +{ + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferELA); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferElems * sizeof(double), (const void*)(&ela[0]), GL_STATIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); +} + +void GlacierTerrain::updateAccumRateGPU() +{ + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferBeta); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferElems * sizeof(double), (const void*)(&beta[0]), GL_STATIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); +} + + +void GlacierTerrain::configSimulation(const ScalarField2& mapELA, const ScalarField2& mapAccum, + double accumRate, double ablateRate, double factorUdeform, double factorUslide) +{ + ela = mapELA; + beta = mapAccum; + simAccumRate = accumRate; + simAblateRate = ablateRate; + simFactorUdeform = factorUdeform; + simFactorUslide = factorUslide; + + std::cerr << "Config simulation " << std::endl; + std::cerr << " - accum rate: " << accumRate << std::endl; + std::cerr << " - ablate rate: " << ablateRate << std::endl; + std::cerr << " - Udeform * " << factorUdeform << std::endl; + std::cerr << " - Uslide * " << factorUslide << std::endl; + + glGenBuffers(1, &glbufferELA); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferELA); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferElems * sizeof(double), (const void*)(&ela[0]), GL_STATIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + glGenBuffers(1, &glbufferBeta); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferBeta); + glBufferData(GL_SHADER_STORAGE_BUFFER, bufferElems * sizeof(double), (const void*)(&beta[0]), GL_STATIC_DRAW); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); +} + + +double GlacierTerrain::runSimulationSteps(unsigned int numSimulSteps, double maxSimulTime) +{ + const double minTimeStep = 1e-8; + const double maxTimeStep = 1.0; + + // run n steps and count the ellapsed time + double deltaTime = 0; + for (unsigned int i = 0; i < numSimulSteps && deltaTime < maxSimulTime; i++) { + shaderSIA = i % 2 == 0 ? shaderSIAaxis : shaderSIAdiag; + + double dt = (doPassthrough ? minTimeStep : 1) * std::max(minTimeStep, std::min(maxTimeStep, getAdaptiveTimestep())); + dt = std::max(0.0, std::min(maxSimulTime - deltaTime, dt)); + stepSIA(dt); + + deltaTime += dt; + std::swap(glbufferIceIn, glbufferIceOut); // double buffer for ice in/out + doPassthrough = false; + } + + // copy the ice buffer from GPU to cpu + glUseProgram(shaderSIA); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferIceIn); // note we have the most recent buffer here due to swap! + void* ptr = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, bufferElems * sizeof(double), GL_MAP_READ_BIT); + memcpy(&ice[0], ptr, bufferElems * sizeof(double)); + glUnmapBuffer(GL_SHADER_STORAGE_BUFFER); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + glUseProgram(0); + + return deltaTime; +} + + +void GlacierTerrain::stepSIA(double dt) +{ + glUseProgram(shaderSIA); + + glProgramUniform1i(shaderSIA, glGetUniformLocation(shaderSIA, "GridSizeX"), ny); // attention X,Y + glProgramUniform1i(shaderSIA, glGetUniformLocation(shaderSIA, "GridSizeY"), nx); + glProgramUniform1d(shaderSIA, glGetUniformLocation(shaderSIA, "dx"), cellHeight()); + glProgramUniform1d(shaderSIA, glGetUniformLocation(shaderSIA, "dy"), cellWidth()); + glProgramUniform1d(shaderSIA, glGetUniformLocation(shaderSIA, "dt"), dt); + glProgramUniform1d(shaderSIA, glGetUniformLocation(shaderSIA, "betaAccum"), simAccumRate); + glProgramUniform1d(shaderSIA, glGetUniformLocation(shaderSIA, "betaAblate"), simAblateRate); + glProgramUniform1d(shaderSIA, glGetUniformLocation(shaderSIA, "factorUdeform"), simFactorUdeform); + glProgramUniform1d(shaderSIA, glGetUniformLocation(shaderSIA, "factorUslide"), simFactorUslide); + + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, glbufferBedrock); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, glbufferIceIn); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, glbufferELA); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, glbufferBeta); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, glbufferIceOut); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, glbufferD); + + glDispatchCompute((ny / WORK_GROUP_SIZE_X) + 1, (nx / WORK_GROUP_SIZE_Y) + 1, 1); // attention X,Y + glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); + + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 5, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 6, 0); + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, glbufferD); + void* ptr = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, bufferElems * sizeof(double), GL_MAP_READ_BIT); + memcpy(&diffusivity[0], ptr, bufferElems * sizeof(double)); + glUnmapBuffer(GL_SHADER_STORAGE_BUFFER); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + + glUseProgram(0); +} + +double GlacierTerrain::getAdaptiveTimestep() +{ + const double c_stab = 0.125; + double dx = cellWidth(); + double dy = cellHeight(); + + double dmax = 0; + for (unsigned int i = 0; i < bufferElems; i++) { + dmax = std::max(dmax, diffusivity[i]); + } + + return c_stab * std::min(dx*dx, dy*dy)/std::max(dmax, 1.0); +} + +ScalarField2 GlacierTerrain::remapIceSurface(const ScalarField2& hiresBed) const +{ + int rx = hiresBed.getSizeX(); + int ry = hiresBed.getSizeY(); + + // upsample ice surface (set to 0 where there is no ice) + ScalarField2 surface(bedrock.getDomain(), nx, ny, 0); + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + if (ice.at(i, j) > 0) { + surface(i, j) = ice.at(i,j) + bedrock.at(i, j); + } + } + } + ScalarField2 surfaceUp = surface.setResolution(rx, ry); + + // iceUp = surfaceUp - hiresBed + ScalarField2 iceUp(bedrock.getDomain(), rx, ry, 0); + for (int i = 0; i < rx; i++) { + for (int j = 0; j < ry; j++) { + iceUp(i, j) = std::max(0.0, surfaceUp.at(i, j) - hiresBed.at(i, j)); + } + } + + // cover holes + for (int k = 0; k < 2*(rx/nx)*(ry/ny); k++) { + ScalarField2 surfaceUp(hiresBed); + surfaceUp += iceUp; + + for (int i = 1; i < rx-1; i++) { + for (int j = 1; j < ry-1; j++) { + + bool hole = true; + int numNeighbors = 0; + double surfaceHeight = 0; + + for (int di = -1; hole && di <= 1; di++) { + for (int dj = -1; hole && dj <= 1; dj++) { + if (di == 0 && dj == 0) continue; + + hole = hole && (surfaceUp.at(i + di, j + dj) > surfaceUp(i, j)); + if (iceUp.at(i + di, j + dj) > 0) { + surfaceHeight += surfaceUp.at(i + di, j + dj); + numNeighbors++; + } + } + } + + if (hole) { + iceUp(i, j) = std::max(0.0, surfaceHeight/numNeighbors - hiresBed.at(i,j)); + } + else if (iceUp.at(i,j) <= 0 && numNeighbors >= 3) { + if (iceUp.at(i - 1, j) > 0 && iceUp.at(i + 1, j) <= 0 && surfaceUp.at(i + 1, j) > surfaceUp(i - 1, j)) + iceUp(i, j) = std::max(0.0, surfaceUp.at(i - 1, j) - hiresBed.at(i, j)); + if (iceUp.at(i + 1, j) > 0 && iceUp.at(i - 1, j) <= 0 && surfaceUp.at(i - 1, j) > surfaceUp(i + 1, j)) + iceUp(i, j) = std::max(0.0, surfaceUp.at(i + 1, j) - hiresBed.at(i, j)); + + if (iceUp.at(i, j - 1) > 0 && iceUp.at(i, j + 1) <= 0 && surfaceUp.at(i, j + 1) > surfaceUp(i, j - 1)) + iceUp(i, j) = std::max(0.0, surfaceUp.at(i, j - 1) - hiresBed.at(i, j)); + if (iceUp.at(i, j + 1) > 0 && iceUp.at(i, j - 1) <= 0 && surfaceUp.at(i, j - 1) > surfaceUp(i, j + 1)) + iceUp(i, j) = std::max(0.0, surfaceUp.at(i, j + 1) - hiresBed.at(i, j)); + } + } + } + }; + + return iceUp; +} + +bool GlacierTerrain::Intersect(const Ray &ray, double &t, Vector3 &q) const +{ + // Compute bounding box + Box2 box = getDomain(); + double ta, tb; + + // Check the intersection with the bounding box + Vector3 r0 = ray(0); + Vector3 r1 = ray(1); + if (!box.Intersect(Vector2(r0[0], r0[1]), Vector2(r1[0], r1[1]), ta, tb)) + return false; + + t = ta + 0.0001; + if (ta < 0.0) + { + t = 0.0; + } + + // Ray marching + while (t < tb) + { + // Point along the ray + Vector3 p = ray(t); + double h = Height(Vector2(p[0], p[1])); + if (h > p[2]) { + q = Vector3(p[0], p[1], h); + return true; + } + else { + t += 1.0; + } + } + return false; +} diff --git a/code/glacierterrain.h b/code/glacierterrain.h new file mode 100644 index 0000000..973c3c0 --- /dev/null +++ b/code/glacierterrain.h @@ -0,0 +1,245 @@ +#ifndef GLACIERTERRAIN_H +#define GLACIERTERRAIN_H + +#include "glew.h" +#include "core.h" +#include "scalarfield2.h" +#include + +class GlacierTerrain +{ + +public: + GlacierTerrain(); + GlacierTerrain(int nx, int ny); + GlacierTerrain(const ScalarField2& bedrock); + GlacierTerrain(const ScalarField2& bedrock, const ScalarField2& ice); + ~GlacierTerrain(); + + void initSimulationGL(); + void configSimulation(const ScalarField2& mapELA, const ScalarField2& mapAccum, double accumRate, double ablateRate, double factorUdeform, double factorUslide); + void resetSimulation(); + double runSimulationSteps(unsigned int numSimulSteps, double maxSimulTime = -1); + + void setIceMap(const ScalarField2& icemap); + ScalarField2 remapIceSurface(const ScalarField2& hiresBed) const; + + + // ScalarFields + ScalarField2& GetBedrock() { return bedrock; } + ScalarField2& GetIce() { return ice; } + ScalarField2& GetELA() { return ela; } + ScalarField2& GetAccumRate() { return beta; } + ScalarField2 GetHeightfield() const { return bedrock + ice; } + const ScalarField2& GetBedrock() const { return bedrock; } + const ScalarField2& GetIce() const { return ice; } + const ScalarField2& GetELA() const {return ela; } + const ScalarField2& GetAccumRate() const { return beta; } + const ScalarField2& GetDiffusivity() const { return diffusivity; } + + // Getters + double Bedrock(const Vector2& p) const { return bedrock.value(p); } + double Bedrock(int i, int j) const { return bedrock.at(i,j); } + double Ice(const Vector2& p) const { return ice.value(p); } + double Ice(int i, int j) const { return ice.at(i, j); } + double Height(const Vector2& p) const { return bedrock.value(p) + ice.value(p); } + double Height(int i, int j) const { return bedrock.at(i,j) + ice.at(i,j); } + + // Physical magnitudes + double terrainArea() { return terrainSize[0]*terrainSize[1]; } + double cellWidth() const { return terrainSize[0]/nx; } + double cellHeight() const { return terrainSize[1]/ny; } + double cellArea() const { return cellWidth()*cellHeight(); } + bool isEmpty() const { return (nx <= 0) || (ny <= 0); } + int numCellsX() const { return nx; } + int numCellsY() const { return ny; } + int vertexIndex(int i, int j) { return i + nx * j; } + Box2 getDomain() const { return bedrock.getDomain(); } + Box3 getBoundingBox() const { + Box2 dom = getDomain(); + double hmin, hmax; + bedrock.getRange(hmin, hmax); + return Box3(Vector3(dom.getMin()[0], dom.getMin()[1], hmin), + Vector3(dom.getMax()[0], dom.getMax()[1], hmax)); + } + + double iceVolume() const; + double elaValue(int, int) const; + bool aboveELA(int i, int j) const; + bool accumArea(int i, int j) const; + double iceThickness(int, int) const; + Vector2 iceGradient(int, int) const; + double iceStress(int, int) const; + double iceSpeed(int, int) const; + double iceSpeedDeform(int, int) const; + double iceSpeedSlide(int, int) const; + Vector2 iceVelocity(int, int) const; + double diffusionTerm(int, int) const; + double diffusionTermRaw(int, int) const; + double yearlyIce(bool withIce) const; + double glaciatedArea() const; + + + // Modifiers + void SmoothRock(int = 1); + void SmoothIce(int = 1); + + // Query + bool Intersect(const Ray& ray, double& t, Vector3& q) const; + QString GetStats() const; + + // GL Buffers + GLuint bufferBedrockId() const { return glbufferBedrock; }; + GLuint bufferIceInId() const { return glbufferIceIn; }; + GLuint bufferIceOutId() const { return glbufferIceOut; }; + GLuint bufferDiffusionId() const { return glbufferD; }; + + void updateBedrockGPU(); + void updateIceGPU(); + void updateELAGPU(); + void updateAccumRateGPU(); + + +protected: + + int nx, ny; + Vector2 terrainSize; + ScalarField2 bedrock; // Bedrock + ScalarField2 ice; // Ice cover + ScalarField2 ela; // ELA map + ScalarField2 beta; // Precipitation modifier + + // Simulation params and constants + double simAccumRate, simAblateRate; + double simFactorUdeform, simFactorUslide; + static constexpr double g = 9.81; // gravity + static constexpr double rho = 910.0; // ice density + static constexpr double Gamma_d = 7.26e-5; // from [Headley et al. 2012] + static constexpr double Gamma_s = 3.27; // from [Headley et al. 2012] + + // Simulation compute shader + static const unsigned int WORK_GROUP_SIZE_X = 32; + static const unsigned int WORK_GROUP_SIZE_Y = 32; + GLuint shaderSIA; + GLuint shaderSIAaxis, shaderSIAdiag; + GLuint glbufferBedrock; + GLuint glbufferIceIn, glbufferIceOut; + GLuint glbufferELA, glbufferBeta; + GLuint glbufferD; + + // buffers + bool initializedBuffers = false; + bool doPassthrough = true; + ScalarField2 diffusivity; + unsigned int bufferElems; + + // simulation functions + void stepSIA(double dt); + double getAdaptiveTimestep(); + + // internal funcs + void updateELA(double avgELA); + +}; + + +inline double GlacierTerrain::iceVolume() const +{ + const ScalarField2& field = GetIce(); + double v = 0; + double a = cellArea(); + for (int i = 0; i < nx; i++) { + for (int j = 0; j < ny; j++) { + v += field.at(i, j); + } + } + return a * v; +} + +inline double GlacierTerrain::elaValue(int i, int j) const +{ + return ela.at(i, j); +} + +inline bool GlacierTerrain::aboveELA(int i, int j) const +{ + return (bedrock.at(i, j) + ice.at(i,j)) > ela.at(i,j); +} + +inline bool GlacierTerrain::accumArea(int i, int j) const +{ + return aboveELA(i, j); +} + +inline double GlacierTerrain::iceThickness(int i, int j) const +{ + return ice.at(i, j); +} + +inline Vector2 GlacierTerrain::iceGradient(int i, int j) const +{ + double dip = i < nx - 1 ? Height(i + 1, j) : Height(i, j); + double dim = i > 0 ? Height(i - 1, j) : Height(i, j); + double dx = i > 0 && i < nx - 1 ? 2 * cellWidth() : cellWidth(); + double djp = j < ny - 1 ? Height(i, j + 1) : Height(i, j); + double djm = j > 0 ? Height(i, j - 1) : Height(i, j); + double dy = j > 0 && j < ny - 1 ? 2 * cellHeight() : cellHeight(); + return Vector2((dip - dim)/dx, (djp - djm)/dy); +} + +inline double GlacierTerrain::iceStress(int i, int j) const +{ + return rho * g * iceThickness(i, j) * Norm(iceGradient(i, j)); +} + +inline double GlacierTerrain::iceSpeed(int i, int j) const +{ + return iceSpeedDeform(i, j) + iceSpeedSlide(i, j); +} + +inline double GlacierTerrain::iceSpeedDeform(int i, int j) const +{ + double h = iceThickness(i, j); + double s = Norm(iceGradient(i, j)); + return simFactorUdeform * Gamma_d * h*h*h*h * s*s; +} + +inline double GlacierTerrain::iceSpeedSlide(int i, int j) const +{ + double h = iceThickness(i, j); + double s = Norm(iceGradient(i, j)); + return simFactorUslide * Gamma_s * h*h * s*s; +} + +inline Vector2 GlacierTerrain::iceVelocity(int i, int j) const +{ + return iceSpeed(i, j) * iceGradient(i, j)/Norm(iceGradient(i,j)); +} + +inline double GlacierTerrain::diffusionTerm(int i, int j) const +{ + return diffusivity.at(i, j); +} + +inline double GlacierTerrain::diffusionTermRaw(int i, int j) const +{ + return iceThickness(i,j) * iceSpeed(i, j); +} + +inline double GlacierTerrain::yearlyIce(bool withIce) const { + double iceAccum = 0; + for (int i = 0; i < nx*ny; i++) { + iceAccum += std::max(0.0, simAccumRate * beta.at(i) * (bedrock.at(i) + withIce*ice.at(i) - ela.at(i))); + } + return iceAccum * cellArea(); +} + +inline double GlacierTerrain::glaciatedArea() const { + int iceCells = 0; + for (int i = 0; i < nx*ny; i++) { + if (ice.at(i) > 0) iceCells++; + } + return iceCells * cellArea(); +} + +#endif diff --git a/data/arrow.png b/data/arrow.png new file mode 100644 index 0000000..bbe6308 Binary files /dev/null and b/data/arrow.png differ diff --git a/data/maps/aiguestortes/dem-hires.png b/data/maps/aiguestortes/dem-hires.png new file mode 100644 index 0000000..6b4d4cb Binary files /dev/null and b/data/maps/aiguestortes/dem-hires.png differ diff --git a/data/maps/aiguestortes/dem.png b/data/maps/aiguestortes/dem.png new file mode 100644 index 0000000..7a30801 Binary files /dev/null and b/data/maps/aiguestortes/dem.png differ diff --git a/data/maps/aiguestortes/ela.png b/data/maps/aiguestortes/ela.png new file mode 100644 index 0000000..dbf4736 Binary files /dev/null and b/data/maps/aiguestortes/ela.png differ diff --git a/data/maps/aiguestortes/ice.png b/data/maps/aiguestortes/ice.png new file mode 100644 index 0000000..4242682 Binary files /dev/null and b/data/maps/aiguestortes/ice.png differ diff --git a/data/maps/aiguestortesCrop/crevassesLong.png b/data/maps/aiguestortesCrop/crevassesLong.png new file mode 100644 index 0000000..08de791 Binary files /dev/null and b/data/maps/aiguestortesCrop/crevassesLong.png differ diff --git a/data/maps/aiguestortesCrop/crevassesMargin.png b/data/maps/aiguestortesCrop/crevassesMargin.png new file mode 100644 index 0000000..8381b26 Binary files /dev/null and b/data/maps/aiguestortesCrop/crevassesMargin.png differ diff --git a/data/maps/aiguestortesCrop/crevassesTrans.png b/data/maps/aiguestortesCrop/crevassesTrans.png new file mode 100644 index 0000000..70c03c4 Binary files /dev/null and b/data/maps/aiguestortesCrop/crevassesTrans.png differ diff --git a/data/maps/aiguestortesCrop/dem-hires.png b/data/maps/aiguestortesCrop/dem-hires.png new file mode 100644 index 0000000..973431f Binary files /dev/null and b/data/maps/aiguestortesCrop/dem-hires.png differ diff --git a/data/maps/aiguestortesCrop/dem.png b/data/maps/aiguestortesCrop/dem.png new file mode 100644 index 0000000..a2b94ed Binary files /dev/null and b/data/maps/aiguestortesCrop/dem.png differ diff --git a/data/maps/aiguestortesCrop/ela.png b/data/maps/aiguestortesCrop/ela.png new file mode 100644 index 0000000..627cb62 Binary files /dev/null and b/data/maps/aiguestortesCrop/ela.png differ diff --git a/data/maps/aiguestortesCrop/ice.png b/data/maps/aiguestortesCrop/ice.png new file mode 100644 index 0000000..b5bc641 Binary files /dev/null and b/data/maps/aiguestortesCrop/ice.png differ diff --git a/data/maps/aiguestortesCrop/icefalls.png b/data/maps/aiguestortesCrop/icefalls.png new file mode 100644 index 0000000..d5663e0 Binary files /dev/null and b/data/maps/aiguestortesCrop/icefalls.png differ diff --git a/data/maps/aiguestortesCrop/icefallsRaw.png b/data/maps/aiguestortesCrop/icefallsRaw.png new file mode 100644 index 0000000..f77a9c9 Binary files /dev/null and b/data/maps/aiguestortesCrop/icefallsRaw.png differ diff --git a/data/maps/aiguestortesCrop/ogivesDistCtr.png b/data/maps/aiguestortesCrop/ogivesDistCtr.png new file mode 100644 index 0000000..eb8fd86 Binary files /dev/null and b/data/maps/aiguestortesCrop/ogivesDistCtr.png differ diff --git a/data/maps/aiguestortesCrop/ogivesDistSrc.png b/data/maps/aiguestortesCrop/ogivesDistSrc.png new file mode 100644 index 0000000..b39ad2b Binary files /dev/null and b/data/maps/aiguestortesCrop/ogivesDistSrc.png differ diff --git a/data/maps/aiguestortesCrop/ogivesId.png b/data/maps/aiguestortesCrop/ogivesId.png new file mode 100644 index 0000000..efc31cc Binary files /dev/null and b/data/maps/aiguestortesCrop/ogivesId.png differ diff --git a/data/maps/aiguestortesCrop/rimayes.png b/data/maps/aiguestortesCrop/rimayes.png new file mode 100644 index 0000000..552b259 Binary files /dev/null and b/data/maps/aiguestortesCrop/rimayes.png differ diff --git a/data/maps/aiguestortesCrop/segmentation.png b/data/maps/aiguestortesCrop/segmentation.png new file mode 100644 index 0000000..8dbfa39 Binary files /dev/null and b/data/maps/aiguestortesCrop/segmentation.png differ diff --git a/data/maps/aiguestortesCrop/seracs.png b/data/maps/aiguestortesCrop/seracs.png new file mode 100644 index 0000000..a4a77ef Binary files /dev/null and b/data/maps/aiguestortesCrop/seracs.png differ diff --git a/data/maps/aiguestortesCrop/valleydist.png b/data/maps/aiguestortesCrop/valleydist.png new file mode 100644 index 0000000..537cb9b Binary files /dev/null and b/data/maps/aiguestortesCrop/valleydist.png differ diff --git a/data/maps/aiguestortesCrop/valleywidth.png b/data/maps/aiguestortesCrop/valleywidth.png new file mode 100644 index 0000000..b72b5ff Binary files /dev/null and b/data/maps/aiguestortesCrop/valleywidth.png differ diff --git a/data/stamps/crevasses_1_mask.png b/data/stamps/crevasses_1_mask.png new file mode 100644 index 0000000..6ee98b6 Binary files /dev/null and b/data/stamps/crevasses_1_mask.png differ diff --git a/data/stamps/crevasses_2_mask.png b/data/stamps/crevasses_2_mask.png new file mode 100644 index 0000000..62f5ff3 Binary files /dev/null and b/data/stamps/crevasses_2_mask.png differ diff --git a/data/stamps/crevasses_3_mask.png b/data/stamps/crevasses_3_mask.png new file mode 100644 index 0000000..87e464b Binary files /dev/null and b/data/stamps/crevasses_3_mask.png differ diff --git a/data/stamps/crevasses_4_mask.png b/data/stamps/crevasses_4_mask.png new file mode 100644 index 0000000..4b4c309 Binary files /dev/null and b/data/stamps/crevasses_4_mask.png differ diff --git a/data/stamps/crevasses_5_mask.png b/data/stamps/crevasses_5_mask.png new file mode 100644 index 0000000..dc45e2d Binary files /dev/null and b/data/stamps/crevasses_5_mask.png differ diff --git a/data/stamps/crevasses_6_mask.png b/data/stamps/crevasses_6_mask.png new file mode 100644 index 0000000..124c0d3 Binary files /dev/null and b/data/stamps/crevasses_6_mask.png differ diff --git a/data/stamps/crevasses_7_mask.png b/data/stamps/crevasses_7_mask.png new file mode 100644 index 0000000..6cd9e7b Binary files /dev/null and b/data/stamps/crevasses_7_mask.png differ diff --git a/data/stamps/crevasses_8_mask.png b/data/stamps/crevasses_8_mask.png new file mode 100644 index 0000000..c5f2905 Binary files /dev/null and b/data/stamps/crevasses_8_mask.png differ diff --git a/data/stamps/crevasses_9_mask.png b/data/stamps/crevasses_9_mask.png new file mode 100644 index 0000000..82daa84 Binary files /dev/null and b/data/stamps/crevasses_9_mask.png differ diff --git a/data/terrains/aiguestortes_20m.png b/data/terrains/aiguestortes_20m.png new file mode 100644 index 0000000..6abb11f Binary files /dev/null and b/data/terrains/aiguestortes_20m.png differ diff --git a/data/terrains/aiguestortes_precipitation.png b/data/terrains/aiguestortes_precipitation.png new file mode 100644 index 0000000..08ad833 Binary files /dev/null and b/data/terrains/aiguestortes_precipitation.png differ diff --git a/data/terrains/aiguestortes_sun.png b/data/terrains/aiguestortes_sun.png new file mode 100644 index 0000000..1048af3 Binary files /dev/null and b/data/terrains/aiguestortes_sun.png differ diff --git a/data/terrains/canyon_10m.png b/data/terrains/canyon_10m.png new file mode 100644 index 0000000..39ae7da Binary files /dev/null and b/data/terrains/canyon_10m.png differ diff --git a/data/terrains/canyon_precipitation.png b/data/terrains/canyon_precipitation.png new file mode 100644 index 0000000..1a9b4a3 Binary files /dev/null and b/data/terrains/canyon_precipitation.png differ diff --git a/data/terrains/canyon_sun.png b/data/terrains/canyon_sun.png new file mode 100644 index 0000000..87ed82c Binary files /dev/null and b/data/terrains/canyon_sun.png differ diff --git a/data/terrains/chartreuse_20m.png b/data/terrains/chartreuse_20m.png new file mode 100644 index 0000000..34ed4ba Binary files /dev/null and b/data/terrains/chartreuse_20m.png differ diff --git a/data/terrains/chartreuse_edit_20m.png b/data/terrains/chartreuse_edit_20m.png new file mode 100644 index 0000000..98a4884 Binary files /dev/null and b/data/terrains/chartreuse_edit_20m.png differ diff --git a/data/terrains/chartreuse_sun.png b/data/terrains/chartreuse_sun.png new file mode 100644 index 0000000..23943a1 Binary files /dev/null and b/data/terrains/chartreuse_sun.png differ diff --git a/data/terrains/ecrins_30m.png b/data/terrains/ecrins_30m.png new file mode 100644 index 0000000..fd80c68 Binary files /dev/null and b/data/terrains/ecrins_30m.png differ diff --git a/data/terrains/ecrins_sun.png b/data/terrains/ecrins_sun.png new file mode 100644 index 0000000..9ee81ff Binary files /dev/null and b/data/terrains/ecrins_sun.png differ diff --git a/data/terrains/kungsleden_25m.png b/data/terrains/kungsleden_25m.png new file mode 100644 index 0000000..d0c81f5 Binary files /dev/null and b/data/terrains/kungsleden_25m.png differ diff --git a/data/terrains/kungsleden_sun.png b/data/terrains/kungsleden_sun.png new file mode 100644 index 0000000..a1e262b Binary files /dev/null and b/data/terrains/kungsleden_sun.png differ diff --git a/data/terrains/mtperdu_20m.png b/data/terrains/mtperdu_20m.png new file mode 100644 index 0000000..4bef1d7 Binary files /dev/null and b/data/terrains/mtperdu_20m.png differ diff --git a/data/terrains/mtperdu_sun.png b/data/terrains/mtperdu_sun.png new file mode 100644 index 0000000..15f2d8b Binary files /dev/null and b/data/terrains/mtperdu_sun.png differ diff --git a/data/terrains/ruapehu_25m.png b/data/terrains/ruapehu_25m.png new file mode 100644 index 0000000..e3255eb Binary files /dev/null and b/data/terrains/ruapehu_25m.png differ diff --git a/data/terrains/ruapehu_sun.png b/data/terrains/ruapehu_sun.png new file mode 100644 index 0000000..9b4fbfe Binary files /dev/null and b/data/terrains/ruapehu_sun.png differ diff --git a/data/terrains/smokies_10m.png b/data/terrains/smokies_10m.png new file mode 100644 index 0000000..69cc6f9 Binary files /dev/null and b/data/terrains/smokies_10m.png differ diff --git a/data/terrains/smokies_sun.png b/data/terrains/smokies_sun.png new file mode 100644 index 0000000..0720f13 Binary files /dev/null and b/data/terrains/smokies_sun.png differ diff --git a/data/terrains/sthelens_10m.png b/data/terrains/sthelens_10m.png new file mode 100644 index 0000000..f337ff5 Binary files /dev/null and b/data/terrains/sthelens_10m.png differ diff --git a/data/terrains/sthelens_sun.png b/data/terrains/sthelens_sun.png new file mode 100644 index 0000000..e49b0ae Binary files /dev/null and b/data/terrains/sthelens_sun.png differ diff --git a/img/Crevasses-Rimayes.jpg b/img/Crevasses-Rimayes.jpg new file mode 100644 index 0000000..f4fb56f Binary files /dev/null and b/img/Crevasses-Rimayes.jpg differ diff --git a/img/Crevasses_Longitudinal-Marginal.jpg b/img/Crevasses_Longitudinal-Marginal.jpg new file mode 100644 index 0000000..484a67f Binary files /dev/null and b/img/Crevasses_Longitudinal-Marginal.jpg differ diff --git a/img/Crevasses_Transverse.jpg b/img/Crevasses_Transverse.jpg new file mode 100644 index 0000000..0c31e7c Binary files /dev/null and b/img/Crevasses_Transverse.jpg differ diff --git a/img/Icefalls-Seracs.jpg b/img/Icefalls-Seracs.jpg new file mode 100644 index 0000000..64e4dde Binary files /dev/null and b/img/Icefalls-Seracs.jpg differ diff --git a/img/Moraines-Transverse.jpg b/img/Moraines-Transverse.jpg new file mode 100644 index 0000000..09c336d Binary files /dev/null and b/img/Moraines-Transverse.jpg differ diff --git a/img/Ogives-Moraines.jpg b/img/Ogives-Moraines.jpg new file mode 100644 index 0000000..70b3eb0 Binary files /dev/null and b/img/Ogives-Moraines.jpg differ diff --git a/img/scene-aiguestortes.jpg b/img/scene-aiguestortes.jpg new file mode 100644 index 0000000..348f7f1 Binary files /dev/null and b/img/scene-aiguestortes.jpg differ diff --git a/img/scene-ecrins.jpg b/img/scene-ecrins.jpg new file mode 100644 index 0000000..427c3bc Binary files /dev/null and b/img/scene-ecrins.jpg differ diff --git a/shaders/instanced-cube.glsl b/shaders/instanced-cube.glsl new file mode 100644 index 0000000..63a8757 --- /dev/null +++ b/shaders/instanced-cube.glsl @@ -0,0 +1,204 @@ +#version 400 + +#ifdef VERTEX_SHADER + +// model attributes +in vec3 a_position; +in vec3 a_normal; + +// instance attributes +in vec3 i_translation; +in float i_height; + +// uniforms +uniform mat4 gl_ModelViewMatrix; +uniform mat4 gl_ProjectionMatrix; +uniform vec2 u_cellSize; +uniform ivec2 u_gridCells; + +// output +out vec3 worldPos; +out vec2 cellCenter; +out vec3 eyePos; +out vec3 worldNormal; +out float cubeHeight; +out ivec2 cellCoords; + +void main(void) +{ + mat4 MVP = gl_ProjectionMatrix * gl_ModelViewMatrix; + + worldPos = a_position*vec3(u_cellSize, i_height) + i_translation; + eyePos = vec3(gl_ModelViewMatrix*vec4(worldPos, 1.0)); + worldNormal = a_normal; + cubeHeight = i_height; + + cellCoords = ivec2(gl_InstanceID/u_gridCells.y, gl_InstanceID%u_gridCells.y); + cellCenter = (vec2(cellCoords) + 0.5) * u_cellSize; + + gl_Position = MVP * vec4(worldPos, 1.0); +} +#endif + +#ifdef FRAGMENT_SHADER + +#define PI 3.1415926538 + +// input from vertex shader +in vec3 worldPos; +in vec2 cellCenter; +in vec3 eyePos; +in vec3 worldNormal; +in float cubeHeight; +in ivec2 cellCoords; + +// uniforms +uniform vec4 u_materialAmbient; +uniform vec4 u_materialDiffuse; +uniform vec4 u_materialSpecular; +uniform float u_materialShininess; +uniform vec3 u_lightPos; + +uniform sampler2D u_texture; +uniform sampler2D u_arrowTexture; + +uniform int u_useTexture; // 0: ambient material color, 1,2: texture, 3: custom shader defined +uniform bool u_shadeCube; +uniform vec2 u_worldSize; +uniform vec2 u_cellSize; + +// raw data for custom shader texturing +uniform sampler2D u_datatexBed; +uniform sampler2D u_datatexIce; +uniform vec3 u_bedRange; // min, max, range +uniform vec3 u_iceRange; + +// edit cursor +uniform bool u_drawCursor; +uniform vec3 u_cursorPoint; +uniform float u_cursorRadius; + +// output +out vec4 fragColor; + +vec4 phongModel(vec4 c_amb, vec4 c_diff, vec4 c_spec) { + + // Returned color set to ambient + vec3 c = c_amb.rgb; + + // Modified diffuse lighting + vec3 L = normalize(u_lightPos - worldPos); + vec3 N = normalize(worldNormal); + float d = 0.5*(1.0 + dot(N, L)); + c += c_diff.rgb * vec3(d*d); + + // Specular + vec3 R = reflect(L, N); + float l = 0.5*(1.0 + dot(R, eyePos)); + float s = pow(l*l, u_materialShininess); + c += c_spec.rgb * vec3(s); + + return vec4(c, 1.0); +} + + +// All components are in the range [0…1], including hue. +vec3 hsv2rgb(vec3 c) +{ + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +vec2 rotate(vec2 v, float a) { + float s = sin(a); + float c = cos(a); + mat2 m = mat2(c, -s, s, c); + return m * v; +} + + +void main() +{ + vec4 colorAmbient; + + // uniform color + if (u_useTexture == 0) { + colorAmbient = u_materialAmbient; + } + + // shading using a texture with blocky appearance + else if (u_useTexture == 1) { + vec2 texCoords = cellCenter.xy/u_worldSize.xy; + colorAmbient = texture(u_texture, texCoords); + } + + // shading using a texture with interpolation + else if (u_useTexture == 2) { + vec2 texCoords = worldPos.xy/u_worldSize.xy; + colorAmbient = texture(u_texture, texCoords); + } + + // custom shading with access to bed and ice layers, for debugging purposes + else if (u_useTexture == 3) { + + vec2 texCoords = cellCenter.xy/u_worldSize.xy; + vec2 texelSize = u_cellSize/u_worldSize.xy; + + float h = u_iceRange.x + u_iceRange.z*texture(u_datatexIce, texCoords).r; + float b = u_bedRange.x + u_bedRange.z*texture(u_datatexBed, texCoords).r; + float s = h + b; + float hxm = u_iceRange.x + u_iceRange.z*texture(u_datatexIce, texCoords + vec2(-1, 0)*texelSize).r; + float hxp = u_iceRange.x + u_iceRange.z*texture(u_datatexIce, texCoords + vec2( 1, 0)*texelSize).r; + float hym = u_iceRange.x + u_iceRange.z*texture(u_datatexIce, texCoords + vec2( 0 -1)*texelSize).r; + float hyp = u_iceRange.x + u_iceRange.z*texture(u_datatexIce, texCoords + vec2( 0, 1)*texelSize).r; + float bxm = u_bedRange.x + u_bedRange.z*texture(u_datatexBed, texCoords + vec2(-1, 0)*texelSize).r; + float bxp = u_bedRange.x + u_bedRange.z*texture(u_datatexBed, texCoords + vec2( 1, 0)*texelSize).r; + float bym = u_bedRange.x + u_bedRange.z*texture(u_datatexBed, texCoords + vec2( 0 -1)*texelSize).r; + float byp = u_bedRange.x + u_bedRange.z*texture(u_datatexBed, texCoords + vec2( 0, 1)*texelSize).r; + float sxm = bxm + hxm; + float sxp = bxp + hxp; + float sym = bym + hym; + float syp = byp + hyp; + + float grad_s_x = 0.5*(sxp - sxm)/u_cellSize.x; + float grad_s_y = 0.5*(syp - sym)/u_cellSize.y; + float grad_s = sqrt(grad_s_x*grad_s_x + grad_s_y*grad_s_y); + vec3 surfNormal = vec3(-grad_s_x, -grad_s_y, 1.0); + float slopeAngle = 0.5*PI - acos(length(vec3(grad_s_x, grad_s_y, 0))/length(surfNormal)); + vec2 flowDir = -normalize(vec2(grad_s_x, grad_s_y)); + float flowDirAngle = atan(flowDir.y, flowDir.x); + + vec3 c; + float ct = PI/8.0; + if (slopeAngle <= ct) + c = mix(vec3( 97.0,130.0,234.0), vec3(221.0,220.0,219.0), slopeAngle/ct); + else + c = mix(vec3(221.0,220.0,219.0), vec3(220.0, 94.0, 75.0), clamp(slopeAngle/ct - 1.0, 0.0, 1.0)); + colorAmbient = vec4(c/255.0, 1.0); + + // draw arrow + vec2 arrowCoords = 1.6*(fract(worldPos.xy/u_cellSize.xy) - 0.5); + arrowCoords = rotate(arrowCoords, flowDirAngle); + vec4 arrowTex = texture(u_arrowTexture, 0.5 + arrowCoords); + float aa = pow(arrowTex.a, 5); + colorAmbient = aa*arrowTex + (1 - aa)*colorAmbient; + } + + vec3 color; + if (u_shadeCube) + color = phongModel(colorAmbient, u_materialDiffuse, u_materialSpecular).rgb; + else + color = colorAmbient.rgb; + + if (u_drawCursor) { + float t = length(worldPos.xy - u_cursorPoint.xy)/u_cursorRadius; + t = 0.5*(1.0 - smoothstep(0.5, 1.0, t)); + color = color*(1 - t) + vec3(0.4,0.8,0.0)*t; + } + + float alpha = min(cubeHeight/1.0, 1.0); + + fragColor = vec4(color, alpha); +} +#endif diff --git a/shaders/sia.glsl b/shaders/sia.glsl new file mode 100644 index 0000000..694b19a --- /dev/null +++ b/shaders/sia.glsl @@ -0,0 +1,273 @@ +#version 430 +#extension GL_ARB_compute_shader : enable +#extension GL_ARB_shader_storage_buffer_object : enable + +# ifdef COMPUTE_SHADER + +uniform int GridSizeX; +uniform int GridSizeY; +uniform double dx; +uniform double dy; +uniform double dt; + +uniform double betaAccum; +uniform double betaAblate; +uniform double factorUdeform; +uniform double factorUslide; + + +layout(std430, binding=1) buffer Bedrock { + double bedrock[]; +}; + +layout(std430, binding=2) buffer InIce { + double inIce[]; +}; + +layout(std430, binding=3) buffer MapELA { + double mapELA[]; +}; + +layout(std430, binding=4) buffer MapBeta { + double mapBeta[]; +}; + +layout(std430, binding=5) buffer OutIce { + double outIce[]; +}; + +layout(std430, binding=6) buffer Diffusion { + double diffusion[]; +}; + + +layout(local_size_x = WORK_GROUP_SIZE_X, local_size_y = WORK_GROUP_SIZE_Y, local_size_z = 1) in; + + + +// avoid divisions by zero +const double eps = 1e-6; + +// Glenn law exponent +const double n = 3; + +// These parameters are defined in section 7.1 +const double A = 7.57e-17;//1e-16; +const double g = 9.81; +const double rho = 910.0; +const double rg = rho * g; +const double Gamma = 2.0 * A * rg*rg*rg / (n + 2); + +const double Gamma_d = 7.26e-5; +const double Gamma_s = 3.27; + + + +int GetOffset(int i, int j) +{ + return i * GridSizeY + j; +} + +double H(int i, int j) +{ + if (i < 0) return 0; + if (j < 0) return 0; + if (i >= GridSizeX) return 0; + if (j >= GridSizeY) return 0; + return inIce[GetOffset(i, j)]; +} + +double B(int i, int j) +{ + if (i < 0) return 0; + if (j < 0) return 0; + if (i >= GridSizeX) return 0; + if (j >= GridSizeY) return 0; + return bedrock[GetOffset(i, j)]; +} + +double ELA(int i, int j) +{ + if (i < 0) return 0; + if (j < 0) return 0; + if (i >= GridSizeX) return 0; + if (j >= GridSizeY) return 0; + return mapELA[GetOffset(i, j)]; +} + +double BetaFactor(int i, int j) +{ + if (i < 0) return 0; + if (j < 0) return 0; + if (i >= GridSizeX) return 0; + if (j >= GridSizeY) return 0; + return mapBeta[GetOffset(i, j)]; +} + +double mdot(double z, double ela, double k) +{ + if (z >= ela) return betaAccum * (z - ela) * k; + else return betaAblate * (z - ela); +} + +double phi(double r) +{ + const double b = 2; + return max(0, max(min(b*r, 1), min(r, b))); +} + +double diffusivity(double grad_s, double h_p, double h_m, double s_p, double s_m) +{ + double D_p = Gamma * h_p*h_p*h_p*h_p*h_p * grad_s; + double D_m = Gamma * h_m*h_m*h_m*h_m*h_m * grad_s; + double D_min = min(D_p, D_m); + double D_max = max(D_p, D_m); + if (s_p <= s_m && h_m <= h_p) return D_min; + if (s_p <= s_m && h_m > h_p) return D_max; + if (s_p > s_m && h_m <= h_p) return D_max; + if (s_p > s_m && h_m > h_p) return D_min; + return 0; +} + +double diffusivityWithSliding(double grad_s, double h_p, double h_m, double s_p, double s_m) +{ + double D_p = h_p*h_p*h_p * (factorUdeform*Gamma_d*h_p*h_p + factorUslide*Gamma_s) * grad_s; + double D_m = h_m*h_m*h_m * (factorUdeform*Gamma_d*h_m*h_m + factorUslide*Gamma_s) * grad_s; + double D_min = min(D_p, D_m); + double D_max = max(D_p, D_m); + if (s_p <= s_m && h_m <= h_p) return D_min; + if (s_p <= s_m && h_m > h_p) return D_max; + if (s_p > s_m && h_m <= h_p) return D_max; + if (s_p > s_m && h_m > h_p) return D_min; + return 0; +} + +void main() +{ + int i = int(gl_GlobalInvocationID.x); + int j = int(gl_GlobalInvocationID.y); + + if (i < 0) return; + if (j < 0) return; + if (i >= GridSizeX) return; + if (j >= GridSizeY) return; + + double ela = ELA(i, j); + double beta = BetaFactor(i, j); + +# ifdef SIA_DIR_AXES + + double dx2 = dx*dx; + double dy2 = dy*dy; + + // access ice height positions in buffer + double h = H(i,j); + double h_ip = H(i+1,j); + double h_ipp = H(i+2,j); + double h_im = H(i-1,j); + double h_imm = H(i-2,j); + double h_jp = H(i,j+1); + double h_jpp = H(i,j+2); + double h_jm = H(i,j-1); + double h_jmm = H(i,j-2); + + // access bedrock positions and compute surface + double z = B(i,j); + double s = h + z; + double s_ip = h_ip + B(i+1,j); + double s_im = h_im + B(i-1,j); + double s_jp = h_jp + B(i,j+1); + double s_jm = h_jm + B(i,j-1); + +# endif +# ifdef SIA_DIR_DIAGONALS + + double dx2 = dx*dx + dy*dy; + double dy2 = dx*dx + dy*dy; + + // access ice height positions in buffer + double h = H(i,j); + double h_ip = H(i+1,j+1); + double h_ipp = H(i+2,j+2); + double h_im = H(i-1,j-1); + double h_imm = H(i-2,j-2); + double h_jp = H(i-1,j+1); + double h_jpp = H(i-2,j+2); + double h_jm = H(i+1,j-1); + double h_jmm = H(i+2,j-2); + + // access bedrock positions and compute surface + double z = B(i,j); + double s = h + z; + double s_ip = h_ip + B(i+1,j+1); + double s_im = h_im + B(i-1,j-1); + double s_jp = h_jp + B(i-1,j+1); + double s_jm = h_jm + B(i+1,j-1); + +# endif + + // compute downstream to upstream ice thickness ratio + double r_i = (h - h_im) /(h_ip - h + eps); + double r_ip = (h_ip - h) /(h_ipp - h_ip + eps); + double r_im = (h_im - h_imm)/(h - h_im + eps); + + double r_j = (h - h_jm) /(h_jp - h + eps); + double r_jp = (h_jp - h) /(h_jpp - h_jp + eps); + double r_jm = (h_jm - h_jmm)/(h - h_jm + eps); + + + // ice thickness at cell boundary (staggered grid) + // up = +1/2, dn = -1/2 + double h_iup_m = h + 0.5 * phi(r_i) * (h_ip - h); + double h_iup_p = h_ip - 0.5 * phi(r_ip) * (h_ipp - h_ip); + double h_idn_m = h_im + 0.5 * phi(r_im) * (h - h_im); + double h_idn_p = h - 0.5 * phi(r_i) * (h_ip - h); + + double h_jup_m = h + 0.5 * phi(r_j) * (h_jp - h); + double h_jup_p = h_jp - 0.5 * phi(r_jp) * (h_jpp - h_jp); + double h_jdn_m = h_jm + 0.5 * phi(r_jm) * (h - h_jm); + double h_jdn_p = h - 0.5 * phi(r_j) * (h_jp - h); + + + // slope gradients + double grad_s_iup = (s_ip - s)*(s_ip - s)/dy2; + double grad_s_idn = (s - s_im)*(s - s_im)/dy2; + double grad_s_jup = (s_jp - s)*(s_jp - s)/dx2; + double grad_s_jdn = (s - s_jm)*(s - s_jm)/dx2; + + + // diffusivities at the 4 cell boundaries + double D_iup = diffusivityWithSliding(grad_s_iup, h_iup_p, h_iup_m, s_ip, s); + double D_idn = diffusivityWithSliding(grad_s_idn, h_idn_p, h_idn_m, s, s_im); + double D_jup = diffusivityWithSliding(grad_s_jup, h_jup_p, h_jup_m, s_jp, s); + double D_jdn = diffusivityWithSliding(grad_s_jdn, h_jdn_p, h_jdn_m, s, s_jm); + + + // flux q divergence + double div_q_i = (D_iup*(s_ip - s) - D_idn*(s - s_im))/dy2; + double div_q_j = (D_jup*(s_jp - s) - D_jdn*(s - s_jm))/dx2; + double div_q = div_q_i + div_q_j; + + + // mass balance + double m = mdot(s, ela, beta); + + + // explicit time stepping + double h_res = h + dt*(m + div_q); + h_res = max(h_res, 0); + + // max diffusion value, needed for adaptive timestep in next iteration + double maxD = max(max(abs(D_iup), abs(D_idn)), max(abs(D_jup), abs(D_jdn))); + // ice delta, used for checking glacier stability + double dIce = h_res - h; + + + // output + int idx = GetOffset(i, j); + outIce[idx] = h_res; + diffusion[idx] = maxD; + +} + +#endif \ No newline at end of file diff --git a/shaders/skybox.glsl b/shaders/skybox.glsl new file mode 100644 index 0000000..488be44 --- /dev/null +++ b/shaders/skybox.glsl @@ -0,0 +1,66 @@ +#version 430 core + +#ifdef VERTEX_SHADER +void main(void) +{ + vec4 vertices[4] = vec4[4](vec4(-1.0, -1.0, 1.0, 1.0), + vec4( 1.0, -1.0, 1.0, 1.0), + vec4(-1.0, 1.0, 1.0, 1.0), + vec4( 1.0, 1.0, 1.0, 1.0)); + vec4 pos = vertices[gl_VertexID]; + + gl_Position = pos; +} +#endif + +#ifdef FRAGMENT_SHADER +layout (depth_greater) out float gl_FragDepth; + +layout(location = 0) uniform vec3 CamPos; +layout(location = 1) uniform vec3 CamLookAt; +layout(location = 2) uniform vec3 CamUp; +layout(location = 3) uniform vec2 iResolution; + + +out vec4 color; + +vec3 BuildRd() +{ + vec3 ro = CamPos; + vec3 ta = CamLookAt; + vec3 camUp = CamUp; + + // Calculate orthonormal camera reference system + vec3 camDir = normalize(ta-ro); // direction for center ray + vec3 camRight = normalize(cross(camDir,camUp)); + + vec2 coord =-1.0+2.0*gl_FragCoord.xy/iResolution.xy; + coord.x *= iResolution.x/iResolution.y; + + // Get direction for this pixel + vec3 rd = normalize(camDir + (coord.x*camRight + coord.y*camUp)) ; + + return rd; +} + +// Compute sky color +// d Ray direction +vec3 SkyShadeBlue(in vec3 d) +{ + // light direction + vec3 lig = normalize(vec3( 0.3,0.5, 0.6)); + float sun = 0.5*(dot(lig,d)+1.0); + vec3 color = vec3(0.35,0.45,0.75)*(0.75-0.25*d.z); + color += vec3(0.65,0.6,0.55)*pow( sun, 18.0 ); + return color; +} + +void main(void) +{ + vec3 rd = BuildRd(); + + color = vec4(SkyShadeBlue(rd),1.0); + + gl_FragDepth = 1.0; +} +#endif diff --git a/shaders/tex-mesh.glsl b/shaders/tex-mesh.glsl new file mode 100644 index 0000000..da8faca --- /dev/null +++ b/shaders/tex-mesh.glsl @@ -0,0 +1,34 @@ +#version 430 + +#ifdef VERTEX_SHADER +in vec3 a_position; + +uniform mat4 gl_ModelViewMatrix; +uniform mat4 gl_ProjectionMatrix; + +out vec3 worldPos; + +void main(void) +{ + mat4 MVP = gl_ProjectionMatrix * gl_ModelViewMatrix; + gl_Position = MVP * (vec4(a_position, 1.0)); + worldPos = a_position; +} +#endif + +#ifdef FRAGMENT_SHADER +uniform vec2 u_worldMin; +uniform vec2 u_worldSize; +uniform sampler2D u_texture; + +in vec3 worldPos; + +out vec4 fragment; + +void main() +{ + vec2 uv = (worldPos.xy - u_worldMin)/u_worldSize; + vec4 c = texture(u_texture, uv); + fragment = vec4(c.rgb, 1.0); +} +#endif