Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/render/intern/multires_bake.c')
-rw-r--r--source/blender/render/intern/multires_bake.c1501
1 files changed, 1501 insertions, 0 deletions
diff --git a/source/blender/render/intern/multires_bake.c b/source/blender/render/intern/multires_bake.c
new file mode 100644
index 00000000000..a4f68419c67
--- /dev/null
+++ b/source/blender/render/intern/multires_bake.c
@@ -0,0 +1,1501 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2012 by Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup render
+ */
+
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_threads.h"
+
+#include "BKE_ccg.h"
+#include "BKE_global.h"
+#include "BKE_image.h"
+#include "BKE_material.h"
+#include "BKE_mesh.h"
+#include "BKE_modifier.h"
+#include "BKE_multires.h"
+#include "BKE_subsurf.h"
+
+#include "DEG_depsgraph.h"
+
+#include "RE_multires_bake.h"
+#include "RE_pipeline.h"
+#include "RE_texture.h"
+
+#include "IMB_imbuf.h"
+#include "IMB_imbuf_types.h"
+
+typedef void (*MPassKnownData)(DerivedMesh *lores_dm,
+ DerivedMesh *hires_dm,
+ void *thread_data,
+ void *bake_data,
+ ImBuf *ibuf,
+ const int face_index,
+ const int lvl,
+ const float st[2],
+ float tangmat[3][3],
+ const int x,
+ const int y);
+
+typedef void *(*MInitBakeData)(MultiresBakeRender *bkr, Image *ima);
+typedef void (*MFreeBakeData)(void *bake_data);
+
+typedef struct MultiresBakeResult {
+ float height_min, height_max;
+} MultiresBakeResult;
+
+typedef struct {
+ MVert *mvert;
+ MPoly *mpoly;
+ MLoop *mloop;
+ MLoopUV *mloopuv;
+ const MLoopTri *mlooptri;
+ float *pvtangent;
+ const float *precomputed_normals;
+ int w, h;
+ int tri_index;
+ DerivedMesh *lores_dm, *hires_dm;
+ int lvl;
+ void *thread_data;
+ void *bake_data;
+ ImBuf *ibuf;
+ MPassKnownData pass_data;
+ /* material aligned UV array */
+ Image **image_array;
+} MResolvePixelData;
+
+typedef void (*MFlushPixel)(const MResolvePixelData *data, const int x, const int y);
+
+typedef struct {
+ int w, h;
+ char *texels;
+ const MResolvePixelData *data;
+ MFlushPixel flush_pixel;
+ short *do_update;
+} MBakeRast;
+
+typedef struct {
+ float *heights;
+ Image *ima;
+ DerivedMesh *ssdm;
+ const int *orig_index_mp_to_orig;
+} MHeightBakeData;
+
+typedef struct {
+ const int *orig_index_mp_to_orig;
+} MNormalBakeData;
+
+typedef struct BakeImBufuserData {
+ float *displacement_buffer;
+ char *mask_buffer;
+} BakeImBufuserData;
+
+static void multiresbake_get_normal(const MResolvePixelData *data,
+ float norm[],
+ const int tri_num,
+ const int vert_index)
+{
+ const int poly_index = data->mlooptri[tri_num].poly;
+ const MPoly *mp = &data->mpoly[poly_index];
+ const bool smoothnormal = (mp->flag & ME_SMOOTH) != 0;
+
+ if (!smoothnormal) { /* flat */
+ if (data->precomputed_normals) {
+ copy_v3_v3(norm, &data->precomputed_normals[poly_index]);
+ }
+ else {
+ BKE_mesh_calc_poly_normal(mp, &data->mloop[mp->loopstart], data->mvert, norm);
+ }
+ }
+ else {
+ const int vi = data->mloop[data->mlooptri[tri_num].tri[vert_index]].v;
+ const short *no = data->mvert[vi].no;
+
+ normal_short_to_float_v3(norm, no);
+ normalize_v3(norm);
+ }
+}
+
+static void init_bake_rast(MBakeRast *bake_rast,
+ const ImBuf *ibuf,
+ const MResolvePixelData *data,
+ MFlushPixel flush_pixel,
+ short *do_update)
+{
+ BakeImBufuserData *userdata = (BakeImBufuserData *)ibuf->userdata;
+
+ memset(bake_rast, 0, sizeof(MBakeRast));
+
+ bake_rast->texels = userdata->mask_buffer;
+ bake_rast->w = ibuf->x;
+ bake_rast->h = ibuf->y;
+ bake_rast->data = data;
+ bake_rast->flush_pixel = flush_pixel;
+ bake_rast->do_update = do_update;
+}
+
+static void flush_pixel(const MResolvePixelData *data, const int x, const int y)
+{
+ const float st[2] = {(x + 0.5f) / data->w, (y + 0.5f) / data->h};
+ const float *st0, *st1, *st2;
+ const float *tang0, *tang1, *tang2;
+ float no0[3], no1[3], no2[3];
+ float fUV[2], from_tang[3][3], to_tang[3][3];
+ float u, v, w, sign;
+ int r;
+
+ st0 = data->mloopuv[data->mlooptri[data->tri_index].tri[0]].uv;
+ st1 = data->mloopuv[data->mlooptri[data->tri_index].tri[1]].uv;
+ st2 = data->mloopuv[data->mlooptri[data->tri_index].tri[2]].uv;
+
+ multiresbake_get_normal(data, no0, data->tri_index, 0); /* can optimize these 3 into one call */
+ multiresbake_get_normal(data, no1, data->tri_index, 1);
+ multiresbake_get_normal(data, no2, data->tri_index, 2);
+
+ resolve_tri_uv_v2(fUV, st, st0, st1, st2);
+
+ u = fUV[0];
+ v = fUV[1];
+ w = 1 - u - v;
+
+ if (data->pvtangent) {
+ tang0 = data->pvtangent + data->mlooptri[data->tri_index].tri[0] * 4;
+ tang1 = data->pvtangent + data->mlooptri[data->tri_index].tri[1] * 4;
+ tang2 = data->pvtangent + data->mlooptri[data->tri_index].tri[2] * 4;
+
+ /* the sign is the same at all face vertices for any non degenerate face.
+ * Just in case we clamp the interpolated value though. */
+ sign = (tang0[3] * u + tang1[3] * v + tang2[3] * w) < 0 ? (-1.0f) : 1.0f;
+
+ /* this sequence of math is designed specifically as is with great care
+ * to be compatible with our shader. Please don't change without good reason. */
+ for (r = 0; r < 3; r++) {
+ from_tang[0][r] = tang0[r] * u + tang1[r] * v + tang2[r] * w;
+ from_tang[2][r] = no0[r] * u + no1[r] * v + no2[r] * w;
+ }
+
+ cross_v3_v3v3(from_tang[1], from_tang[2], from_tang[0]); /* B = sign * cross(N, T) */
+ mul_v3_fl(from_tang[1], sign);
+ invert_m3_m3(to_tang, from_tang);
+ }
+ else {
+ zero_m3(to_tang);
+ }
+
+ data->pass_data(data->lores_dm,
+ data->hires_dm,
+ data->thread_data,
+ data->bake_data,
+ data->ibuf,
+ data->tri_index,
+ data->lvl,
+ st,
+ to_tang,
+ x,
+ y);
+}
+
+static void set_rast_triangle(const MBakeRast *bake_rast, const int x, const int y)
+{
+ const int w = bake_rast->w;
+ const int h = bake_rast->h;
+
+ if (x >= 0 && x < w && y >= 0 && y < h) {
+ if ((bake_rast->texels[y * w + x]) == 0) {
+ bake_rast->texels[y * w + x] = FILTER_MASK_USED;
+ flush_pixel(bake_rast->data, x, y);
+ if (bake_rast->do_update) {
+ *bake_rast->do_update = true;
+ }
+ }
+ }
+}
+
+static void rasterize_half(const MBakeRast *bake_rast,
+ const float s0_s,
+ const float t0_s,
+ const float s1_s,
+ const float t1_s,
+ const float s0_l,
+ const float t0_l,
+ const float s1_l,
+ const float t1_l,
+ const int y0_in,
+ const int y1_in,
+ const int is_mid_right)
+{
+ const int s_stable = fabsf(t1_s - t0_s) > FLT_EPSILON ? 1 : 0;
+ const int l_stable = fabsf(t1_l - t0_l) > FLT_EPSILON ? 1 : 0;
+ const int w = bake_rast->w;
+ const int h = bake_rast->h;
+ int y, y0, y1;
+
+ if (y1_in <= 0 || y0_in >= h) {
+ return;
+ }
+
+ y0 = y0_in < 0 ? 0 : y0_in;
+ y1 = y1_in >= h ? h : y1_in;
+
+ for (y = y0; y < y1; y++) {
+ /*-b(x-x0) + a(y-y0) = 0 */
+ int iXl, iXr, x;
+ float x_l = s_stable != 0 ? (s0_s + (((s1_s - s0_s) * (y - t0_s)) / (t1_s - t0_s))) : s0_s;
+ float x_r = l_stable != 0 ? (s0_l + (((s1_l - s0_l) * (y - t0_l)) / (t1_l - t0_l))) : s0_l;
+
+ if (is_mid_right != 0) {
+ SWAP(float, x_l, x_r);
+ }
+
+ iXl = (int)ceilf(x_l);
+ iXr = (int)ceilf(x_r);
+
+ if (iXr > 0 && iXl < w) {
+ iXl = iXl < 0 ? 0 : iXl;
+ iXr = iXr >= w ? w : iXr;
+
+ for (x = iXl; x < iXr; x++) {
+ set_rast_triangle(bake_rast, x, y);
+ }
+ }
+ }
+}
+
+static void bake_rasterize(const MBakeRast *bake_rast,
+ const float st0_in[2],
+ const float st1_in[2],
+ const float st2_in[2])
+{
+ const int w = bake_rast->w;
+ const int h = bake_rast->h;
+ float slo = st0_in[0] * w - 0.5f;
+ float tlo = st0_in[1] * h - 0.5f;
+ float smi = st1_in[0] * w - 0.5f;
+ float tmi = st1_in[1] * h - 0.5f;
+ float shi = st2_in[0] * w - 0.5f;
+ float thi = st2_in[1] * h - 0.5f;
+ int is_mid_right = 0, ylo, yhi, yhi_beg;
+
+ /* skip degenerates */
+ if ((slo == smi && tlo == tmi) || (slo == shi && tlo == thi) || (smi == shi && tmi == thi)) {
+ return;
+ }
+
+ /* sort by T */
+ if (tlo > tmi && tlo > thi) {
+ SWAP(float, shi, slo);
+ SWAP(float, thi, tlo);
+ }
+ else if (tmi > thi) {
+ SWAP(float, shi, smi);
+ SWAP(float, thi, tmi);
+ }
+
+ if (tlo > tmi) {
+ SWAP(float, slo, smi);
+ SWAP(float, tlo, tmi);
+ }
+
+ /* check if mid point is to the left or to the right of the lo-hi edge */
+ is_mid_right = (-(shi - slo) * (tmi - thi) + (thi - tlo) * (smi - shi)) > 0 ? 1 : 0;
+ ylo = (int)ceilf(tlo);
+ yhi_beg = (int)ceilf(tmi);
+ yhi = (int)ceilf(thi);
+
+ /*if (fTmi>ceilf(fTlo))*/
+ rasterize_half(bake_rast, slo, tlo, smi, tmi, slo, tlo, shi, thi, ylo, yhi_beg, is_mid_right);
+ rasterize_half(bake_rast, smi, tmi, shi, thi, slo, tlo, shi, thi, yhi_beg, yhi, is_mid_right);
+}
+
+static int multiresbake_test_break(MultiresBakeRender *bkr)
+{
+ if (!bkr->stop) {
+ /* this means baker is executed outside from job system */
+ return 0;
+ }
+
+ return *bkr->stop || G.is_break;
+}
+
+/* **** Threading routines **** */
+
+typedef struct MultiresBakeQueue {
+ int cur_tri;
+ int tot_tri;
+ SpinLock spin;
+} MultiresBakeQueue;
+
+typedef struct MultiresBakeThread {
+ /* this data is actually shared between all the threads */
+ MultiresBakeQueue *queue;
+ MultiresBakeRender *bkr;
+ Image *image;
+ void *bake_data;
+
+ /* thread-specific data */
+ MBakeRast bake_rast;
+ MResolvePixelData data;
+
+ /* displacement-specific data */
+ float height_min, height_max;
+} MultiresBakeThread;
+
+static int multires_bake_queue_next_tri(MultiresBakeQueue *queue)
+{
+ int face = -1;
+
+ /* TODO: it could worth making it so thread will handle neighbor faces
+ * for better memory cache utilization
+ */
+
+ BLI_spin_lock(&queue->spin);
+ if (queue->cur_tri < queue->tot_tri) {
+ face = queue->cur_tri;
+ queue->cur_tri++;
+ }
+ BLI_spin_unlock(&queue->spin);
+
+ return face;
+}
+
+static void *do_multires_bake_thread(void *data_v)
+{
+ MultiresBakeThread *handle = (MultiresBakeThread *)data_v;
+ MResolvePixelData *data = &handle->data;
+ MBakeRast *bake_rast = &handle->bake_rast;
+ MultiresBakeRender *bkr = handle->bkr;
+ int tri_index;
+
+ while ((tri_index = multires_bake_queue_next_tri(handle->queue)) >= 0) {
+ const MLoopTri *lt = &data->mlooptri[tri_index];
+ const MPoly *mp = &data->mpoly[lt->poly];
+ const short mat_nr = mp->mat_nr;
+ const MLoopUV *mloopuv = data->mloopuv;
+
+ if (multiresbake_test_break(bkr)) {
+ break;
+ }
+
+ Image *tri_image = mat_nr < bkr->ob_image.len ? bkr->ob_image.array[mat_nr] : NULL;
+ if (tri_image != handle->image) {
+ continue;
+ }
+
+ data->tri_index = tri_index;
+
+ bake_rasterize(
+ bake_rast, mloopuv[lt->tri[0]].uv, mloopuv[lt->tri[1]].uv, mloopuv[lt->tri[2]].uv);
+
+ /* tag image buffer for refresh */
+ if (data->ibuf->rect_float) {
+ data->ibuf->userflags |= IB_RECT_INVALID;
+ }
+
+ data->ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID;
+
+ /* update progress */
+ BLI_spin_lock(&handle->queue->spin);
+ bkr->baked_faces++;
+
+ if (bkr->do_update) {
+ *bkr->do_update = true;
+ }
+
+ if (bkr->progress) {
+ *bkr->progress = ((float)bkr->baked_objects +
+ (float)bkr->baked_faces / handle->queue->tot_tri) /
+ bkr->tot_obj;
+ }
+ BLI_spin_unlock(&handle->queue->spin);
+ }
+
+ return NULL;
+}
+
+/* some of arrays inside ccgdm are lazy-initialized, which will generally
+ * require lock around accessing such data
+ * this function will ensure all arrays are allocated before threading started
+ */
+static void init_ccgdm_arrays(DerivedMesh *dm)
+{
+ CCGElem **grid_data;
+ CCGKey key;
+ int grid_size;
+ const int *grid_offset;
+
+ grid_size = dm->getGridSize(dm);
+ grid_data = dm->getGridData(dm);
+ grid_offset = dm->getGridOffset(dm);
+ dm->getGridKey(dm, &key);
+
+ (void)grid_size;
+ (void)grid_data;
+ (void)grid_offset;
+}
+
+static void do_multires_bake(MultiresBakeRender *bkr,
+ Image *ima,
+ bool require_tangent,
+ MPassKnownData passKnownData,
+ MInitBakeData initBakeData,
+ MFreeBakeData freeBakeData,
+ MultiresBakeResult *result)
+{
+ DerivedMesh *dm = bkr->lores_dm;
+ const MLoopTri *mlooptri = dm->getLoopTriArray(dm);
+ const int lvl = bkr->lvl;
+ int tot_tri = dm->getNumLoopTri(dm);
+
+ if (tot_tri > 0) {
+ MultiresBakeThread *handles;
+ MultiresBakeQueue queue;
+
+ ImBuf *ibuf = BKE_image_acquire_ibuf(ima, NULL, NULL);
+ MVert *mvert = dm->getVertArray(dm);
+ MPoly *mpoly = dm->getPolyArray(dm);
+ MLoop *mloop = dm->getLoopArray(dm);
+ MLoopUV *mloopuv = dm->getLoopDataArray(dm, CD_MLOOPUV);
+ const float *precomputed_normals = dm->getPolyDataArray(dm, CD_NORMAL);
+ float *pvtangent = NULL;
+
+ ListBase threads;
+ int i, tot_thread = bkr->threads > 0 ? bkr->threads : BLI_system_thread_count();
+
+ void *bake_data = NULL;
+
+ if (require_tangent) {
+ if (CustomData_get_layer_index(&dm->loopData, CD_TANGENT) == -1) {
+ DM_calc_loop_tangents(dm, true, NULL, 0);
+ }
+
+ pvtangent = DM_get_loop_data_layer(dm, CD_TANGENT);
+ }
+
+ /* all threads shares the same custom bake data */
+ if (initBakeData) {
+ bake_data = initBakeData(bkr, ima);
+ }
+
+ if (tot_thread > 1) {
+ BLI_threadpool_init(&threads, do_multires_bake_thread, tot_thread);
+ }
+
+ handles = MEM_callocN(tot_thread * sizeof(MultiresBakeThread), "do_multires_bake handles");
+
+ init_ccgdm_arrays(bkr->hires_dm);
+
+ /* faces queue */
+ queue.cur_tri = 0;
+ queue.tot_tri = tot_tri;
+ BLI_spin_init(&queue.spin);
+
+ /* fill in threads handles */
+ for (i = 0; i < tot_thread; i++) {
+ MultiresBakeThread *handle = &handles[i];
+
+ handle->bkr = bkr;
+ handle->image = ima;
+ handle->queue = &queue;
+
+ handle->data.mpoly = mpoly;
+ handle->data.mvert = mvert;
+ handle->data.mloopuv = mloopuv;
+ handle->data.mlooptri = mlooptri;
+ handle->data.mloop = mloop;
+ handle->data.pvtangent = pvtangent;
+ handle->data.precomputed_normals = precomputed_normals; /* don't strictly need this */
+ handle->data.w = ibuf->x;
+ handle->data.h = ibuf->y;
+ handle->data.lores_dm = dm;
+ handle->data.hires_dm = bkr->hires_dm;
+ handle->data.lvl = lvl;
+ handle->data.pass_data = passKnownData;
+ handle->data.thread_data = handle;
+ handle->data.bake_data = bake_data;
+ handle->data.ibuf = ibuf;
+
+ handle->height_min = FLT_MAX;
+ handle->height_max = -FLT_MAX;
+
+ init_bake_rast(&handle->bake_rast, ibuf, &handle->data, flush_pixel, bkr->do_update);
+
+ if (tot_thread > 1) {
+ BLI_threadpool_insert(&threads, handle);
+ }
+ }
+
+ /* run threads */
+ if (tot_thread > 1) {
+ BLI_threadpool_end(&threads);
+ }
+ else {
+ do_multires_bake_thread(&handles[0]);
+ }
+
+ /* construct bake result */
+ result->height_min = handles[0].height_min;
+ result->height_max = handles[0].height_max;
+
+ for (i = 1; i < tot_thread; i++) {
+ result->height_min = min_ff(result->height_min, handles[i].height_min);
+ result->height_max = max_ff(result->height_max, handles[i].height_max);
+ }
+
+ BLI_spin_end(&queue.spin);
+
+ /* finalize baking */
+ if (freeBakeData) {
+ freeBakeData(bake_data);
+ }
+
+ MEM_freeN(handles);
+
+ BKE_image_release_ibuf(ima, ibuf, NULL);
+ }
+}
+
+/* mode = 0: interpolate normals,
+ * mode = 1: interpolate coord */
+static void interp_bilinear_grid(
+ CCGKey *key, CCGElem *grid, float crn_x, float crn_y, int mode, float res[3])
+{
+ int x0, x1, y0, y1;
+ float u, v;
+ float data[4][3];
+
+ x0 = (int)crn_x;
+ x1 = x0 >= (key->grid_size - 1) ? (key->grid_size - 1) : (x0 + 1);
+
+ y0 = (int)crn_y;
+ y1 = y0 >= (key->grid_size - 1) ? (key->grid_size - 1) : (y0 + 1);
+
+ u = crn_x - x0;
+ v = crn_y - y0;
+
+ if (mode == 0) {
+ copy_v3_v3(data[0], CCG_grid_elem_no(key, grid, x0, y0));
+ copy_v3_v3(data[1], CCG_grid_elem_no(key, grid, x1, y0));
+ copy_v3_v3(data[2], CCG_grid_elem_no(key, grid, x1, y1));
+ copy_v3_v3(data[3], CCG_grid_elem_no(key, grid, x0, y1));
+ }
+ else {
+ copy_v3_v3(data[0], CCG_grid_elem_co(key, grid, x0, y0));
+ copy_v3_v3(data[1], CCG_grid_elem_co(key, grid, x1, y0));
+ copy_v3_v3(data[2], CCG_grid_elem_co(key, grid, x1, y1));
+ copy_v3_v3(data[3], CCG_grid_elem_co(key, grid, x0, y1));
+ }
+
+ interp_bilinear_quad_v3(data, u, v, res);
+}
+
+static void get_ccgdm_data(DerivedMesh *lodm,
+ DerivedMesh *hidm,
+ const int *index_mp_to_orig,
+ const int lvl,
+ const MLoopTri *lt,
+ const float u,
+ const float v,
+ float co[3],
+ float n[3])
+{
+ CCGElem **grid_data;
+ CCGKey key;
+ float crn_x, crn_y;
+ int grid_size, S, face_side;
+ int *grid_offset, g_index;
+ int poly_index = lt->poly;
+
+ grid_size = hidm->getGridSize(hidm);
+ grid_data = hidm->getGridData(hidm);
+ grid_offset = hidm->getGridOffset(hidm);
+ hidm->getGridKey(hidm, &key);
+
+ if (lvl == 0) {
+ MPoly *mpoly;
+ face_side = (grid_size << 1) - 1;
+
+ mpoly = lodm->getPolyArray(lodm) + poly_index;
+ g_index = grid_offset[poly_index];
+ S = mdisp_rot_face_to_crn(lodm->getVertArray(lodm),
+ mpoly,
+ lodm->getLoopArray(lodm),
+ lt,
+ face_side,
+ u * (face_side - 1),
+ v * (face_side - 1),
+ &crn_x,
+ &crn_y);
+ }
+ else {
+ /* number of faces per grid side */
+ int polys_per_grid_side = (1 << (lvl - 1));
+ /* get the original cage face index */
+ int cage_face_index = index_mp_to_orig ? index_mp_to_orig[poly_index] : poly_index;
+ /* local offset in total cage face grids
+ * (1 << (2 * lvl)) is number of all polys for one cage face */
+ int loc_cage_poly_offs = poly_index % (1 << (2 * lvl));
+ /* local offset in the vertex grid itself */
+ int cell_index = loc_cage_poly_offs % (polys_per_grid_side * polys_per_grid_side);
+ int cell_side = (grid_size - 1) / polys_per_grid_side;
+ /* row and column based on grid side */
+ int row = cell_index / polys_per_grid_side;
+ int col = cell_index % polys_per_grid_side;
+
+ /* S is the vertex whose grid we are examining */
+ S = poly_index / (1 << (2 * (lvl - 1))) - grid_offset[cage_face_index];
+ /* get offset of grid data for original cage face */
+ g_index = grid_offset[cage_face_index];
+
+ crn_y = (row * cell_side) + u * cell_side;
+ crn_x = (col * cell_side) + v * cell_side;
+ }
+
+ CLAMP(crn_x, 0.0f, grid_size);
+ CLAMP(crn_y, 0.0f, grid_size);
+
+ if (n != NULL) {
+ interp_bilinear_grid(&key, grid_data[g_index + S], crn_x, crn_y, 0, n);
+ }
+
+ if (co != NULL) {
+ interp_bilinear_grid(&key, grid_data[g_index + S], crn_x, crn_y, 1, co);
+ }
+}
+
+/* mode = 0: interpolate normals,
+ * mode = 1: interpolate coord */
+
+static void interp_bilinear_mpoly(DerivedMesh *dm,
+ MLoop *mloop,
+ MPoly *mpoly,
+ const float u,
+ const float v,
+ const int mode,
+ float res[3])
+{
+ float data[4][3];
+
+ if (mode == 0) {
+ dm->getVertNo(dm, mloop[mpoly->loopstart].v, data[0]);
+ dm->getVertNo(dm, mloop[mpoly->loopstart + 1].v, data[1]);
+ dm->getVertNo(dm, mloop[mpoly->loopstart + 2].v, data[2]);
+ dm->getVertNo(dm, mloop[mpoly->loopstart + 3].v, data[3]);
+ }
+ else {
+ dm->getVertCo(dm, mloop[mpoly->loopstart].v, data[0]);
+ dm->getVertCo(dm, mloop[mpoly->loopstart + 1].v, data[1]);
+ dm->getVertCo(dm, mloop[mpoly->loopstart + 2].v, data[2]);
+ dm->getVertCo(dm, mloop[mpoly->loopstart + 3].v, data[3]);
+ }
+
+ interp_bilinear_quad_v3(data, u, v, res);
+}
+
+static void interp_barycentric_mlooptri(DerivedMesh *dm,
+ MLoop *mloop,
+ const MLoopTri *lt,
+ const float u,
+ const float v,
+ const int mode,
+ float res[3])
+{
+ float data[3][3];
+
+ if (mode == 0) {
+ dm->getVertNo(dm, mloop[lt->tri[0]].v, data[0]);
+ dm->getVertNo(dm, mloop[lt->tri[1]].v, data[1]);
+ dm->getVertNo(dm, mloop[lt->tri[2]].v, data[2]);
+ }
+ else {
+ dm->getVertCo(dm, mloop[lt->tri[0]].v, data[0]);
+ dm->getVertCo(dm, mloop[lt->tri[1]].v, data[1]);
+ dm->getVertCo(dm, mloop[lt->tri[2]].v, data[2]);
+ }
+
+ interp_barycentric_tri_v3(data, u, v, res);
+}
+
+/* **************** Displacement Baker **************** */
+
+static void *init_heights_data(MultiresBakeRender *bkr, Image *ima)
+{
+ MHeightBakeData *height_data;
+ ImBuf *ibuf = BKE_image_acquire_ibuf(ima, NULL, NULL);
+ DerivedMesh *lodm = bkr->lores_dm;
+ BakeImBufuserData *userdata = ibuf->userdata;
+
+ if (userdata->displacement_buffer == NULL) {
+ userdata->displacement_buffer = MEM_callocN(sizeof(float) * ibuf->x * ibuf->y,
+ "MultiresBake heights");
+ }
+
+ height_data = MEM_callocN(sizeof(MHeightBakeData), "MultiresBake heightData");
+
+ height_data->ima = ima;
+ height_data->heights = userdata->displacement_buffer;
+
+ if (!bkr->use_lores_mesh) {
+ SubsurfModifierData smd = {{NULL}};
+ int ss_lvl = bkr->tot_lvl - bkr->lvl;
+
+ CLAMP(ss_lvl, 0, 6);
+
+ if (ss_lvl > 0) {
+ smd.levels = smd.renderLevels = ss_lvl;
+ smd.uv_smooth = SUBSURF_UV_SMOOTH_PRESERVE_CORNERS;
+ smd.quality = 3;
+
+ height_data->ssdm = subsurf_make_derived_from_derived(
+ bkr->lores_dm, &smd, bkr->scene, NULL, 0);
+ init_ccgdm_arrays(height_data->ssdm);
+ }
+ }
+
+ height_data->orig_index_mp_to_orig = lodm->getPolyDataArray(lodm, CD_ORIGINDEX);
+
+ BKE_image_release_ibuf(ima, ibuf, NULL);
+
+ return (void *)height_data;
+}
+
+static void free_heights_data(void *bake_data)
+{
+ MHeightBakeData *height_data = (MHeightBakeData *)bake_data;
+
+ if (height_data->ssdm) {
+ height_data->ssdm->release(height_data->ssdm);
+ }
+
+ MEM_freeN(height_data);
+}
+
+/* MultiresBake callback for heights baking
+ * general idea:
+ * - find coord of point with specified UV in hi-res mesh (let's call it p1)
+ * - find coord of point and normal with specified UV in lo-res mesh (or subdivided lo-res
+ * mesh to make texture smoother) let's call this point p0 and n.
+ * - height wound be dot(n, p1-p0) */
+static void apply_heights_callback(DerivedMesh *lores_dm,
+ DerivedMesh *hires_dm,
+ void *thread_data_v,
+ void *bake_data,
+ ImBuf *ibuf,
+ const int tri_index,
+ const int lvl,
+ const float st[2],
+ float UNUSED(tangmat[3][3]),
+ const int x,
+ const int y)
+{
+ const MLoopTri *lt = lores_dm->getLoopTriArray(lores_dm) + tri_index;
+ MLoop *mloop = lores_dm->getLoopArray(lores_dm);
+ MPoly *mpoly = lores_dm->getPolyArray(lores_dm) + lt->poly;
+ MLoopUV *mloopuv = lores_dm->getLoopDataArray(lores_dm, CD_MLOOPUV);
+ MHeightBakeData *height_data = (MHeightBakeData *)bake_data;
+ MultiresBakeThread *thread_data = (MultiresBakeThread *)thread_data_v;
+ float uv[2], *st0, *st1, *st2, *st3;
+ int pixel = ibuf->x * y + x;
+ float vec[3], p0[3], p1[3], n[3], len;
+
+ /* ideally we would work on triangles only, however, we rely on quads to get orthogonal
+ * coordinates for use in grid space (triangle barycentric is not orthogonal) */
+ if (mpoly->totloop == 4) {
+ st0 = mloopuv[mpoly->loopstart].uv;
+ st1 = mloopuv[mpoly->loopstart + 1].uv;
+ st2 = mloopuv[mpoly->loopstart + 2].uv;
+ st3 = mloopuv[mpoly->loopstart + 3].uv;
+ resolve_quad_uv_v2(uv, st, st0, st1, st2, st3);
+ }
+ else {
+ st0 = mloopuv[lt->tri[0]].uv;
+ st1 = mloopuv[lt->tri[1]].uv;
+ st2 = mloopuv[lt->tri[2]].uv;
+ resolve_tri_uv_v2(uv, st, st0, st1, st2);
+ }
+
+ clamp_v2(uv, 0.0f, 1.0f);
+
+ get_ccgdm_data(
+ lores_dm, hires_dm, height_data->orig_index_mp_to_orig, lvl, lt, uv[0], uv[1], p1, NULL);
+
+ if (height_data->ssdm) {
+ get_ccgdm_data(lores_dm,
+ height_data->ssdm,
+ height_data->orig_index_mp_to_orig,
+ 0,
+ lt,
+ uv[0],
+ uv[1],
+ p0,
+ n);
+ }
+ else {
+ if (mpoly->totloop == 4) {
+ interp_bilinear_mpoly(lores_dm, mloop, mpoly, uv[0], uv[1], 1, p0);
+ interp_bilinear_mpoly(lores_dm, mloop, mpoly, uv[0], uv[1], 0, n);
+ }
+ else {
+ interp_barycentric_mlooptri(lores_dm, mloop, lt, uv[0], uv[1], 1, p0);
+ interp_barycentric_mlooptri(lores_dm, mloop, lt, uv[0], uv[1], 0, n);
+ }
+ }
+
+ sub_v3_v3v3(vec, p1, p0);
+ len = dot_v3v3(n, vec);
+
+ height_data->heights[pixel] = len;
+
+ thread_data->height_min = min_ff(thread_data->height_min, len);
+ thread_data->height_max = max_ff(thread_data->height_max, len);
+
+ if (ibuf->rect_float) {
+ float *rrgbf = ibuf->rect_float + pixel * 4;
+ rrgbf[0] = rrgbf[1] = rrgbf[2] = len;
+ rrgbf[3] = 1.0f;
+ }
+ else {
+ char *rrgb = (char *)ibuf->rect + pixel * 4;
+ rrgb[0] = rrgb[1] = rrgb[2] = unit_float_to_uchar_clamp(len);
+ rrgb[3] = 255;
+ }
+}
+
+/* **************** Normal Maps Baker **************** */
+
+static void *init_normal_data(MultiresBakeRender *bkr, Image *UNUSED(ima))
+{
+ MNormalBakeData *normal_data;
+ DerivedMesh *lodm = bkr->lores_dm;
+
+ normal_data = MEM_callocN(sizeof(MNormalBakeData), "MultiresBake normalData");
+
+ normal_data->orig_index_mp_to_orig = lodm->getPolyDataArray(lodm, CD_ORIGINDEX);
+
+ return (void *)normal_data;
+}
+
+static void free_normal_data(void *bake_data)
+{
+ MNormalBakeData *normal_data = (MNormalBakeData *)bake_data;
+
+ MEM_freeN(normal_data);
+}
+
+/**
+ * MultiresBake callback for normals' baking.
+ *
+ * General idea:
+ * - Find coord and normal of point with specified UV in hi-res mesh.
+ * - Multiply it by tangmat.
+ * - Vector in color space would be `norm(vec) / 2 + (0.5, 0.5, 0.5)`.
+ */
+static void apply_tangmat_callback(DerivedMesh *lores_dm,
+ DerivedMesh *hires_dm,
+ void *UNUSED(thread_data),
+ void *bake_data,
+ ImBuf *ibuf,
+ const int tri_index,
+ const int lvl,
+ const float st[2],
+ float tangmat[3][3],
+ const int x,
+ const int y)
+{
+ const MLoopTri *lt = lores_dm->getLoopTriArray(lores_dm) + tri_index;
+ MPoly *mpoly = lores_dm->getPolyArray(lores_dm) + lt->poly;
+ MLoopUV *mloopuv = lores_dm->getLoopDataArray(lores_dm, CD_MLOOPUV);
+ MNormalBakeData *normal_data = (MNormalBakeData *)bake_data;
+ float uv[2], *st0, *st1, *st2, *st3;
+ int pixel = ibuf->x * y + x;
+ float n[3], vec[3], tmp[3] = {0.5, 0.5, 0.5};
+
+ /* ideally we would work on triangles only, however, we rely on quads to get orthogonal
+ * coordinates for use in grid space (triangle barycentric is not orthogonal) */
+ if (mpoly->totloop == 4) {
+ st0 = mloopuv[mpoly->loopstart].uv;
+ st1 = mloopuv[mpoly->loopstart + 1].uv;
+ st2 = mloopuv[mpoly->loopstart + 2].uv;
+ st3 = mloopuv[mpoly->loopstart + 3].uv;
+ resolve_quad_uv_v2(uv, st, st0, st1, st2, st3);
+ }
+ else {
+ st0 = mloopuv[lt->tri[0]].uv;
+ st1 = mloopuv[lt->tri[1]].uv;
+ st2 = mloopuv[lt->tri[2]].uv;
+ resolve_tri_uv_v2(uv, st, st0, st1, st2);
+ }
+
+ clamp_v2(uv, 0.0f, 1.0f);
+
+ get_ccgdm_data(
+ lores_dm, hires_dm, normal_data->orig_index_mp_to_orig, lvl, lt, uv[0], uv[1], NULL, n);
+
+ mul_v3_m3v3(vec, tangmat, n);
+ normalize_v3_length(vec, 0.5);
+ add_v3_v3(vec, tmp);
+
+ if (ibuf->rect_float) {
+ float *rrgbf = ibuf->rect_float + pixel * 4;
+ rrgbf[0] = vec[0];
+ rrgbf[1] = vec[1];
+ rrgbf[2] = vec[2];
+ rrgbf[3] = 1.0f;
+ }
+ else {
+ unsigned char *rrgb = (unsigned char *)ibuf->rect + pixel * 4;
+ rgb_float_to_uchar(rrgb, vec);
+ rrgb[3] = 255;
+ }
+}
+
+/* TODO: restore ambient occlusion baking support, using BLI BVH? */
+#if 0
+/* **************** Ambient Occlusion Baker **************** */
+
+// must be a power of two
+# define MAX_NUMBER_OF_AO_RAYS 1024
+
+static unsigned short ao_random_table_1[MAX_NUMBER_OF_AO_RAYS];
+static unsigned short ao_random_table_2[MAX_NUMBER_OF_AO_RAYS];
+
+static void init_ao_random(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_NUMBER_OF_AO_RAYS; i++) {
+ ao_random_table_1[i] = rand() & 0xffff;
+ ao_random_table_2[i] = rand() & 0xffff;
+ }
+}
+
+static unsigned short get_ao_random1(const int i)
+{
+ return ao_random_table_1[i & (MAX_NUMBER_OF_AO_RAYS - 1)];
+}
+
+static unsigned short get_ao_random2(const int i)
+{
+ return ao_random_table_2[i & (MAX_NUMBER_OF_AO_RAYS - 1)];
+}
+
+static void build_permutation_table(unsigned short permutation[],
+ unsigned short temp_permutation[],
+ const int number_of_rays,
+ const int is_first_perm_table)
+{
+ int i, k;
+
+ for (i = 0; i < number_of_rays; i++) {
+ temp_permutation[i] = i;
+ }
+
+ for (i = 0; i < number_of_rays; i++) {
+ const unsigned int nr_entries_left = number_of_rays - i;
+ unsigned short rnd = is_first_perm_table != false ? get_ao_random1(i) : get_ao_random2(i);
+ const unsigned short entry = rnd % nr_entries_left;
+
+ /* pull entry */
+ permutation[i] = temp_permutation[entry];
+
+ /* delete entry */
+ for (k = entry; k < nr_entries_left - 1; k++) {
+ temp_permutation[k] = temp_permutation[k + 1];
+ }
+ }
+
+ /* verify permutation table
+ * every entry must appear exactly once
+ */
+# if 0
+ for (i = 0; i < number_of_rays; i++) temp_permutation[i] = 0;
+ for (i = 0; i < number_of_rays; i++) ++temp_permutation[permutation[i]];
+ for (i = 0; i < number_of_rays; i++) BLI_assert(temp_permutation[i] == 1);
+# endif
+}
+
+static void create_ao_raytree(MultiresBakeRender *bkr, MAOBakeData *ao_data)
+{
+ DerivedMesh *hidm = bkr->hires_dm;
+ RayObject *raytree;
+ RayFace *face;
+ CCGElem **grid_data;
+ CCGKey key;
+ int num_grids, grid_size /*, face_side */, num_faces;
+ int i;
+
+ num_grids = hidm->getNumGrids(hidm);
+ grid_size = hidm->getGridSize(hidm);
+ grid_data = hidm->getGridData(hidm);
+ hidm->getGridKey(hidm, &key);
+
+ /* face_side = (grid_size << 1) - 1; */ /* UNUSED */
+ num_faces = num_grids * (grid_size - 1) * (grid_size - 1);
+
+ raytree = ao_data->raytree = RE_rayobject_create(
+ bkr->raytrace_structure, num_faces, bkr->octree_resolution);
+ face = ao_data->rayfaces = (RayFace *)MEM_callocN(num_faces * sizeof(RayFace),
+ "ObjectRen faces");
+
+ for (i = 0; i < num_grids; i++) {
+ int x, y;
+ for (x = 0; x < grid_size - 1; x++) {
+ for (y = 0; y < grid_size - 1; y++) {
+ float co[4][3];
+
+ copy_v3_v3(co[0], CCG_grid_elem_co(&key, grid_data[i], x, y));
+ copy_v3_v3(co[1], CCG_grid_elem_co(&key, grid_data[i], x, y + 1));
+ copy_v3_v3(co[2], CCG_grid_elem_co(&key, grid_data[i], x + 1, y + 1));
+ copy_v3_v3(co[3], CCG_grid_elem_co(&key, grid_data[i], x + 1, y));
+
+ RE_rayface_from_coords(face, ao_data, face, co[0], co[1], co[2], co[3]);
+ RE_rayobject_add(raytree, RE_rayobject_unalignRayFace(face));
+
+ face++;
+ }
+ }
+ }
+
+ RE_rayobject_done(raytree);
+}
+
+static void *init_ao_data(MultiresBakeRender *bkr, Image *UNUSED(ima))
+{
+ MAOBakeData *ao_data;
+ DerivedMesh *lodm = bkr->lores_dm;
+ unsigned short *temp_permutation_table;
+ size_t permutation_size;
+
+ init_ao_random();
+
+ ao_data = MEM_callocN(sizeof(MAOBakeData), "MultiresBake aoData");
+
+ ao_data->number_of_rays = bkr->number_of_rays;
+ ao_data->bias = bkr->bias;
+
+ ao_data->orig_index_mp_to_orig = lodm->getPolyDataArray(lodm, CD_ORIGINDEX);
+
+ create_ao_raytree(bkr, ao_data);
+
+ /* initialize permutation tables */
+ permutation_size = sizeof(unsigned short) * bkr->number_of_rays;
+ ao_data->permutation_table_1 = MEM_callocN(permutation_size, "multires AO baker perm1");
+ ao_data->permutation_table_2 = MEM_callocN(permutation_size, "multires AO baker perm2");
+ temp_permutation_table = MEM_callocN(permutation_size, "multires AO baker temp perm");
+
+ build_permutation_table(
+ ao_data->permutation_table_1, temp_permutation_table, bkr->number_of_rays, 1);
+ build_permutation_table(
+ ao_data->permutation_table_2, temp_permutation_table, bkr->number_of_rays, 0);
+
+ MEM_freeN(temp_permutation_table);
+
+ return (void *)ao_data;
+}
+
+static void free_ao_data(void *bake_data)
+{
+ MAOBakeData *ao_data = (MAOBakeData *)bake_data;
+
+ RE_rayobject_free(ao_data->raytree);
+ MEM_freeN(ao_data->rayfaces);
+
+ MEM_freeN(ao_data->permutation_table_1);
+ MEM_freeN(ao_data->permutation_table_2);
+
+ MEM_freeN(ao_data);
+}
+
+/* builds an X and a Y axis from the given Z axis */
+static void build_coordinate_frame(float axisX[3], float axisY[3], const float axisZ[3])
+{
+ const float faX = fabsf(axisZ[0]);
+ const float faY = fabsf(axisZ[1]);
+ const float faZ = fabsf(axisZ[2]);
+
+ if (faX <= faY && faX <= faZ) {
+ const float len = sqrtf(axisZ[1] * axisZ[1] + axisZ[2] * axisZ[2]);
+ axisY[0] = 0;
+ axisY[1] = axisZ[2] / len;
+ axisY[2] = -axisZ[1] / len;
+ cross_v3_v3v3(axisX, axisY, axisZ);
+ }
+ else if (faY <= faZ) {
+ const float len = sqrtf(axisZ[0] * axisZ[0] + axisZ[2] * axisZ[2]);
+ axisX[0] = axisZ[2] / len;
+ axisX[1] = 0;
+ axisX[2] = -axisZ[0] / len;
+ cross_v3_v3v3(axisY, axisZ, axisX);
+ }
+ else {
+ const float len = sqrtf(axisZ[0] * axisZ[0] + axisZ[1] * axisZ[1]);
+ axisX[0] = axisZ[1] / len;
+ axisX[1] = -axisZ[0] / len;
+ axisX[2] = 0;
+ cross_v3_v3v3(axisY, axisZ, axisX);
+ }
+}
+
+/* return false if nothing was hit and true otherwise */
+static int trace_ao_ray(MAOBakeData *ao_data, float ray_start[3], float ray_direction[3])
+{
+ Isect isect = {{0}};
+
+ isect.dist = RE_RAYTRACE_MAXDIST;
+ copy_v3_v3(isect.start, ray_start);
+ copy_v3_v3(isect.dir, ray_direction);
+ isect.lay = -1;
+
+ normalize_v3(isect.dir);
+
+ return RE_rayobject_raycast(ao_data->raytree, &isect);
+}
+
+static void apply_ao_callback(DerivedMesh *lores_dm,
+ DerivedMesh *hires_dm,
+ void *UNUSED(thread_data),
+ void *bake_data,
+ ImBuf *ibuf,
+ const int tri_index,
+ const int lvl,
+ const float st[2],
+ float UNUSED(tangmat[3][3]),
+ const int x,
+ const int y)
+{
+ const MLoopTri *lt = lores_dm->getLoopTriArray(lores_dm) + tri_index;
+ MPoly *mpoly = lores_dm->getPolyArray(lores_dm) + lt->poly;
+ MLoopUV *mloopuv = lores_dm->getLoopDataArray(lores_dm, CD_MLOOPUV);
+ MAOBakeData *ao_data = (MAOBakeData *)bake_data;
+
+ int i, k, perm_offs;
+ float pos[3], nrm[3];
+ float cen[3];
+ float axisX[3], axisY[3], axisZ[3];
+ float shadow = 0;
+ float value;
+ int pixel = ibuf->x * y + x;
+ float uv[2], *st0, *st1, *st2, *st3;
+
+ /* ideally we would work on triangles only, however, we rely on quads to get orthogonal
+ * coordinates for use in grid space (triangle barycentric is not orthogonal) */
+ if (mpoly->totloop == 4) {
+ st0 = mloopuv[mpoly->loopstart].uv;
+ st1 = mloopuv[mpoly->loopstart + 1].uv;
+ st2 = mloopuv[mpoly->loopstart + 2].uv;
+ st3 = mloopuv[mpoly->loopstart + 3].uv;
+ resolve_quad_uv_v2(uv, st, st0, st1, st2, st3);
+ }
+ else {
+ st0 = mloopuv[lt->tri[0]].uv;
+ st1 = mloopuv[lt->tri[1]].uv;
+ st2 = mloopuv[lt->tri[2]].uv;
+ resolve_tri_uv_v2(uv, st, st0, st1, st2);
+ }
+
+ clamp_v2(uv, 0.0f, 1.0f);
+
+ get_ccgdm_data(
+ lores_dm, hires_dm, ao_data->orig_index_mp_to_orig, lvl, lt, uv[0], uv[1], pos, nrm);
+
+ /* offset ray origin by user bias along normal */
+ for (i = 0; i < 3; i++) {
+ cen[i] = pos[i] + ao_data->bias * nrm[i];
+ }
+
+ /* build tangent frame */
+ for (i = 0; i < 3; i++) {
+ axisZ[i] = nrm[i];
+ }
+
+ build_coordinate_frame(axisX, axisY, axisZ);
+
+ /* static noise */
+ perm_offs = (get_ao_random2(get_ao_random1(x) + y)) & (MAX_NUMBER_OF_AO_RAYS - 1);
+
+ /* importance sample shadow rays (cosine weighted) */
+ for (i = 0; i < ao_data->number_of_rays; i++) {
+ int hit_something;
+
+ /* use N-Rooks to distribute our N ray samples across
+ * a multi-dimensional domain (2D)
+ */
+ const unsigned short I =
+ ao_data->permutation_table_1[(i + perm_offs) % ao_data->number_of_rays];
+ const unsigned short J = ao_data->permutation_table_2[i];
+
+ const float JitPh = (get_ao_random2(I + perm_offs) & (MAX_NUMBER_OF_AO_RAYS - 1)) /
+ ((float)MAX_NUMBER_OF_AO_RAYS);
+ const float JitTh = (get_ao_random1(J + perm_offs) & (MAX_NUMBER_OF_AO_RAYS - 1)) /
+ ((float)MAX_NUMBER_OF_AO_RAYS);
+ const float SiSqPhi = (I + JitPh) / ao_data->number_of_rays;
+ const float Theta = (float)(2 * M_PI) * ((J + JitTh) / ao_data->number_of_rays);
+
+ /* this gives results identical to the so-called cosine
+ * weighted distribution relative to the north pole.
+ */
+ float SiPhi = sqrtf(SiSqPhi);
+ float CoPhi = SiSqPhi < 1.0f ? sqrtf(1.0f - SiSqPhi) : 0;
+ float CoThe = cosf(Theta);
+ float SiThe = sinf(Theta);
+
+ const float dx = CoThe * CoPhi;
+ const float dy = SiThe * CoPhi;
+ const float dz = SiPhi;
+
+ /* transform ray direction out of tangent frame */
+ float dv[3];
+ for (k = 0; k < 3; k++) {
+ dv[k] = axisX[k] * dx + axisY[k] * dy + axisZ[k] * dz;
+ }
+
+ hit_something = trace_ao_ray(ao_data, cen, dv);
+
+ if (hit_something != 0) {
+ shadow += 1;
+ }
+ }
+
+ value = 1.0f - (shadow / ao_data->number_of_rays);
+
+ if (ibuf->rect_float) {
+ float *rrgbf = ibuf->rect_float + pixel * 4;
+ rrgbf[0] = rrgbf[1] = rrgbf[2] = value;
+ rrgbf[3] = 1.0f;
+ }
+ else {
+ unsigned char *rrgb = (unsigned char *)ibuf->rect + pixel * 4;
+ rrgb[0] = rrgb[1] = rrgb[2] = unit_float_to_uchar_clamp(value);
+ rrgb[3] = 255;
+ }
+}
+#endif
+
+/* ******$***************** Post processing ************************* */
+
+static void bake_ibuf_filter(ImBuf *ibuf, char *mask, const int filter)
+{
+ /* must check before filtering */
+ const bool is_new_alpha = (ibuf->planes != R_IMF_PLANES_RGBA) && BKE_imbuf_alpha_test(ibuf);
+
+ /* Margin */
+ if (filter) {
+ IMB_filter_extend(ibuf, mask, filter);
+ }
+
+ /* if the bake results in new alpha then change the image setting */
+ if (is_new_alpha) {
+ ibuf->planes = R_IMF_PLANES_RGBA;
+ }
+ else {
+ if (filter && ibuf->planes != R_IMF_PLANES_RGBA) {
+ /* clear alpha added by filtering */
+ IMB_rectfill_alpha(ibuf, 1.0f);
+ }
+ }
+}
+
+static void bake_ibuf_normalize_displacement(ImBuf *ibuf,
+ const float *displacement,
+ const char *mask,
+ float displacement_min,
+ float displacement_max)
+{
+ int i;
+ const float *current_displacement = displacement;
+ const char *current_mask = mask;
+ float max_distance;
+
+ max_distance = max_ff(fabsf(displacement_min), fabsf(displacement_max));
+
+ for (i = 0; i < ibuf->x * ibuf->y; i++) {
+ if (*current_mask == FILTER_MASK_USED) {
+ float normalized_displacement;
+
+ if (max_distance > 1e-5f) {
+ normalized_displacement = (*current_displacement + max_distance) / (max_distance * 2);
+ }
+ else {
+ normalized_displacement = 0.5f;
+ }
+
+ if (ibuf->rect_float) {
+ /* currently baking happens to RGBA only */
+ float *fp = ibuf->rect_float + i * 4;
+ fp[0] = fp[1] = fp[2] = normalized_displacement;
+ fp[3] = 1.0f;
+ }
+
+ if (ibuf->rect) {
+ unsigned char *cp = (unsigned char *)(ibuf->rect + i);
+ cp[0] = cp[1] = cp[2] = unit_float_to_uchar_clamp(normalized_displacement);
+ cp[3] = 255;
+ }
+ }
+
+ current_displacement++;
+ current_mask++;
+ }
+}
+
+/* **************** Common functions public API relates on **************** */
+
+static void count_images(MultiresBakeRender *bkr)
+{
+ BLI_listbase_clear(&bkr->image);
+ bkr->tot_image = 0;
+
+ for (int i = 0; i < bkr->ob_image.len; i++) {
+ Image *ima = bkr->ob_image.array[i];
+ if (ima) {
+ ima->id.tag &= ~LIB_TAG_DOIT;
+ }
+ }
+
+ for (int i = 0; i < bkr->ob_image.len; i++) {
+ Image *ima = bkr->ob_image.array[i];
+ if (ima) {
+ if ((ima->id.tag & LIB_TAG_DOIT) == 0) {
+ LinkData *data = BLI_genericNodeN(ima);
+ BLI_addtail(&bkr->image, data);
+ bkr->tot_image++;
+ ima->id.tag |= LIB_TAG_DOIT;
+ }
+ }
+ }
+
+ for (int i = 0; i < bkr->ob_image.len; i++) {
+ Image *ima = bkr->ob_image.array[i];
+ if (ima) {
+ ima->id.tag &= ~LIB_TAG_DOIT;
+ }
+ }
+}
+
+static void bake_images(MultiresBakeRender *bkr, MultiresBakeResult *result)
+{
+ LinkData *link;
+
+ for (link = bkr->image.first; link; link = link->next) {
+ Image *ima = (Image *)link->data;
+ ImBuf *ibuf = BKE_image_acquire_ibuf(ima, NULL, NULL);
+
+ if (ibuf->x > 0 && ibuf->y > 0) {
+ BakeImBufuserData *userdata = MEM_callocN(sizeof(BakeImBufuserData),
+ "MultiresBake userdata");
+ userdata->mask_buffer = MEM_callocN(ibuf->y * ibuf->x, "MultiresBake imbuf mask");
+ ibuf->userdata = userdata;
+
+ switch (bkr->mode) {
+ case RE_BAKE_NORMALS:
+ do_multires_bake(
+ bkr, ima, true, apply_tangmat_callback, init_normal_data, free_normal_data, result);
+ break;
+ case RE_BAKE_DISPLACEMENT:
+ do_multires_bake(bkr,
+ ima,
+ false,
+ apply_heights_callback,
+ init_heights_data,
+ free_heights_data,
+ result);
+ break;
+/* TODO: restore ambient occlusion baking support. */
+#if 0
+ case RE_BAKE_AO:
+ do_multires_bake(bkr, ima, false, apply_ao_callback, init_ao_data, free_ao_data, result);
+ break;
+#endif
+ }
+ }
+
+ BKE_image_release_ibuf(ima, ibuf, NULL);
+
+ ima->id.tag |= LIB_TAG_DOIT;
+ }
+}
+
+static void finish_images(MultiresBakeRender *bkr, MultiresBakeResult *result)
+{
+ LinkData *link;
+ bool use_displacement_buffer = bkr->mode == RE_BAKE_DISPLACEMENT;
+
+ for (link = bkr->image.first; link; link = link->next) {
+ Image *ima = (Image *)link->data;
+ ImBuf *ibuf = BKE_image_acquire_ibuf(ima, NULL, NULL);
+ BakeImBufuserData *userdata = (BakeImBufuserData *)ibuf->userdata;
+
+ if (ibuf->x <= 0 || ibuf->y <= 0) {
+ continue;
+ }
+
+ if (use_displacement_buffer) {
+ bake_ibuf_normalize_displacement(ibuf,
+ userdata->displacement_buffer,
+ userdata->mask_buffer,
+ result->height_min,
+ result->height_max);
+ }
+
+ bake_ibuf_filter(ibuf, userdata->mask_buffer, bkr->bake_filter);
+
+ ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID;
+ BKE_image_mark_dirty(ima, ibuf);
+
+ if (ibuf->rect_float) {
+ ibuf->userflags |= IB_RECT_INVALID;
+ }
+
+ if (ibuf->mipmap[0]) {
+ ibuf->userflags |= IB_MIPMAP_INVALID;
+ imb_freemipmapImBuf(ibuf);
+ }
+
+ if (ibuf->userdata) {
+ if (userdata->displacement_buffer) {
+ MEM_freeN(userdata->displacement_buffer);
+ }
+
+ MEM_freeN(userdata->mask_buffer);
+ MEM_freeN(userdata);
+ ibuf->userdata = NULL;
+ }
+
+ BKE_image_release_ibuf(ima, ibuf, NULL);
+ DEG_id_tag_update(&ima->id, 0);
+ }
+}
+
+void RE_multires_bake_images(MultiresBakeRender *bkr)
+{
+ MultiresBakeResult result;
+
+ count_images(bkr);
+ bake_images(bkr, &result);
+ finish_images(bkr, &result);
+}