Turning a card emulating 3D?

Hello @StatCrunch

I wanted to know if you were able to produce some effective solution. I do not know maybe even some ploy without kernel …

As always I thank you in advance

Hi. I just worked out a little bit of the math assuming that the object had some thickness, and got a little bit farther:

 --snap to use as a card or tile (Slightly larger to have the desidered border) local card = display.newSnapshot( 230, 230 ) card.x = display.contentCenterX card.y = display.contentCenterY --snap group local cardG = card.group --background (used as a card board) local bg = display.newRect( cardG, 0, 0, 200, 200 ) bg:setFillColor( 1, 1, 1, 0 ) bg.strokeWidth = 20 bg:setStrokeColor(.7, .7, .7, 1) --[[--hearts suit local hearts = display.newImageRect( cardG, "hearts.png", 60, 60 ) hearts.x = bg.x - (bg.width\*0.5) + (hearts.width\*0.5) + bg.strokeWidth --diamonds suit local diamonds = display.newImageRect( cardG, "diamonds.png", 60, 60 ) diamonds.x = bg.x + (bg.width\*0.5) - (diamonds.width\*0.5) - bg.strokeWidth --clubs suit local clubs = display.newImageRect( cardG, "clubs.png", 60, 60 ) clubs.y = bg.y - (bg.height\*0.5) + (clubs.height\*0.5) + bg.strokeWidth --spades suit local spades = display.newImageRect( cardG, "spades.png", 60, 60 ) spades.y = bg.y + (bg.height\*0.5) - (spades.height\*0.5) - bg.strokeWidth --centre of card local centre = display.newRect( cardG, 0, 0, 60, 60 )]] local cc=display.newImageRect(cardG, "cropped.png", bg.width, bg.height) cc.x, cc.y = bg.x, bg.y --button that serves the user to turn the card local rotateButton = display.newRect( 0, 0, 30, 50 ) rotateButton:setFillColor( 0, 1, 0 ) rotateButton.x = rotateButton.width rotateButton.y = display.contentCenterY --rotateButton touch event function rotateButton:touch( event ) if ( event.phase == "began" ) then display.getCurrentStage():setFocus(self, event.id) self.isFocus = true elseif(self.isFocus) then if event.phase == "moved" then --distance path with his finger by the user local distanceTraveled = (event.x-event.xStart) --distance in proportion to the width of the paper local distanceInProportion = (distanceTraveled/card.width)\*2 print(distanceInProportion) if(distanceInProportion \> 0) and (distanceInProportion \< 2) then --tilting the paper by changing the scale (old method) --card.xScale = 1-distanceInProportion --tilting the paper with the new effect --I multiplied by seven only to see the operation but do not understand how to calculate the right number card.fill.effect.t = distanceInProportion / 4--\*7 end elseif event.phase == "ended" or event.phase == "cancelled" then display.getCurrentStage():setFocus( self, nil ) self.isFocus = false end end return true end rotateButton:addEventListener( "touch" ) local kernel = { category = "filter", name = "rotate" } kernel.vertexData = { { index = 0, name = "t", default = 0 }, { index = 1, name = "thickness", default = .075 }, } kernel.fragment = [[const P\_UV float PI = 180.;//3.14159; const P\_UV float WIDTH = 13.; #define NPIXELS CoronaTexelSize.z \* WIDTH #define TO\_NEIGHBOR CoronaTexelSize.z \* 14. P\_UV float LessThan (P\_UV float threshold, P\_UV float x) { return step(abs(x), threshold); // 1.0 if true, 0.0 otherwise } P\_UV float MoreThan (P\_UV float threshold, P\_UV float x) { return step(-abs(x), threshold); // 1.0 if true, 0.0 otherwise (the negative just accounts for exactly equal values) } P\_UV float ProjectX (P\_UV float x, P\_UV float denom) { return .5 + (x - .5) / denom; } P\_UV float InBounds (P\_UV vec2 uv) { P\_UV vec2 from\_center = step(abs(uv - .5), vec2(.4875)); // 1.0 if inside [0, 1] along axis, 0.0 otherwise (done for x and y) return min(from\_center.x, from\_center.y); // If either is 0.0, we're out-of-bounds; otherwise we're inside } P\_COLOR vec4 FragmentKernel( P\_UV vec2 texCoord ) { P\_UV float angle = radians(CoronaVertexUserData.x \* 2. \* PI); P\_UV float cosa = cos(angle), sina = sin(angle), xcur = texCoord.x; P\_UV float denom = sign(cosa) \* max(abs(cosa), 1e-4); // avoid divides by 0 to avoid driver issues, etc. P\_UV float off\_edge = InBounds(texCoord); texCoord.x = ProjectX(texCoord.x, denom); P\_COLOR vec4 pixel = texture2D(CoronaSampler0, texCoord) \* off\_edge; P\_UV float neighbor\_x = texCoord.x - CoronaVertexUserData.y \* sina; // look a few pixels to the left or right (depending on angle and distance from sideways) P\_UV float at\_center\_and\_sideways = MoreThan(.9975, sina) \* LessThan(NPIXELS / 2., xcur - .5); // the multiply is basically an "and" // if both predicates return 1.0, it's 1.0 // otherwise it's 0.0 texCoord.x = mix(neighbor\_x, .5, at\_center\_and\_sideways); // "interpolate" (using our 0.0 or 1.0 result) to pick a value P\_COLOR vec4 neighbor = texture2D(CoronaSampler0, texCoord) \* InBounds(texCoord); return CoronaColorScale(mix(mix(vec4(0.), vec4(1., 0., 0., 1.), neighbor.a), pixel, pixel.a)); } ]] graphics.defineEffect(kernel) --Apply the new effect card.fill.effect = "filter.custom.rotate"

(You didn’t give me the images, so I took a screenshot and worked from there… you can restore that code, of course.)

Also I converted it to degrees in Lua, in case that’s more familiar to you (then converted back in the shader). I adjusted a couple other lines to make the touch do one spin.

It still looks a little weird at some angles, probably because the slab is only half the thickness (in the back). (This is a kernel parameter / CoronaVertexUserData.y , whose units are the fraction of the texture width.) The image needs to be offset a little to account for foreground thickness, which should be some similar computations. I’ll play around with it again at some point. At this point my mind is sort of stuck in shader land, but of course maybe somebody else has further ideas too. (One of the scene transitions looks a bit like this, for instance.) 

THANK YOU  :smiley:

I thank you very much now really came out well, even I’ll try to make some modifications and other touches to use it. I also saw I quell effect is called flip if I’m not mistaken. However, for now your help is more than enough.

Thank you again for your time. I hope I can do the same for you one day. Sincere greetings

Hi.

I threw together something like this a few months back for somebody, but can’t find the snippet, so I’ve tried to reproduce it here:

Turning effect

You can change x to see it as time progresses, or just replace CoronaVertexUserData.x with CoronaTotalTime to see something that changes with time.

If that fits your needs, you can follow this and this to use it in your own code.

I haven’t seen divide-by-0s cause issues, but if you find those, namely in your #4 and #5 cases, that can be addressed by clamping or adding a small value, though it’s a bit messy with the numbers being able to change sign.

Hello thanks for response :slight_smile:

I’m trying again and again but I can not run the kernel. Is the first time, I try to use it and I’m definitely doing something wrong … :huh:

I also downloaded other projects, for example, but always gives me problems when I step in the fill.

Would not you have something ready so I can see where I am wrong and if all goes well.

I also tried it like you said to replace CoronaVertexUserData.x  with  CoronaTotalTime.

I also decreased the time, but it seems like mine transaction lua …

Thanks again

Just threw it into something of mine. Give this a go, replacing  image3 by your own image:

local kernel = { category = "filter", name = "rotate" } kernel.vertexData = { { index = 0, name = "t", default = 0 } } kernel.fragment = [[P\_COLOR vec4 FragmentKernel( P\_UV vec2 texCoord ) { texCoord.x = .5 + (texCoord.x - .5) / cos(CoronaVertexUserData.x \* 2. \* 3.14159); // As mentioned, you can also use CoronaTotalTime here ^^^^^^^ return CoronaColorScale(texture2D(CoronaSampler0, texCoord)); }]] graphics.defineEffect(kernel) image3.fill.effect = "filter.custom.rotate" transition.to(image3.fill.effect, { t = 15, time = 7500 })

thank you!

I tried the code and it works. :slight_smile:

But not still solve my problama in the points 4 and 5.

Also the holes during rotation have no “depth” … :frowning:

A trivial solution that I thought for the holes was to put a picture alike in slightly more to the left and maybe just more dark color.

While for steps 4 and 5 I do not know. I wish I was more 3D and emulates a thickness to the paper.

I hope I do not ask too much. And the only part that I can not fix

Ah, I completely misread what you wrote, then.  :stuck_out_tongue:

I puttered around a bit more and now have this: updated This tries to look for some nearby opaque neighbors (when the pixel itself is somewhat transparent) and if it finds them, use red, with some more stuff to try to handle the 4 / 5 case. These tricks are pretty image-dependent, as you can sort of see by spinning the plane around, but in your case you seem to have a pretty simple shape.

wow!

It works and it’s what I needed. I apologize for my English that led you astray. But I think there is a problem but I can not figure out what kind.

During rotation in the simulator, I see a background between the paper and the edge … what can it be? I am attaching a photo(name:first).

Lastly the problem of the previous 4 and 5 seems solved, but you can cover the empty space (name:second)? or maybe now I ask too much?

thank you so much for your time, I therein am truly grateful :smiley:

for your “xScale=0” problem on the middle frame, I handled it in a decidedly low-tech way.  given a known fixed number of animation frames, i just predefined the values i wanted. for example, say you need 10 frames, then:

local xScales = { 0.9, 0.8, 0.6, 0.4, 0.1, -0.1, -0.4, -0.6, -0.8, -0.9 } card.xScale = xScales[card.frame]

those values are just approximations here as examples but note that zero is avoided.  (actual values should be the cosine of a fixed angular step, say from 0-180 degrees by 20’s, to avoid 90) do this in an enterFrame -type loop so that you can make it “discrete”, not via a transition which is too “continuous” (and will often have that troublesome ‘exactly half-way’ t=0.5 frame)

thank you davebollinger

This way I could effectively avoid the problem but I can not give the 3D effect. Missing the edge …

I am continuing to make several attempts of code. But I can not fix.

I also wanted to he could move the whole user with the touch but with this new code I find difficulty …

This is the only part of the game that requires a 3D emulation for the rest I have no problems.

So I’m looking for the best solution

does your flip animation really last a full second?  cards are really thin (relative to their width/height) and if you did it at a quicker speed then perhaps no-one would notice if your very fancy effect were absent? but if they’re thicker, like tiles rather than cards, then it might be easier to pre-render – the trick then would be to exactly match your “stencil” image of the card face on top as it’s flipping relative to the 3D underneath.  (as long as you knew the exact rotation and perspective projection used to render it, then ought to be possible – tricky, but possible)

In efects they could be considered tiles. The photo you’ve attached is what I want. Only with two additions: 1. It can be rotated with the touch of the user. 2. Give the same effect to the holes in the tiles. “that’s all”

@maximo97

No worries about your English. I was just careless looking over it the first time.

I think the issue is that the edge pixels get clamped (notice that the error doesn’t happen when the corner starts to get round) and so you’re never finding transparencies. Could you try it with this? I tried to break it up and explain some of the steps, too, so it isn’t quite so magical.  :stuck_out_tongue: (I usually refer to this doc, myself.)

Alternatively, just pad the sides of the image with transparent pixels.

Very good

Sorry I’m late, but I was doing various tests on the effects. The comments were very helpful.

I rewrote a piece of code out of context so I can show you. Then I think he goes. Only thing I do not understand is in touch method that give value “t” because executes a perfect turning.  Also how I decide which way I want to go the edge (photo)?

I think I will have solved this all perfect. I thank you again

code:

 --snap to use as a card or tile (Slightly larger to have the desidered border) local card = display.newSnapshot( 230, 230 ) card.x = display.contentCenterX card.y = display.contentCenterY --snap group local cardG = card.group --background (used as a card board) local bg = display.newRect( cardG, 0, 0, 200, 200 ) bg:setFillColor( 1, 1, 1, 0 ) bg.strokeWidth = 20 bg:setStrokeColor(.7, .7, .7, 1) --hearts suit local hearts = display.newImageRect( cardG, "hearts.png", 60, 60 ) hearts.x = bg.x - (bg.width\*0.5) + (hearts.width\*0.5) + bg.strokeWidth --diamonds suit local diamonds = display.newImageRect( cardG, "diamonds.png", 60, 60 ) diamonds.x = bg.x + (bg.width\*0.5) - (diamonds.width\*0.5) - bg.strokeWidth --clubs suit local clubs = display.newImageRect( cardG, "clubs.png", 60, 60 ) clubs.y = bg.y - (bg.height\*0.5) + (clubs.height\*0.5) + bg.strokeWidth --spades suit local spades = display.newImageRect( cardG, "spades.png", 60, 60 ) spades.y = bg.y + (bg.height\*0.5) - (spades.height\*0.5) - bg.strokeWidth --centre of card local centre = display.newRect( cardG, 0, 0, 60, 60 ) --button that serves the user to turn the card local rotateButton = display.newRect( 0, 0, 30, 50 ) rotateButton:setFillColor( 0, 1, 0 ) rotateButton.x = rotateButton.width rotateButton.y = display.contentCenterY --rotateButton touch event function rotateButton:touch( event ) if ( event.phase == "began" ) then display.getCurrentStage():setFocus(self, event.id) self.isFocus = true elseif(self.isFocus) then if event.phase == "moved" then --distance path with his finger by the user local distanceTraveled = (event.x-event.xStart) --distance in proportion to the width of the paper local distanceInProportion = (distanceTraveled/card.width)\*2 print(distanceInProportion) if(distanceInProportion \> 0) and (distanceInProportion \< 2) then --tilting the paper by changing the scale (old method) --card.xScale = 1-distanceInProportion --tilting the paper with the new effect --I multiplied by seven only to see the operation but do not understand how to calculate the right number card.fill.effect.t = distanceInProportion\*7 end elseif event.phase == "ended" or event.phase == "cancelled" then display.getCurrentStage():setFocus( self, nil ) self.isFocus = false end end return true end rotateButton:addEventListener( "touch" ) local kernel = { category = "filter", name = "rotate" } kernel.vertexData = { { index = 0, name = "t", default = 0 } } kernel.fragment = [[const P\_UV float PI = 3.14159; const P\_UV float WIDTH = 13.; #define NPIXELS CoronaTexelSize.z \* WIDTH P\_UV float LessThan (P\_UV float threshold, P\_UV float x) { return step(abs(x), threshold); // 1.0 if true, 0.0 otherwise } P\_UV float MoreThan (P\_UV float threshold, P\_UV float x) { return step(-abs(x), threshold); // 1.0 if true, 0.0 otherwise (the negative just accounts for exactly equal values) } P\_UV float ProjectX (P\_UV float x, P\_UV float denom) { return .5 + (x - .5) / denom; } P\_UV float InBounds (P\_UV vec2 uv) { P\_UV vec2 from\_center = step(vec2(-.5), abs(uv - .5)); // 1.0 if inside [0, 1] along axis, 0.0 otherwise (done for x and y) return min(from\_center.x, from\_center.y); // If either is 0.0, we're out-of-bounds; otherwise we're inside } P\_COLOR vec4 FragmentKernel( P\_UV vec2 texCoord ) { P\_UV float angle = CoronaVertexUserData.x \* .09 \* PI; P\_UV float cosa = cos(angle), sina = sin(angle), xcur = texCoord.x; P\_UV float denom = sign(cosa) \* max(abs(cosa), 1e-4); // avoid divides by 0 to avoid driver issues, etc. P\_UV float off\_edge = InBounds(texCoord); texCoord.x = ProjectX(texCoord.x, denom); P\_COLOR vec4 pixel = texture2D(CoronaSampler0, texCoord) \* off\_edge; P\_UV float neighbor\_x = ProjectX(xcur - NPIXELS \* sina, denom); // look a few pixels to the left or right (depending on angle and distance from sideways) P\_UV float at\_center\_and\_sideways = MoreThan(.9975, sina) \* LessThan(NPIXELS / 2., xcur - .5); // the multiply is basically an "and" // if both predicates return 1.0, it's 1.0 // otherwise it's 0.0 texCoord.x = mix(neighbor\_x, .5, at\_center\_and\_sideways); // "interpolate" (using our 0.0 or 1.0 result) to pick a value P\_COLOR vec4 neighbor = texture2D(CoronaSampler0, texCoord) \* InBounds(texCoord); return CoronaColorScale(mix(mix(vec4(0.), vec4(1., 0., 0., 1.), neighbor.a), pixel, pixel.a)); } ]] graphics.defineEffect(kernel) --Apply the new effect card.fill.effect = "filter.custom.rotate"

Hi.

2. * PI , in the shader, is one full turn. With the constant now being .09 you’d have to scale up a bit, or change that back. Also, t  corresponds to CoronaVertexUserData.x , if that wasn’t clear. (The “index = 0” in the kernel.vertexData means “put this name into x”.) You can give it a better name if you want.

Basically, t = 1 or t = -1 should mean a full turn, .5 or -.5 a halfway one. So I figured you’d have something like:

if card.fill.effect.t \< .5 then -- original transition.to(card.fill.effect, { t = .5, time = 1000 }) else -- flipped transition.to(card.fill.effect, { t = 0, time = 1000 }) end

If it’s easier to think about, you can use degrees here (so replace the .5 values with 180). Then just change

P\_UV float angle = CoronaVertexUserData.x \* .09 \* PI;

to

P\_UV float angle = rad(CoronaVertexUserData.x);

well, now it is more clear !!

But I have to keep doing tests because I find a problem for every solution looks a bit what happens …  :huh:

But I will not bother you too much hope to take care of myself

thanks again for the help :slight_smile:

Oh, heh. The black holes must be because it’s looking too far into the texture (i.e.,  -NPIXELS * sina ). You could try making WIDTH smaller, but of course it shrinks your red bar too. Or just use a different scale factor than NPIXELS.

Not sure about the other case. I’ll see if I can sit down with your example later, maybe tomorrow. (I’d like to use this effect later too, so it’s not really a bother.)

Great!

Well then they are waiting anxiously. Meanwhile I will do tests alone

Hello StarCrunch

You have some news about my code?

I have done so many tests also during the holidays but still nothing perfect …

thank you