Generating vertices programmatically - ios

I have a 3D object (SCNPlane) i want to divide this plane into squares. Idea is to divide the plane into tiles and each tile has its own textures (texture coordinates). And the number of tiles is controlled by user interface.
In the above image the plane is divided into 3 tile along x axis and 3 tile along y axis and each tile is further divided into two triangles. Right now i am trying to use the for loops to generate these vertices. I am new to this metal/opengl world if anyone can point me in the right direction it would be great.
Thanks

This is just math...
var x0 = 0, x1 = 1000
var y0 = 0, y1 = 1000
var ySplit = 4
var xSplit = 6
for y in (0..<ySplit).reverse() {
var localY0 = y * ((y1 - y0) / ySplit)
var localY1 = (y+1) * ((y1 - y0) / ySplit)
for x in 0..<xSplit {
var localX0 = x * ((x1 - x0) / xSplit)
var localX1 = (x+1) * ((x1 - x0) / xSplit)
//Now you can easily get any vertex/square/triangle set from the given (x0,y) (x1,y1)
}
}

SCNPlane has widthSegmentCount and heightSegmentCount properties that do just that (see SCNPlane Reference).
Edit
If you need custom texture coordinates then you'll have to build a custom geometry using SCNGeometry.init(sources:elements:) and compute vertex positions yourself.
Note that in Swift you have the handy
SCNGeometryElement.init(indices:primitiveType:)
and the following convenience initializers in iOS 10, tvOS 10, macOS 10.12 and watchOS 3:
SCNGeometrySource.init(normals:)
SCNGeometrySource.init(textureCoordinates:)
SCNGeometrySource.init(vertices:)

Related

Object picking with Ray casting in elm-webgl

Demo almost (?) working example: https://ellie-app.com/4h9F8FNcRPya1/1
For demo: Click to draw ray, and rotate camera with left and right to see ray. (As the origin is from the camera, you can't see it from the position it is created)
Context
I am working on an elm & elm-webgl project where I would like to know if the mouse is over an object when clicked. To do is I tried to implement a simple ray cast. What I need is two things:
1) The coordinate of the camera (This one is easy)
2) The coordinate/direction in 3D space of where was clicked
Problem
The steps to get from 2D view space to 3D world space as I understand are:
a) Make coordinates to be in a range of -1 to 1 relative to view port
b) Invert projection matrix and perspective matrix
c) Multiply projection and perspective matrix
d) Create Vector4 from normalised mouse coordinates
e) Multiply combined matrices with Vector4
f) Normalise result
Try so far
I have made a function to transform a Mouse.Position to a coordinate to draw a line to:
getClickPosition : Model -> Mouse.Position -> Vec3
getClickPosition model pos =
let
x =
toFloat pos.x
y =
toFloat pos.y
normalizedPosition =
( (x * 2) / 1000 - 1, (1 - y / 1000 * 2) )
homogeneousClipCoordinates =
Vec4.vec4
(Tuple.first normalizedPosition)
(Tuple.second normalizedPosition)
-1
1
inversedProjectionMatrix =
Maybe.withDefault Mat4.identity (Mat4.inverse (camera model))
inversedPerspectiveMatrix =
Maybe.withDefault Mat4.identity (Mat4.inverse perspective)
inversedMatrix2 =
Mat4.mul inversedProjectionMatrix inversedPerspectiveMatrix
to =
Vec4.vec4
(Tuple.first normalizedPosition)
(Tuple.second normalizedPosition)
1
1
toInversed =
mulVector inversedMatrix2 to
toNorm =
Vec4.normalize toInversed
toVec3 =
vec3 (Vec4.getX toNorm) (Vec4.getY toNorm) (Vec4.getZ toNorm)
in
toVec3
Result
The result of this function is that the rays are too much to the center to where I click. I added a screenshot where I clicked in all four of the top face of the cube. If I click on the center of the viewport the ray will be correctly positioned.
It feels close, but not quite there yet and I can't figure out what I am doing wrong!
After trying other approaches I found a solution:
getClickPosition : Model -> Mouse.Position -> Vec3
getClickPosition model pos =
let
x =
toFloat pos.x
y =
toFloat pos.y
normalizedPosition =
( (x * 2) / 1000 - 1, (1 - y / 1000 * 2) )
homogeneousClipCoordinates =
Vec4.vec4
(Tuple.first normalizedPosition)
(Tuple.second normalizedPosition)
-1
1
inversedViewMatrix =
Maybe.withDefault Mat4.identity (Mat4.inverse (camera model))
inversedProjectionMatrix =
Maybe.withDefault Mat4.identity (Mat4.inverse perspective)
vec4CameraCoordinates = mulVector inversedProjectionMatrix homogeneousClipCoordinates
direction = Vec4.vec4 (Vec4.getX vec4CameraCoordinates) (Vec4.getY vec4CameraCoordinates) -1 0
vec4WorldCoordinates = mulVector inversedViewMatrix direction
vec3WorldCoordinates = vec3 (Vec4.getX vec4WorldCoordinates) (Vec4.getY vec4WorldCoordinates) (Vec4.getZ vec4WorldCoordinates)
normalizedVec3WorldCoordinates = Vec3.normalize vec3WorldCoordinates
origin = model.cameraPos
scaledDirection = Vec3.scale 20 normalizedVec3WorldCoordinates
destination = Vec3.add origin scaledDirection
in
destination
I left it as verbose as possible, if someone finds I use incorrect terminology please make a comment and I will update the answer.
I am sure there are lots of optimisations possible (Multiplying matrices before inverting or combining some of the steps.)
Updated the ellie app here: https://ellie-app.com/4hZ9s8S92PSa1/0

