/* * See Copyright Notice in atomorph.h */ #include "atomorph.h" namespace am { morph::morph() { std::default_random_engine e(0); e1 = e; } morph::~morph() { clear(); } void morph::clear() { while (!frames.empty()) { frame f = frames.begin()->second; while (!f.blobs.empty()) { delete f.blobs.back(); f.blobs.pop_back(); } frames.erase(frames.begin()); } frames.clear(); while (!chains.empty()) { chain c = chains.begin()->second; clear_chain(&c); chains.erase(chains.begin()); } chains.clear(); identifier= 0; energy = 0.0; bbox_x1 = UINT16_MAX; bbox_y1 = UINT16_MAX; bbox_x2 = 0; bbox_y2 = 0; if (worker.is_running()) { worker.stop(); worker.clear(); } if (fluid) delete fluid; } void morph::compute() { if (is_busy()) return; if (worker.is_running()) worker.resume(); else worker.start(); } void morph::iterate(size_t iterations) { if (is_busy()) return; if (worker.is_running()) worker.resume(iterations); else worker.start (iterations); } void morph::compute(double seconds) { if (is_busy()) return; if (worker.is_running()) worker.resume(seconds); else worker.start(seconds); } void morph::suspend() { while (is_busy()) { // Attempt to suspend the worker. worker.pause(); std::this_thread::sleep_for(std::chrono::milliseconds(0)); } } bool morph::suspend(double timeout) { if (!is_busy()) return true; std::chrono::steady_clock::time_point t_start, t_end; t_start = std::chrono::steady_clock::now(); while (1) { // Attempt to suspend the worker. worker.pause(); if (worker.is_paused()) break; t_end = std::chrono::steady_clock::now(); if (std::chrono::duration_cast(t_end - t_start).count() > (timeout * 1000000000)) { return false; } std::this_thread::sleep_for(std::chrono::milliseconds(0)); } return true; } bool morph::synchronize() { if (!worker.is_paused()) return false; if (worker.get_seed() != seed) worker.set_seed(seed); worker.set_blob_delimiter (blob_delimiter); worker.set_blob_threshold (blob_threshold); worker.set_blob_max_size (blob_max_size); worker.set_blob_min_size (blob_min_size); worker.set_blob_box_grip (blob_box_grip); worker.set_blob_box_samples(blob_box_samples); worker.set_blob_number (blob_number); worker.set_degeneration (degeneration); worker.set_density (density); worker.set_threads (threads); worker.set_cycle_length (cycle_length); worker.set_bbox(bbox_x1, bbox_y1, bbox_x2, bbox_y2); worker.set_blob_weights(blob_rgba_weight, blob_size_weight, blob_xy_weight); if (identifier != worker.get_identifier()) { // Reset everything. worker.clear(); std::map::iterator it; refresh_frames(); for (it=frames.begin(); it!=frames.end(); ++it) { worker.set_frame(it->first, &(it->second)); } worker.set_identifier(identifier); if (fluid) delete fluid; fluid = nullptr; } { // Load results. state = worker.get_state(); energy = worker.get_energy(); std::map::iterator fit; for (fit=frames.begin(); fit!=frames.end(); ++fit) { frame *to = &(fit->second); frame *from = worker.get_frame(fit->first); while (!to->blobs.empty()) { delete to->blobs.back(); to->blobs.pop_back(); } to->owners.clear(); for (size_t i=0; iblobs.size(); ++i) { blob* b= from->blobs[i]; if (!b) continue; blob* new_blob = new blob; new_blob->index = to->blobs.size(); new_blob->r = b->r; new_blob->g = b->g; new_blob->b = b->b; new_blob->a = b->a; new_blob->x = b->x; new_blob->y = b->y; new_blob->surface = b->surface; new_blob->group = b->group; to->blobs.push_back(new_blob); } } const std::map *chains_from = worker.get_chains(); std::map::const_iterator cit; // Get chain updates from worker's chains. for (cit=chains_from->begin(); cit!=chains_from->end(); ++cit) { size_t chain_key = cit->first; // If this chain already exists, it might have different attributes. if ((chains[chain_key].width != chains_from->at(chain_key).width || chains[chain_key].height != chains_from->at(chain_key).height)) { // Chains are different, free the target to allocate memory again later. if (!renew_chain(&(chains[chain_key]), cit->second.width, cit->second.height)) { chains.erase(chain_key); return false; } } // Normally the above memory allocation is not needed because the morph should // not change its parameters very often, although the implementation allows it // by essentially restarting the whole procedure whenever critical parameters // change. For normal synchronization, only the below code is called to refresh // the key points and splines for example. for (size_t y=0; yat(chain_key).points[x][y]; double vx = chains[chain_key].points[x][y].s.x + chains[chain_key].points[x][y].s.x_fract / double(UINT8_MAX+1); double vy = chains[chain_key].points[x][y].s.y + chains[chain_key].points[x][y].s.y_fract / double(UINT8_MAX+1); if (y == 0) chains[chain_key].splines[x].clearCPoints(); chains[chain_key].splines[x].AddSplinePoint(glnemo::Vec3D(vx, vy, 0.0)); } } if (chains[chain_key].max_surface != chains_from->at(chain_key).max_surface) { // Number of particles in the fluid simulator has changed, reset the simulation. chains[chain_key].max_surface = chains_from->at(chain_key).max_surface; if (fluid) { delete fluid; fluid = nullptr; } } } // Delete chains that don't exist in worker's chains. std::vector delete_keys; for (cit=chains.begin(); cit!=chains.end(); ++cit) { if (chains_from->find(cit->first) != chains_from->end()) continue; delete_keys.push_back(cit->first); } while (!delete_keys.empty()) { clear_chain(&(chains[delete_keys.back()])); chains.erase(delete_keys.back()); delete_keys.pop_back(); } worker.set_identifier(identifier); if (skip_state) { worker.next_state(); skip_state = false; } // Initialize the fluid simulator if needed. if (!fluid && width > 0 && height > 0) { size_t particle_count = 0; for (cit=chains.begin(); cit!=chains.end(); ++cit) { particle_count += cit->second.max_surface; } fluid = new (std::nothrow) FluidModel(width*sparseness+20, height*sparseness+20, particle_count); if (fluid) { Particle *particles = fluid->getParticles(); for (size_t i=0; iclear(); p->x = width *sparseness/2.0 - 10; p->y = height*sparseness/2.0 - 10; p->u = 0.0; p->v = 0.0; p->gravity_x = p->x; p->gravity_y = p->y; p->freedom_r = 1.0; p->R = 0.0; p->r = p->R; p->G = 1.0; p->g = p->G; // Should never appear in morphs. p->B = 0.0; p->b = p->B; p->A = 1.0; p->a = p->A; p->active = false; p->mature = false; } } } } return true; } void morph::refresh_frames() { std::map::iterator it; size_t index = 0; for (it=frames.begin(); it!=frames.end(); ++it) { it->second.index = index++; if (it->second.pixels.empty()) { it->second.x = (bbox_x1 <= bbox_x2 ? (bbox_x1 + bbox_x2)/2.0 : 0.0); it->second.y = (bbox_y1 <= bbox_y2 ? (bbox_y1 + bbox_y2)/2.0 : 0.0); it->second.r = 0.0; it->second.g = 0.0; it->second.b = 0.0; it->second.a = 0.0; } } } bool morph::add_frame(size_t frame_key) { if (frames.find(frame_key) != frames.end()) return false; frames[frame_key].x = (bbox_x1 <= bbox_x2 ? (bbox_x1 + bbox_x2)/2.0 : 0.0); frames[frame_key].y = (bbox_y1 <= bbox_y2 ? (bbox_y1 + bbox_y2)/2.0 : 0.0); refresh_frames(); return true; } bool morph::add_pixel(size_t frame_key, pixel px) { if (blob_delimiter == HSP) { px.c = rgb_to_hsp(px.c); } add_frame(frame_key); am::frame *f = &(frames[frame_key]); auto pixelmap = &(f->pixels); size_t pos = px.y * (UINT16_MAX+1) + px.x; (*pixelmap)[pos] = px; identifier++; if (px.x < bbox_x1) bbox_x1 = px.x; if (px.y < bbox_y1) bbox_y1 = px.y; if (px.x > bbox_x2) bbox_x2 = px.x; if (px.y > bbox_y2) bbox_y2 = px.y; if (pixelmap->size() == 1) { f->x = px.x; f->y = px.y; f->r = px.c.r / 255.0; f->g = px.c.g / 255.0; f->b = px.c.b / 255.0; f->a = px.c.a / 255.0; return true; } double weight = 1.0 / double(pixelmap->size()); f->x = (1.0 - weight)*f->x + weight*px.x; f->y = (1.0 - weight)*f->y + weight*px.y; f->r = (1.0 - weight)*f->r + weight*(px.c.r/255.0); f->g = (1.0 - weight)*f->g + weight*(px.c.g/255.0); f->b = (1.0 - weight)*f->b + weight*(px.c.b/255.0); f->a = (1.0 - weight)*f->a + weight*(px.c.a/255.0); return true; } pixel morph::get_average_pixel (size_t f) { pixel px = create_pixel(0,0,0,0,0,0); if (!has_frame(f)) return px; px.x = round(frames[f].x); px.y = round(frames[f].y); px.c.r = round(255.0*frames[f].r); px.c.g = round(255.0*frames[f].g); px.c.b = round(255.0*frames[f].b); px.c.a = round(255.0*frames[f].a); if (blob_delimiter == HSP) { px.c = hsp_to_rgb(px.c); } return px; } pixel morph::get_average_pixel (size_t f, size_t b) { pixel px = create_pixel(0,0,0,0,0,0); if (!has_frame(f) || frames[f].blobs.size() <= b || frames[f].blobs[b] == nullptr) { return px; } px.x = round(frames[f].blobs[b]->x); px.y = round(frames[f].blobs[b]->y); px.c.r = round(255.0*frames[f].blobs[b]->r); px.c.g = round(255.0*frames[f].blobs[b]->g); px.c.b = round(255.0*frames[f].blobs[b]->b); px.c.a = round(255.0*frames[f].blobs[b]->a); if (blob_delimiter == HSP) { px.c = hsp_to_rgb(px.c); } return px; } pixel morph::get_pixel (size_t f, size_t pos) { pixel px = create_pixel(0,0,0,0,0,0); if (!has_pixel(f,pos)) { // Asking a pixel from void gives // a fully transparent pixel. return px; } px = frames[f].pixels[pos]; if (blob_delimiter == HSP) { px.c = hsp_to_rgb(px.c); } return px; } size_t morph::get_pixel_count (size_t f) { if (!has_frame(f)) return 0; return frames[f].pixels.size(); } size_t morph::get_blob_count (size_t f) { if (!has_frame(f)) return 0; return frames[f].blobs.size(); } size_t morph::get_frame_key (double t) { double integ; t = std::modf(t, &integ); if (t < 0.0) t += 1.0; size_t f = t * frames.size(); std::map::iterator it; size_t i=0; size_t frame = SIZE_MAX; for (it=frames.begin(); it!=frames.end(); ++it) { if (i++ == f) { frame = it->first; break; } } return frame; } size_t morph::get_blob_count () { size_t count = 0; std::map::iterator it; for (it=frames.begin(); it!=frames.end(); ++it) { count += get_blob_count(it->first); } return count; } void morph::set_seed(unsigned seed) { std::default_random_engine e(seed); e1 = e; PerlinNoise l_map(seed); lag_map = l_map; PerlinNoise s_map(seed+1); slope_map = s_map; this->seed = seed; } double morph::normalize_time(double t) { double integ, time = std::modf(t, &integ); if (time < 0.0) time += 1.0; return time; } const blob* morph::get_pixels(size_t blob_index, double time, std::vector *to) { time = normalize_time(time); double t = time; if (frames.empty()) return nullptr; size_t frame_key = get_frame_key(t); size_t blob_count = get_blob_count(frame_key); if (blob_index >= blob_count) return nullptr; double dt = 1.0 / double(frames.size()); t = std::max(0.0, (t - (frame_key * dt)) / dt); const blob * bl = get_blob(frame_key, blob_index); if (!bl) return nullptr; if (chains.empty()) { std::set::iterator it; for (it=bl->surface.begin(); it!=bl->surface.end(); ++it) { pixel px = get_pixel(frame_key, *it); to->push_back(px); } } else { std::map> colors; std::map> weights; if (chains.find(bl->group) == chains.end()) return nullptr; size_t w = chains[bl->group].width; size_t h = chains[bl->group].height; point**p = chains[bl->group].points; size_t y = frames[frame_key].index; size_t y_next = (y+1)%h; assert(y < h); std::map::iterator it = frames.find(frame_key); ++it; if (it == frames.end()) it = frames.begin(); size_t next_frame_key = it->first; for (size_t x=0; xgroup].splines) { glnemo::Vec3D v = chains[bl->group].splines[x].GetInterpolatedSplinePoint(time); double fract, integ; fract = std::modf(v.x, &integ); pt.s.x = integ; pt.s.x_fract = std::round(fract * UINT8_MAX); fract = std::modf(v.y, &integ); pt.s.y = integ; pt.s.y_fract = std::round(fract * UINT8_MAX); } pixel px; px.x = pt.s.x; px.y = pt.s.y; if (fading == PERLIN) { double f = 8.0; // Frequency int octaves = 8; // Octaves double bbox_w = bbox_x2 - bbox_x1 + 1.0; double bbox_h = bbox_y2 - bbox_y1 + 1.0; double perlin_x = (((pt1.s.x-bbox_x1)*(UINT8_MAX+1)+pt1.s.x_fract) / double(bbox_w*(UINT8_MAX+1)))*f; double perlin_y = (((pt1.s.y-bbox_y1)*(UINT8_MAX+1)+pt1.s.y_fract) / double(bbox_h*(UINT8_MAX+1)))*f; double lag = lag_map. octaveNoise(perlin_x, perlin_y, octaves)*0.5 + 0.5; double slope = slope_map.octaveNoise(perlin_x, perlin_y, 8)*0.5 + 0.5; px.c = interpolate(px1.c, px2.c, lag, slope, 1.0 - t); } else if (fading == COSINE) px.c = interpolate(px1.c, px2.c, 0.5, 0.5, 1.0 - t); else px.c = interpolate(px1.c, px2.c, 1.0 - t); if (px.x >= width || px.y >= height) { if (px.x > bbox_x2 || px.x < bbox_x1 || px.y > bbox_y2 || px.y < bbox_y1) continue; } // Bilinear interpolation: double total_weight= UINT8_MAX * UINT8_MAX; double weight_x1y1 = ((UINT8_MAX - pt.s.x_fract) * (UINT8_MAX - pt.s.y_fract)) / total_weight; double weight_x2y1 = ( pt.s.x_fract * (UINT8_MAX - pt.s.y_fract)) / total_weight; double weight_x1y2 = ((UINT8_MAX - pt.s.x_fract) * pt.s.y_fract ) / total_weight; double weight_x2y2 = ( pt.s.x_fract * pt.s.y_fract ) / total_weight; size_t pos = xy2pos(px.x, px.y); if (weight_x1y1 > 0.0) { colors [pos].push_back(px.c); weights[pos].push_back(weight_x1y1); } if ((px.x < bbox_x2 || px.x+1 < width) && weight_x2y1 > 0.0) { pos = xy2pos(px.x + 1, px.y); colors [pos].push_back(px.c); weights[pos].push_back(weight_x2y1); } if ((px.y < bbox_y2 || px.y+1 < height) && weight_x1y2 > 0.0) { pos = xy2pos(px.x, px.y + 1); colors [pos].push_back(px.c); weights[pos].push_back(weight_x1y2); } if (weight_x2y2 > 0.0) { if ((px.y < bbox_y2 && px.x < bbox_x2) || (px.y+1 < height && px.x+1 < width)) { pos = xy2pos(px.x + 1, px.y + 1); colors [pos].push_back(px.c); weights[pos].push_back(weight_x2y2); } } } std::map blob; std::map>::iterator cit; for (cit = colors.begin(); cit!=colors.end(); ++cit) { std::vector *cs = &(cit->second); std::vector*ws = &(weights[cit->first]); size_t vsz = cs->size(); double r = 0.0, g = 0.0, b = 0.0, a = 0.0; double weight_sum = 0.0; for (size_t c = 0; cat(c); r += cs->at(c).r * ws->at(c); g += cs->at(c).g * ws->at(c); b += cs->at(c).b * ws->at(c); a += cs->at(c).a * ws->at(c); } double w = density > 0 ? double(vsz)/double(density) : 0.0; if (w > 1.0) w = 1.0; r = std::round(r/weight_sum); g = std::round(g/weight_sum); b = std::round(b/weight_sum); a = std::round(w*(a/weight_sum)); size_t pos = cit->first; uint16_t x = pos % (UINT16_MAX+1); uint16_t y = pos / (UINT16_MAX+1); pixel px = create_pixel(x,y,r,g,b,a); if (feather > 0) blob[pos] = px; else to->push_back(px); } if (feather > 0) { std::vector> layers; std::map peeled_blob = blob; while (!peeled_blob.empty() && layers.size() < feather) { std::set border; std::map::iterator pit; for (pit = peeled_blob.begin(); pit!=peeled_blob.end(); ++pit) { size_t pos = pit->first; uint16_t x = pos % (UINT16_MAX+1); uint16_t y = pos / (UINT16_MAX+1); if (x == 0 || x == UINT16_MAX || y == 0 || y == UINT16_MAX) { border.insert(pos); continue; } if (peeled_blob.find(xy2pos(x+1, y )) == peeled_blob.end() || peeled_blob.find(xy2pos(x-1, y )) == peeled_blob.end() || peeled_blob.find(xy2pos(x, y+1)) == peeled_blob.end() || peeled_blob.find(xy2pos(x, y-1)) == peeled_blob.end()) { border.insert(pos); } } std::set::iterator bt; for (bt = border.begin(); bt != border.end(); ++bt) { size_t pos = *bt; peeled_blob.erase(pos); } layers.push_back(border); } size_t lsz = layers.size(); for (size_t l=0; l *layer = &(layers[l]); std::set::iterator lt; for (lt = layer->begin(); lt != layer->end(); ++lt) { pixel px = blob[*lt]; px.c.a = std::round(double(px.c.a)*(double(l+1)/double(feather+1))); to->push_back(px); } } std::map::iterator pit; for (pit = peeled_blob.begin(); pit!=peeled_blob.end(); ++pit) { to->push_back(pit->second); } } } return bl; } void morph::update_particle( Particle *p, const blob * blob_before, const blob * blob_after, point **points, std::map& sources, std::map& destinations, double t, size_t y, size_t y_next, size_t frame_key, size_t next_frame_key, size_t chain_key, double time ) { double previous_surface = blob_before->surface.size(); double next_surface = blob_after->surface.size(); size_t src_pos = p->source_pos; size_t dst_pos = p->destination_pos; size_t src_x = sources[src_pos]; size_t dst_x = destinations[dst_pos]; // If area is growing then gain the real source x from the destination. if (next_surface > previous_surface) { src_x = destinations[dst_pos]; } { point pt1 = points[src_x][y]; point pt2 = points[dst_x][y_next]; pixel px1, px2; if (point_has_pixel(pt1) && !point_has_pixel(pt2)) { px1 = get_pixel(frame_key, xy2pos(pt1.s.x, pt1.s.y)); px2 = px1; px2.x = pt2.s.x; px2.y = pt2.s.y; if (next_surface > 0.0) px2.c.a = 0; } else if (point_has_pixel(pt2) && !point_has_pixel(pt1)) { px2 = get_pixel(next_frame_key, xy2pos(pt2.s.x, pt2.s.y)); px1 = px2; px1.x = pt1.s.x; px1.y = pt1.s.y; if (previous_surface > 0.0) px1.c.a = 0; } else { px1 = get_pixel(frame_key, xy2pos(pt1.s.x, pt1.s.y)); px2 = get_pixel(next_frame_key, xy2pos(pt2.s.x, pt2.s.y)); } color c; if (fading == PERLIN) { double f = 8.0; // Frequency int octaves = 8; // Octaves double bbox_w = bbox_x2 - bbox_x1 + 1.0; double bbox_h = bbox_y2 - bbox_y1 + 1.0; double perlin_x = (((pt1.s.x-bbox_x1)*(UINT8_MAX+1)+pt1.s.x_fract) / double(bbox_w*(UINT8_MAX+1)))*f; double perlin_y = (((pt1.s.y-bbox_y1)*(UINT8_MAX+1)+pt1.s.y_fract) / double(bbox_h*(UINT8_MAX+1)))*f; double lag = lag_map. octaveNoise(perlin_x, perlin_y, octaves)*0.5 + 0.5; double slope = slope_map.octaveNoise(perlin_x, perlin_y, 8)*0.5 + 0.5; c = interpolate(px1.c, px2.c, lag, slope, 1.0 - t); } else if (fading == COSINE) c = interpolate(px1.c, px2.c, 0.5, 0.5, 1.0 - t); else c = interpolate(px1.c, px2.c, 1.0 - t); point pt = pt1; if (motion == LINEAR) pt = interpolate(pt1, pt2, 1.0 - t); else if (motion == SPLINE && chains[chain_key].splines) { glnemo::Vec3D v = chains[chain_key].splines[src_x].GetInterpolatedSplinePoint(time); double fract, integ; fract = std::modf(v.x, &integ); pt.s.x = integ; pt.s.x_fract = std::round(fract * UINT8_MAX); fract = std::modf(v.y, &integ); pt.s.y = integ; pt.s.y_fract = std::round(fract * UINT8_MAX); } float ptx, pty; point2xy(pt, &ptx, &pty); p->gravity_x = std::min(std::max((ptx * sparseness) + 10.0, 1.0), double(width *sparseness + 10.0) ); p->gravity_y = std::min(std::max((pty * sparseness) + 10.0, 1.0), double(height*sparseness + 10.0) ); p->R = c.r/255.0; p->G = c.g/255.0; p->B = c.b/255.0; p->A = c.a/255.0; if (show_blobs == DISTINCT) { std::mt19937 gen(chain_key); std::uniform_real_distribution uniform_dist_color(0.0, 1.0); p->R = uniform_dist_color(gen); p->G = uniform_dist_color(gen); p->B = uniform_dist_color(gen); p->A = 1.0; } else if (show_blobs == AVERAGE) { p->R = blob_before->r; p->G = blob_before->g; p->B = blob_before->b; p->A = blob_before->a; } p->r = t*p->R + (1.0-t)*p->r; p->g = t*p->G + (1.0-t)*p->g; p->b = t*p->B + (1.0-t)*p->b; p->a = t*p->A + (1.0-t)*p->a; if (!p->mature) { if (p->source_owner) { p->x = p->gravity_x; p->y = p->gravity_y; // These lines should only be called // for a newly created particle that was // first to occupy its source location: p->r = p->R; p->g = p->G; p->b = p->B; p->a = p->A; // Otherwise wrongly coloured single particles // start appearing during the morph when new // particles are created. } if (previous_surface == 0.0) p->strength = 0.1; else p->strength = 1.0; p->freedom_r = 1.0; } } } void morph::step_fluid(size_t frame_key, double t, double time) { // t is for current transition between 2 images, time is overall time across all key frames. size_t iptc = 0; size_t particle_count = fluid->get_particle_count(); Particle *particles = fluid->getParticles(); std::map::const_iterator cit; for (cit=chains.begin(); cit!=chains.end(); ++cit) { size_t chain_key = cit->first; size_t w = cit->second.width; size_t h = cit->second.height; point **points = cit->second.points; size_t y = frames[frame_key].index; size_t y_next = (y+1)%h; std::map::iterator fit = frames.find(frame_key); ++fit; if (fit == frames.end()) fit = frames.begin(); size_t next_frame_key = fit->first; const blob* blob_before = find_blob_by_group(frame_key, chain_key); const blob* blob_after = find_blob_by_group(next_frame_key, chain_key); double previous_surface = blob_before->surface.size(); double next_surface = blob_after->surface.size(); double current_surface = std::round((1.0-t)*previous_surface + t*next_surface); size_t active_count = 0; size_t active_limit = current_surface; { std::map sources; // pt1 has fluid, pt2 maybe not std::map destinations; // pt2 has fluid, pt1 maybe not std::map> particles_by_destination; std::map> particles_by_source; std::vector free_particles; std::vector update_particles; for (size_t x=0; xactive && p->frame_key == frame_key) { // Map this particle as an existing particle. active_count++; particles_by_destination[p->destination_pos].push_back(p); particles_by_source [p->source_pos ].push_back(p); update_particles.push_back(p); } else { p->active = false; free_particles.push_back(p); } } } if (active_count < active_limit) { size_t to_create = active_limit - active_count; // Add new particles. while (to_create > 0) { assert(!free_particles.empty()); Particle *p = free_particles.back(); free_particles.pop_back(); update_particles.push_back(p); p->clear(); p->frame_key = frame_key; { bool source_found = false; std::map::iterator it; std::vector source_candidates; // First see if any of the source positions is still unused, // it is the highest priority to have these filled. for (it=sources.begin(); it!=sources.end(); ++it) { size_t pos = it->first; if (particles_by_source.find(pos) != particles_by_source.end() && !particles_by_source[pos].empty()) { // This source is already taken. continue; } // Free source was found, take it. source_candidates.push_back(pos); } if (!source_candidates.empty()) { std::uniform_int_distribution uniform_dist_candidate(0, source_candidates.size()-1); size_t pos = source_candidates[uniform_dist_candidate(e1)]; assert(sources.find(pos) != sources.end()); size_t x = sources[pos]; p->source_pos = pos; particles_by_source[pos].push_back(p); source_found = true; { // This source was unoccupied, take its default destination // even if it leads to a place without HAS_FLUID flag and thus this // particle must be destroyed sooner or later during this morph. point pt2 = points[x][y_next]; size_t destination_pos = xy2pos(pt2.s.x, pt2.s.y); p->destination_pos = destination_pos; particles_by_destination[destination_pos].push_back(p); assert(destinations.find(destination_pos) != destinations.end()); } } if (!source_found) { // All sources are already occupied, this new particle must now // share the source with some other particle, but it will still // must get an unused destination. Search for such destination. bool destination_found = false; for (it=destinations.begin(); it!=destinations.end(); ++it) { size_t pos = it->first; size_t x = it->second; if (particles_by_destination.find(pos) != particles_by_destination.end() && !particles_by_destination[pos].empty()) { // This destination is already taken. continue; } // Free destination was found, take it. p->destination_pos = pos; particles_by_destination[pos].push_back(p); destination_found = true; { // Destination was found but all the sources were already // occupied. Now see the pt1 of this destination even if // it does not have HAS_FLUID flag. point pt1 = points[x][y]; size_t source_pos = xy2pos(pt1.s.x, pt1.s.y); p->source_pos = source_pos; particles_by_source[source_pos].push_back(p); source_found = true; assert(sources.find(source_pos) != sources.end()); } break; } assert(destination_found); } assert(source_found); } { size_t pos = p->source_pos; // Find a suitable starting position for this particle. if (particles_by_source.find(pos) != particles_by_source.end() && particles_by_source[pos].size() > 1) { // Another particle exists with the same source, // copy its location for seamless creation. std::uniform_int_distribution uniform_dist_p(0, particles_by_source[pos].size()-2); Particle *parent = particles_by_source[pos].at(uniform_dist_p(e1)); assert(parent != p); p->copy_from(parent); p->mature = parent->mature; p->strength = parent->strength; std::uniform_real_distribution uniform_dist_fuzz(0.0, 1.0); p->x = std::min(p->x + uniform_dist_fuzz(e1), width * sparseness-10.0); p->y = std::min(p->y + uniform_dist_fuzz(e1), height* sparseness-10.0); p->source_owner = false; } else { // No other particle currently exists with the // same source location. Copy the attractor's // current position for the initial setup of this // new particle. p->mature = false; p->source_owner = true; update_particle(p, blob_before, blob_after, points, sources, destinations, t, y, y_next, frame_key, next_frame_key, chain_key, time); } } p->active = true; to_create--; } } else if (active_count > active_limit) { size_t to_delete = active_count - active_limit; // Delete particles. std::shuffle(update_particles.begin(), update_particles.end(), e1); while (to_delete > 0) { assert(!update_particles.empty()); Particle *p = update_particles.back(); p->active = false; update_particles.pop_back(); to_delete--; } } { // Particles either created or deleted by now, update their positions. size_t usz = update_particles.size(); for (size_t i=0; i < usz; ++i) { Particle *p = update_particles[i]; if (!p->active) continue; update_particle(p, blob_before, blob_after, points, sources, destinations, t, y, y_next, frame_key, next_frame_key, chain_key, time); } } } } double freedom = std::sqrt(width*width + height*height) * double(sparseness) / 4.0; double freedom_radius = freedom*(1.0 - t); for (size_t step = 0; step < fluidsteps; ++step) fluid->step(fluidsteps - (step+1), freedom_radius, t); } void morph::draw_fluid(double time, std::vector *image) { if (!fluid || chains.empty()) return; time = normalize_time(time); double t = time; if (frames.empty()) return; size_t frame_key = get_frame_key(t); double dt = 1.0 / double(frames.size()); t = std::max(0.0, (t - (frame_key * dt)) / dt); // t is 0.0 at the beginning of frame // t is 1.0 at the end of this frame step_fluid(frame_key, t, time); std::map image_buffer; Particle *particles = fluid->getParticles(); size_t particle_count = fluid->get_particle_count(); std::map> colors; std::map> weights; std::map> startups; for (size_t i=0; iactive == false) continue; { // Correct the colours so they would not blur too much. // Blobs that appear from nothingness should be blended // with their surroundings a lot though to avoid sharp // gradient spots in the very beginnning of the morph. //double w = 1.0 / (100.0 * std::pow(t, 2.0)+1.0); double w = (1.0 / (100.0*std::pow((t-(1.0*(1.0-p->strength))), 2.0)+1.0)) * std::pow(p->strength, 2.0); p->r = (w * p->R)+(1.0 - w)*p->r; p->g = (w * p->G)+(1.0 - w)*p->g; p->b = (w * p->B)+(1.0 - w)*p->b; p->a = (w * p->A)+(1.0 - w)*p->a; } double px, py; if (p->x <= 10.0) px = 0.0; else if (p->x >= width*sparseness + 10.0) px = (width-1); else px = (p->x-10.0)/sparseness; if (p->y <= 10.0) py = 0.0; else if (p->y >= height*sparseness+ 10.0) py = (height-1); else py = (p->y-10.0)/sparseness; uint16_t x = std::min(int(px), width - 1); uint16_t y = std::min(int(py), height- 1); size_t pos = xy2pos(x, y); if (!p->mature) { p->mature = true; } double x_fract, y_fract; x_fract = std::modf(px, &px); y_fract = std::modf(py, &py); if (px >= width || py >= height) { if (px > bbox_x2 || px < bbox_x1 || py > bbox_y2 || py < bbox_y1) continue; } // Bilinear interpolation: double weight_x1y1 = ((1.0 - x_fract) * (1.0 - y_fract)); double weight_x2y1 = ( x_fract * (1.0 - y_fract)); double weight_x1y2 = ((1.0 - x_fract) * y_fract ); double weight_x2y2 = ( x_fract * y_fract ); color c = create_color(p->r, p->g, p->b, p->a); if (weight_x1y1 > 0.0) { colors [pos].push_back(c); weights[pos].push_back(weight_x1y1); } if ((x < bbox_x2 || x+1 < width) && weight_x2y1 > 0.0) { pos = xy2pos(x + 1, y); colors [pos].push_back(c); weights[pos].push_back(weight_x2y1); } if ((y < bbox_y2 || y+1 < height) && weight_x1y2 > 0.0) { pos = xy2pos(x, y + 1); colors [pos].push_back(c); weights[pos].push_back(weight_x1y2); } if (weight_x2y2 > 0.0) { if ((y < bbox_y2 && x < bbox_x2) || (y+1 < height && x+1 < width)) { pos = xy2pos(x + 1, y + 1); colors [pos].push_back(c); weights[pos].push_back(weight_x2y2); } } } std::map blob; std::map>::iterator cit; for (cit = colors.begin(); cit!=colors.end(); ++cit) { std::vector *cs = &(cit->second); std::vector *ws = &(weights[cit->first]); size_t vsz = cs->size(); double r = 0.0, g = 0.0, b = 0.0, a = 0.0; double weight_sum = 0.0; for (size_t c = 0; cat(c); weight_sum += weight; r += cs->at(c).r * weight; g += cs->at(c).g * weight; b += cs->at(c).b * weight; a += cs->at(c).a * weight; } r = std::round(r/weight_sum); g = std::round(g/weight_sum); b = std::round(b/weight_sum); a = std::round(a/weight_sum); if (keep_background) { double alpha = 1.0 - std::pow(t, 24.0); a *= alpha; } size_t pos = cit->first; uint16_t x = pos % (UINT16_MAX+1); uint16_t y = pos / (UINT16_MAX+1); pixel px = create_pixel(x,y,r,g,b,a); size_t imgpos = y*width + x; if (feather > 0) blob[pos] = px.c; else image_buffer[imgpos] = px.c; } if (feather > 0) { for (size_t layer = 0; layer < feather; ++layer) { double alpha = 1.0 - (double(layer + 1) / double(feather + 1)); std::set border; std::map::iterator it; for (it = blob.begin(); it!=blob.end(); ++it) { size_t pos = it->first; uint16_t x = pos % (UINT16_MAX+1); uint16_t y = pos / (UINT16_MAX+1); size_t ps; if (x+1 < width && blob.find( (ps = xy2pos(x+1, y )) ) == blob.end()) border.insert(ps); if (y+1 < height && blob.find( (ps = xy2pos(x, y+1)) ) == blob.end()) border.insert(ps); if (x > 0 && blob.find( (ps = xy2pos(x-1, y )) ) == blob.end()) border.insert(ps); if (y > 0 && blob.find( (ps = xy2pos(x, y-1)) ) == blob.end()) border.insert(ps); } if (border.empty()) break; std::map blob_buf; for (const auto& pos: border) { uint16_t x = pos % (UINT16_MAX+1); uint16_t y = pos / (UINT16_MAX+1); std::set components; size_t ps; if (x+1 < width && blob.find( (ps = xy2pos(x+1, y )) ) != blob.end()) components.insert(ps); if (y+1 < height && blob.find( (ps = xy2pos(x, y+1)) ) != blob.end()) components.insert(ps); if (x > 0 && blob.find( (ps = xy2pos(x-1, y )) ) != blob.end()) components.insert(ps); if (y > 0 && blob.find( (ps = xy2pos(x, y-1)) ) != blob.end()) components.insert(ps); assert(!components.empty()); double r = 0.0, g = 0.0, b = 0.0, a = 0.0; double w = components.size(); for (const auto& comp: components) { color c = blob[comp]; r += (c.r/255.0) / w; g += (c.g/255.0) / w; b += (c.b/255.0) / w; a += (c.a/255.0) / w; } a *= alpha; blob_buf[pos] = create_color(r, g, b, a); } for (it = blob_buf.begin(); it!=blob_buf.end(); ++it) { blob[it->first] = it->second; } } std::map::iterator it; for (it = blob.begin(); it!=blob.end(); ++it) { size_t pos = it->first; uint16_t x = pos % (UINT16_MAX+1); uint16_t y = pos / (UINT16_MAX+1); size_t imgpos = y*width + x; image_buffer[imgpos] = it->second; } } { std::map::iterator cit; for (cit = image_buffer.begin(); cit!=image_buffer.end(); ++cit) { size_t pos = cit->first; color c = cit->second; if (keep_background) { color bgc = image->at(pos).c; double bgr = double(bgc.r)/255.0; double bgg = double(bgc.g)/255.0; double bgb = double(bgc.b)/255.0; double bga = double(bgc.a)/255.0; double r = c.r/255.0; double g = c.g/255.0; double b = c.b/255.0; double a = c.a/255.0; r = a*r + (1.0-a)*bgr; g = a*g + (1.0-a)*bgg; b = a*b + (1.0-a)*bgb; a = bga + (1.0-bga)*a; c.r = std::round(r*255.0); c.g = std::round(g*255.0); c.b = std::round(b*255.0); c.a = std::round(a*255.0); } image->at(pos).c = c; } } } void morph::draw_atoms(double t, std::vector *image) { std::map> colors; const am::blob *bl; size_t b = 0; std::vector pixels; while ( (bl = get_pixels(b++, t, &pixels)) != nullptr ) { size_t group = bl->group; pixel px_ave = blob2pixel(bl); while (!pixels.empty()) { pixel px = pixels.back(); size_t pos = px.y*width + px.x; pixels.pop_back(); if (pos >= image->size()) continue; unsigned char rr,gg,bb,aa; if (show_blobs == DISTINCT) { std::mt19937 gen(group); std::uniform_int_distribution uniform_dist_byte(0, 255); rr = uniform_dist_byte(gen); gg = uniform_dist_byte(gen); bb = uniform_dist_byte(gen); aa = 255; } else if (show_blobs == AVERAGE) { rr = px_ave.c.r; gg = px_ave.c.g; bb = px_ave.c.b; aa = px_ave.c.a; } else { rr = px.c.r; gg = px.c.g; bb = px.c.b; aa = px.c.a; } if (aa == 0) continue; colors[pos].push_back(create_color(rr,gg,bb,aa)); } } std::map>::iterator cit; for (cit = colors.begin(); cit!=colors.end(); ++cit) { size_t pos = cit->first; std::vector *cs = &(cit->second); size_t vsz = cs->size(); double r = 0.0, g = 0.0, b = 0.0, a = 0.0; double rsum = 0.0, gsum = 0.0, bsum = 0.0, asum = 0.0; for (size_t c = 0; cat(c).r/255.0; double sg = cs->at(c).g/255.0; double sb = cs->at(c).b/255.0; double sa = cs->at(c).a/255.0; asum += sa; rsum += sr*sa; gsum += sg*sa; bsum += sb*sa; if (c == 0) { r = sr; g = sg; b = sb; a = sa; } else { r = sa*sr + (1.0-sa)*r; g = sa*sg + (1.0-sa)*g; b = sa*sb + (1.0-sa)*b; a = a + (1.0-a)*(cs->at(c).a/255.0); } } if (blend_blobs) { r = rsum/asum; g = gsum/asum; b = bsum/asum; } if (keep_background) { color bgc = image->at(pos).c; double bgr = double(bgc.r)/255.0; double bgg = double(bgc.g)/255.0; double bgb = double(bgc.b)/255.0; double bga = double(bgc.a)/255.0; r = a*r + (1.0-a)*bgr; g = a*g + (1.0-a)*bgg; b = a*b + (1.0-a)*bgb; a = bga + (1.0-bga)*a; } image->at(pos).c = create_color(r, g, b, a); } } void morph::get_pixels(double t, std::vector *image) { image->clear(); image->reserve(width*height); for (size_t y=0; ypush_back(px); } } if (fluid && fluidsteps > 0) draw_fluid(t, image); else draw_atoms(t, image); return; } pixel morph::blob2pixel(const blob *bl) { pixel px = create_pixel(bl->x, bl->y, create_color(bl->r, bl->g, bl->b, bl->a)); if (blob_delimiter == HSP) { px.c = hsp_to_rgb(px.c); } return px; } color morph::get_background(uint16_t x, uint16_t y, double time) { if (frames.empty()) return create_color(0.0, 0.0 ,0.0 ,0.0); size_t position = xy2pos(x, y); time = normalize_time(time); double t = time; size_t frame_key = get_frame_key(t); std::map::iterator it = frames.find(frame_key); ++it; if (it == frames.end()) it = frames.begin(); size_t next_frame_key = it->first; double dt = 1.0 / double(frames.size()); t = std::max(0.0, (t - (frame_key * dt)) / dt); color c1 = get_pixel(frame_key, position).c; color c2 = get_pixel(next_frame_key, position).c; if (fading == PERLIN) { double f = 8.0; // Frequency int octaves = 8; // Octaves double bbox_w = bbox_x2 - bbox_x1 + 1.0; double bbox_h = bbox_y2 - bbox_y1 + 1.0; double perlin_x = ((x-bbox_x1) / double(bbox_w))*f; double perlin_y = ((y-bbox_y1) / double(bbox_h))*f; double lag = lag_map. octaveNoise(perlin_x, perlin_y, octaves)*0.5 + 0.5; double slope = slope_map.octaveNoise(perlin_x, perlin_y, 8)*0.5 + 0.5; return interpolate(c1, c2, lag, slope, 1.0 - t); } else if (fading == COSINE) return interpolate(c1, c2, 0.5, 0.5, 1.0 - t); return interpolate(c1, c2, 1.0 - t); } color morph::interpolate(color c1, color c2, double lag, double slope, double str) { if (fading == COSINE || fading == PERLIN) { const double pi = 3.14159265358; double s = (slope+0.1)/1.1; double l = (1.0 - s) * lag; if (str <= l ) str = 0.0; else if (str >= (l+s)) str = 1.0; else str = ((-cos((str-l)*(pi/s))+1.0)/2.0); } return interpolate(c1, c2, str); } color morph::interpolate(color c1, color c2, double c1_weight) { color c; c.r = std::round(c1_weight * double(c1.r) + (1.0 - c1_weight) * double(c2.r)); c.g = std::round(c1_weight * double(c1.g) + (1.0 - c1_weight) * double(c2.g)); c.b = std::round(c1_weight * double(c1.b) + (1.0 - c1_weight) * double(c2.b)); c.a = std::round(c1_weight * double(c1.a) + (1.0 - c1_weight) * double(c2.a)); return c; } pixel morph::interpolate(pixel px1, pixel px2, double px1_weight) { pixel px; px.x = std::round(px1_weight * double(px1.x) + (1.0 - px1_weight) * double(px2.x)); px.y = std::round(px1_weight * double(px1.y) + (1.0 - px1_weight) * double(px2.y)); px.c.r = std::round(px1_weight * double(px1.c.r) + (1.0 - px1_weight) * double(px2.c.r)); px.c.g = std::round(px1_weight * double(px1.c.g) + (1.0 - px1_weight) * double(px2.c.g)); px.c.b = std::round(px1_weight * double(px1.c.b) + (1.0 - px1_weight) * double(px2.c.b)); px.c.a = std::round(px1_weight * double(px1.c.a) + (1.0 - px1_weight) * double(px2.c.a)); return px; } point morph::interpolate(point pt1, point pt2, double pt1_weight) { point pt; double x1 = (double(UINT8_MAX+1)*pt1.s.x + pt1.s.x_fract); double y1 = (double(UINT8_MAX+1)*pt1.s.y + pt1.s.y_fract); double x2 = (double(UINT8_MAX+1)*pt2.s.x + pt2.s.x_fract); double y2 = (double(UINT8_MAX+1)*pt2.s.y + pt2.s.y_fract); double x = (pt1_weight * x1 + (1.0 - pt1_weight) * x2); double y = (pt1_weight * y1 + (1.0 - pt1_weight) * y2); pt.s.x = x / double(UINT8_MAX+1); pt.s.x_fract = x - (pt.s.x*(UINT8_MAX+1)); pt.s.y = y / double(UINT8_MAX+1); pt.s.y_fract = y - (pt.s.y*(UINT8_MAX+1)); return pt; } void morph::set_resolution(uint16_t w, uint16_t h) { width = w; height = h; } double morph::get_time(size_t frame_index, size_t total_frames_out) { if (total_frames_out == 0) return 0.0; if (finite) { if (total_frames_out == 1) { if (get_frame_count() == 0) return 0.0; return (1.0 - (1.0/double(get_frame_count())))/2.0; } double t = double(frame_index) / double(total_frames_out-1); t*= 1.0 - (1.0/double(get_frame_count())); return t; } return double(frame_index) / double(total_frames_out); } blob * morph::find_blob_by_group(size_t frame_key, size_t group) { if (!has_frame(frame_key)) return nullptr; std::vector *blobs = &(frames[frame_key].blobs); size_t bsz = blobs->size(); for (size_t i=0; i< bsz; ++i) { if (blobs->at(i)->group == group) return blobs->at(i); } return nullptr; } void morph::set_fluid(unsigned f) { if (fluidsteps != f) { fluidsteps = f; if (fluidsteps > 0 && density > 1) { set_density(1); } } } void morph::set_density(uint16_t d) { if (density != d) { density = d; identifier++; if (fluidsteps != 0 && density > 1) { set_fluid(0); } } } }