図形の更新機能を追加する【実践的Macintoshプログラミング解説】

印刷用表示 |テキストサイズ 小 |中 |大 |

CoreData版 Repeating Motif Generator の開発 Repeating Motif Wonderland CoreData 実践的 Macintosh プログラミング解説

LinkIconホーム

更新日 2009-05-24

モデルを作る

図形の更新機能を追加する

 Oscillatorの波形を元にしてリサージュ図形を更新する機能を追加します。

Lissajousの波形更新メソッド

 波形を更新するメソッドupdateBezierPathは以下の様になります。 各発振器出力を取り出して、一方をX軸の値、もう一方をY軸の値として線を繋げていくとリサージュ図形ができ上がります。でき上がった図形をsetBezierPath:でセットして終了です。

 コメントにも書きましたが、各発振器の出力波形が先に更新されている事が前提となっています。

// 
//  Lissajous.m
//

#pragma mark -
#pragma mark Update path

//  OscillatorのbezierPathが先に更新されている事
- (void)updateBezierPath
{
    int             i;
    NSPoint         px,py;
    NSBezierPath    *path = [NSBezierPath bezierPath];
    NSBezierPath    *waveformX = [[self x] bezierPath];
    NSBezierPath    *waveformY = [[self y] bezierPath];

    if([waveformX elementCount] != [waveformY elementCount])  return;
    for(i=0;i<[[self resolution] intValue];i++)
    {
        [waveformX elementAtIndex:i associatedPoints:&px];
        [waveformY elementAtIndex:i associatedPoints:&py];
        if(i == 0)
            [path moveToPoint:NSMakePoint(px.y,py.y)];
        else
            [path lineToPoint:NSMakePoint(px.y,py.y)];
    }
    [path closePath];
    [self setBezierPath:path];
}

 

【失敗例】誰が出しているエラーメッセージなの?


 X軸の波形とY軸の波形のelementCountを比較して、一致していなければ何もしないようにしていますが、これを省略するとresolutionを一度減らしてから増やす時に、コンソールに以下のメッセージが表示されます。

  • _segmentIndexForElementIndex:: index (14) beyond bounds (14)

 これはNSBezierPathが出すエラーメッセージの様です。例えばresolutionを14にしてから200にすると、二つのOscillatorに順番にupdateBezierPathメッセージが送られるため、waveformXが200角形、waveformYが14角形の状態でこのメソッドが呼ばれてから、waveformXが200角形、waveformYが200角形の状態でこのメソッドが呼ばれます。
 waveformXが200角形、waveformYが14角形の状態では、0番目のエレメントから13番目のエレメントにアクセスしている時はよいのですが、14番目のエレメントにアクセスしようとすると配列の範囲外にアクセスする事になるので上記のエラーメッセージが表示されるわけです。

 _segmentIndexForElementIndexというのはNSBezierPathが出しているエラーメッセージではないか?と気づいて、ようやくエラーの原因に気づきました。エラーメッセージにクラス名を含めてくれると助かるのですが…

波形の更新が必要になる時

 波形の更新が必要になるのはresolutionが変更された時と、各発振器の出力波形が変更された時です。前者はresolutionのSetterで、後者は発振器のupdateBezierPathでLissajousのupdateBezierPathを呼び出せばOKです。

 まずresolutionのSetterを修正します。resolutionに値をセットしてから各発振器に出力波形を更新するようにメッセージを送っています。こうすればOscillatorがLissajousのupdateBezierPathを呼び出すので、その後LissajousのupdateBezierPathを呼び出す必要はありません。

 NSSetのmakeObjectsPerformSelector:は便利なメソッドで、NSSetの中に入っている各オブジェクトにセレクタで指定したメッセージを送る事ができます。ループを回すよりもコンパクトに記述できます。

// 
//  Lissajous.m
//

- (void)setResolution:(NSNumber *)value 
{
    [self setValue:value forKey:@"resolution" action:@"setResolution"];
    [[self mutableSetValueForKey:@"oscillators"] makeObjectsPerformSelector:@selector(updateBezierPath)];
}

