Unsupported — We cannot guarantee that this software will work properly on Mac OS 10.8 and above. Please be careful.

Release: Texture, v0.4

Release Type: Production
Version: 0.4
Release Notes

Texture Patch

This is the first public release of our "Texture" patch --- a set of 26 patches for procedural texture generation, using techniques derived from Perlin Noise.

This is pretty much a 1-to-1 translation of the excellent libnoise project, so the standard libnoise documentation applies verbatim.

Unlike other patches, this patch is LGPL consequent to libnoise being LGPL.

Known issues:

  • module::Curve and module::Terrace haven't yet been translated as they require some additional interface elements not currently present in Quartz Composer.
  • The new "KinemeTexture" QCPort type doesn't properly handle cable removal --- so the Texture port is left hanging with a stale object. If you try to save the composition while a Texture port is disconnected, QC will throw the following exception:
    -[KinemeTexturePort stateValue]: Function not implemented
    (This exception isn't the problem itself, just a side-effect. Any ideas about how we can catch the cable removal action?) When this happens, don't panic --- you should be able to recover from the exception, attach a cable, and save the composition properly.
PreviewAttachmentSize
TexturePatch-0.4.zip144.08 KB
TexturePatch-0.4-src.zip267.76 KB

smokris's picture
libnoise examples, translated too

Clouds Over Water Perlin Noise example

PreviewAttachmentSize
AfricanJade.qtz9.41 KB
CloudsOverWater.qtz11.59 KB
Granite.qtz8.87 KB
GreenSlime.qtz9.32 KB
StainedWood.qtz10.09 KB

smokris's picture
Demoscene flashback

Demoscene flashback.

or something.

PreviewAttachmentSize
SuperGRAPHIC.qtz7.47 KB

cwright's picture
catching disconnects

The method you'll want to implement is

- (void)_setConnectedPort:(QCPort*)port

which receives null (not nil) when disconnecting.

[further research] -- I didn't like the idea of using a private method if there's a more "standard" way of doing it. Anyway, here are a couple backtraces from when that method is called, so you can go higher up the call stack if you'd like. It doesn't appear to be a port-level notfication, but a patch-level one?

[connecting a texture to a port:]

Breakpoint 1, 0x090f56c6 in -[KinemeTexturePort _setConnectedPort:] () (gdb) bt #0 0x090f56c6 in -[KinemeTexturePort _setConnectedPort:] () #1 0x04f45c88 in -[QCLink(Override) initWithGraph:sourcePort:destinationPort:arguments:] () #2 0x04f45a8b in -[GFGraph createConnectionFromPort:toPort:forKey:] () #3 0x04f4526e in -[QCPatch(Override) createConnectionFromPort:toPort:forKey:] () #4 0x04f64436 in -[GFGraph connectPort:toPort:] () #5 0x04f70f8c in -[GFGraphView(Specific) trackConnection:fromPort:atPoint:] () #6 0x04f87aed in -[QCPatchActor trackMouse:inNode:bounds:view:] () #7 0x04f7d30f in -[GFGraphView(LocalNodeActor) _trackMouse:inNode:bounds:] () #8 0x04f6f723 in -[GFGraphView(Specific) trackMouse:] () #9 0x04f6e2ed in -[GFGraphView mouseDown:] () #10 0x0139a3af in -[NSWindow sendEvent:] () #11 0x0138c350 in -[NSApplication sendEvent:] () #12 0x012b6dfe in -[NSApplication run] ()

[and disconnecting said texture port:]

Breakpoint 1, 0x090f56c6 in -[KinemeTexturePort _setConnectedPort:] () (gdb) bt #0 0x090f56c6 in -[KinemeTexturePort _setConnectedPort:] () #1 0x04f619be in -[QCLink(Override) connectionWillDeleteFromGraph] () #2 0x04f6194e in -[GFGraph deleteConnectionForKey:] () #3 0x04f618cc in -[QCPatch(Override) deleteConnectionForKey:] () #4 0x04f878f7 in -[QCPatchActor trackMouse:inNode:bounds:view:] () #5 0x04f7d30f in -[GFGraphView(LocalNodeActor) _trackMouse:inNode:bounds:] () #6 0x04f6f723 in -[GFGraphView(Specific) trackMouse:] () #7 0x04f6e2ed in -[GFGraphView mouseDown:] () #8 0x0139a3af in -[NSWindow sendEvent:] () #9 0x0138c350 in -[NSApplication sendEvent:] () #10 0x012b6dfe in -[NSApplication run] ()

Have fun, please document what you find :)

smokris's picture
grr private methods

