#pragma once // Restricted scalar field math shared by the standalone C++ GPU adapter and its HLSL. // Define VF_FIELD_HLSL before including this file from a shader. Keep this header free of // containers, engine types, and vector intrinsics so it remains usable by both compilers. #if defined(VF_FIELD_HLSL) #define VF_FIELD_INLINE #define VF_FIELD_LOOP [loop] typedef int VF_FieldI32; typedef uint VF_FieldU32; typedef float VF_FieldReal; #define VF_FIELD_CAST_I32(v) int(v) #define VF_FIELD_CAST_U32(v) uint(v) #define VF_FIELD_CAST_F32(v) float(v) #define VF_FIELD_FLOOR(v) floor(v) #define VF_FIELD_ABS(v) abs(v) #define VF_FIELD_SQRT(v) sqrt(v) #define VF_FIELD_MIN(a,b) min(a,b) #define VF_FIELD_MAX(a,b) max(a,b) #define VF_FIELD_CLAMP(v,a,b) clamp(v,a,b) #define VF_FIELD_LERP(a,b,t) lerp(a,b,t) #else #include #include #include #define VF_FIELD_INLINE inline #define VF_FIELD_LOOP typedef std::int32_t VF_FieldI32; typedef std::uint32_t VF_FieldU32; typedef double VF_FieldReal; #define VF_FIELD_CAST_I32(v) static_cast(v) #define VF_FIELD_CAST_U32(v) static_cast(v) #define VF_FIELD_CAST_F32(v) static_cast(v) #define VF_FIELD_FLOOR(v) std::floor(v) #define VF_FIELD_ABS(v) std::abs(v) #define VF_FIELD_SQRT(v) std::sqrt(v) #define VF_FIELD_MIN(a,b) std::min(a,b) #define VF_FIELD_MAX(a,b) std::max(a,b) #define VF_FIELD_CLAMP(v,a,b) std::clamp(v,a,b) #define VF_FIELD_LERP(a,b,t) std::lerp(a,b,t) #endif // High-level far-field primitive data. The CPU instance uses double intermediates to retain the // reference path's precision; HLSL instantiates the same fields and routines as float. The packed // upload record is a separate float-only API type with the same semantic members. struct VF_FieldPrimitive { VF_FieldI32 flags; VF_FieldI32 minVoxel[3], maxVoxel[3]; VF_FieldReal center[3], q[9], minRadius; VF_FieldReal capsuleStart[3], capsuleDelta[3]; VF_FieldReal capsuleAxisLengthSquared, capsuleAxisLength; VF_FieldReal capsuleStartRadius, capsuleEndRadius; VF_FieldReal profileStartT, profileEndT, profileStartRadius, profileEndRadius; VF_FieldReal radiusSlopeStart, radiusSlopeEnd; VF_FieldReal roundedHalfExtent[3], rounding; VF_FieldReal blendRadius, floorBlendRadius; VF_FieldReal floorCenterZ, floorSlopeX, floorSlopeY; VF_FieldReal floorCurveOriginX, floorCurveOriginY; VF_FieldReal floorCurveDeltaX, floorCurveDeltaY, floorCurveLength; VF_FieldReal floorCurveStartZ, floorCurveEndZ; VF_FieldReal floorCurveTangentStart, floorCurveTangentEnd; VF_FieldReal floorCurveStartBlend, floorCurveEndBlend; }; static const VF_FieldI32 VF_FIELD_PRIMITIVE_CAPSULE = 1; static const VF_FieldI32 VF_FIELD_PRIMITIVE_SMOOTH_CAPSULE = 2; static const VF_FieldI32 VF_FIELD_PRIMITIVE_ROUNDED_BOX = 4; static const VF_FieldI32 VF_FIELD_PRIMITIVE_HAS_FLOOR = 8; static const VF_FieldI32 VF_FIELD_PRIMITIVE_CURVED_FLOOR = 16; static const VF_FieldI32 VF_FIELD_PRIMITIVE_CARVE = 32; static const VF_FieldI32 VF_FIELD_PRIMITIVE_CRITICAL = 64; static const VF_FieldI32 VF_FIELD_PRIMITIVE_PASSAGE = 128; VF_FIELD_INLINE VF_FieldReal VF_FieldEvaluateFloorProfile(VF_FieldReal a, VF_FieldReal b, VF_FieldReal tangentA, VF_FieldReal tangentB, VF_FieldReal length, VF_FieldReal startBlend, VF_FieldReal endBlend, VF_FieldReal s) { const VF_FieldReal secant = (b - a) / length; if (s < 0.0) return a + tangentA * s; if (s > length) return b + tangentB * (s - length); VF_FieldReal result = VF_FIELD_LERP(a, b, s / length); if (startBlend > 0.0 && s < startBlend) { const VF_FieldReal t = s / startBlend; const VF_FieldReal correction = (tangentA - secant) * startBlend * (t - 2.0 * t * t + t * t * t); result += correction; } if (endBlend > 0.0 && length - s < endBlend) { const VF_FieldReal t = (length - s) / endBlend; const VF_FieldReal correction = (tangentB - secant) * endBlend * (t - 2.0 * t * t + t * t * t); result -= correction; } return result; } VF_FIELD_INLINE VF_FieldReal VF_FieldPrimitiveFloorAt(VF_FieldPrimitive p, VF_FieldReal x, VF_FieldReal y) { if ((p.flags & VF_FIELD_PRIMITIVE_CURVED_FLOOR) != 0) { const VF_FieldReal t = ((x - p.floorCurveOriginX) * p.floorCurveDeltaX + (y - p.floorCurveOriginY) * p.floorCurveDeltaY) / (p.floorCurveLength * p.floorCurveLength); return VF_FieldEvaluateFloorProfile(p.floorCurveStartZ, p.floorCurveEndZ, p.floorCurveTangentStart, p.floorCurveTangentEnd, p.floorCurveLength, p.floorCurveStartBlend, p.floorCurveEndBlend, t * p.floorCurveLength); } return p.floorCenterZ + p.floorSlopeX * (x - p.center[0]) + p.floorSlopeY * (y - p.center[1]); } VF_FIELD_INLINE float VF_FieldCapsuleRadiusAtT(VF_FieldPrimitive p, VF_FieldReal t, VF_FieldReal axisLength) { t = VF_FIELD_CLAMP(t, 0.0, 1.0); if ((p.flags & VF_FIELD_PRIMITIVE_SMOOTH_CAPSULE) == 0) return VF_FIELD_CAST_F32(p.capsuleStartRadius + (p.capsuleEndRadius - p.capsuleStartRadius) * t); const VF_FieldReal t0 = p.profileStartT, t1 = p.profileEndT; if (t < t0) return VF_FIELD_CAST_F32(p.profileStartRadius + p.radiusSlopeStart * axisLength * (t - t0)); if (t > t1) return VF_FIELD_CAST_F32(p.profileEndRadius + p.radiusSlopeEnd * axisLength * (t - t1)); const VF_FieldReal spanT = VF_FIELD_MAX(1.0e-9, t1 - t0); const VF_FieldReal u = VF_FIELD_CLAMP((t - t0) / spanT, 0.0, 1.0); const VF_FieldReal u2 = u * u, u3 = u2 * u; const VF_FieldReal spanDistance = axisLength * spanT; const VF_FieldReal h00 = 2.0 * u3 - 3.0 * u2 + 1.0; const VF_FieldReal h10 = u3 - 2.0 * u2 + u; const VF_FieldReal h01 = -2.0 * u3 + 3.0 * u2; const VF_FieldReal h11 = u3 - u2; return VF_FIELD_CAST_F32(h00 * p.profileStartRadius + h10 * spanDistance * p.radiusSlopeStart + h01 * p.profileEndRadius + h11 * spanDistance * p.radiusSlopeEnd); } VF_FIELD_INLINE float VF_FieldPrimitiveDistance(VF_FieldPrimitive p, VF_FieldReal x, VF_FieldReal y, VF_FieldReal z) { if ((p.flags & VF_FIELD_PRIMITIVE_CAPSULE) != 0) { const VF_FieldReal position[3] = {x, y, z}; VF_FieldReal v[3], d[3], vd = 0.0; for (VF_FieldI32 axis = 0; axis < 3; ++axis) { v[axis] = position[axis] - p.capsuleStart[axis]; d[axis] = p.capsuleDelta[axis]; vd += v[axis] * d[axis]; } const VF_FieldReal d2 = p.capsuleAxisLengthSquared; const VF_FieldReal t = d2 > 1.0e-12 ? VF_FIELD_CLAMP(vd / d2, 0.0, 1.0) : 0.0; VF_FieldReal distanceSquared = 0.0; for (VF_FieldI32 axis = 0; axis < 3; ++axis) { const VF_FieldReal e = v[axis] - t * d[axis]; distanceSquared += e * e; } const float radius = VF_FieldCapsuleRadiusAtT(p, t, p.capsuleAxisLength); return VF_FIELD_CAST_F32(VF_FIELD_SQRT(distanceSquared) - radius); } if ((p.flags & VF_FIELD_PRIMITIVE_ROUNDED_BOX) != 0) { const VF_FieldReal d[3] = { VF_FIELD_ABS(x - p.center[0]) - p.roundedHalfExtent[0], VF_FIELD_ABS(y - p.center[1]) - p.roundedHalfExtent[1], VF_FIELD_ABS(z - p.center[2]) - p.roundedHalfExtent[2]}; const VF_FieldReal ox = VF_FIELD_MAX(d[0], 0.0); const VF_FieldReal oy = VF_FIELD_MAX(d[1], 0.0); const VF_FieldReal oz = VF_FIELD_MAX(d[2], 0.0); const VF_FieldReal outside = VF_FIELD_SQRT(ox * ox + oy * oy + oz * oz); const VF_FieldReal inside = VF_FIELD_MIN(VF_FIELD_MAX(d[0], VF_FIELD_MAX(d[1], d[2])), 0.0); return VF_FIELD_CAST_F32(outside + inside - p.rounding); } const VF_FieldReal d[3] = {x - p.center[0], y - p.center[1], z - p.center[2]}; VF_FieldReal q = 0.0; for (VF_FieldI32 i = 0; i < 3; ++i) for (VF_FieldI32 j = 0; j < 3; ++j) q += p.q[i * 3 + j] * d[i] * d[j] * 256.0; return VF_FIELD_CAST_F32((VF_FIELD_SQRT(VF_FIELD_MAX(0.0, q)) - 1.0) * p.minRadius); } VF_FIELD_INLINE VF_FieldU32 VF_FieldMix(VF_FieldU32 x) { x ^= x >> 16; x *= 0x7FEB352Du; x ^= x >> 15; x *= 0x846CA68Bu; x ^= x >> 16; return x; } VF_FIELD_INLINE VF_FieldU32 VF_FieldNoiseHash(VF_FieldI32 x, VF_FieldI32 y, VF_FieldI32 z, VF_FieldU32 seed) { VF_FieldU32 h = VF_FieldMix(seed ^ 0xB5297A4Du); h = VF_FieldMix(h ^ VF_FIELD_CAST_U32(x) * 0x68E31DA4u); h = VF_FieldMix(h ^ VF_FIELD_CAST_U32(y) * 0x1B56C4E9u); return VF_FieldMix(h ^ VF_FIELD_CAST_U32(z) * 0x9E3779B9u); } VF_FIELD_INLINE float VF_FieldNoiseValue(VF_FieldI32 x, VF_FieldI32 y, VF_FieldI32 z, VF_FieldU32 seed) { return VF_FIELD_CAST_F32(VF_FieldNoiseHash(x, y, z, seed) >> 8) * (2.0f / 16777215.0f) - 1.0f; } VF_FIELD_INLINE float VF_FieldFade(float t) { return t * t * (3.0f - 2.0f * t); } VF_FIELD_INLINE float VF_FieldLerp(float a, float b, float t) { return a + (b - a) * t; } VF_FIELD_INLINE float VF_FieldValueNoise(float x, float y, float z, VF_FieldI32 wavelength, VF_FieldU32 seed) { const float inv = 1.0f / VF_FIELD_CAST_F32(wavelength); const float fx = x * inv, fy = y * inv, fz = z * inv; const VF_FieldI32 x0 = VF_FIELD_CAST_I32(VF_FIELD_FLOOR(fx)); const VF_FieldI32 y0 = VF_FIELD_CAST_I32(VF_FIELD_FLOOR(fy)); const VF_FieldI32 z0 = VF_FIELD_CAST_I32(VF_FIELD_FLOOR(fz)); const float tx = VF_FieldFade(fx - VF_FIELD_CAST_F32(x0)); const float ty = VF_FieldFade(fy - VF_FIELD_CAST_F32(y0)); const float tz = VF_FieldFade(fz - VF_FIELD_CAST_F32(z0)); const float z00 = VF_FieldLerp(VF_FieldNoiseValue(x0, y0, z0, seed), VF_FieldNoiseValue(x0 + 1, y0, z0, seed), tx); const float z10 = VF_FieldLerp(VF_FieldNoiseValue(x0, y0 + 1, z0, seed), VF_FieldNoiseValue(x0 + 1, y0 + 1, z0, seed), tx); const float z01 = VF_FieldLerp(VF_FieldNoiseValue(x0, y0, z0 + 1, seed), VF_FieldNoiseValue(x0 + 1, y0, z0 + 1, seed), tx); const float z11 = VF_FieldLerp(VF_FieldNoiseValue(x0, y0 + 1, z0 + 1, seed), VF_FieldNoiseValue(x0 + 1, y0 + 1, z0 + 1, seed), tx); return VF_FieldLerp(VF_FieldLerp(z00, z10, ty), VF_FieldLerp(z01, z11, ty), tz); } VF_FIELD_INLINE float VF_FieldSurfaceRelief(float x, float y, float z, VF_FieldU32 seed) { const float macro = VF_FieldValueNoise(x, y, z, 32, seed ^ 0xB5297A4Du); const float mid = VF_FieldValueNoise(x, y, z, 16, seed ^ 0x63D83595u); const float fine = VF_FieldValueNoise(x, y, z, 4, seed ^ 0xA511E9B3u); return 1.20f * macro + (0.80f + 0.25f * macro) * (0.40f * mid + 0.60f * fine); } // Scalar version of VoxelNoise::Perlin3D and its owner-world roughness variant. // The integer hash and operation order are shared so the standalone field and HLSL // evaluate the same authored detail from the same world parameters. VF_FIELD_INLINE VF_FieldU32 VF_FieldVoxelHashMix(VF_FieldU32 x) { x ^= x >> 16; x *= 0x45d9f3bu; x ^= x >> 16; x *= 0x45d9f3bu; x ^= x >> 16; return x; } VF_FIELD_INLINE float VF_FieldVoxelSeedOffset(VF_FieldU32 seed,float siteKey) { const VF_FieldU32 site=VF_FIELD_CAST_U32(siteKey*100.0f+0.5f); return VF_FIELD_CAST_F32(VF_FieldVoxelHashMix(seed^(site*2654435761u))&0x3FFFu); } VF_FIELD_INLINE VF_FieldU32 VF_FieldPerlinHash(VF_FieldI32 x,VF_FieldI32 y,VF_FieldI32 z) { VF_FieldU32 h=VF_FIELD_CAST_U32(x)*0x9E3779B1u ^VF_FIELD_CAST_U32(y)*0x85EBCA77u ^VF_FIELD_CAST_U32(z)*0xC2B2AE3Du; h^=h>>15;h*=0x2C1B3C6Du; h^=h>>12;h*=0x297A2D39u; h^=h>>15; return h; } VF_FIELD_INLINE float VF_FieldPerlinFade(float t) { const float inner=t*(t*6.0f-15.0f)+10.0f; const float t3=t*t*t; return t3*inner; } VF_FIELD_INLINE float VF_FieldPerlinGrad(VF_FieldU32 hash,float x,float y,float z) { const VF_FieldU32 h=hash&15u; const float u=(h&8u)==0u?x:y; float v; if((h&12u)==0u)v=y; else if((h&13u)==12u)v=x; else v=z; const float ru=(h&1u)==0u?u:-u; const float rv=(h&2u)==0u?v:-v; return ru+rv; } VF_FIELD_INLINE float VF_FieldPerlinLerp(float a,float b,float t) { return a+t*(b-a); } VF_FIELD_INLINE float VF_FieldPerlin3D(float x,float y,float z) { const float xf=VF_FIELD_FLOOR(x),yf=VF_FIELD_FLOOR(y),zf=VF_FIELD_FLOOR(z); const VF_FieldI32 X=VF_FIELD_CAST_I32(xf),Y=VF_FIELD_CAST_I32(yf),Z=VF_FIELD_CAST_I32(zf); const float fx=x-xf,fy=y-yf,fz=z-zf; const float su=VF_FieldPerlinFade(fx),sv=VF_FieldPerlinFade(fy),sw=VF_FieldPerlinFade(fz); const VF_FieldU32 h000=VF_FieldPerlinHash(X,Y,Z),h100=VF_FieldPerlinHash(X+1,Y,Z); const VF_FieldU32 h010=VF_FieldPerlinHash(X,Y+1,Z),h110=VF_FieldPerlinHash(X+1,Y+1,Z); const VF_FieldU32 h001=VF_FieldPerlinHash(X,Y,Z+1),h101=VF_FieldPerlinHash(X+1,Y,Z+1); const VF_FieldU32 h011=VF_FieldPerlinHash(X,Y+1,Z+1),h111=VF_FieldPerlinHash(X+1,Y+1,Z+1); const float fx1=fx-1.0f,fy1=fy-1.0f,fz1=fz-1.0f; const float n000=VF_FieldPerlinGrad(h000,fx,fy,fz); const float n100=VF_FieldPerlinGrad(h100,fx1,fy,fz); const float n010=VF_FieldPerlinGrad(h010,fx,fy1,fz); const float n110=VF_FieldPerlinGrad(h110,fx1,fy1,fz); const float n001=VF_FieldPerlinGrad(h001,fx,fy,fz1); const float n101=VF_FieldPerlinGrad(h101,fx1,fy,fz1); const float n011=VF_FieldPerlinGrad(h011,fx,fy1,fz1); const float n111=VF_FieldPerlinGrad(h111,fx1,fy1,fz1); const float x00=VF_FieldPerlinLerp(n000,n100,su); const float x10=VF_FieldPerlinLerp(n010,n110,su); const float x01=VF_FieldPerlinLerp(n001,n101,su); const float x11=VF_FieldPerlinLerp(n011,n111,su); const float y0=VF_FieldPerlinLerp(x00,x10,sv); const float y1=VF_FieldPerlinLerp(x01,x11,sv); return VF_FieldPerlinLerp(y0,y1,sw); } VF_FIELD_INLINE float VF_FieldOwnerFractal(float x,float y,float z,VF_FieldI32 octaves, VF_FieldI32 mode) { float total=0.0f,maxValue=0.0f,frequency=1.0f,amplitude=1.0f,weight=1.0f; VF_FIELD_LOOP for(VF_FieldI32 i=0;i0.0f))return 0.0f; const float effectiveZ=verticalScale>0.0f&&verticalScale!=1.0f?z/verticalScale:z; const float mainX=x*roughnessFrequency+VF_FieldVoxelSeedOffset(seed,11.3f); const float mainY=y*roughnessFrequency+VF_FieldVoxelSeedOffset(seed,13.7f); const float mainZ=effectiveZ*roughnessFrequency+VF_FieldVoxelSeedOffset(seed,17.1f); float fineX=x*roughnessFrequency*3.0f+VF_FieldVoxelSeedOffset(seed,19.1f)+2000.0f; float fineY=y*roughnessFrequency*3.0f+VF_FieldVoxelSeedOffset(seed,23.7f)+2500.0f; float fineZ=effectiveZ*roughnessFrequency*3.0f+VF_FieldVoxelSeedOffset(seed,29.3f)+3000.0f; float mx=mainX,my=mainY,mz=mainZ; if(domainWarpStrength>0.0f) { const float wf=domainWarpFrequency,ws=domainWarpStrength; float wx=VF_FieldPerlin3D(x*wf+VF_FieldVoxelSeedOffset(seed,5.2f), y*wf+VF_FieldVoxelSeedOffset(seed,1.3f), effectiveZ*wf+VF_FieldVoxelSeedOffset(seed,9.7f)); float wy=VF_FieldPerlin3D(x*wf+100.0f+VF_FieldVoxelSeedOffset(seed,7.7f), y*wf+200.0f+VF_FieldVoxelSeedOffset(seed,3.1f),effectiveZ*wf+300.0f); float wz=VF_FieldPerlin3D(x*wf+400.0f,y*wf+500.0f+VF_FieldVoxelSeedOffset(seed,11.9f), effectiveZ*wf+600.0f+VF_FieldVoxelSeedOffset(seed,13.3f)); wx*=1.25f*ws;wy*=1.25f*ws;wz*=1.25f*ws; mx+=wx;my+=wy;mz+=wz; fineX+=wx;fineY+=wy;fineZ+=wz; } float rough=0.0f,fine=0.0f; if(noiseType==1u) { rough=VF_FieldOwnerFractal(mx,my,mz,3,1); fine=VF_FieldOwnerFractal(fineX,fineY,fineZ,2,1); } else if(noiseType==2u) { rough=(VF_FieldOwnerFractal(mx,my,mz,3,0)+VF_FieldOwnerFractal(mx,my,mz,3,1))*0.5f; fine=(VF_FieldOwnerFractal(fineX,fineY,fineZ,2,0)+VF_FieldOwnerFractal(fineX,fineY,fineZ,2,1))*0.5f; } else if(noiseType==3u) { rough=VF_FieldOwnerCellular(mx,my,mz); fine=VF_FieldOwnerCellular(fineX,fineY,fineZ); } else { rough=VF_FieldOwnerFractal(mx,my,mz,3,0); fine=VF_FieldOwnerFractal(fineX,fineY,fineZ,2,0); } return (rough+fine*0.4f)*1.25f*surfaceRoughness; } VF_FIELD_INLINE float VF_FieldSmoothUnit(float value) { #if defined(VF_FIELD_HLSL) value = clamp(value, 0.0f, 1.0f); #else value = std::clamp(value, 0.0f, 1.0f); #endif return value * value * (3.0f - 2.0f * value); } VF_FIELD_INLINE float VF_FieldSmoothMin(float a, float b, float width) { if (width <= 0.0f) #if defined(VF_FIELD_HLSL) return a < b ? a : b; #else return std::min(a, b); #endif const float delta = a - b; #if defined(VF_FIELD_HLSL) const float absolute = abs(delta); const float minimum = min(a, b); const float h = max(width - absolute, 0.0f) / width; #else const float absolute = std::abs(delta); const float minimum = std::min(a, b); const float h = std::max(width - absolute, 0.0f) / width; #endif return minimum - (h * h * h) * (width / 6.0f); } VF_FIELD_INLINE float VF_FieldSmoothMax(float a, float b, float width) { return -VF_FieldSmoothMin(-a, -b, width); } // CPU and GPU use the same edge-root rule. The optional midpoint is the exact analytic // field evaluation used by the reference LOD0 mesher for roots close to an edge midpoint. VF_FIELD_INLINE float VF_FieldRefineRoot(float d1, float d2, float midpoint, VF_FieldU32 hasMidpoint) { float t = (d2 - d1 < 1.0e-4f && d1 - d2 < 1.0e-4f) ? 0.5f : (-d1) / (d2 - d1); #if defined(VF_FIELD_HLSL) t = clamp(t, 0.0f, 1.0f); #else t = t < 0.0f ? 0.0f : (t > 1.0f ? 1.0f : t); #endif // hasMidpoint is an integer CPU decision made from the reference edge parameter. Do not // repeat its threshold test in floating point on different GPU implementations. if (hasMidpoint != 0u) { if (midpoint < 1.0e-6f && midpoint > -1.0e-6f) return 0.5f; if ((midpoint < 0.0f) == (d1 < 0.0f)) { const float denominator = d2 - midpoint; if (denominator < -1.0e-4f || denominator > 1.0e-4f) { float half = (-midpoint) / denominator; #if defined(VF_FIELD_HLSL) half = clamp(half, 0.0f, 1.0f); #else half = half < 0.0f ? 0.0f : (half > 1.0f ? 1.0f : half); #endif return 0.5f + 0.5f * half; } } else { const float denominator = midpoint - d1; if (denominator < -1.0e-4f || denominator > 1.0e-4f) { float half = (-d1) / denominator; #if defined(VF_FIELD_HLSL) half = clamp(half, 0.0f, 1.0f); #else half = half < 0.0f ? 0.0f : (half > 1.0f ? 1.0f : half); #endif return 0.5f * half; } } } return t; } #undef VF_FIELD_INLINE #undef VF_FIELD_CAST_I32 #undef VF_FIELD_CAST_U32 #undef VF_FIELD_CAST_F32 #undef VF_FIELD_FLOOR #undef VF_FIELD_ABS #undef VF_FIELD_SQRT #undef VF_FIELD_MIN #undef VF_FIELD_MAX #undef VF_FIELD_CLAMP #undef VF_FIELD_LERP