Metal / Scenekit - Repeating texture on sampler - scenekit

not sure why but I am not able to repeat the texture when using customer fragment shader
Here is my fragment
fragment float4 bFragment( VertexOut vertexOut [[stage_in]],
texture2d<float, access::sample> textureInput [[texture(0)]],)
{
constexpr sampler sampler2d(coord::normalized, address::repeat, filter::linear, address::repeat);
float4 outputColor;
outputColor = textureInput.sample(sampler2d, vertexOut.texCoord);
return float4(outputColor.x , outputColor.y , outputColor.z , 1.0);
}
Here is how I pass the texture:
let imageProperty = SCNMaterialProperty(contents: texture)
imageProperty.wrapS = .repeat
imageProperty.wrapT = .repeat
imageProperty.contentsTransform = SCNMatrix4MakeScale(screenRatio * numberOfRepetitionsOnX, numberOfRepetitionsOnX , 1)
node.geometry!.firstMaterial!.setValue(imageProperty, forKey: "textureInput")
Image is NOT repeated, just clamped to the object, no matter the size of the texture.
If I use the same settings with NO customer shader:
let myMaterial = SCNMaterial()
myMaterial.lightingModel = .constant
myMaterial.diffuse.contents = texture
myMaterial.diffuse.wrapS = .repeat
myMaterial.diffuse.wrapT = .repeat
myMaterial.diffuse.contentsTransform = SCNMatrix4MakeScale(screenRatio * numberOfRepetitionsOnX, numberOfRepetitionsOnX , 1)
node.geometry!.firstMaterial! = myMaterial
Texture correctly repeated
Questions:
What I have to change in order to be effective contentsTransform value also when using the sampler in custom fragment shader?
If that is not possible, what is the easiest way to achieve that? (Scaling, repeating.redrawing the texture is not an option)
Thanks.

