/** Storm */ local Name = "$Name$"; local Description = "$Description$"; local storm_debug = false; local streams, n_streams; local execution_index = 0, n_exec_per_loop = 1; local max_stream_len = 1000; // maximum number of intervals to be calculated for each stream of this storm local exec_interval = 10; // exec every n frames local find_mask; // Find_-condition for objects to be flung by wind local strength; // storm strength local stream_density = 20; local stream_border_dist = 20; local map, map_res1, map_res2, map_size1, map_size2, map_off1, map_off2; //local debug_map; local storm_particles; // storm particle definition, for pretty visuals local StormStream; static g_storm; func Initialize() { // singleton g_storm = this; SetPosition(); // defaults storm_particles = { Size = 1, Stretch = PV_Speed(PV_Linear(4000, 0), 0), Alpha = PV_KeyFrames(0, 0, 0, 100, 255, 1000, 255), Rotation = PV_Direction(), CollisionVertex = 1000, OnCollision = PC_Die() }; StormStream = { max_segment_stretch = 100, // maximum number of pixels per segment that can be deviated from dir in either direction max_segment_stretch_want = 5, // maximum movement back into original position that is preferred (i.e.: speed at which gaps behind sky islands close) search_steps = 10, // steps in pixels in which to search for holes to blow through search_steps_mult = 200, // multiplyer, in percent, by which search steps get larger with each iteration }; find_mask = Find_And(Find_Category(C4D_Vehicle | C4D_Living | C4D_Object), Find_Not(Find_Func("IsEnvironment"))); SetStorm(20,0, 2000); } func Clear() { // timer RemoveEffect("IntExecute", this); // helper objects for (var i=0; iRemoveObject(); streams = nil; n_streams = 0; map = nil; } private func InitMap() { // init empty wind map according to parameters // determine coordinate borders var wdt=LandscapeWidth()-1, hgt = LandscapeHeight()-1; var w1_min = Min(Min(MapXYToW1(0,0),MapXYToW1(0,hgt)),Min(MapXYToW1(wdt,0),MapXYToW1(wdt,hgt))); var w1_max = Max(Max(MapXYToW1(0,0),MapXYToW1(0,hgt)),Max(MapXYToW1(wdt,0),MapXYToW1(wdt,hgt))); var w2_min = Min(Min(MapXYToW2(0,0),MapXYToW2(0,hgt)),Min(MapXYToW2(wdt,0),MapXYToW2(wdt,hgt))); var w2_max = Max(Max(MapXYToW2(0,0),MapXYToW2(0,hgt)),Max(MapXYToW2(wdt,0),MapXYToW2(wdt,hgt))); // implement to cover complete border range map_res1 = StormStream.dir_len; map_res2 = stream_density; map_off1 = w1_min - map_res1/2; map_off2 = w2_min - map_res2/2; map_size1 = (w1_max - map_off1) / map_res1 + 1; map_size2 = (w2_max - map_off2) / map_res2 + 1; // allocate map map = CreateArray(map_size1 * map_size2); //debug_map = CreateArray(map_size1 * map_size2); return true; } private func MapXYToIdx(int x, int y) { if (x<0 || x>=LandscapeWidth() || y<0 || y>=LandscapeHeight()) return -1; return (MapXYToW1(x, y)-map_off1)/map_res1 + ((MapXYToW2(x, y)-map_off2)/map_res2) * map_size1; } private func MapXYToW1(int x, int y) { // coordinate transform from x/y space to in-wind-direction coordinate return (x*StormStream.dir_x + y*StormStream.dir_y) / StormStream.dir_len; } private func MapXYToW2(int x, int y) { // coordinate transform from x/y space to perpendicular-to-wind-direction coordinate return (x*StormStream.dir_y - y*StormStream.dir_x) / StormStream.dir_len; } // dir_*: vector pointing in storm direction. vector length is equal to segment intervals. // strength: how much to fling objects func SetStorm(int dir_x, int dir_y, int astrength) { // clear old Clear(); // add new var d = Distance(dir_x, dir_y); if (!astrength || !d) return; strength = astrength; StormStream.dir_x = dir_x; StormStream.dir_y = dir_y; StormStream.dir_len = d; // init map InitMap(); // create streams n_streams = ((Abs(LandscapeWidth()*dir_y) + Abs(LandscapeHeight()*dir_x))/d - 2*stream_border_dist) / stream_density; streams = CreateArray(n_streams); var i_stream = 0, s, x0, y0, sgn_x=1, sgn_y=1; var wdt = LandscapeWidth()-1, hgt = LandscapeHeight()-1; if (dir_y<0) { y0 = hgt; sgn_y = -1; } if (dir_x<0) { x0 = wdt; sgn_x = -1; } //Log("creating %d streams", n_streams); for (var i = 0; i0); // initial stream is blocked and will be unblocked on first execution } // Create stream data struct var stream_debug; if (storm_debug) stream_debug = CreateObjectAbove(Storm_DebugDisplay,0,0,NO_OWNER); var new_stream = { Prototype = StormStream, "x0" = x0, "y0" = y0, // "a"=a because Guenther said so "len" = len, "x" = x, "y" = y, "is_blocked" = is_blocked, "debug" = stream_debug, }; return new_stream; } private func ExecuteStream(proplist s) { //Log("ExecStream %v", s); // Execute stream against wind direction, so changes dont propagate immediately but only segment-by-segment var do_particles = !Random(3); for (var i_segment = s.len-2; i_segment>=0; --i_segment) { // propagate block if (s.is_blocked[i_segment]) { //Log("segment %d", i_segment); if (!s.is_blocked[i_segment+1]) StreamBlockVertex(s, i_segment+1); if (storm_debug) CreateParticle("SphereSpark", s.x[i_segment], s.y[i_segment], 0, 0, 36, {Size = 12}); continue; } // current segment base point var x = s.x[i_segment], y = s.y[i_segment]; var tx = s.x[i_segment+1], ty = s.y[i_segment+1]; // determine direction of current segment var vx = tx - x; var vy = ty - y; // determine where we want to go var want_vx = s.x0+(i_segment+1)*s.dir_x - x; var want_vy = s.y0+(i_segment+1)*s.dir_y - y; var want_stretch = (s.dir_x*want_vy-s.dir_y*want_vx) / s.dir_len; //if (i_segment==8) Log("%v", want_stretch); // can turn? if (Abs(want_stretch) > s.max_segment_stretch_want) { // We cannot go all the way...turn as much as we can var stretch_dir = Abs(want_stretch)/want_stretch; // sign of direction want_stretch = s.max_segment_stretch_want * stretch_dir; } // check from want_v alternating in both directions for a free path var search_range = (Abs(want_stretch) + s.max_segment_stretch); var search_off, has_found = false; for (var search_offset = 0; search_offset <= search_range; search_offset = search_offset * s.search_steps_mult/100 + s.search_steps) { // search up search_off = want_stretch - search_offset; if (search_off >= -s.max_segment_stretch) if (StreamCheckPathFree(s,x,y,search_off)) { has_found=true; break; } if (!search_offset) continue; // don't check direction -0 and +0 twice // search down search_off = want_stretch + search_offset; if (search_off <= s.max_segment_stretch) if (StreamCheckPathFree(s,x,y,search_off)) { has_found=true; break; } } // did we find a path? if (has_found) { // path found if (s.is_blocked[i_segment+1]) StreamUnblockVertex(s, i_segment+1); var new_tx = x + s.dir_x - search_off * s.dir_y / s.dir_len; var new_ty = y + s.dir_y + search_off * s.dir_x / s.dir_len; if (new_tx != tx || new_ty != ty) StreamMoveVertex(s, i_segment+1, tx, ty, new_tx, new_ty); tx = new_tx; ty = new_ty; // determine storm density at this position var map_idx = MapXYToIdx(tx, ty), local_strength; if (map_idx>=0) local_strength = map[map_idx]; else local_strength=1; // fling objects along path vx = vx * strength / s.dir_len; vy = vy * strength / s.dir_len; // - 20; var fling_objs = FindObjects(find_mask, Find_OnLine(x,y,new_tx,new_ty)), obj; for (obj in fling_objs) if (obj->GetID()==ElevatorCase) { fling_objs = []; break; } // do not fling stuff in elevator case for (obj in fling_objs) { // check if object can be pushed if (obj->Stuck()) continue; if (!PathFree(x,y,obj->GetX(),obj->GetY())) continue; // don't push through solid // determine push strength. subsequent pushes of overlapping storm pathes stack diminishingly var push_strength = strength/20,pushfx; if (pushfx=GetEffect("StormPush",obj)) { push_strength /= pushfx.count++; if (!push_strength) continue; } else { pushfx=AddEffect("StormPush", obj, 1, 5, this); if (pushfx) pushfx.count = 1; } // now push var ovx = obj->GetXDir(100); var ovy = obj->GetYDir(100); // check max speed if (Distance(ovx,ovy,vx,vy) > push_strength*6) { if (Distance(ovx,ovy) > 500) obj->Fling(BoundBy(vx-ovx,-push_strength,push_strength),BoundBy(vy-ovy,-push_strength,push_strength),100,true); else { obj->SetXDir(ovx+BoundBy(vx-ovx,-push_strength,push_strength),100); obj->SetYDir(ovy+BoundBy(vy-ovy,-push_strength,push_strength),100); } } } // Gfx if (do_particles && map_idx>=0) { if (local_strength >= 1) { // Two streams coincide here. Gfx! vx = tx-x; vy = ty-y; var v = Distance(vx,vy); vx = vx * s.dir_len / v; vy = vy * s.dir_len / v / 2; CreateParticle("Dust", PV_Random(x - 10, x + 10), PV_Random(y - 10, y + 10), PV_Random(vx * 80 / 100, vx * 120 / 100), PV_Random(vy, vy * 140 / 100), PV_Random(20, 40), storm_particles,local_strength); } } } else { // path not found. segment blocked. if (!s.is_blocked[i_segment+1]) StreamBlockVertex(s, i_segment+1); } } if (s.debug) s.debug->ShowData(s.x, s.y); } private func StreamCheckPathFree(proplist s, int x, int y, int offset) { // determine target coordinates var tx = x + s.dir_x - offset * s.dir_y / s.dir_len; var ty = y + s.dir_y + offset * s.dir_x / s.dir_len; // check path return PathFree(x,y,tx,ty); } private func StreamMoveVertex(proplist s, int i, int old_x, int old_y, int new_x, int new_y) { //Log("moving %d/%d to %d/%d", old_x, old_y, new_x, new_y); // adjust vertex s.x[i] = new_x; s.y[i] = new_y; // adjust map var idx = MapXYToIdx(old_x, old_y); if (idx>=0) --map[idx]; //DebugMapAdd(idx, Format("m%d.%d", s.y0, i)); idx = MapXYToIdx(new_x, new_y); if (idx>=0) ++map[idx]; //DebugMapAdd(idx, Format("M%d.%d", s.y0, i)); return true; } private func StreamBlockVertex(proplist s, int i) { //Log("blocking at %d/%d", s.x[i], s.y[i]); // adjust vertex s.is_blocked[i] = true; // adjust map var idx = MapXYToIdx(s.x[i], s.y[i]); if (idx>=0) --map[idx]; //DebugMapAdd(idx, Format("X%d.%d", s.y0, i)); return true; } private func StreamUnblockVertex(proplist s, int i) { //Log("unblocking at %d/%d", s.x[i], s.y[i]); // adjust vertex s.is_blocked[i] = false; // adjust map var idx = MapXYToIdx(s.x[i], s.y[i]); if (idx>=0) ++map[idx]; //DebugMapAdd(idx, Format("O%d.%d", s.y0, i)); return true; } private func DumpStreamInfo(int i) { var s = streams[i], q=[], idcs = []; for (var j=0; jGetWindEx(x+GetX(),y+GetY()); return _inherited(x,y,...); }