Difference between revisions of "SculptGenMax"

From Second Life Wiki
Jump to navigation Jump to search
Line 24: Line 24:


[[Image:http://files.liferain.com/downloads/wp-content/uploads/2007/12/sculptgenmax-in-3dsmax.jpg]]
[[Image:http://files.liferain.com/downloads/wp-content/uploads/2007/12/sculptgenmax-in-3dsmax.jpg]]
== The Script ==
-- ***************************************************************************************
--                                SculptGenMax
--                          Copyright 2007 Shack Dougall
--
-- This is a MAXScript written for 3ds Max 9.  It might work for earlier versions of 3ds Max,
-- but this has not been tested.  It does not work for Max5.
--
-- The script generates a Second Life sculptie texture from the selected object.
--
-- Revision History
-- ====================
-- 0.2 First released version
-- 0.2.1 Added proportional
-- 0.2.2 Fixed rotation issues
--
-- 0.3      Shifted bitmap by 1 pixel in X and Y.  changed defaults "NO SMOOTH" 128x128
--          no longer have to move the top and bottom vertices when you create the object.
--
-- 0.3.1 added x:, y:, and z: display boxes, so that you can know what dimensions to use
-- on your prim if you have the proportional feature turned off
--
-- 0.4 a) sculptmap now matches the 3dsMax UV map.
-- You should now be able to bake textures using the UV map and they
-- should line up with the sculptie in SL. NOTE: because I now use the UV map, if
-- your sculptie looks inside out, then your UV map is probably wrong.
-- b) only one vertex is used in the top and bottom row of the cylinder.  The vertices
-- used are the top and bottom vertices on the seam of the UV map,
-- i.e., the vertices at the top left and bottom left of the UV map.
-- c) sculptmap now has one more row of data.  The cylinder should be 32x32.
-- sculptmap conforms to http://forums.secondlife.com/showpost.php?p=1522861
-- d) smooth feature removed.  wasn't helpful.
-- e) 32x32 option removed.  64x64 is the lowest resolution sculptmap supported.
-- I might change this later, but it needs work to support 32x32 properly.
-- f) now supports a NURBS cylinder. See description below.
-- 0.4.1 a) fixed bug with UV map. Now handles UV maps correctly that have more vertices than are
-- in the mesh.
-- b) Poly sphere 32x32 supported.
-- c) NURBS Sphere 32x32 supported.
-- d) added drop down to allow you to change the uv map channel that is used.
-- *** CREATING THE POLY/MESH OBJECT ***
-- The script assumes a specific topology for the object.  You **CANNOT** use the script on
-- an arbitrary mesh.
--
-- Create the following object as a starting point:
-- 1) create a cylinder with 32 height segments and 32 side segments
-- 2) Apply an Edit Mesh modifier
-- 3) delete the vertex in the top and the vertex in the bottom
--
-- Now, you can move the vertices any way that you like.
--
-- When you're done, make sure that the object is selected and generate the texture.
--
-- NOTE: as of Version 0.4.1, you can also use a sphere. You need a 32x32 poly or mesh sphere.
-- The easiest way to create this is to follow the directions for the NURBS sphere
--      and then convert to mesh or poly.
-- *** CREATING THE NURBS OBJECT ***
-- As of Version 0.4, you can also use a NURBS cylinder as your starting object.
-- As of Version 0.4.1, you can use a NURBS Sphere.
--
-- 1) Create a cylinder or sphere
-- 2) Convert to NURBS
-- 3) If cylinder (skip for sphere), expand the "NURBS Surface" modifier and select "Surface" under it.
-- Select and delete the top and bottom of the cylinder.
-- 3) Go back to "NURBS Surface".
-- In the NURBS "Surface Approximation" Rollout, select Tessellation Method "Regular"
-- and set U Steps and V Steps to 32.
-- Now, you can NURB away.  Hint: Select NURBS Surface>Surface CV to see control vertices
-- When you're done, make sure that the object is selected and generate the texture.
-- *** SCRIPT INSTALLATION ***
-- MAXScript>Run Script...
-- in the dialog, select the script and press Open.
-- Customize>Customize User Interface...
-- click on the Toolbars tab. Select Category "SecondLife".  Drag SculptGenMax to one of the
-- toolbars.
--
-- *** RUNNING THE SCRIPT ***
-- Install script
-- Create object, modify it as desired, and select it.
-- Press SculptGenMax in the toolbar where you installed it.
-- A dialog will pop up.  Select desired bitmap resolution.  Then on the dialog's menu bar,
-- File>Generate Sculpt Texture
-- this will generate the texture in the dialog.
-- File>Save as..
-- to save the texture to a file.
--
-- *** UNINSTALLING THE SCRIPT ***
-- Right click on the SculptGenMax button in the toolbar and select "Delete Button".
-- Find the file "SecondLife-SculptGenMax.mcr" and delete it.
-- Restart 3ds Max
--
-- ***************************************************************************************
macroScript SculptGenMax category:"SecondLife"
(
local PROG_NAME = "SculptGenMax"
local PROG_VERSION = "0.4.1"
global SculptGenMax_CanvasRollout
try(destroyDialog SculptieGenMax_CanvasRollout)catch()
local default_keep_proportions = true -- determines the default value of the "proportional" checkbox
local default_uv_map_channel = 1 -- the default map channel from which we get the uv coordinates.
local default_resolution = 2 --determines the default selection of the resolution listbox
local defaultBitmapSize = 128 --determines the default resolution.
-- IMPORTANT: if you change default_resolution, then you must also change defaultBitmapSize
local keep_proportions = default_keep_proportions -- if true, we preserve the proportions of the model
-- otherwise, each dimension scales independently
local MAX_BITMAP = 256
local BITMAP_X = BITMAP_Y = defaultBitmapSize -- dimensions of the bitmap to be generated
local MESH_ROWS = 31  -- number of rows in mesh, excluding poles
local MESH_COLS = 32  -- number of columns in mesh
local UV_MAP_CHANNEL = default_uv_map_channel -- The map channel from which we get the UV coordinates  1 by default.
local POLE_V_LIMIT = #( 0.02, 0.98 )  -- A vertex must have a V value less than 0.02 or greater than
-- 0.98 in order to be considered a pole vertex.
-- A 64x64 bitmap is the smallest fully-specified sculptmap.  So, we need to know how much
-- bigger our current bitmap is than a 64x64 map
local bitMapTo64RatioX = BITMAP_X / 64;
local bitMapTo64RatioY = BITMAP_Y / 64;
local HORIZ_FLIP = false;  -- set this to true to flip the texture horizontally
local sculptBitmap = bitmap BITMAP_X BITMAP_Y color:white  -- generate the bitmap here
  local blankBitmap = bitmap BITMAP_X BITMAP_Y color:white  -- used to reset the dialog
local minDimensions = [0,0,0] --minimum X, Y, and Z values in the mesh
local maxDimensions = [0,0,0] --maximum X, Y, and Z values in the mesh
local maxScale -- the maximum difference between the min and max in any single dimension
local propPrimSize = [0,0,0]  --if proportional is off, this is the size we should make the prim in SL
-- so that it is proportional
local selectedPoly  -- contains a copy of the user's selection. we read the mesh data from here
-- it must be deleted after we are done or it will show up as a new object
-- in the scene
local firstRow = #() -- the indexes of the vertices in the top row of the bitmap
local lastRow = #() -- the indexes of the vertices in the bottom row of the bitmap
-- prevRow and currRow are used while iterating over the mesh
local prevRow = #() -- contains the indexes of the vertices in the previous full row of the bitmap
local currRow = #() -- contains the indexes of the vertices in the row of the bitmap that we are
-- drawing right now.
local edgeCount = #( 0, 0, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 )
--local debug = newScript()
-- uncomment previous line if you want to use print statements for debugging
-- examples of debug statements.  These will show up in the debug window if the debug line is uncommented
-- print "foo" to:debug
-- format "x = %\n" vertex.x to:debug
rcMenu CanvasMenu
(
subMenu "File"
(
menuItem generate "Generate Sculpt Texture"
menuItem save_as "Save As..."
separator file_menu_1
menuItem quit_tool "Quit Dialog" 
)
subMenu "Edit" ( )
subMenu "Help"
(
menuItem about_tool "About..."
)
fn calcPrimSizeToKeepProportions =
(
propPrimSize = [0, 0, 0]
propPrimSize.x = (maxDimensions.x - minDimensions.x) / maxScale
propPrimSize.y = (maxDimensions.y - minDimensions.y) / maxScale
propPrimSize.z = (maxDimensions.z - minDimensions.z) / maxScale
propPrimSize
)
fn scaleX xValue xMin xMax =
(
local xScale = (xValue - xMin) / (xMax - xMin)
if keep_proportions then
(
xScale = (xScale - 0.5) * ((xMax - Xmin) / maxScale) + 0.5
)
local result = floor (xScale * 255 + 0.5)
result
) --scaleX
fn vertexToColor vertPos =
(
local vertColor = [127,127,127]
vertColor.x = scaleX vertPos.x minDimensions.x maxDimensions.x
vertColor.y = scaleX vertPos.y minDimensions.y maxDimensions.y
vertColor.z = scaleX vertPos.z minDimensions.z maxDimensions.z
vertColor
) --vertexToColor
fn maxOf value1 value2 =
(
local maxValue
if value1 >= value2 then
maxValue = value1
else
maxValue = value2
maxValue
) -- maxOf
fn minOf value1 value2 =
(
local minValue
if value1 <= value2 then
minValue = value1
else
minValue = value2
minValue
) -- minOf
fn getVertexPos vertId =
(
local aPos = selectedPoly.GetVertex vertId
(in coordsys world aPos) * selectedPoly.objecttransform
)
fn getUVMapVerticesFor vertId =
(
-- we have to do this because the vertIds in the poly do not correspond directly to the
-- vertIds in the UV map.  In fact, the relationship between poly verts and uv map verts is
-- one to many.  i.e., each poly vert can be represented by 1 or more uv map verts.
--
-- to find the map verts, we get the poly faces for the poly vert.  Then, we get the
-- corresponding uv map faces.  And this gives us the map verts.
--
-- this works because the faceIds in the poly have a one-to-one correspondence with
-- the faceIds in the UV map.
--
-- get the poly vert's faces.
local faceIds = (polyOp.getFacesUsingVert selectedPoly vertId) as array
local mapVerts = #()
for faceId in faceIds do
(
-- get the vertIds for this poly face
local polyFaceVerts = polyOp.getFaceVerts selectedPoly faceId
-- get the vertIds for this map face
local mapFaceVerts = polyOp.getMapFace selectedPoly UV_MAP_CHANNEL faceId
-- find the index of the poly vert in the poly face.
polyVertIndex = findItem polyFaceVerts vertId
-- get the cooresponding mapVertId by using the same index on the mapFace
local mapVertId = mapFaceVerts[polyVertIndex]
-- put the mapVertId in mapVerts, if we haven't already
if findItem mapVerts mapVertId == 0 do append mapVerts mapVertId
)
-- at the end of this we have an array of all the map vertices that correspond to the poly vert
mapVerts
)
fn getUVpos vertId =
(
-- returns the uv coords for the vertex.  If the vertex has more than one UV coordinate,
-- then we return the one with the lowest U value.
local mapVerts = getUVMapVerticesFor vertId
local uvPos = [2,0]
for mapVert in mapVerts do
(
local tempPos = polyOp.getMapVert selectedPoly UV_MAP_CHANNEL mapVert
if tempPos.x < uvPos.x then uvPos = tempPos
)
uvPos
)
fn pass1 =
(
-- in Pass 1, we iterate over all vertices, collecting max/min x,y, and z
-- and we find the two end points
firstRow = #()
lastRow = #()
firstRowMinU = 1.0  -- set minU to 1.  At least one U value should be less than this.
lastRowMinU = 1.0
local numVertices = selectedPoly.GetNumVertices()
--format "numVertices = %\n" numVertices to:debug
for i = 1 to numVertices do
(
pos = getVertexPos i
if i == 1 then
(
minDimensions = copy pos
maxDimensions = copy pos
)
minDimensions.x = minOf minDimensions.x pos.x
minDimensions.y = minOf minDimensions.y pos.y
minDimensions.z = minOf minDimensions.z pos.z
maxDimensions.x = maxOf maxDimensions.x pos.x
maxDimensions.y = maxOf maxDimensions.y pos.y
maxDimensions.z = maxOf maxDimensions.z pos.z
numEdges = selectedPoly.GetVertexEdgeCount i
-- find the pole vertices by their position in the UV map.
local uvPos = getUVpos i
if uvPos.y < POLE_V_LIMIT[1] or uvPos.y > POLE_V_LIMIT[2] then
(
if uvPos.y > 0.5 then
(
if uvPos.x < firstRowMinU then
insertItem i firstRow 1
else
append firstRow i
firstRowMinU = minOf firstRowMinU uvPos.x
)
else
(
if uvPos.x < lastRowMinU then
insertItem i lastRow 1
else
append lastRow i
lastRowMinU = minOf lastRowMinU uvPos.x
)
)
)
maxScale = maxOf (maxDimensions.x - minDimensions.x) (maxDimensions.y - minDimensions.y)
maxScale = maxOf maxScale (maxDimensions.z - minDimensions.z)
calcPrimSizeToKeepProportions()
) -- pass1
fn getOtherSideOf aVertex edgeNum =
(
local edgeId = selectedPoly.GetVertexEdge aVertex edgeNum
anotherVertex = selectedPoly.GetEdgeVertex edgeId 1
if anotherVertex == aVertex then
anotherVertex = selectedPoly.GetEdgeVertex edgeId 2
anotherVertex
) -- getOtherSideOf
fn connectsToPreviousRow aVertex =
(
local numEdges = selectedPoly.GetVertexEdgeCount aVertex
local isConnected = false
for i = 1 to numEdges do
(
otherVertex = getOtherSideOf aVertex i
isConnected = findItem prevRow otherVertex != 0
if isConnected then exit
)
isConnected
) -- connectsToPreviousRow
fn isNotInPreviousTwoRows aVertex =
(
(findItem prevRow aVertex == 0) and (findItem currRow aVertex == 0)
) -- isNotInPreviousTwoRows
fn getFirstVertexInNextRow =
(
local currRowStart = currRow[1]
local numEdges = selectedPoly.GetVertexEdgeCount currRowStart
local firstVertex = -1
for i = 1 to numEdges do
(
aVertex = getOtherSideOf currRowStart i
if isNotInPreviousTwoRows aVertex then
(
firstVertex = aVertex
exit
)
)
firstVertex
) -- getFirstVertexInNextRow
fn getNextVertexInRow curVertex =
(
local curUV = getUVpos curVertex
local curNumEdges = selectedPoly.GetVertexEdgeCount curVertex
local nextVertex = -1
for i = 1 to curNumEdges do
(
aVertex = getOtherSideOf curVertex i
local aUV = getUVpos aVertex
--the following conditionals might be more complicated than necessary.
--the first IF was the original algorithm.
--the nested IF is a revised algorithm to use the UV map for guidance.
--it might be that the first IF can be simplified, but I haven't gotten around to it.
if connectsToPreviousRow aVertex and isNotInPreviousTwoRows aVertex then
(
if aUV.x > curUV.x and aUV.x - curUV.x < 0.5 then
(
nextVertex = aVertex
exit
)
)
)
--if nextVertex is -1, then we might have a problem
--except that we always call getNextVertexInRow one more time than we need to
--so it is -1 at the end of every row.  But we don't use that value.
nextVertex
) -- getNextVertexInRow
fn setInBitmap bitmapPos color =
(
setPixels sculptBitmap bitMapPos #(color)
)
fn putInBitmap vertId meshRow meshCol =
(
-- bitmap: x and y start at 0.  x is horizontal, y is vertical
-- mesh: row and col start at 0. row is vertical, col is horizontal
--
-- middle meshRows are put in the 64x64 bitmap at odd Y values 1..61 (31 rows total)
-- X values for middle rows are even (32 columns total)
--    so they go [0,1], [2,1]...[62,1]
--      then [0,3], [2,3]...[62,3]  .... [0,61], [2,61]...[62,61]
--
-- Poles are defined at [32,0] and [32,63].  Giving us 33 rows with data in a 64x64 bitmap
-- even Y values 2..62 are unused. (31 rows unused)
-- in the following line *bitmapTo64Ratio compensates for the size of bigger bitmaps
-- while (meshRow*2) + 1 ensures that the rows are odd.
--    and meshCol*2 ensures that the columns are even
local bitmapPos = [(meshCol * 2) * bitmapTo64RatioX, ((meshRow * 2) + 1) * bitmapTo64RatioY]
if HORIZ_FLIP then
(
bitmapPos.x = BITMAP_X - bitmapPos.x - 2 * bitmapTo64RatioX
)
local vertPos = getVertexPos vertId
local vertColor = vertexToColor vertPos
-- shift is a hack to reduce JPEG compression artifacts
local shift = 0
if bitmapTo64RatioX > 1 then
shift = -1
-- the loops allow us to write more than one pixel for each mesh vertex.
-- for example, if the mesh is 32x32 and the bitmap is 64x64, then each vertex in
-- the mesh is represented by 4 pixels in the bitmap
for xOffset = 0 + shift to bitmapTo64RatioX * 2 - 1 do
(
for yOffset = 0 + shift to bitmapTo64RatioY * 2 - 1 do
(
local x = bitmapPos.x + xOffset
local y = bitmapPos.y + yOffset
if x >= 0 then
setInBitmap [x, y] vertColor
)
)
) -- putInBitmap
fn processEndPoints vertexArray yStart =
(
local vertId = vertexArray[1]  -- arbitrarily choose the first one, they're all the same
local vertPos = getVertexPos vertId
local vertColor = vertexToColor vertPos
for x = 0 to BITMAP_X - 1 do
(
for yOffset = 0 to bitmapTo64RatioY - 1 do
(
setInBitmap [x, yStart + yOffset] vertColor
)
)
) -- processEndPoints
fn refreshDisplay =
(
SculptGenMax_CanvasRollout.theCanvas.bitmap = sculptBitmap
)
fn pass2 =
(
-- in Pass 2, we start with an endpoint and traverse the mesh one row at a time.
-- row and column numbers start at 0
-- if the bitmap resolution is greater than the mesh resolution, then we just copy the
-- a mesh point into surrounding pixels.  Thus, each point in a 32x32 mesh will
-- be represented by 4 identical pixels in a 64x64 bitmap.
local currVertex
processEndPoints firstRow 0  -- put the first row in the bitmap
processEndPoints lastRow (BITMAP_Y - bitmapTo64RatioY) -- put the last row in the bitmap
currRow = firstRow
prevRow = #()
for rowNum = 0 to MESH_ROWS-1 do  -- loop through the middle rows
(
currVertex = getFirstVertexInNextRow()
prevRow = currRow
currRow = #()
for colNum = 0 to MESH_COLS-1 do
(
putInBitmap currVertex rowNum colNum
append currRow currVertex
currVertex = getNextVertexInRow currVertex
)
refreshDisplay()
)
) -- pass2
fn updatePrimSizeTextBoxes =
(
SculptGenMax_CanvasRollout.prim_size_x.text = propPrimSize.x as string
SculptGenMax_CanvasRollout.prim_size_y.text = propPrimSize.y as string
SculptGenMax_CanvasRollout.prim_size_z.text = propPrimSize.z as string
)
fn makeHorizStripeTexture =
(
-- this function creates a texture map with a green horizontal stripe across the top and
-- then alternating black and red horizontal stripes.
-- useful for debugging UV mapping issues.
local BITMAP_XY = 512
local XY_ADJUST = BITMAP_XY/64
local texture = bitmap BITMAP_XY BITMAP_XY color:white  -- generate the bitmap here
local red = #( [255,0,0] )
local black = #( [0,0,0] )
local green = #( [0,255,0] )
for x = 0 to BITMAP_XY - 1 do
(
for yOffset = 0 to XY_ADJUST * 2 - 1 do
(
setPixels texture [x, 0 + yOffset] green
)
local y = 1
local color = black
while y <= 61 do
(
for yOffset = 0 to XY_ADJUST * 2 - 1 do
(
setPixels texture [x, (y + 1) * XY_ADJUST + yOffset] color
)
y += 2
if color == black then
color = red
else
color = black
)
) -- makeHorizStripeTexture
local theSaveName = getSaveFileName types:"Targa (*.tga)|*.tga|BMP (*.bmp)|*.bmp|JPEG (*.jpg)|*.jpg"
if theSaveName != undefined do
(
texture.filename = theSaveName
save texture
)
)
on generate picked do
(
SculptGenMax_CanvasRollout.theCanvas.bitmap = blankBitmap
sculptBitmap = copy blankBitmap
-- this builds a test texture to show how uv mapping works.
-- upload texture and apply it as the diffuse texture of the sculptie
-- the generated texture has green on the top line and then alternates red and black.
--makeHorizStripeTexture()
anObject = snapshot selection[1]
selectedPoly = anObject
try
(
convertTo selectedPoly (Editable_Poly) --convert to Editable_Poly
pass1()
updatePrimSizeTextBoxes()
pass2()
) catch
(
-- if anything goes wrong, make sure that we delete the temp copy of the
-- selection.  If we don't, it will show up in the scene.
delete selectedPoly
throw()
)
delete selectedPoly
refreshDisplay()
) --generate picked
on save_as picked do
(
theSaveName = getSaveFileName types:"Targa (*.tga)|*.tga|BMP (*.bmp)|*.bmp|JPEG (*.jpg)|*.jpg"
if theSaveName != undefined do
(
sculptBitmap.filename = theSaveName
save sculptBitmap
)
) --save_as picked
on about_tool picked do
(
local msgString = PROG_NAME + "\nVersion " + PROG_VERSION + "\nCopyright \xa9 2007 Shack Dougall"
messagebox msgString title:"About..."
)
on quit_tool picked do destroyDialog SculptGenMax_CanvasRollout
) --rcMenu CanvasMenu
rollout SculptGenMax_CanvasRollout PROG_NAME
(
bitmap theCanvas pos:[0,0] width:BITMAP_X height:BITMAP_Y bitmap:sculptBitmap
listbox resolution items:#("64x64", "128x128", "256x256") selection: default_resolution pos:[MAX_BITMAP+5,0] width:90 height:3
checkbox keep_props "Proportional" checked: default_keep_proportions align: #right
edittext prim_size_x "X:" fieldWidth:10 width:80 labelOnTop:false enabled:false align: #right
edittext prim_size_y "Y:" fieldWidth:10 width:80 labelOnTop:false enabled:false align: #right
edittext prim_size_z "Z:" fieldWidth:10 width:80 labelOnTop:false enabled:false align: #right
dropdownlist map_channel "UV Map Channel" items:#("Channel 1","2","3","4","5","6","7","8","9") selection:default_uv_map_channel height:11 width:80 align: #right
on resolution selected index do
(
BITMAP_X = BITMAP_Y = 2 ^ (index + 5)
bitmapTo64RatioX = BITMAP_X/64
bitmapTo64RatioY = BITMAP_Y/64
sculptBitmap = bitmap BITMAP_X BITMAP_Y color:white  -- generate the bitmap here
blankBitmap = bitmap BITMAP_X BITMAP_Y color:white  -- used to reset the dialog
theCanvas.width = BITMAP_X
theCanvas.height = BITMAP_Y
theCanvas.bitmap = copy blankBitmap
) -- resolution selected
on keep_props changed theValue do
(
keep_proportions = theValue
prim_size_x.enabled = not keep_proportions
prim_size_y.enabled = not keep_proportions
prim_size_z.enabled = not keep_proportions
) -- keep_props changed
on map_channel selected theMap do
(
UV_MAP_CHANNEL = theMap
)
) --rollout
createDialog SculptGenMax_CanvasRollout (MAX_BITMAP+100) (MAX_BITMAP+30) menu:CanvasMenu
) --macroscript

Revision as of 13:16, 18 December 2007

Description

SculptGenMax is a lightweight script that allows users to create sculptie prims in 3ds Max and export them to Second Life.

Current Version

1.0 RC 1 — released 2007/12/18

Features

   * Roundtrip workflow from 3ds Max to Second Life and back
   * Load sculptmaps from disk to import sculpties into 3ds Max
   * Create sculpties in 3ds Max and export them to Second Life
   * All sculptmap topologies supported: sphere, torus, plane, and cylinder
   * Editable Poly, Editable Mesh, and NURBS supported
   * Preserves UV Mapping — Baked textures work seamlessly

License

   * SculptGenMax is licensed under GPL3
   * Copywrite 2007 Shack Dougall
   * GPL FAQ
   * Quick Guide to GPL3

File:Http://files.liferain.com/downloads/wp-content/uploads/2007/12/sculptgenmax-in-3dsmax.jpg