Practical Space Mapping for interaction
Space mapping is the process to establish a correspondence between elements from distinct spaces. Computer graphic systems like 3ds Max render a virtual three-dimensional space as seen through a camera on a two-dimensional space, represented by the screen monitor. There is therefore a relationship between 3D space points and 2D screen pixels. Every space is defined by an orthogonal axis system following the right-hand rule.

Spaces Representation of a standard 3ds Max scene. View is shown as a standard Camera to support the explanation. The Screen frame representation is fictional to help figuring how the system works.






[*] in MaxScript language "View Coordinate System" is called "Screen Coordinate System". Mouse space isn't considered a coordinate system at all. It is confusing, therefore in this article more suggestive names have been chosen. It doesn't affects programming functionality, but only the way to name variables and figure out their meaning.



Space Mapping: WorldCS \(\to\) ScreenCS The most basic mapping, allows to express in ScreenCS the coordinates of a point in the scene expressed in WorldCS. Identity matrix (Matrix3 1) represents the World transform matrix.
-- Create a Point Helper
hPoint = Point()

--------------------------------------------------------------------------------
-- Space Mapping: WorldCS -> ScreenCS
--------------------------------------------------------------------------------

-- Point coordinates in WorldCS (system units)
p3PointWorldPos = hPoint.position

-- WorldCS (system units) -> ScreenCS (pixels)
gw.setTransform (Matrix3 1)
p3Temp = gw.transPoint p3PointWorldPos
p2PointScreenPos = [(ceil p3Temp).x, (ceil p3Temp).y]



Space Mapping: WorldCS \(\to\) ViewCS \(\to\) ScreenCS Create a point helper in the scene and place wherever needed, it provides the WorldCS position to express in other coordinate systems. The three steps show how to get point helper coordinates relative to ViewCS in system units, and in ScreenCS in pixels.

gw.setTransform sets the active viewport's graphics window transformation matrix to the specified matrix3 value, and updates the modeling coordinates to normalized projection coordinates matrix. Identity matrix (Matrix3 1) represents the World transform matrix.

In the step from ViewCS to ScreenCS the Z axis value is discarded as being not meaningful. The ceil rounding of X and Y got from gw.transPoint gives the most accurate results.
-- Create a Point Helper
hPoint = Point()

--------------------------------------------------------------------------------
-- Space Mapping: WorldCS -> ViewCS -> ScreenCS
--------------------------------------------------------------------------------

-- Point coordinates in WorldCS (system units)
p3PointWorldPos = hPoint.position

-- WorldCS (system units) -> ViewCS (system units)
p3PointViewPos = p3PointWorldPos * getViewTM()

-- ViewCS (system units) -> ScreenCS (pixels)
gw.setTransform (inverse (getViewTM()))
p3Temp = gw.transPoint p3PointViewPos
p2PointScreenPos = [(ceil p3Temp).x, (ceil p3Temp).y]



Space Mapping: ScreenCS \(\to\) ViewCS \(\to\) WorldCS The passage from a 2D to a 3D system requires Z axis value in addition to X and Y axis values provided. Mapping a ScreenCS to ViewCS can be pictured as projecting the mouse pointer on a virtual plane parallel to the current view plane. The Z axis value defines the projection plane distance from ViewCS origin.

To manage this process the Z axis value is seldom defined by hand, more often there's something in the scene needing to be manipulated. Create a point helper in the scene and use it as a reference, the virtual projection plane will pass through it. The distance between View and reference point positions gives the Z axis value that must be corrected by the cosine of the angle between View axis and the direction from View to the point helper.
-- Create a Point Helper
hPoint = Point()
-- Point coordinates in WorldCS (system units)
p3PointWorldPos = hPoint.position

-- Get View position in WorldCS
p3ViewWorldPos = (inverse(getViewTM())).row4
-- Get View direction in WorldCS
p3ViewWorldDir = -(inverse(getViewTM())).row3

-- Get the angle between the View axis and the Point Helper
fViewAngle = acos (dot (normalize (p3PointWorldPos - p3ViewWorldPos)) p3ViewWorldDir)
-- Correct the distance between View and a Point Helper
fViewDepth = (distance p3PointWorldPos p3ViewWorldPos) * (cos fViewAngle)