How Are Orthographic Coordinates Normalized?

WebGL draws coordinates that vary from -1 to 1. These coordinates become normalized by dividing by w -- the perspective divide. How does this happen with an orthographic projection because the orthographic projection matrix is the identity matrix. That is w will remain 1. How are the coordinates then normalized from [-1,1] with an orthographic projection?
What do you mean by "normalized"?
WebGL doesn't care what your matrices are it just cares what you set gl_Position to.
A typical orthographic matrix just scales and translates x and y (and z) and sets w to 1.
The formula for how what you set gl_Position to gets converted to a pixel is something like
var x = gl_Position.x / gl.Position.w;
var y = gl_Position.y / gl.Position.w;
// convert from -1 <-> 1 to 0 to 1
var zeroToOneX = x * 0.5 + 0.5;
var zeroToOneY = y * 0.5 + 0.5;
// convert from 0 <-> 1 to viewportX <-> (viewportX + viewportWidth)
// and do something similar for Y
var pixelX = viewportX + zeroToOneX * viewportWidth;
var pixelY = viewportY + zeroToOneY * viewportHeight;
Where viewportX, viewportY, viewportWidth, and viewportHeight are set with gl.viewport
If you want the exact formula you can look in the spec under rasterization.
Maybe you might find these tutorials helpful.

Graphing Polar Functions with UIBezierPath

Is it possible to graph a polar function with UIBezierPath? More than just circles, I'm talking about cardioids, limacons, lemniscates, etc. Basically I have a single UIView, and want to draw the shape in the view.
There are no built in methods for shapes like that, but you can always approximate them with a series of very short straight lines. I've had reason to approximate a circle this way, and a circle with ~100 straight lines looks identical to a circle drawn with ovalInRect. It was easiest when doing this, to create the points in polar coordinates first, then convert those in a loop to rectangular coordinates before passing the points array to a method where I add the lines to a bezier path.
Here's my swift helper function (fully commented) that generates the (x,y) coordinates in a given CGRect from a polar coordinate function.
func cartesianCoordsForPolarFunc(frame: CGRect, thetaCoefficient:Double, thetaCoefficientDenominator:Double, cosScalar:Double, iPrecision:Double) -> Array<CGPoint> {
// Frame: The frame in which to fit this curve.
// thetaCoefficient: The number to scale theta by in the cos.
// thetaCoefficientDenominator: The denominator of the thetaCoefficient
// cosScalar: The number to multiply the cos by.
// iPrecision: The step for continuity. 0 < iPrecision <= 2.pi. Defaults to 0.1
// Clean inputs
var precision:Double = 0.1 // Default precision
if iPrecision != 0 {// Can't be 0.
precision = iPrecision
}
// This is ther polar function
// var theta: Double = 0 // 0 <= theta <= 2pi
// let r = cosScalar * cos(thetaCoefficient * theta)
var points:Array<CGPoint> = [] // We store the points here
for theta in stride(from: 0, to: 2*Double.pi * thetaCoefficientDenominator, by: precision) { // Try to recreate continuity
let x = cosScalar * cos(thetaCoefficient * theta) * cos(theta) // Convert to cartesian
let y = cosScalar * cos(thetaCoefficient * theta) * sin(theta) // Convert to cartesian
let scaled_x = (Double(frame.width) - 0)/(cosScalar*2)*(x-cosScalar)+Double(frame.width) // Scale to the frame
let scaled_y = (Double(frame.height) - 0)/(cosScalar*2)*(y-cosScalar)+Double(frame.height) // Scale to the frame
points.append(CGPoint(x: scaled_x, y:scaled_y)) // Add the result
}
return points
}
Given those points here's an example of how you would draw a UIBezierPath. In my example, this is in a custom UIView function I would call UIPolarCurveView.
let flowerPath = UIBezierPath() // Declare my path
// Custom Polar scalars
let k: Double = 9/4
let length = 50
// Draw path
let points = cartesianCoordsForPolarFunc(frame: frame, thetaCoefficient: k, thetaCoefficientDenominator:4 cosScalar: length, iPrecision: 0.01) flowerPath.move(to: points[0])
for i in 2...points.count {
flowerPath.addLine(to: points[i-1])
}
flowerPath.close()
Here's the result:
PS: If you plan on having multiple graphs in the same frame, make sure to modify the scaling addition by making the second cosScalar the largest of the cosScalars used.You can do this by adding an argument to the function in the example.