OscillatorのupdateBezierPathを修正する

 第一のプロジェクトでは、OscillatorのupdateWaveformでresolutionというパラメータをとりあえず固定値にしておきましたが、これを修正してLissajousから得る様にします。そして波形を更新したらリサージュ図形も同時に更新する様に、updateBezierPathメソッドを呼びだすコードを追加します。
 ただし初期化コードが呼びだされる順番が関係する為か、lissajous関連からresolutionを取得できない場合がありました。そこでそのような場合は固定値200とする様にしてあります。

 waveform属性を削除して、PathHolderのbezierPath属性を使う事にしたので、このメソッド名もupdateBezierPathに変更します。

 Setterと初期化メソッドでupdateWaveformを呼び出しているので、メソッド名を修正します。

// 
//  OscillatorCD.m
//  RepeatingMotifGenerator
//

- (void)updateBezierPath
{
    int             i,resolution;
    double          output,normalizedPhase;
    NSBezierPath    *path = [NSBezierPath bezierPath];
    Lissajous       *lissajous = [self lissajous];

    if(lissajous == nil)
        resolution = 200;
    else
        resolution = [[lissajous resolution] intValue];

    for(i=0;i<resolution;i++)
    {
        normalizedPhase = i/(double)resolution;
        output = [self output:normalizedPhase];
        if(i == 0)
            [path moveToPoint:NSMakePoint(i,output)];
        else
            [path lineToPoint:NSMakePoint(i,output)];
    }
    [self setBezierPath:path];
    [lissajous updateBezierPath];
}

- (void)setAmplitude:(NSNumber *)value
{
    [self setValue:value forKey:@"amplitude" action:@"setAmplitude"];
    [self updateBezierPath];
}

- (void)setFrequency:(NSNumber *)value 
{
    [self setValue:value forKey:@"frequency" action:@"setFrequency"];
    [self updateBezierPath];
}

- (void)setPhaseLag:(NSNumber *)value 
{
    [self setValue:value forKey:@"phaseLag" action:@"setPhaseLag"];
    [self updateBezierPath];
}

- (void)commonAwake
{
    [self updateBezierPath];
}

 OscillatorCDのインターフェイスファイルからupdateWaveformメソッドの宣言を削除します。

//
//  OscillatorCD.h
//  RepeatingMotifGenerator
//
//  Copyright 2008 NovemberKou. All rights reserved.
//

#import <CoreData/CoreData.h>
#import "PathHolder.h"

@class Lissajous;

@interface OscillatorCD :  PathHolder  
{
}

- (void)updateWaveform;

- (NSNumber *)amplitude;
- (void)setAmplitude:(NSNumber *)value;

- (NSNumber *)frequency;
- (void)setFrequency:(NSNumber *)value;

- (NSNumber *)phaseLag;
- (void)setPhaseLag:(NSNumber *)value;

- (Lissajous *)lissajous;
- (void)setLissajous:(Lissajous *)value;

@end

 PathHolderのインターフェイスファイルにupdateBezierPathメソッドの宣言を追加します。

//
//  PathHolder.h
//  RepeatingMotifGenerator
//
//  Copyright 2008 NovemberKou. All rights reserved.
//

#import <CoreData/CoreData.h>
#import "RMGRoot.h"


@interface PathHolder :  RMGRoot  
{
}

- (void)updateBezierPath;

- (NSBezierPath *)bezierPath;
- (void)setBezierPath:(NSBezierPath *)value;

@end

 PathHolderにはサブクラスでオーバーライドされる事を前提として、空のupdateBezierPathメソッドを実装しておきます。PathHolderはこのメソッドとbezierPath属性のアクセサメソッドしかないので、ここで全て載せておきます。

// 
//  PathHolder.m
//  RepeatingMotifGenerator
//
//  Copyright 2008 NovemberKou. All rights reserved.
//

#import "PathHolder.h"


@implementation PathHolder 

#pragma mark -
#pragma mark Update path

- (void)updateBezierPath
{
}

#pragma mark -
#pragma mark Accessor methods

- (NSBezierPath *)bezierPath                {return  VFK("bezierPath");}
- (void)setBezierPath:(NSBezierPath *)value {[self setValue:value forKey:@"bezierPath" action:nil];}

@end