/*
---------------------------------------------------------------------------------------------

:: UV VERTEX PIXEL SNAP MAXSCRIPT UTILITY :: 

Release Candidate 4 (version 0.994)
18 MAY 2026

- Azure Midsummer -
https://millenia3d.net

---------------------------------------------------------------------------------------------

USAGE:

Preferably, put in (3ds max install folder)/scripts, then execute it to initialise the maxscript.

To assign a hotkey: Customize > Hotkey Editor > Category: "Millenia3D" > M3D - Pixel Snap UVs.
I personally like Ctrl + Alt + Shift + P , but it's your call!
Hitting the hotkey will pop up the interface (or close it, if it is already open).

Alternatively/additionally Customize > Customize User Interface > Toolbars, 
and drag/drop M3D - Pixel Snap UVs to a toolbar of your choice, but I prefer the hotkey method.

---------------------------------------------------------------------------------------------
This script is released and licensed permissively under CC0
(Creative Commons 0), no rights are reserved by the author.
I fully welcome you to edit and iterate on this utility.
If you do end up improving on it and releasing/using it in any way, 
I do appreciate credit, but it is up to you, dear reader! :)
---------------------------------------------------------------------------------------------
*/

macroScript UVPixelSnap
    category:"Millenia3D"
    buttonText:"M3D - Pixel Snap UVs"
    tooltip:"M3D - Pixel Snap UVs"
(
    rollout uvPixelSnapperRollout "::: Snap UV coordinates to nearest pixels! :::"
    (
					label sep0 "" align:#center
        label desc1 "Requires UVW Unwrap modifier on target object," align:#center
		label desc2 "and only affects topmost UVW Unwrap modifier in stack." align:#center
		label desc3 "Operates on ONE target object, future versions may support multiple." align:#center
					label sep1 "" align:#center
        spinner texWidth  "WIDTH  ::: "  range:[1,32768,256] type:#integer width:96 align:#center
        spinner texHeight "HEIGHT ::: "  range:[1,32768,256] type:#integer width:96 align:#center
					label sep2 "" align:#center
        checkbox chkSnapToCentre "Tick this to snap to pixel centre instead of corner" checked:false align:#center
					label sep3 "" align:#center
        button btnSnap "This is the button that does the thing!" width:128 height:64 align:#center
					label sep4 "" align:#center
        label credit1 "::: © Azure Midsummer (Millenia3D) :::" align:#center
					label sep5 "" align:#center
        label credit2 "License - Creative Commons 0 (CC0)" align:#center
        label credit3 "No rights reserved - credit appreciated. " align:#center
					label sep6 "" align:#center
        label credit4 "Version RC4 (0.994)" align:#center
        label credit5 ":: 18 May 2026 ::" align:#center
        label credit6 "https://millenia3d.net"

        on btnSnap pressed do
        (
            if selection.count == 0 then
            (
                messageBox "ERROR: No object selected."
                return()
            )

            if selection.count > 1 then
            (
                messageBox ("ERROR: " + (selection.count as string) + " objects selected. Please select a SINGLE target object and try again.")
                return()
            )

            obj = selection[1]

            unwrapMod = undefined
            for m in obj.modifiers do
            (
                if classOf m == Unwrap_UVW then
                (
                    unwrapMod = m
                    exit
                )
            )

            if unwrapMod == undefined then
            (
                messageBox "ERROR: Target object requires Unwrap UVW modifier in its modifier stack. Please add Unwrap UVW modifier and try again."
                return()
            )

            numVerts = unwrapMod.numberVertices()

            for i = 1 to numVerts do
            (
                pos = unwrapMod.getVertexPosition (sliderTime) i

                if chkSnapToCentre.checked then
                (
                    snappedU = (floor(pos.x * texWidth.value) + 0.5) / texWidth.value
                    snappedV = (floor(pos.y * texHeight.value) + 0.5) / texHeight.value
                )
                else
                (
                    snappedU = floor(pos.x * texWidth.value + 0.5) / texWidth.value
                    snappedV = floor(pos.y * texHeight.value + 0.5) / texHeight.value
                )

                newPos = [snappedU, snappedV, pos.z]
                unwrapMod.setVertexPosition2 (sliderTime) i newPos false false
            )

            unwrapMod.UpdateView()

            snapType = if chkSnapToCentre.checked then "::: CENTRES :::" else "::: CORNERS :::"

            messageBox (" ::: " + obj.name + " ::: " + "

" + (numVerts as string) + " UV vertices pixel-snapped, to nearest" + "

" + snapType + "

" + "at a target resolution of" + "

" + "::: " + (texWidth.value as string) + " x " + (texHeight.value as string) + " :::")
        )
    )

    on execute do
    (
        if uvPixelSnapperRollout.open then
            destroyDialog uvPixelSnapperRollout
        else
            createDialog uvPixelSnapperRollout 350 440
    )
)