|
8 | 8 | prop2mesh.primitive = {} |
9 | 9 | local addon = prop2mesh.primitive |
10 | 10 |
|
| 11 | +-- Primitive.minSize, copied rather than read, so this library doesn't need that addon installed |
| 12 | +local MinSize = 0.4 |
| 13 | + |
11 | 14 |
|
12 | 15 | ------------------------------- |
13 | 16 | local bit, math, util, table, isvector, WorldToLocal, LocalToWorld, Vector, Angle = |
|
233 | 236 | @RETURN: |
234 | 237 | either a function or a coroutine that will build the mesh |
235 | 238 | --]] |
236 | | - function addon.construct.generate( construct, param, threaded, physics ) |
| 239 | + function addon.construct.generate( construct, param, threaded, physics, missingname ) |
237 | 240 | if SERVER then threaded = nil end |
238 | 241 |
|
239 | | - -- Non-existant construct, error model CODE 1 |
| 242 | + -- Non-existant construct, error model CODE 1. Named by the caller, since a construct |
| 243 | + -- that doesn't exist has no data table to read a name out of. |
240 | 244 | if construct == nil then |
241 | | - return true, errorModel( 1, name ) |
| 245 | + return true, errorModel( 1, missingname or "NO_NAME" ) |
242 | 246 | end |
243 | 247 |
|
244 | 248 | construct.data.name = construct.data.name or "NO_NAME" |
|
271 | 275 | either a function or a coroutine that will build the mesh |
272 | 276 | --]] |
273 | 277 | function addon.construct.get( name, param, threaded, physics ) |
274 | | - return addon.construct.generate( construct_types[name], param, threaded, physics ) |
| 278 | + return addon.construct.generate( construct_types[name], param, threaded, physics, name ) |
275 | 279 | end |
276 | 280 | end |
277 | 281 |
|
@@ -2870,3 +2874,175 @@ registerType( "ladder", function( param, data, threaded, physics ) |
2870 | 2874 |
|
2871 | 2875 | return model |
2872 | 2876 | end ) |
| 2877 | + |
| 2878 | + |
| 2879 | +-- PARALLELOGRAM |
| 2880 | +-- Ported from the primitive addon. This library is a fork, so a type only the addon knows |
| 2881 | +-- about falls through to the error model, and its clips then cut up a 24 unit cube instead. |
| 2882 | +registerType( "parallelogram", function( param, data, threaded, physics ) |
| 2883 | + local dx = ( isvector( param.PrimSIZE ) and param.PrimSIZE[1] or 1 ) * 0.5 |
| 2884 | + local dy = ( isvector( param.PrimSIZE ) and param.PrimSIZE[2] or 1 ) * 0.5 |
| 2885 | + local dz = ( isvector( param.PrimSIZE ) and param.PrimSIZE[3] or 1 ) * 0.5 |
| 2886 | + |
| 2887 | + local shift = tonumber( param.PrimSLANT ) or 0 |
| 2888 | + |
| 2889 | + local model = simpleton.New() |
| 2890 | + local verts = model.verts |
| 2891 | + |
| 2892 | + -- Bottom face at z=-dz is unshifted; all horizontal offset goes to the top face. |
| 2893 | + -- The entity origin sits at the center of the bottom face. |
| 2894 | + model:PushXYZ( dx, dy, -dz ) -- 1 bottom y+ x+ |
| 2895 | + model:PushXYZ( dx, -dy, -dz ) -- 2 bottom y- x+ |
| 2896 | + model:PushXYZ( -dx, -dy, -dz ) -- 3 bottom y- x- |
| 2897 | + model:PushXYZ( -dx, dy, -dz ) -- 4 bottom y+ x- |
| 2898 | + model:PushXYZ( dx + shift, dy, dz ) -- 5 top y+ x+ |
| 2899 | + model:PushXYZ( dx + shift, -dy, dz ) -- 6 top y- x+ |
| 2900 | + model:PushXYZ( -dx + shift, -dy, dz ) -- 7 top y- x- |
| 2901 | + model:PushXYZ( -dx + shift, dy, dz ) -- 8 top y+ x- |
| 2902 | + |
| 2903 | + if CLIENT then |
| 2904 | + model:PushFace( 1, 2, 3, 4 ) -- bottom (-z) |
| 2905 | + model:PushFace( 5, 8, 7, 6 ) -- top (+z) |
| 2906 | + model:PushFace( 1, 4, 8, 5 ) -- y+ side |
| 2907 | + model:PushFace( 2, 6, 7, 3 ) -- y- side |
| 2908 | + model:PushFace( 2, 1, 5, 6 ) -- x+ side (slanted) |
| 2909 | + model:PushFace( 4, 3, 7, 8 ) -- x- side (slanted) |
| 2910 | + end |
| 2911 | + |
| 2912 | + if physics then |
| 2913 | + model.convexes = { verts } |
| 2914 | + end |
| 2915 | + |
| 2916 | + util_Transform( verts, param.PrimMESHROT, param.PrimMESHPOS, threaded ) |
| 2917 | + |
| 2918 | + return model |
| 2919 | +end ) |
| 2920 | + |
| 2921 | + |
| 2922 | +-- DOME_HOLLOW |
| 2923 | +-- Ported from the primitive addon, see the note on parallelogram above |
| 2924 | +local function pushHollowRings( model, maxseg2, numseg2, maxseg, numseg, dx, dy, dz, idx, idy, idz ) |
| 2925 | + for r = 0, numseg2 do |
| 2926 | + local t = math_pi * 0.5 * ( 1 - r / maxseg2 ) |
| 2927 | + local sinT, cosT = math_sin( t ), math_cos( t ) |
| 2928 | + for v = 0, numseg do |
| 2929 | + local p = math_tau * v / maxseg |
| 2930 | + model:PushXYZ( -dx * math_cos( p ) * sinT, dy * math_sin( p ) * sinT, dz * cosT ) -- outer |
| 2931 | + model:PushXYZ( -idx * math_cos( p ) * sinT, idy * math_sin( p ) * sinT, idz * cosT ) -- inner |
| 2932 | + end |
| 2933 | + end |
| 2934 | +end |
| 2935 | + |
| 2936 | +registerType( "dome_hollow", function( param, data, threaded, physics ) |
| 2937 | + local maxseg = param.PrimMAXSEGH or 1 |
| 2938 | + local numseg = param.PrimNUMSEGH or 1 |
| 2939 | + local maxseg2 = param.PrimMAXSEGV or 1 |
| 2940 | + local numseg2 = param.PrimNUMSEGV or 1 |
| 2941 | + |
| 2942 | + -- Legacy subdiv param will always come with these set to 1. Backwards compatibility measure. |
| 2943 | + -- Normally you wouldn't set these anyways (degenerate cases) |
| 2944 | + if maxseg == 1 and numseg == 1 and maxseg2 == 1 and numseg2 == 1 then |
| 2945 | + local subdiv = math_clamp( 2 * math_round( ( param.PrimSUBDIV or 8 ) / 2 ), 4, 32 ) |
| 2946 | + maxseg, numseg = subdiv, subdiv |
| 2947 | + maxseg2, numseg2 = subdiv / 2, subdiv / 2 |
| 2948 | + end |
| 2949 | + |
| 2950 | + -- Horizontal (longitude) arc: numseg of maxseg segments around the full 360 degrees. |
| 2951 | + maxseg = math_clamp( maxseg, 1, 16 ) |
| 2952 | + numseg = math_clamp( numseg, 1, maxseg ) |
| 2953 | + |
| 2954 | + -- Vertical (latitude) arc: numseg2 of maxseg2 segments over the 90 degrees from equator to apex. |
| 2955 | + maxseg2 = math_clamp( maxseg2, 1, 8 ) |
| 2956 | + numseg2 = math_clamp( numseg2, 1, maxseg2 ) |
| 2957 | + |
| 2958 | + local dx = ( isvector( param.PrimSIZE ) and param.PrimSIZE[1] or 1 ) * 0.5 |
| 2959 | + local dy = ( isvector( param.PrimSIZE ) and param.PrimSIZE[2] or 1 ) * 0.5 |
| 2960 | + local dz = ( isvector( param.PrimSIZE ) and param.PrimSIZE[3] or 1 ) * 0.5 |
| 2961 | + local dt = math_max( math_min( param.PrimDT or 1, dx - MinSize, dy - MinSize, dz - MinSize ), MinSize ) |
| 2962 | + |
| 2963 | + local idx = dx - dt |
| 2964 | + local idy = dy - dt |
| 2965 | + local idz = dz - dt |
| 2966 | + |
| 2967 | + local ringSize = numseg + 1 -- verts per ring: numseg segments need numseg+1 verts to span the arc |
| 2968 | + local twoRing = ringSize * 2 -- stride per ring in interleaved layout |
| 2969 | + |
| 2970 | + local model = simpleton.New() |
| 2971 | + |
| 2972 | + if CLIENT then |
| 2973 | + pushHollowRings( model, maxseg2, numseg2, maxseg, numseg, dx, dy, dz, idx, idy, idz ) |
| 2974 | + |
| 2975 | + -- Emit quad strips between adjacent rings. Each ring occupies twoRing slots, |
| 2976 | + -- so adding twoRing to an index moves to the same position one ring toward the apex. |
| 2977 | + for r = 1, numseg2 do |
| 2978 | + for v = 0, numseg - 1 do |
| 2979 | + local a = 1 + ( r - 1 ) * twoRing + v * 2 -- bottom-left outer |
| 2980 | + local d = a + twoRing -- top-left outer |
| 2981 | + model:PushFace( d, d + 2, a + 2, a ) -- outer, outward normals |
| 2982 | + model:PushFace( a + 1, a + 3, d + 3, d + 1 ) -- inner, inward normals |
| 2983 | + end |
| 2984 | + end |
| 2985 | + |
| 2986 | + -- Close the open bottom edge with a ring of rim quads connecting outer equator to inner equator. |
| 2987 | + for v = 0, numseg - 1 do |
| 2988 | + local ao = 1 + v * 2 |
| 2989 | + model:PushFace( ao, ao + 2, ao + 3, ao + 1 ) -- rim, downward normals |
| 2990 | + end |
| 2991 | + |
| 2992 | + -- A partial vertical arc stops short of the apex, so close that ring too. |
| 2993 | + if numseg2 ~= maxseg2 then |
| 2994 | + local top = 1 + numseg2 * twoRing |
| 2995 | + for v = 0, numseg - 1 do |
| 2996 | + local ao = top + v * 2 |
| 2997 | + model:PushFace( ao + 1, ao + 3, ao + 2, ao ) -- cap, upward normals |
| 2998 | + end |
| 2999 | + end |
| 3000 | + |
| 3001 | + -- When the arc is partial, cap the two cut edges with wall quads spanning outer to inner. |
| 3002 | + if numseg ~= maxseg then |
| 3003 | + for r = 1, numseg2 do |
| 3004 | + local aStart = 1 + ( r - 1 ) * twoRing |
| 3005 | + local dStart = aStart + twoRing |
| 3006 | + model:PushFace( aStart, aStart + 1, dStart + 1, dStart ) |
| 3007 | + |
| 3008 | + local aEnd = aStart + numseg * 2 |
| 3009 | + local dEnd = aEnd + twoRing |
| 3010 | + model:PushFace( aEnd, dEnd, dEnd + 1, aEnd + 1 ) |
| 3011 | + end |
| 3012 | + end |
| 3013 | + |
| 3014 | + util_Transform( model.verts, param.PrimMESHROT, param.PrimMESHPOS, threaded ) |
| 3015 | + end |
| 3016 | + |
| 3017 | + if physics then |
| 3018 | + -- Limit physics to 8 * 4 = 32 convexes at most. |
| 3019 | + local physMaxseg = math_min( maxseg, 8 ) |
| 3020 | + local physNumseg = math_max( math_round( numseg * physMaxseg / maxseg ), 1 ) |
| 3021 | + local physMaxseg2 = math_min( maxseg2, 4 ) |
| 3022 | + local physNumseg2 = math_max( math_round( numseg2 * physMaxseg2 / maxseg2 ), 1 ) |
| 3023 | + local physTwoRing = ( physNumseg + 1 ) * 2 |
| 3024 | + local physModel = simpleton.New() |
| 3025 | + local physVerts = physModel.verts |
| 3026 | + |
| 3027 | + pushHollowRings( physModel, physMaxseg2, physNumseg2, physMaxseg, physNumseg, dx, dy, dz, idx, idy, idz ) |
| 3028 | + |
| 3029 | + -- One convex per patch: four outer corners + four inner corners of each wall quad. |
| 3030 | + -- This approximates the hollow shell as a set of thin wedge-shaped convex hulls. |
| 3031 | + local convexes = {} |
| 3032 | + for r = 0, physNumseg2 - 1 do |
| 3033 | + for v = 0, physNumseg - 1 do |
| 3034 | + local oa = 1 + r * physTwoRing + v * 2 -- bottom-left outer |
| 3035 | + local od = oa + physTwoRing -- top-left outer |
| 3036 | + convexes[#convexes + 1] = { |
| 3037 | + physVerts[oa], physVerts[oa + 2], physVerts[od], physVerts[od + 2], -- outer quad corners |
| 3038 | + physVerts[oa + 1], physVerts[oa + 3], physVerts[od + 1], physVerts[od + 3], -- inner quad corners |
| 3039 | + } |
| 3040 | + end |
| 3041 | + end |
| 3042 | + |
| 3043 | + model.convexes = convexes |
| 3044 | + util_Transform( physVerts, param.PrimMESHROT, param.PrimMESHPOS, threaded ) |
| 3045 | + end |
| 3046 | + |
| 3047 | + return model |
| 3048 | +end ) |
0 commit comments