diff --git a/lua/primitive/core/construct.lua b/lua/primitive/core/construct.lua index ce51e23..222bf21 100644 --- a/lua/primitive/core/construct.lua +++ b/lua/primitive/core/construct.lua @@ -12,8 +12,8 @@ local addon = Primitive local bit, math, util, table, isvector, WorldToLocal, LocalToWorld, Vector, Angle = bit, math, util, table, isvector, WorldToLocal, LocalToWorld, Vector, Angle -local math_abs, math_sin, math_cos, math_tan, math_asin, math_acos, math_atan, math_atan2, math_rad, math_deg, math_sqrt = - math.abs, math.sin, math.cos, math.tan, math.asin, math.acos, math.atan, math.atan2, math.rad, math.deg, math.sqrt +local math_abs, math_sin, math_cos, math_asin, math_acos, math_atan, math_atan2, math_rad, math_deg, math_sqrt = + math.abs, math.sin, math.cos, math.asin, math.acos, math.atan, math.atan2, math.rad, math.deg, math.sqrt local math_ceil, math_floor, math_round, math_min, math_max, math_clamp = math.ceil, math.floor, math.Round, math.min, math.max, math.Clamp @@ -270,6 +270,15 @@ do either a function or a coroutine that will build the mesh --]] function addon.construct.get( name, param, threaded, physics ) + if param and param.PrimUNITS == "millimeters" then + param = table.Copy( param ) + if isvector( param.PrimSIZE ) then + local s = param.PrimSIZE * 0.03937 + param.PrimSIZE = Vector( math_max( s.x, 1 ), math_max( s.y, 1 ), math_max( s.z, 1 ) ) + end + if isnumber( param.PrimDT ) then param.PrimDT = math_max( param.PrimDT * 0.03937, 1 ) end + if isnumber( param.PrimSLANT ) then param.PrimSLANT = param.PrimSLANT * 0.03937 end + end return addon.construct.generate( construct_types[name], param, threaded, physics ) end end @@ -1918,6 +1927,96 @@ registerType( "dome", function( param, data, threaded, physics ) end, { canThread = true, domePlane = { pos = Vector(), normal = Vector( 0, 0, 1 ) } } ) +-- DOME_HOLLOW +-- Pushes interleaved outer+inner hemisphere verts into model over rings+1 latitude bands. +-- Each position v in ring r stores [outer, inner] consecutively; twoRing = 2*(sdiv+1) gives the same place on the next ring. +local function pushHollowRings( model, rings, sdiv, dx, dy, dz, idx, idy, idz ) + for r = 0, rings do + local t = math_pi * 0.5 * ( 1 - r / rings ) + local sinT, cosT = math_sin( t ), math_cos( t ) + for v = 0, sdiv do + local p = math_tau * v / sdiv + model:PushXYZ( -dx * math_cos( p ) * sinT, dy * math_sin( p ) * sinT, dz * cosT ) -- outer + model:PushXYZ( -idx * math_cos( p ) * sinT, idy * math_sin( p ) * sinT, idz * cosT ) -- inner + end + end +end + +registerType( "dome_hollow", function( param, data, threaded, physics ) + local subdiv = 2 * math_round( ( param.PrimSUBDIV or 8 ) / 2 ) + if subdiv < 4 then subdiv = 4 elseif subdiv > 32 then subdiv = 32 end + + local dx = ( isvector( param.PrimSIZE ) and param.PrimSIZE[1] or 1 ) * 0.5 + local dy = ( isvector( param.PrimSIZE ) and param.PrimSIZE[2] or 1 ) * 0.5 + local dz = ( isvector( param.PrimSIZE ) and param.PrimSIZE[3] or 1 ) * 0.5 + local dt = math_max( math_min( param.PrimDT or 1, dx - 1, dy - 1, dz - 1 ), 1 ) + + local idx = dx - dt + local idy = dy - dt + local idz = dz - dt + + local numRings = subdiv / 2 + local ringSize = subdiv + 1 -- verts per ring: subdiv segments need subdiv+1 verts to close the loop + local twoRing = ringSize * 2 -- stride per ring in interleaved layout + + local model = simpleton.New() + + if CLIENT then + pushHollowRings( model, numRings, subdiv, dx, dy, dz, idx, idy, idz ) + + -- Emit quad strips between adjacent rings. Each ring occupies twoRing slots, + -- so adding twoRing to an index moves to the same position one ring toward the apex. + for r = 1, numRings do + for v = 0, subdiv - 1 do + local a = 1 + ( r - 1 ) * twoRing + v * 2 -- bottom-left outer + local d = a + twoRing -- top-left outer + model:PushFace( d, d + 2, a + 2, a ) -- outer, outward normals + model:PushFace( a + 1, a + 3, d + 3, d + 1 ) -- inner, inward normals + end + end + + -- Close the open bottom edge with a ring of rim quads connecting outer equator to inner equator. + for v = 0, subdiv - 1 do + local ao = 1 + v * 2 + model:PushFace( ao, ao + 2, ao + 3, ao + 1 ) -- rim, downward normals + end + + util_Transform( model.verts, param.PrimMESHROT, param.PrimMESHPOS, threaded ) + end + + if physics then + -- Physics uses a lower subdivision cap to keep the convex count reasonable. + local physSubdiv = math_min( subdiv, 8 ) + local physRings = physSubdiv / 2 + local physRingSz = physSubdiv + 1 + local physTwoRing = physRingSz * 2 + local physModel = simpleton.New() + local physVerts = physModel.verts + + pushHollowRings( physModel, physRings, physSubdiv, dx, dy, dz, idx, idy, idz ) + + -- One convex per patch: four outer corners + four inner corners of each wall quad. + -- This approximates the hollow shell as a set of thin wedge-shaped convex hulls. + local convexes = {} + for r = 0, physRings - 1 do + for v = 0, physSubdiv - 1 do + local oa = 1 + r * physTwoRing + v * 2 -- bottom-left outer + local od = oa + physTwoRing -- top-left outer + convexes[#convexes + 1] = { + physVerts[oa], physVerts[oa + 2], physVerts[od], physVerts[od + 2], -- outer quad corners + physVerts[oa + 1], physVerts[oa + 3], physVerts[od + 1], physVerts[od + 3], -- inner quad corners + } + end + end + + model.convexes = convexes + util_Transform( physVerts, param.PrimMESHROT, param.PrimMESHPOS, threaded ) + end + + return model +end ) + + -- PLANE registerType( "plane", function( param, data, threaded, physics ) local dx = ( isvector( param.PrimSIZE ) and param.PrimSIZE[1] or 1 ) * 0.5 @@ -2369,6 +2468,47 @@ registerType( "wedge_corner", function( param, data, threaded, physics ) end ) +-- PARALLELOGRAM +registerType( "parallelogram", function( param, data, threaded, physics ) + local dx = ( isvector( param.PrimSIZE ) and param.PrimSIZE[1] or 1 ) * 0.5 + local dy = ( isvector( param.PrimSIZE ) and param.PrimSIZE[2] or 1 ) * 0.5 + local dz = ( isvector( param.PrimSIZE ) and param.PrimSIZE[3] or 1 ) * 0.5 + + local shift = tonumber( param.PrimSLANT ) or 0 + + local model = simpleton.New() + local verts = model.verts + + -- Bottom face at z=-dz is unshifted; all horizontal offset goes to the top face. + -- The entity origin sits at the center of the bottom face. + model:PushXYZ( dx, dy, -dz ) -- 1 bottom y+ x+ + model:PushXYZ( dx, -dy, -dz ) -- 2 bottom y- x+ + model:PushXYZ( -dx, -dy, -dz ) -- 3 bottom y- x- + model:PushXYZ( -dx, dy, -dz ) -- 4 bottom y+ x- + model:PushXYZ( dx + shift, dy, dz ) -- 5 top y+ x+ + model:PushXYZ( dx + shift, -dy, dz ) -- 6 top y- x+ + model:PushXYZ( -dx + shift, -dy, dz ) -- 7 top y- x- + model:PushXYZ( -dx + shift, dy, dz ) -- 8 top y+ x- + + if CLIENT then + model:PushFace( 1, 2, 3, 4 ) -- bottom (-z) + model:PushFace( 5, 8, 7, 6 ) -- top (+z) + model:PushFace( 1, 4, 8, 5 ) -- y+ side + model:PushFace( 2, 6, 7, 3 ) -- y- side + model:PushFace( 2, 1, 5, 6 ) -- x+ side (slanted) + model:PushFace( 4, 3, 7, 8 ) -- x- side (slanted) + end + + if physics then + model.convexes = { verts } + end + + util_Transform( verts, param.PrimMESHROT, param.PrimMESHPOS, threaded ) + + return model +end ) + + -- AIRFOIL local function NACA4DIGIT( distr, points, chord, M, P, T, openEdge, ox, oy, oz ) ox = ox or 0 diff --git a/lua/primitive/entities/shapes.lua b/lua/primitive/entities/shapes.lua index 7340d6a..02c14ca 100644 --- a/lua/primitive/entities/shapes.lua +++ b/lua/primitive/entities/shapes.lua @@ -2,8 +2,8 @@ do local class = {} - local typen = { "cone", "cube", "cube_magic", "cube_hole", "cylinder", "dome", "plane", "pyramid", "sphere", "torus", "tube", "wedge", "wedge_corner" } - local typek, defaults = {}, {} + local typen = { "cone", "cube", "cube_magic", "cube_hole", "cylinder", "dome", "dome_hollow", "parallelogram", "plane", "pyramid", "sphere", "torus", "tube", "wedge", "wedge_corner" } + local typek, unitk, defaults = {}, { source = "source", millimeters = "millimeters" }, {} do for k, v in pairs( typen ) do @@ -18,9 +18,11 @@ do PrimNUMSEG = 16, PrimSIDES = 0, PrimSIZE = Vector( 48, 48, 48 ), + PrimSLANT = 0, PrimSUBDIV = 8, PrimTX = 0, PrimTY = 0, + PrimUNITS = "source", }, cone = { PrimMAXSEG = 16, @@ -70,6 +72,19 @@ do PrimSUBDIV = 8, PrimTYPE = "dome", }, + dome_hollow = { + PrimDT = 4, + PrimMESHSMOOTH = 65, + PrimSIZE = Vector( 48, 48, 48 ), + PrimSUBDIV = 8, + PrimTYPE = "dome_hollow", + }, + parallelogram = { + PrimMESHSMOOTH = 0, + PrimSIZE = Vector( 48, 48, 48 ), + PrimSLANT = 0, + PrimTYPE = "parallelogram", + }, plane = { PrimMESHSMOOTH = 0, PrimSIZE = Vector( 48, 48, 48 ), @@ -158,11 +173,13 @@ do function class:PrimitiveSetupDataTables() self:PrimitiveVar( "PrimTYPE", "String", { category = "modify", title = "type", panel = "combo", values = typek, icons = "primitive/icons/%s.png" }, true ) + self:PrimitiveVar( "PrimUNITS", "String", { global = true, category = "modify", title = "units", panel = "combo", values = unitk }, true ) self:PrimitiveVar( "PrimSIZE", "Vector", { category = "modify", title = "size", panel = "vector", min = Vector( 1, 1, 1 ), max = Vector( 1000, 1000, 1000 ) }, true ) self:PrimitiveVar( "PrimDT", "Float", { category = "modify", title = "thickness", panel = "float", min = 1, max = 1000 }, true ) self:PrimitiveVar( "PrimTX", "Float", { category = "modify", title = "taper x", panel = "float", min = -1, max = 1 }, true ) self:PrimitiveVar( "PrimTY", "Float", { category = "modify", title = "taper y", panel = "float", min = -1, max = 1 }, true ) + self:PrimitiveVar( "PrimSLANT", "Float", { category = "modify", title = "overhang", panel = "float", min = -1000, max = 1000 }, true ) self:PrimitiveVar( "PrimSUBDIV", "Int", { category = "modify", title = "subdivide", panel = "int", min = 1, max = 32 }, true ) self:PrimitiveVar( "PrimMAXSEG", "Int", { category = "modify", title = "max segments", panel = "int", min = 1, max = 32 }, true ) @@ -180,6 +197,8 @@ do { category = "shapes", entity = "primitive_shape", title = "cube_hole", command = "cube_hole 1 48" }, { category = "shapes", entity = "primitive_shape", title = "cylinder", command = "cylinder 1 48" }, { category = "shapes", entity = "primitive_shape", title = "dome", command = "dome 1 48" }, + { category = "shapes", entity = "primitive_shape", title = "dome_hollow", command = "dome_hollow 1 48" }, + { category = "shapes", entity = "primitive_shape", title = "parallelogram", command = "parallelogram 1 48" }, { category = "shapes", entity = "primitive_shape", title = "plane", command = "plane 1 48" }, { category = "shapes", entity = "primitive_shape", title = "pyramid", command = "pyramid 1 48" }, { category = "shapes", entity = "primitive_shape", title = "sphere", command = "sphere 1 48" }, @@ -374,6 +393,7 @@ do function class:PrimitiveSetupDataTables() self:PrimitiveVar( "PrimTYPE", "String", { category = "modify", title = "type", panel = "combo", values = typek, icons = "primitive/icons/%s.png" }, true ) + self:PrimitiveVar( "PrimUNITS", "String", { global = true, category = "modify", title = "units", panel = "combo", values = unitk }, true ) self:PrimitiveVar( "PrimSIZE", "Vector", { category = "modify", title = "size", panel = "vector", min = Vector( 1, 1, 1 ), max = Vector( 1000, 1000, 1000 ) }, true ) -- self:PrimitiveVar( "PrimDT", "Float", { category = "modify", title = "thickness", panel = "float", min = 1, max = 1000 }, true ) diff --git a/materials/primitive/icons/dome_hollow.png b/materials/primitive/icons/dome_hollow.png new file mode 100644 index 0000000..42209f1 Binary files /dev/null and b/materials/primitive/icons/dome_hollow.png differ diff --git a/materials/primitive/icons/parallelogram.png b/materials/primitive/icons/parallelogram.png new file mode 100644 index 0000000..ec67f53 Binary files /dev/null and b/materials/primitive/icons/parallelogram.png differ