--------------------------------------------------------------------------------
-- Space mapping: ScreenCS -> ViewCS -> WorldCS
--------------------------------------------------------------------------------

-- Mouse coordinates in ScreenCS (pixels)
p2MouseScreenPos = mouse.pos

-- ScreenCS (pixels) -> ViewCS (system units)
p3MouseViewPos = mapScreenToView p2MouseScreenPos (-fViewDepth)

-- ViewCS (system units) -> WorldCS (system units)
p3MouseWorldPos = p3MouseViewPos * (inverse (getViewTM()))



Space Mapping: WorldCS \(\to\) GridCS \(\to\) ScreenCS Create a point helper in the scene and place where needed, it provides the WorldCS position to express in other coordinate systems. Create a grid helper, place and orient it as needed. If the purpose is to get the screen coordinates of the starting point, grid helper transformations aren't relevant and provide the same results in any case, otherwise set them properly. The three steps show how to get point helper coordinates relative to GridCS in system units, and in ScreenCS in pixels.

In the step from GridCS to ScreenCS the Z axis value is discarded as being not meaningful. The ceil rounding of X and Y got from gw.transPoint gives the most accurate results.
-- Create a Point Helper
hPoint = Point()

-- Create a Grid Helper
hGrid = Grid()
-- Orient the Grid Helper like View
hGrid.rotation = (getViewTM()).rotation

-- Activate the new Grid Helper
activeGrid = hGrid
-- Activate the World Construction Plane
activeGrid = undefined

--------------------------------------------------------------------------------
-- Space mapping: WorldCS -> GridCS -> ScreenCS
--------------------------------------------------------------------------------

-- Point coordinates in WorldCS (system units)
p3PointWorldPos = hPoint.position

-- WorldCS (system units) -> GridCS (system units)
p3PointGridPos = p3PointWorldPos * (inverse hGrid.transform)

-- WorldCS (system units) -> ScreenCS (pixels)
gw.setTransform (hGrid.transform)
p3Temp = gw.transPoint p3PointGridPos
p2PointScreenPos = [(ceil p3Temp).x, (ceil p3Temp).y]



Space Mapping: ScreenCS \(\to\) GridCS \(\to\)WorldCS GridCS offers a greater flexibility than ViewCS, since a grid helper can be placed anywhere in the scene and oriented in any direction. For this discussion let's assume to replicate the same path walked with View, where grid helper replicates the virtual projection plane, oriented like the view plane, with its origin in a desired position in space.
-- Create a Point Helper
hPoint = Point()

-- Create a Grid Helper
hGrid = Grid()

-- Orient the Grid Helper like View
hGrid.rotation = (getViewTM()).rotation -- or whatever needed
-- Position the Grid Helper like the Point Helper
hGrid.position = hPoint.position -- or whatever needed

-- Activate the new Grid Helper
activeGrid = hGrid
-- Activate the World Construction Plane
activeGrid = undefined

--------------------------------------------------------------------------------
-- Space mapping: ScreenCS -> GridCS -> WorldCS
--------------------------------------------------------------------------------

-- Mouse coordinates in ScreenCS (pixels)
p2MouseScreenPos = mouse.pos

-- ScreenCS (pixels) -> GridCS (system units)
p3MouseGridPos = gw.getPointOnCP p2MouseScreenPos -- based on the currently active grid

-- GridCS (system units) -> WorldCS (system units)
p3MouseWorldPos = gw.mapCPToWorld p3MouseGridPos
p3MouseWorldPos = p3MouseGridPos * hGrid.transform



Space Mapping: LocalCS \(\leftrightarrow\) WorldCS When working with sub-objects, for example vertices in an editable poly object, their coordinates are often expressed in the node LocalCS. It is required to express them in WorldCS before every other transformation.
-- Object LocalCS -> WorldCS
p3PointWorld = p3PointLocal * node.transform

-- WorldCS -> Object LocalCS
p3PointLocal = p3PointWorld * (inverse (node.transform))
A vector can be transformed from one space to another with above rules by using the proper rotation matrix.
-- Object LocalCS -> WorldCS
p3VectorWorld = p3VectorLocal * (node.transform.rotationPart as Matrix3)

-- WorldCS -> Object LocalCS
p3VectorLocal = p3VectorWorld * (inverse (node.transform.rotationPart as Matrix3))