Drawing a circle with an evenly-distributed set-amount of points

I was wondering how you would go about this assuming you were working with a 2D coordinate frame in pixels. I created some examples of what I mean:
Red dot represents the origin point
Grey circle shows the radius but wouldn't actually be drawn
Green dots have a set amount and get evenly distributed along the
circle
With 3 dots:
http://prntscr.com/5vbj86
With 8 dots:
http://prntscr.com/5vbobd
Spektre answered my question but in C++, here it is in lua for anyone interested:
local x,y
local n = 10
local r = 100.0
local x0 = 250.0
local y0 = 250.0
local da = 2.0 * math.pi/n
local a = 0.0
for i = 0, n - 1 do
x = x0 + r * math.cos(a)
y = y0 + r * math.sin(a)
-- draw here using x,y
a = a + da
end
on circle very easy
for evenly distributed points the angle is increasing with the same step
so for N points the step is da=2.0*M_PI/N;
The code in C++ is like this:
int i,n=10;
double x,y,a,da;
double r=100.0,x0=250.0,y0=250.0; // circle definition
da=2.0*M_PI/double(n);
for (a=0.0,i=0;i<n;i++,a+=da)
{
x=x0+r*cos(a);
y=y0+r*sin(a);
// here draw or do something with (x,y) point
}

Tile to CGPoint conversion with Retina display

I have a project that uses a tilemap. I have a separate tilemap for low-res (29x29 Tilesize) and high-res (58x58). I have these methods to calculate tileCoord to position and back again.
- (CGPoint)tileCoordForPosition:(CGPoint)position {
int x = position.x / _tileMap.tileSize.width;
int y = ((_tileMap.mapSize.height * _tileMap.tileSize.height) - position.y) / _tileMap.tileSize.height;
return ccp(x, y);
}
- (CGPoint)positionForTileCoord:(CGPoint)tileCoord {
int x = (tileCoord.x * _tileMap.tileSize.width) + _tileMap.tileSize.width/2;
int y = (_tileMap.mapSize.height * _tileMap.tileSize.height) - (tileCoord.y * _tileMap.tileSize.height) - _tileMap.tileSize.height/2;
return ccp(x, y);
}
I got this from RayWenderLich and I do honeslty not understand how it works, and why it has to be so complicated. But this doesn't work when I use retina tilemaps, only on 480x320. Can someone clever come up with a way to make this work for HD? Does not have to work on low-res either, I do not plan on supporting sub-iOS 7.
I want the output to be in the low-res coordinate scale tho, as you might know, cocos2d does the resizing to HD for you. (By multiplying by two)
i think this will work
- (CGPoint)tileCoordForPosition:(CGPoint)position {
    int x = position.x/29;
    int y = ((11*29)-position.y) / 29;
    
    return ccp(x, y);
}
- (CGPoint)positionForTileCoord:(CGPoint)tileCoord {
    double x = tileCoord.x * 29 + 14.5;
    double y = (11*29) - (tileCoord.y * 29) - 14.5;
    return ccp(x, y);
}
Here you're trying to compute your map X coordinate:
int x = position.x / _tileMap.tileSize.width;
The problem here is that (as of v0.99.5-rc0, cocos2d generally uses points for positions, but CCTMXTiledMap always uses pixels for tileSize. On a low-res device, 1 point = 1 pixel, but on a Retina device, 1 point = 2 pixels. Thus on a Retina device, you need to multiply by 2.
You can use the CC_CONTENT_SCALE_FACTOR() macro to fix this:
int x = CC_CONTENT_SCALE_FACTOR() * position.x / _tileMap.tileSize.width;
Here you're trying to compute yoru map Y coordinate:
int y = ((_tileMap.mapSize.height * _tileMap.tileSize.height) - position.y) / _tileMap.tileSize.height;
The extra math here is trying to account for the difference between Cocos2D's normal coordinate system and your map's flipped coordinate system. In standard Cartesian coordinates, the origin is at the lower left and Y coordinates increase as you move up. In a flipped coordinate system, the origin is at the upper left and Y coordinates increase as you move down. Thus you must subtract your position's Y coordinate from the height of the map (in scene units, which are points) to flip it to map coordinates.
The problem again is that _tileMap.tileSize is in pixels, not points. You can again fix that by using CC_CONTENT_SCALE_FACTOR():
CGFloat tileHeight = _tileMap.tileSize.height / CC_CONTENT_SCALE_FACTOR();
int y = ((_tileMap.mapSize.height * tileHeight) - position.y) / tileHeight;

Resources