// SprayWall.cs // A wall for SprayCan: put this script on a flat object (a plane, a thin box) and every spray can paints it where the // player aims. Close up a small sharp dot, further back a wider, fainter cloud; quick sweeps join into lines. A scene // can have several walls, each with this script. Everybody sees the same paint, also players who join later. // // Set up: WallMaterial (which material shows the paint), WallWidth (the wall's width in metres, for the spray size), // CanvasSize and WallColor; optionally a ClearButton (any object to click) that paints the wall clean again. using IHXR.Simulation; public class SprayWall : ScriptBase { protected override bool SyncTransformStateByDefault => false; [Tooltip("Which material of this object shows the paint (0 = the first).")] public int WallMaterial = 0; [Tooltip("The wall's width in metres (for the spray size).")] public float WallWidth = 4f; [Tooltip("Paint picture size in pixels (64 to 1024).")] public int CanvasSize = 1024; [Tooltip("The wall's colour before any paint.")] public Color WallColor = new Color(0.92f, 0.92f, 0.9f); [Tooltip("See-through background: only the paint shows (WallColor is not used), the rest of the surface is see-through (paint on glass, a decal).")] public bool TransparentBackground = false; [Tooltip("Optional object to click that paints the wall clean again.")] public SceneObject ClearButton; private ScriptCanvas canvas; private readonly Dictionary strokes = new Dictionary(); // sprayer -> last x, y, time public override void Start() { int size = Mathf.Clamp(CanvasSize, 64, 1024); canvas = CreateCanvas(WallMaterial, size, size, WallColor, transparentBackground: TransparentBackground).SetUpdateRate(20); ListenForScriptEvent("SprayPaint", (ScriptEventInfo info) => OnPaint(info.Data)); if (ClearButton != null && !ClearButton.IsNull) MakeInteractable(ClearButton, click => { canvas.Clear(); strokes.Clear(); }, true, null, true, 10f, "Clean the wall"); } private bool IsMe(string id) => !string.IsNullOrEmpty(id) && string.Equals(id, Self.ObjectId, System.StringComparison.OrdinalIgnoreCase); // The spray hit this wall, or a part of it (a model's mesh), or the wall is part of what was hit. private bool HitsMe(ScriptEventData data) { if (IsMe(data.GetString("objectId")) || IsMe(data.GetString("rootObjectId"))) return true; foreach (string id in data.GetString("objectIds").Split('|')) if (IsMe(id)) return true; return false; } private void OnPaint(ScriptEventData data) { if (!HitsMe(data)) return; // another wall int material = data.GetInt("material"); if (material != WallMaterial && material != 0) return; float u = data.GetFloat("u"), v = data.GetFloat("v"), distance = data.GetFloat("distance"); float maxDistance = Mathf.Max(0.1f, data.GetFloat("maxDistance", 2.5f)); Color paint = new Color(data.GetFloat("r"), data.GetFloat("g"), data.GetFloat("b")); Paint(data.GetString("sprayer"), u * canvas.Width, (1f - v) * canvas.Height, distance, maxDistance, data.GetFloat("nozzle", 0.03f), data.GetFloat("spread", 0.18f), paint); } // From the sprayer's last spot to this one (so a quick sweep is a line, not dots): a soft round core and a scatter of // fine droplets around it, both fainter and wider the further away the can is. private void Paint(string sprayer, float x, float y, float distance, float maxDistance, float nozzle, float spread, Color colour) { float pixelsPerMetre = canvas.Width / Mathf.Max(0.1f, WallWidth); float radius = Mathf.Max(1.5f, (nozzle + spread * distance) * 0.5f * pixelsPerMetre); float strength = Mathf.Clamp01(1f - distance / maxDistance); float now = Time.time; float[] last; bool joined = strokes.TryGetValue(sprayer ?? "", out last) && now - last[2] < 0.4f; float fromX = joined ? last[0] : x, fromY = joined ? last[1] : y; float length = Mathf.Sqrt((x - fromX) * (x - fromX) + (y - fromY) * (y - fromY)); int steps = Mathf.Clamp(Mathf.CeilToInt(length / Mathf.Max(1f, radius * 0.5f)), 1, 12); Color core = new Color(colour.r, colour.g, colour.b, 0.12f + 0.55f * strength); Color mist = new Color(colour.r, colour.g, colour.b, 0.1f + 0.5f * strength); for (int i = 1; i <= steps; i++) { float t = (float)i / steps; int px = Mathf.RoundToInt(fromX + (x - fromX) * t), py = Mathf.RoundToInt(fromY + (y - fromY) * t); canvas.Dot(px, py, Mathf.Max(2, Mathf.RoundToInt(radius * 1.2f)), core, CanvasBrush.Soft); canvas.Spray(px, py, Mathf.Max(2, Mathf.RoundToInt(radius * 1.6f)), Mathf.Clamp(Mathf.RoundToInt(radius * 3f), 6, 400), mist); } strokes[sprayer ?? ""] = new[] { x, y, now }; if (strokes.Count > 64) strokes.Clear(); } }