diff --git a/TerrainMesh3D.xcodeproj/project.pbxproj b/TerrainMesh3D.xcodeproj/project.pbxproj index 7330d20..6d86b12 100644 --- a/TerrainMesh3D.xcodeproj/project.pbxproj +++ b/TerrainMesh3D.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 972B7A1926D7ECAE0048CFE2 /* SCN_Math.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SCN_Math.h; sourceTree = ""; }; C901C20B1DCFC07200FC1F5A /* TerrainMesh3D.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TerrainMesh3D.app; sourceTree = BUILT_PRODUCTS_DIR; }; C901C20E1DCFC07200FC1F5A /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; C901C20F1DCFC07200FC1F5A /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -67,6 +68,7 @@ C901C20D1DCFC07200FC1F5A /* TerrainMesh3D */ = { isa = PBXGroup; children = ( + 972B7A1926D7ECAE0048CFE2 /* SCN_Math.h */, C901C2261DCFC0BC00FC1F5A /* TerrainMesh.h */, C901C2271DCFC0BC00FC1F5A /* TerrainMesh.m */, C901C22C1DCFD5CF00FC1F5A /* TerrainView.h */, @@ -139,6 +141,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); diff --git a/TerrainMesh3D/AppDelegate.m b/TerrainMesh3D/AppDelegate.m index 55f75b7..ba3a2fc 100644 --- a/TerrainMesh3D/AppDelegate.m +++ b/TerrainMesh3D/AppDelegate.m @@ -20,14 +20,20 @@ // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. +// +// web page: +// github sources: +// +// Modifications by on Aug 26, 2021 +// Added orbiting omni light +// #import "AppDelegate.h" #import #import "TerrainMesh.h" #import "TerrainView.h" -#define BIGRANDOMVAL 9999999 -#define RANDOMPERCENTAGE ((float)((arc4random()%BIGRANDOMVAL) / (float)BIGRANDOMVAL)) +#define RANDOMPERCENTAGE ((CGFloat) (arc4random() / (CGFloat) RAND_MAX)) @interface AppDelegate () @@ -44,52 +50,63 @@ @interface AppDelegate () @implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - + //resize our window to our main monitor screen size + NSScreen *screen = [[NSScreen screens] objectAtIndex: 0]; + + //set the window's orgin and frame based on the screen's orgin and frame + NSRect screenFrame = [screen visibleFrame]; + [self.window setFrameOrigin:screenFrame.origin]; + [self.window setFrame:screen.frame display:YES animate:NO]; + /* Set up our TerrainView. This is a simple SCNView subclass which handles the mouse events and passes those clicks along to the mesh so we can deform the terrain in the demo. */ - + TerrainView *terrainView = [[TerrainView alloc] initWithFrame:self.window.contentView.bounds options:nil]; terrainView.allowsCameraControl = NO; terrainView.autoenablesDefaultLighting = NO; terrainView.showsStatistics = YES; + //terrainView.debugOptions = SCNDebugOptionRenderAsWireframe; [self.window.contentView addSubview:terrainView]; self.terrainView = terrainView; - + /* Create a simple scene with a sky background */ SCNScene *scene = [SCNScene scene]; scene.background.contents = [NSImage imageNamed:@"sky2"]; terrainView.scene = scene; - + /* Give our scene some basic lighting */ [self configureDefaultLighting]; - + /* Create the terrain mesh */ const int kMeshResolution = 40; TerrainMesh *mesh = [TerrainMesh terrainMeshWithResolution:kMeshResolution sideLength:10.0]; - + /* Give the terrain a nice terrain-y texture */ SCNMaterial *mat = [SCNMaterial material]; mat.diffuse.contents = [NSImage imageNamed:@"grass1"]; mat.doubleSided = YES; mesh.geometry.materials = @[mat]; - + /* Add the terrain to the scene */ [scene.rootNode addChildNode:mesh]; self.mesh = mesh; - + + [self exampleClicked:NULL]; // add a mountain + /* Give the starting terrain a nice little spin intro for the Demo */ [SCNTransaction begin]; [SCNTransaction setAnimationDuration:2.2]; - mesh.rotation = SCNVector4Make(1.0, 0.2, 0, -M_PI_4); - mesh.position = SCNVector3Make(0.0, 2.0, 0.0); +// mesh.rotation = SCNVector4Make(1.0, 0.2, 0, -M_PI_4); +// mesh.position = SCNVector3Make(0.0, 2.0, 0.0); + scene.rootNode.rotation = SCNVector4Make(1.0, 0.0, 0, -M_PI_4); [SCNTransaction commit]; - + /* Add the tools view, make sure it's above the SCNView debug controls */ [self.window.contentView addSubview:self.toolsView positioned:NSWindowAbove relativeTo:terrainView]; NSRect toolsFrame = self.toolsView.frame; - toolsFrame.origin.y += 20.0; + toolsFrame.origin.y += 20; self.toolsView.frame = toolsFrame; } @@ -98,21 +115,33 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - (void)configureDefaultLighting { SCNScene *scene = self.terrainView.scene; - + SCNNode *ambientLightNode = [SCNNode node]; ambientLightNode.light = [SCNLight light]; ambientLightNode.light.type = SCNLightTypeAmbient; - ambientLightNode.light.color = [NSColor colorWithRed:1.0 green:1.0 blue:0.7 alpha:1.0]; + ambientLightNode.light.color = [NSColor colorWithRed:1 green:1 blue:0.7 alpha:1]; ambientLightNode.light.intensity = 300; [scene.rootNode addChildNode:ambientLightNode]; - + SCNNode *spotLightNode = [SCNNode node]; spotLightNode.light = [SCNLight light]; - spotLightNode.light.type = SCNLightTypeSpot; + spotLightNode.light.type = SCNLightTypeOmni; spotLightNode.light.color = [NSColor colorWithWhite:1.0 alpha:0.0]; spotLightNode.light.intensity = 1000; - spotLightNode.position = SCNVector3Make(5.0, 5.0, 10); + spotLightNode.position = SCNVector3Make(0, 5, 3); [scene.rootNode addChildNode:spotLightNode]; + + SCNNode *planetNode = [SCNNode node]; + planetNode.geometry = [SCNSphere sphereWithRadius:.1]; + [spotLightNode addChildNode:planetNode]; + + NSTimeInterval duration = 6; + [spotLightNode runAction:[SCNAction repeatActionForever:[SCNAction customActionWithDuration:duration + actionBlock:^(SCNNode * _Nonnull node, CGFloat elapsedTime) { + double angleRAD = elapsedTime * M_PI * 2 / duration; + CGFloat orbit_radius = 3; + node.position = SCNVector3Make(5 + (orbit_radius * sin(angleRAD)), 5 + (orbit_radius * cos(angleRAD)), 3); + }]]]; } #pragma mark - Demo Actions @@ -132,28 +161,27 @@ - (IBAction)resetClicked:(id)sender /* Reset the mesh to be completely flat. */ [self.mesh updateGeometry:^double(int x, int y) { - return 0.0; + return 0; }]; } - (IBAction)exampleClicked:(id)sender { int meshResolution = self.mesh.verticesPerSide; - + int mapHalf = (meshResolution/2); + [self.mesh updateGeometry:^double(int x, int y) { /* Randomize terrain to be slightly bumpy with a small mountain in the center. */ - - int mapHalf = (meshResolution/2); + double xDelta = (x - mapHalf); double yDelta = (y - mapHalf); double distance = sqrt((xDelta * xDelta) + (yDelta * yDelta)); distance /= mapHalf; - distance /= 1; - distance = 1.0 - distance; + distance = 1 - distance; distance = sin((distance * distance) * M_PI_2); - - return (distance * 2.75) + (RANDOMPERCENTAGE * .10); + + return (distance * 2.75) + (RANDOMPERCENTAGE * .1); }]; } diff --git a/TerrainMesh3D/Base.lproj/MainMenu.xib b/TerrainMesh3D/Base.lproj/MainMenu.xib index 9514b4c..f08c4fc 100644 --- a/TerrainMesh3D/Base.lproj/MainMenu.xib +++ b/TerrainMesh3D/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - - + + @@ -326,24 +326,25 @@ + - + + - - + - + + + - + - + + + + Click 'Paint Brush' and then click on the terrain to deform. Click 'Adjust Camera' and then drag around to move. Tip: Option-click with Paint Brush lowers terrain. @@ -371,9 +377,8 @@ - - - + + + + + + + + + + + + + + + + + + + + diff --git a/TerrainMesh3D/SCN_Math.h b/TerrainMesh3D/SCN_Math.h new file mode 100644 index 0000000..2014f52 --- /dev/null +++ b/TerrainMesh3D/SCN_Math.h @@ -0,0 +1,340 @@ +// +// SCN_Math.h +// +// Created by George Warner on 8/15/21. +// +// No rights reserved... Use at your own risk... blah, blah, blah. +// + +#ifndef SCN_Math_h +#define SCN_Math_h + +#if PRAGMA_ONCE +#pragma once +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if PRAGMA_IMPORT +#pragma import on +#endif + +#if PRAGMA_STRUCT_ALIGN +#pragma options align=mac68k +#elif PRAGMA_STRUCT_PACKPUSH +#pragma pack(push, 2) +#elif PRAGMA_STRUCT_PACK +#pragma pack(2) +#endif + +//----------------------------------------------------- +//typedef's, struct's, enum's, etc. +//----------------------------------------------------- + +#ifndef DEG2RAD +#define DEG2RAD(d) ((CGFloat)((d) * M_PI / 180)) +#endif + +#ifndef RAD2DEG +#define RAD2DEG(r) ((CGFloat)((r) * 180 / M_PI)) +#endif + +#if !defined(MIN) +#define MIN(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) +#endif + +#if !defined(MAX) +#define MAX(A, B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a > __b ? __a : __b; }) +#endif + +#if !defined(PIN) +#define PIN(minValue, maxValue, theValue) (MAX(minValue, MIN(maxValue, theValue))) +#endif + +#if !defined(ABS) +#define ABS(A) ({ __typeof__(A) __a = (A); __a < 0 ? -__a : __a; }) +#endif + +#if !defined(SWAP) +#define SWAP(A, B) ({__typeof__(A) __T = (A); (A) = (B); (B) = __T;}) +#endif + +#if !defined(XY) +#define XY(V) (V).x, (V).y +#endif // !defined(XY) + +#if !defined(XYZ) +#define XYZ(V) (V).x, (V).y, (V).z +#endif // !defined(XYZ) + +#if !defined(XYZW) +#define XYZW(V) (V).x, (V).y, (V).z, (V).w +#endif // !defined(XYZW) + +#if !defined(RGB) +#define RGB(c) (c).r, (c).g, (c).b +#endif //if !defined(RGB) + +#if !defined(RGBA) +#define RGBA(c) (c).r, (c).g, (c).b, (c).a +#endif //if !defined(RGBA) + +#if CGFLOAT_IS_DOUBLE +#define SIN(r) sin(r) +#define COS(r) cos(r) +#define HYPOT(x, y) hypot(x, y) +#define FMOD(x, y) fmod(x, y) +#define SQRT(x) sqrt(x) +#define FLOOR(x) floor(x) +#define CEIL(x) ceil(x) +#define ROUND(x) round(x) +#define LOG(x) log(x) +#define POW(n, e) pow(n, e) +#define FMIN(a, b) fmin(a, b) +#define FMAX(a, b) fmax(a, b) +#else +#define SIN(r) sinf(r) +#define COS(r) cosf(r) +#define HYPOT(x, y) hypotf(x, y) +#define FMOD(x, y) fmodf(x, y) +#define SQRT(x) sqrtf(x) +#define FLOOR(x) floorf(x) +#define CEIL(x) ceilf(x) +#define ROUND(x) roundf(x) +#define LOG(x) logf(x) +#define POW(n, e) powf(n, e) +#define FMIN(a, b) fminf(a, b) +#define FMAX(a, b) fmaxf(a, b) +#endif + +#define FPIN(minValue, maxValue, theValue) (FMAX(minValue, FMIN(maxValue, theValue))) + +typedef struct SCN_Plane3D_Struct { + union { + CGFloat f[6]; + struct { + SCNVector3 o; + SCNVector3 n; + }; + }; +}SCN_Plane3D_Rec, *SCN_Plane3D_Ptr; + +//---------------------------------------------------- +//external (exported) global consts +//---------------------------------------------------- + +//---------------------------------------------------- +//static inlined functions +//---------------------------------------------------- +#pragma mark - "* CGFloat" + +static inline CGFloat CGFloatSign(CGFloat n) { + return (n > 0) - (n < 0); +} + +static inline CGFloat CGFloatRandom(CGFloat inCGFloatMin, CGFloat inCGFloatMax) +{ + //CGFloat range = (b - a < 0) ? (b - a - 1) : (b - a + 1); + CGFloat range = inCGFloatMax - inCGFloatMin; + range += CGFloatSign(range); + CGFloat value = (CGFloat)(range * ((CGFloat) arc4random() / (CGFloat) RAND_MAX)); + return (value == range) ? inCGFloatMin : (inCGFloatMin + value); +} + +static inline CGFloat CGFloatLERP(CGFloat inCGFloatA, CGFloat inCGFloatB, CGFloat inFraction) +{ + return (1 - inFraction) * inCGFloatA + inCGFloatA * inCGFloatB; +} + +static inline CGFloat CGFloatSLERP(CGFloat inCGFloatA, CGFloat inCGFloatB, CGFloat inFraction) +{ + return CGFloatLERP(inCGFloatA, inCGFloatB, (1 - COS(FMOD(inFraction, 1) * M_PI)) / 2); +} + +#pragma mark - "* CGPoint" + +static inline CGPoint CGPointRandom(CGPoint inCGPointA, CGPoint inCGPointB) { + return CGPointMake(CGFloatRandom(inCGPointA.x, inCGPointB.x), CGFloatRandom(inCGPointA.y, inCGPointB.y)); +} + +static inline CGPoint CGPointAdd(CGPoint inCGPointA, CGPoint inCGPointB) { + return CGPointMake(inCGPointA.x + inCGPointB.x, inCGPointA.y + inCGPointB.y); +} + +static inline CGPoint CGPointSubtract(CGPoint inCGPointA, CGPoint inCGPointB) { + return CGPointMake(inCGPointA.x - inCGPointB.x, inCGPointA.y - inCGPointB.y); +} + +static inline CGPoint CGPointScale(CGPoint inCGPointA, CGFloat inScalar) { + return CGPointMake(inCGPointA.x * inScalar, inCGPointA.y * inScalar); +} + +static inline CGPoint CGPointMultiply(CGPoint inCGPointA, CGFloat inScalar) { + return CGPointScale(inCGPointA, inScalar); +} + +static inline CGPoint CGPointDivide(CGPoint inCGPointA, CGFloat inScalar) { + return CGPointScale(inCGPointA, 1 / inScalar); +} + +static inline CGFloat CGPointDistance(CGPoint inCGPointA, CGPoint inCGPointB) { + CGPoint diff = CGPointSubtract(inCGPointA, inCGPointB); + return HYPOT(diff.x, diff.y); +} + +static inline CGPoint CGPointLerp(CGPoint inCGPointA, CGPoint inCGPointB, CGFloat inFraction) { + return CGPointMake(CGFloatLERP(inCGPointA.x, inCGPointB.x, inFraction), CGFloatLERP(inCGPointA.y, inCGPointB.y, inFraction)); +} + +/** + * get (signed) distance p3 is from line segment defined by p1 and p2 + * + * @param linePointA the first point on the line segment + * @param linePointB the second point on the line segment + * @param thePoint the point whose distance from the line segment you wish to calculate + * @return the distance (note: plus/minus determines the (left/right) side of the line) + */ +static inline CGFloat CGPointDistanceToLine(CGPoint linePointA, + CGPoint linePointB, + CGPoint thePoint) { + double a = linePointA.y - linePointB.y; + double b = linePointB.x - linePointA.x; + double c = (linePointA.x * linePointB.y) - (linePointB.x * linePointA.y); + + return (a * thePoint.x + b * thePoint.y + c) / HYPOT(a, b); +} + +#pragma mark - "* SCNVector3" + +static inline SCNVector3 SCNVector3Add(const SCNVector3 inVectorA, const SCNVector3 inVectorB) +{ + SCNVector3 result; + result.x = inVectorA.x + inVectorB.x; + result.y = inVectorA.y + inVectorB.y; + result.z = inVectorA.z + inVectorB.z; + return result; +} + +static inline SCNVector3 SCNVector3Sub(const SCNVector3 inVectorA, const SCNVector3 inVectorB) +{ + SCNVector3 result; + result.x = inVectorA.x - inVectorB.x; + result.y = inVectorA.y - inVectorB.y; + result.z = inVectorA.z - inVectorB.z; + return result; +} + +static inline SCNVector3 SCNVector3Mul(const SCNVector3 inVector, CGFloat inScalar) +{ + SCNVector3 result; + result.x = inVector.x * inScalar; + result.y = inVector.y * inScalar; + result.z = inVector.z * inScalar; + return result; +} + +static inline SCNVector3 SCNVector3Div(const SCNVector3 inVector, CGFloat inScalar) +{ + return SCNVector3Mul(inVector, 1 / inScalar); +} + +static inline SCNVector3 SCNVector3Cross(const SCNVector3 inVectorA, const SCNVector3 inVectorB) +{ + SCNVector3 result; + result.x = inVectorA.y * inVectorB.z - inVectorA.z * inVectorB.y; + result.y = inVectorA.z * inVectorB.x - inVectorA.x * inVectorB.z; + result.z = inVectorA.x * inVectorB.y - inVectorA.y * inVectorB.x; + return result; +} + +static inline CGFloat SCNVector3Dot(const SCNVector3 inVectorA, const SCNVector3 inVectorB) +{ + return (inVectorA.x * inVectorB.x + inVectorA.y * inVectorB.y + inVectorA.z * inVectorB.z); +} + +static inline SCNVector3 SCNVector3Lerp(const SCNVector3 inVectorA, const SCNVector3 inVectorB, CGFloat inFraction) +{ + CGFloat ratio = fmodf(inFraction, 1); + return SCNVector3Add(SCNVector3Mul(inVectorA, 1 - ratio), SCNVector3Mul(inVectorB, ratio)); +} + +static inline CGFloat SCNVector3LenSqr(const SCNVector3 inVector) +{ + return SCNVector3Dot(inVector, inVector); +} + +#define SCNVector3Len SCNVector3Length +static inline CGFloat SCNVector3Length(const SCNVector3 inVector) +{ + return SQRT(SCNVector3LenSqr(inVector)); +} + +#define SCNVector3Dis2 SCNVector3DisSqr +static inline CGFloat SCNVector3DisSqr(const SCNVector3 inVectorA, const SCNVector3 inVectorB) +{ + return SCNVector3LenSqr(SCNVector3Sub(inVectorA, inVectorB)); +} + +#define SCNVector3Dis SCNVector3Distance +static inline CGFloat SCNVector3Distance(const SCNVector3 inVectorA, const SCNVector3 inVectorB) +{ + return SCNVector3Length(SCNVector3Sub(inVectorA, inVectorB)); +} + +static inline SCNVector3 SCNVector3Mid(const SCNVector3 inVectorA, const SCNVector3 inVectorB) +{ + return SCNVector3Mul(SCNVector3Add(inVectorA, inVectorB), 0.5f); +} + +static inline SCNVector3 SCNVector3Norm(const SCNVector3 inVector) +{ + CGFloat length = SCNVector3Length(inVector); + if (length < 1.0e-8f) + return inVector; + else + return SCNVector3Mul(inVector, 1 / length); +} + +static inline SCNVector3 SCNVector3NormCross(const SCNVector3 inVectorA, const SCNVector3 inVectorB) +{ + return SCNVector3Norm(SCNVector3Cross(inVectorA, inVectorB)); +} + +#pragma mark - "* Vector4F" + +#pragma mark - "* Plane3D" + +static inline SCN_Plane3D_Rec SCN_SetPlane3D(const SCNVector3 inVectorA, const SCNVector3 inVectorB, const SCNVector3 inVectorC) +{ + SCN_Plane3D_Rec plane; + plane.o = inVectorA; + plane.n = SCNVector3NormCross(SCNVector3Sub(inVectorB, inVectorA), SCNVector3Sub(inVectorC, inVectorA)); + return plane; +} + +static inline CGFloat SCN_DistanceToPlane3D(const SCN_Plane3D_Rec inPlane, const SCNVector3 inVector) +{ + return SCNVector3Dot(SCNVector3Sub(inVector, inPlane.o), inPlane.n); +} + +#if PRAGMA_STRUCT_ALIGN +#pragma options align=reset +#elif PRAGMA_STRUCT_PACKPUSH +#pragma pack(pop) +#elif PRAGMA_STRUCT_PACK +#pragma pack() +#endif + +#ifdef PRAGMA_IMPORT_OFF +#pragma import off +#elif PRAGMA_IMPORT +#pragma import reset +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* SCN_Math_h */ diff --git a/TerrainMesh3D/TerrainMesh.m b/TerrainMesh3D/TerrainMesh.m index 8a4986f..ed973c4 100644 --- a/TerrainMesh3D/TerrainMesh.m +++ b/TerrainMesh3D/TerrainMesh.m @@ -20,9 +20,18 @@ // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. +// +// web page: +// github sources: +// +// Modifications by on Aug 26, 2021 +// compute normals based on cross product of triangle vertices +// #import "TerrainMesh.h" +#import "SCN_Math.h" // for SCNVector3Cross & SCNVector3Sub + @interface TerrainMesh () //Internal data structures @@ -40,8 +49,7 @@ @implementation TerrainMesh #pragma mark - Init / Factory Methods + (instancetype)terrainMeshWithResolution:(int)verticesPerSide - sideLength:(double)sideLength -{ + sideLength:(double)sideLength { return [self terrainMeshWithResolution:verticesPerSide sideLength:sideLength vertexHeight:nil]; @@ -49,46 +57,41 @@ + (instancetype)terrainMeshWithResolution:(int)verticesPerSide + (instancetype)terrainMeshWithResolution:(int)verticesPerSide sideLength:(double)sideLength - vertexHeight:(double (^)(int x, int y))vertexComputationBlock; -{ + vertexHeight:(double (^)(int x, int y))vertexComputationBlock { TerrainMesh *mesh = [[TerrainMesh alloc] initWithResolution:verticesPerSide sideLength:sideLength vertexHeight:vertexComputationBlock]; - + return mesh; } - (instancetype)initWithResolution:(int)verticesPerSide sideLength:(double)sideLength - vertexHeight:(double (^)(int x, int y))vertexComputationBlock -{ + vertexHeight:(double (^)(int x, int y))vertexComputationBlock { self = [super init]; - - if (self) - { + + if (self) { NSAssert(verticesPerSide >= 2, @"Vertices per side must be at least 2."); - + _verticesPerSide = verticesPerSide; _sideLength = sideLength; _vertexHeightComputationBlock = [vertexComputationBlock copy]; - + [self allocateDataBuffers]; [self populateDataBuffersWithStartingValues]; [self configureGeometry]; } - + return self; } -- (void)allocateDataBuffers -{ - if (_verticesPerSide) - { +- (void)allocateDataBuffers { + if (_verticesPerSide) { NSInteger totalVertices = _verticesPerSide * _verticesPerSide; NSInteger squaresPerSide = (_verticesPerSide - 1); NSInteger totalSquares = squaresPerSide * squaresPerSide; NSInteger totalTriangles = totalSquares * 2; - + _meshVertices = malloc(sizeof(SCNVector3) * totalVertices); _normals = malloc(sizeof(SCNVector3) * totalVertices); _triangleIndices = malloc(sizeof(int) * totalTriangles * 3); @@ -96,83 +99,98 @@ - (void)allocateDataBuffers } } -- (void)populateDataBuffersWithStartingValues -{ +- (void)populateDataBuffersWithStartingValues { NSAssert(_verticesPerSide > 0, @"Invalid mesh resolution."); - + int totalVertices = _verticesPerSide * _verticesPerSide; int squaresPerSide = (_verticesPerSide - 1); int totalSquares = squaresPerSide * squaresPerSide; int totalTriangles = totalSquares * 2; - - for (int i = 0; i < totalVertices; i++) - { + + for (int i = 0; i < totalVertices; i++) { int ix = i % _verticesPerSide; int iy = i / _verticesPerSide; - + double ixf = ((double)ix / (double)(_verticesPerSide - 1)); double iyf = ((double)iy / (double)(_verticesPerSide - 1)); double x = ixf * _sideLength; double y = iyf * _sideLength; - + /* Create vertices */ double vertexZDepth = 0.0; - - if (_vertexHeightComputationBlock) - { + + if (_vertexHeightComputationBlock) { vertexZDepth = _vertexHeightComputationBlock(ix, iy); } - + _meshVertices[i] = SCNVector3Make(x, y, vertexZDepth); - + /* Create normals for each vertex */ _normals[i] = SCNVector3Make(0, 0, 1); - + /* Create texture coords (an X,Y pair for each vertex) */ int ti = i * 2; _textureCoordinates[ti] = ixf; _textureCoordinates[ti+1] = iyf; } - - for (int i = 0; i < totalTriangles; i += 2) - { + + for (int i = 0; i < totalTriangles; i += 2) { /* Define the triangles that make up the terrain mesh */ int squareIndex = (i / 2); int squareX = squareIndex % squaresPerSide; int squareY = squareIndex / squaresPerSide; - + int vPerSide = (int)_verticesPerSide; - int toprightIndex = ((squareY + 1) * vPerSide) + squareX + 1; - int topleftIndex = toprightIndex - 1; - int bottomleftIndex = toprightIndex - vPerSide - 1; - int bottomrightIndex = toprightIndex - vPerSide; - + int bottomleftIndex = (squareY * vPerSide) + squareX; + int bottomrightIndex = bottomleftIndex + 1; + int topleftIndex = bottomleftIndex + vPerSide; + int toprightIndex = topleftIndex + 1; + int i1 = i * 3; - + _triangleIndices[i1] = toprightIndex; _triangleIndices[i1+1] = topleftIndex; _triangleIndices[i1+2] = bottomleftIndex; - + _triangleIndices[i1+3] = toprightIndex; _triangleIndices[i1+4] = bottomleftIndex; _triangleIndices[i1+5] = bottomrightIndex; + + // compute normals + SCNVector3 topleftPoint = _meshVertices[topleftIndex]; + SCNVector3 toprightPoint = _meshVertices[toprightIndex]; + SCNVector3 bottomleftPoint = _meshVertices[bottomleftIndex]; + + _normals[bottomleftIndex] = SCNVector3Cross(SCNVector3Sub(bottomleftPoint, toprightPoint), SCNVector3Sub(bottomleftPoint, topleftPoint)); + } +} + +#pragma mark - Compute Normals + +- (void) computeNormals { + int squaresPerSide = (_verticesPerSide - 1); + int totalSquares = squaresPerSide * squaresPerSide; + int totalTriangles = totalSquares * 2; + for (int i = 0; i < totalTriangles * 3; i += 6) { + int toprightIndex = _triangleIndices[i]; + int topleftIndex = _triangleIndices[i + 1]; + int bottomleftIndex = _triangleIndices[i + 2]; + + SCNVector3 topleftPoint = _meshVertices[topleftIndex]; + SCNVector3 toprightPoint = _meshVertices[toprightIndex]; + SCNVector3 bottomleftPoint = _meshVertices[bottomleftIndex]; + + _normals[bottomleftIndex] = SCNVector3Cross(SCNVector3Sub(bottomleftPoint, toprightPoint), SCNVector3Sub(bottomleftPoint, topleftPoint)); } } #pragma mark - Private Configuration -- (void)configureGeometry -{ +- (void)configureGeometry { NSAssert(_verticesPerSide > 0, @"Invalid mesh resolution."); - - NSArray *originalMaterials = self.geometry.materials; - - NSInteger totalVertices = _verticesPerSide * _verticesPerSide; - NSInteger squaresPerSide = (_verticesPerSide - 1); - NSInteger totalSquares = squaresPerSide * squaresPerSide; - NSInteger totalTriangles = totalSquares * 2; + NSInteger totalVertices = _verticesPerSide * _verticesPerSide; NSMutableData *textureData = [NSMutableData dataWithBytes:_textureCoordinates length:totalVertices * sizeof(float) * 2]; SCNGeometrySource *textureSource = [SCNGeometrySource geometrySourceWithData:textureData semantic:SCNGeometrySourceSemanticTexcoord @@ -182,34 +200,34 @@ - (void)configureGeometry bytesPerComponent:sizeof(float) dataOffset:0 dataStride:sizeof(float) * 2]; - - SCNGeometrySource *vertexSource = - [SCNGeometrySource geometrySourceWithVertices:_meshVertices count:totalVertices]; - - SCNGeometrySource *normalSource = - [SCNGeometrySource geometrySourceWithNormals:_normals count:totalVertices]; - + + SCNGeometrySource *vertexSource = [SCNGeometrySource geometrySourceWithVertices:_meshVertices count:totalVertices]; + + SCNGeometrySource *normalSource = [SCNGeometrySource geometrySourceWithNormals:_normals count:totalVertices]; + + NSInteger squaresPerSide = (_verticesPerSide - 1); + NSInteger totalSquares = squaresPerSide * squaresPerSide; + NSInteger totalTriangles = totalSquares * 2; NSData *indexData = [NSData dataWithBytes:_triangleIndices length:sizeof(int) * totalTriangles * 3]; SCNGeometryElement *element = [SCNGeometryElement geometryElementWithData:indexData primitiveType:SCNGeometryPrimitiveTypeTriangles primitiveCount:totalTriangles bytesPerIndex:sizeof(int)]; - + SCNGeometry *geometry = [SCNGeometry geometryWithSources:@[vertexSource, normalSource, textureSource] elements:@[element]]; - geometry.materials = originalMaterials; - + geometry.materials = self.geometry.materials; + self.geometry = geometry; } #pragma mark - Updating Terrain -- (void)updateGeometry:(double (^)(int x, int y))vertexComputationBlock -{ - if (vertexComputationBlock) - { +- (void)updateGeometry:(double (^)(int x, int y))vertexComputationBlock { + if (vertexComputationBlock) { self.vertexHeightComputationBlock = vertexComputationBlock; //TODO: This approach is much less efficient than it could be [self populateDataBuffersWithStartingValues]; + [self computeNormals]; [self configureGeometry]; } } @@ -218,34 +236,31 @@ - (void)updateGeometry:(double (^)(int x, int y))vertexComputationBlock - (void)derformTerrainAt:(CGPoint)point brushRadius:(double)brushRadius - intensity:(double)intensity -{ + intensity:(double)intensity { double radiusInIndices = brushRadius * (double)_verticesPerSide; double vx = ((double)_verticesPerSide * point.x); double vy = ((double)_verticesPerSide * point.y); - - for (int y = 0; y < _verticesPerSide; y++) - { - for (int x = 0; x < _verticesPerSide; x++) - { + + for (int y = 0; y < _verticesPerSide; y++) { + for (int x = 0; x < _verticesPerSide; x++) { double xDelta = (vx - x); double yDelta = (vy - y); double dist = sqrt((xDelta * xDelta) + (yDelta * yDelta)); - - if (dist < radiusInIndices) - { + + if (dist < radiusInIndices) { int index = (y * _verticesPerSide) + x; - + double relativeIntensity = (1.0 - (dist / radiusInIndices)); - + relativeIntensity = sin(relativeIntensity * M_PI_2); relativeIntensity *= intensity; - + _meshVertices[index].z += relativeIntensity; } } } - + + [self computeNormals]; [self configureGeometry]; } diff --git a/TerrainMesh3D/TerrainView.m b/TerrainMesh3D/TerrainView.m index b32b3c2..d27665c 100644 --- a/TerrainMesh3D/TerrainView.m +++ b/TerrainMesh3D/TerrainView.m @@ -31,14 +31,11 @@ - (void)mouseDown:(NSEvent *)event /* For the demo, if camera control is on we allow the normal SCNView machinery to handle the event. If not, we apply the paint brush deformation below. */ - - if (!self.allowsCameraControl) - { - [self applyDeformToMesh:event]; - } - else - { + + if (self.allowsCameraControl) { [super mouseDown:event]; + } else { + [self applyDeformToMesh:event]; } } @@ -47,40 +44,30 @@ - (void)mouseDragged:(NSEvent *)event /* For the demo, if camera control is on we allow the normal SCNView machinery to handle the event. If not, we apply the paint brush deformation below. */ - - if (!self.allowsCameraControl) - { - [self applyDeformToMesh:event]; - } - else - { + + if (self.allowsCameraControl) { [super mouseDragged:event]; + } else { + [self applyDeformToMesh:event]; } } #pragma mark - Terrain Paint Brush -- (void)applyDeformToMesh:(NSEvent *)event -{ - NSPoint point = event.locationInWindow; - point = [self convertPoint:point fromView:nil]; - - NSArray *hitResults = [self hitTest:point options:nil]; - - for (SCNHitTestResult *result in hitResults) - { - id node = result.node; - - if ([node isKindOfClass:[TerrainMesh class]]) - { - TerrainMesh *mesh = node; +- (void)applyDeformToMesh:(NSEvent *)event { + NSPoint point = [self convertPoint:event.locationInWindow fromView:nil]; + double intensity = (event.modifierFlags & NSEventModifierFlagOption) ? -0.025 : 0.025; + + for (SCNHitTestResult *result in [self hitTest:point options:nil]) { + if ([result.node isKindOfClass:[TerrainMesh class]]) { + TerrainMesh *mesh = (TerrainMesh *) result.node; double meshSize = mesh.sideLength; - + CGPoint relativeLocation = CGPointMake(result.localCoordinates.x / meshSize, result.localCoordinates.y / meshSize); [mesh derformTerrainAt:relativeLocation brushRadius:0.25 - intensity:.025 * ((event.modifierFlags & NSEventModifierFlagOption) ? -1.0 : 1.0)]; + intensity:intensity]; } } }