Thanks... I had already tried all the deleteConnectionForKey methods, but wasn't able to get any to trigger. (Apparently the default method in the "QCPatch(Override)" category has precedence over the method in my KinemeTexturePort subclass? I'm not certain of the semantics of this...)

But the _setConnectedPort does indeed work:

- (void)_setConnectedPort:(QCPort *)port
{
   [super _setConnectedPort:port];

   // if the port is disconnected, i (manually) reset myself
   if(!port)
      [self setTextureValue:nil];
}

cwright's picture
subclass

QCPatch is not in the ancestry of KinemeTexturePort, QCPort is. I don't know that the port methods get called all that often? or maybe I'm completely mistaken. shrugs

smokris's picture
err

err. i'm blind. lemme try that again..

smokris's picture
no

No, that was just a typo. I mean, "Apparently the default method in the "QCPatch(Override)" category has precedence over the method in my TexturePatch subclass?"

I meant TexturePatch, not KinemeTexturePort.

Doesn't work if I try to override deleteConnectionForKey in TexturePatch.

cwright's picture
categories

Aren't categories automatically prioritized over non-categories? (causing all those cool injection hacks from the podcasts, including namespace trampling)

maybe try including the category name for that method in the implementation line?

@implementation ClassName ( CategoryName ) // method definitions @end

smokris's picture
Yes, but now...

Yes, you're right.

So, I can do this:

@interface QCPatch (KinemeTexturePort)
- (void)deleteConnectionForKey:(id)fp8;
@end

@implementation QCPatch (KinemeTexturePort)
- (void)deleteConnectionForKey:(id)fp8
{
    ...
}

But now the fp8 is just an NSString, and I need some way to resolve this into an actual QCPort instance. Investigating..

Inside the above deleteConnectionForKey, self points to a null patch instance. Weird.

cwright's picture
self is null?

That's stumped me then. I was going to guess you'd do something like [self objectForKey:fp8] or something similar... but without self... hmm :)

Aren't you doing categories kind of incorrectly? In other words, shouldn't it be a TexturePatch method, with maybe the same category as the original? the category name is probably superfluous, but the object might be something significant...

smokris's picture
working but semi-incorrect

well, i mean, self != null, but if you do

NSLog(@"%@",self);

it prints out "<QCPatch = 0x15F4AFC0 "(null)">".

Anyway, "objectForKey" doesn't work, but "linkForKey" does. And QCLink is a thin wrapper from which it's easy to extract the QCPort.

So, the following code works:

@implementation QCPatch (KinemeTexturePort)
- (void)deleteConnectionForKey:(NSString *)key
{
   QCPort *p=[[self linkForKey:key] destinationInput];

   // if this is a KinemeTexturePort, i (manually) reset its value
   if( [ [p class] isSubclassOfClass:[KinemeTexturePort class] ] )
      [p setValue:nil];

   [super deleteConnectionForKey:key];
}
@end

I don't want to attach the Category to only TexturePatch because not all of my patches are derived from TexturePatch. (Though perhaps I should add another class from which all of my classes inherit, and attach the Category there.. I'll try that.)

And I think naming the category KinemeTexturePort is conventional, as category names I've seen seem to be arbitrary strings that indicate scope/function.

smokris's picture
no again

I tried making a common base class and attaching the deleteConnectionForKey category there, but it isn't triggered. So it looks like we'll have to keep it attached to QCPatch.. :^(

cwright's picture
this is wrong

Category on QCPatch is probably not the correct way to solve this problem. It also doesn't work in Leopard :)

By making KinemeTexturePort a subclass of QCStructurePort and using its internal class variables for storage, we get correct behaviour without hacking a category onto QCPatch. This means either A) we're doing it wrong or B) QCPatch already handles every existing port type, but not additional ones. I can't see why they would do this though, when every other non-number type of port appears to exhibit this common behaviour. Perhaps it's a QCObjectPort subclass thing?

some disassembling of deleteConnectionForKey shows no such class-specific behaviour.

[Later: yep, properly inheriting from QCObjectPort gives us correct behaviour, without all the hackery]

cwright's picture
helpful features

[from a discussion @smokris and I had while driving to work this week]

Having the texture renderer render in a separate thread would help the UI not stall when starting up or while rendering difficult textures.

Provide an option to render at lower resolutions at first, to give that "Netscape over dialup" incremental detail feel :) This would have a side effect of trivial mipmap generation.

And, of course, converting the texture to GLSL shaders would make for true infinitely detailed textures. This might not always be possible though, but would be an interesting exercise.

Have the renderer's state store the output, to prevent regeneration after each startup.

Installation Instructions

Place the plugin file in
/Users/[you]/Library/Graphics/Quartz Composer Patches/
(Create the folder if it doesn't already exist.)