When using SCNProgram the contentsTransform property of SCNMaterialProperty has no effect (because support is implemented in SceneKit's built-in vertex and fragment shaders).
You will need to pass the 2D matrix to the custom SCNProgram shaders and manually transform the texture coordinates there.

Related

How to Create Emboss and engrave text on OpenCASCADE

I am searching an API for creating emboss text on my AIS_Shape , If there is no API any better way to do that ?,I am thinking of creating text extrude and doing cut operation with AIS_Shape , can we extrude Text ?
OCCT doesn't provide direct tool defining an emboss text - so you are right, you have to do that using a general Boolean operation.
A general idea can be found within standard samples coming with Draw Harness - take a look onto "Modeling" -> "OCCT Tutorial bottle sample" sample (source $env(CSF_OCCTSamplesPath)/tcl/bottle.tcl):
Tools to use:
Font_BRepFont to load a font and convert a glyph into a planar TopoDS_Shape.
Font_BRepTextBuilder to format a text using Font_BRepFont.
BRepPrimAPI_MakePrism to define a solid from text.
BRepAlgoAPI_Cut to perform a Boolean operation between text solid and another shape.
Here is a pseudo-code:
// text2brep
const double aFontHeight = 20.0;
Font_BRepFont aFont (Font_NOF_SANS_SERIF, Font_FontAspect_Bold, aFontHeight);
Font_BRepTextBuilder aBuilder;
TopoDS_Shape aTextShape2d = aBuilder.Perform (aFont, "OpenCASCADE");
// prism
const double anExtrusion = 5.0;
BRepPrimAPI_MakePrism aPrismTool (aTextShape2d, gp_Vec (0,0,1) * anExtrusion);
TopoDS_Shape aTextShape3d = aPrismTool->Shape();
//aTextShape3d.SetLocation(); // move where needed
// bopcut
TopoDS_Shape theMainShape; // defined elsewhere
BRepAlgoAPI_Cut aCutTool (theMainShape, aTextShape3d);
if (!aCutTool.IsDone()) { error }
TopoDS_Shape aResult = aCutTool.Shape();

how to apply normal map texture to custom geometry in scenekit, ios?

I'd created a custom SCNGeometry using following code:
let data = NSData(bytes: position, length: MemoryLayout<float3>.size * self.objectMesh.pointCount)
let vertexSource = SCNGeometrySource(data: data as Data,
semantic: .vertex,
vectorCount: self.objectMesh.pointCount,
usesFloatComponents: true,
componentsPerVector: 3,
bytesPerComponent: MemoryLayout<Float32>.size,
dataOffset: 0,
dataStride: MemoryLayout<float3>.size)
let data2 = NSData(bytes: uvs, length: MemoryLayout<float2>.size * self.objectMesh.pointCount)
let tSource = SCNGeometrySource(data: data2 as Data,
semantic: .texcoord,
vectorCount: self.objectMesh.pointCount,
usesFloatComponents: true,
componentsPerVector: 2,
bytesPerComponent: MemoryLayout<Float32>.size,
dataOffset: 0,
dataStride: MemoryLayout<float2>.stride)
let indexData = NSData(bytes: faces, length: MemoryLayout<Int32>.size * self.objectMesh.faceCount)
let element = SCNGeometryElement(
data: indexData as Data,
primitiveType: .triangles,
primitiveCount: self.objectMesh.faceCount/3,
bytesPerIndex: MemoryLayout<Int32>.size)
let Geometry = SCNGeometry(sources: [vertexSource, tSource], elements: [element])
Geometry.firstMaterial?.diffuse.contents = self.MainTexture
Geometry.firstMaterial?.normal.contents = self.NormalTexture
now the problem is: the texture shows up but the normal texture map is not applied to the geometry. I'm not going to use per vertex normals. this texture and normaltexture are works when applied to for example SCNPlane geometry. but I don't now how to use a normal map texture with my custom geometry.
The normal property on SCNMaterial expects a normal map in tangent space and then the effective normals are based on a combination of the normal map and the vertex normals of your custom geometry.
Model i/o includes MDLMesh which provide an addNormals method that can be used to create the normals programmatically (create an MDLMesh based on the SCNGeometry, use addNormals, and create a SCNGeometry based on the MDLMesh and use that new SCNGeometry instead). But ideally you include the normals with the custom geometry.

Change texture with random

I have some textures
var texturesOfEnemies = [SKTexture(imageNamed: "EnemyTexture1"),
SKTexture(imageNamed: "EnemyTexture2"),
SKTexture(imageNamed: "EnemyTexture3"),
SKTexture(imageNamed: "EnemyTexture4"),
SKTexture(imageNamed: "EnemyTexture5")]
I'm using arc4random to select a random texture
var randomTextureOfEnemies = Int(arc4random_uniform(UInt32(texturesOfEnemies.count)))
and assign the selected texture to a node by
enemy.texture = texturesOfEnemies[randomTextureOfEnemies]
I have some actions for my node. When the actions are done, I want to change the texture of my node.
enemy.run(someAction, completion:{enemy.texture = texturesOfEnemies[randomTextureOfEnemies]})
This actions repeat and are working well, but the textures are only changing once. How can I change the texture every time the action completes?

Swift - Create circle and add to array

I've been looking for a little while on how to create a circle in Swift, but it seems quite complicated. I want to create a circle at run time, with a specified x and y, and width and height. I then want to add this circle to an array where I can create more circles and add to it.
How do I do this?
Edit: This is what I've tried so far:
var center : CGPoint = touchLocation
var myContext : CGContextRef = UIGraphicsGetCurrentContext()
let color : [CGFloat] = [0, 0, 1, 0.5]
CGContextSetStrokeColor (myContext, color);
CGContextStrokeEllipseInRect (myContext, CGRectMake(touchLocation.x, touchLocation.y, 20, 20));
touchLocation is the location of the users finger. This crashes on execution on this line:
var myContext : CGContextRef = UIGraphicsGetCurrentContext()
The error says "Unexpectedly found nil while unwrapping an Optional value
Also, this doesn't allow me to add the circle to an array, because I don't know what variable type it is.
There are many ways to draw a circle, here is a snippet that I have been hacking with:
func circleWithCenter(c:CGPoint, radius r:CGFloat,
strokeColor sc: UIColor = UIColor.blackColor(),
fillColor fc: UIColor = UIColor.clearColor()) -> CAShapeLayer {
var circle = CAShapeLayer()
circle.frame = CGRect(center:c, size:CGSize(width:2*r, height:2*r))
circle.path = UIBezierPath(ovalInRect:circle.bounds).CGPath
circle.fillColor = fc.CGColor
circle.strokeColor = sc.CGColor
circle.fillColor = fc == UIColor.clearColor() ? nil : fc.CGColor
return circle
}
Note that I extended CGRect (using Swift specific features) to add a initializer that takes a center, but that is not material to your question.
Your code is not "creating" a circle as an object, it is attempting to draw one in the graphics context. What you need to do is to create a bezier path object, draw into that and save the path in your array. Something like:
var circleArray = [CGMutablePathRef]()
// ...
let path: CGMutablePathRef = CGPathCreateMutable()
CGPathAddArc(path, nil, touchLocation.x, touchLocation.y, radius, 0, M_PI * 2, true)
circleArray += [path]
You then need to stroke the path(s) in your draw routine.

MKMapView setRegion not exact

Starting Situation
iOS6, Apple Maps. I have two annotations on a MKMapView. The coordinates of the two annotations have the same values for the latitude component, but different longitudes.
What I want
I now want to zoom the map so that one annotation is exactly on the left edge of the mapView's frame, and the other annotation on the right edge of the frame. For that I tried MKMapView's setRegion and setVisibleMapRect methods.
The Problem
The problem is that those methods seem to snap to certain zoom levels and therefore not setting the region as exactly as I need it. I saw a lot of questions on Stack Overflow that point out, that this behavior is normal in iOS5 and below. But since Apple Maps are using vector graphics, the view is not bound to certain zoom levels to display imagery in proper resolution.
Tested in...
I tested on iPhone 4, 4S, 5, iPad 3 and iPad Mini (all with iOS6.1), and in the Simulator on iOS6.1.
My question
So why is setRegion and setVisibleMapRect snapping to certain zoom levels and does not adjust the exact region to the values I pass it?
Sample Code
in view did appear I define 4 different Locations as iVars, and set up the map view:
// define coords
_coord1 = CLLocationCoordinate2DMake(46.0, 13.0);
_coord2 = CLLocationCoordinate2DMake(46.0, 13.1);
_coord3 = CLLocationCoordinate2DMake(46.0, 13.2);
_coord4 = CLLocationCoordinate2DMake(46.0, 13.3);
// define frame for map in landscape mode
CGRect mainScreen = [[UIScreen mainScreen] bounds];
CGRect newSRect = mainScreen;
newSRect.size.width = mainScreen.size.height;
newSRect.size.height = mainScreen.size.width;
// setup map view
customMapView = [[MKMapView alloc] initWithFrame:newSRect];
[customMapView setDelegate:self];
[customMapView setShowsUserLocation:NO];
[self.view addSubview:customMapView];
Then I add 3 buttons. Those trigger all the same method addAnnotationsWithCoord1:coord2. The first button passes _coord1 and _coord2, the second button passes _coord1 and _coord3 and the third button passes _coord1 and _coord4. The method looks like so (TapAnnotation is my subclass of MKAnnotation):
-(void)addAnnotationsWithCoord1:(CLLocationCoordinate2D)coord1 coord2:(CLLocationCoordinate2D)coord2{
// Make 2 new annotations with the passed coordinates
TapAnnotation *annot1 = [[TapAnnotation alloc] initWithNumber:0 coordinate:coord1];
TapAnnotation *annot2 = [[TapAnnotation alloc] initWithNumber:0 coordinate:coord2];
// Remove all existing annotations
for(id<MKAnnotation> annotation in customMapView.annotations){
[customMapView removeAnnotation:annotation];
}
// Add annotations to map
[customMapView addAnnotation:annot1];
[customMapView addAnnotation:annot2];
}
After that I determine the SouthWest and NorthEast points that will determine the rect which is containing my 2 annotations.
// get northEast and southWest
CLLocationCoordinate2D northEast;
CLLocationCoordinate2D southWest;
northEast.latitude = MAX(coord1.latitude, coord2.latitude);
northEast.longitude = MAX(coord1.longitude, coord2.longitude);
southWest.latitude = MIN(coord1.latitude, coord2.latitude);
southWest.longitude = MIN(coord1.longitude, coord2.longitude);
Then I calculate the center point between the two coordinates and set the center coordinate of the map to it (remember, since all coordinates have same latitudes the following calculation should be correct):
// determine center coordinate and set to map
double centerLon = ((coord1.longitude + coord2.longitude) / 2.0f);
CLLocationCoordinate2D center = CLLocationCoordinate2DMake(southWest.latitude, centerLon);
[customMapView setCenterCoordinate:center animated:NO];
Now I try to set the region of the map so that it fits like I want:
CLLocation *loc1 = [[CLLocation alloc] initWithLatitude:southWest.latitude longitude:southWest.longitude];
CLLocation *loc2 = [[CLLocation alloc] initWithLatitude:northEast.latitude longitude:northEast.longitude];
CLLocationDistance meterSpanLong = [loc1 distanceFromLocation:loc2];
CLLocationDistance meterSpanLat = 0.1;
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(center, meterSpanLat, meterSpanLong);
[customMapView setRegion:region animated:NO];
This does not behave as expected, so I try this:
MKCoordinateSpan span = MKCoordinateSpanMake(0.01, northEast.longitude-southWest.longitude);
MKCoordinateRegion region = MKCoordinateRegionMake(center, span);
[customMapView setRegion:region animated:NO];
This still not behaves as expected, so I try it with setVisibleMapRect:
MKMapPoint westPoint = MKMapPointForCoordinate(southWest);
MKMapPoint eastPoint = MKMapPointForCoordinate(northEast);
MKMapRect mapRect = MKMapRectMake(westPoint.x, westPoint.y,eastPoint.x-westPoint.x,1);
[customMapView setVisibleMapRect:mapRect animated:NO];
And still, it does not behave like I want. As a verification, I calculate the point distance from the left annotation to the left edge of the mapView's frame:
// log the distance from the soutwest point to the left edge of the mapFrame
CGPoint tappedWestPoint = [customMapView convertCoordinate:southWest toPointToView:customMapView];
NSLog(#"distance: %f", tappedWestPoint.x);
For _coord1 and _coord2 it shows: 138
For _coord1 and _coord3 it shows: 138
For _coord1 and _coord4 it shows: 65
So why do I get these values? If anything works as expected, these should all be 0.
Thanks for any help, struggling with this problem for a week now.
Read the docs on setRegion:
When setting this property, the map may adjust the new region value so that it fits the visible area of the map precisely. This is normal and is done to ensure that the value in this property always reflects the visible portion of the map. However, it does mean that if you get the value of this property right after setting it, the returned value may not match the value you set. (You can use the regionThatFits: method to determine the region that will actually be set by the map.)
You will have to figure out the math yourself if you want a precise zoom.

Resources