let it leave me like a long breath

let it dissipate or fade in the background

Entries tagged with code

Profile

xax: purple-orange {11/3 knotwork star, pointed down (Default)
howling howling howling

Nav

  • Recent Entries
  • Archive
  • Reading
  • Tags
  • Memories
  • Profile

Tags

  • art - 2 uses
  • asteroid garden - 4 uses
  • code - 19 uses
  • demos - 1 use
  • dreams - 5 uses
  • ff7 fangame - 23 uses
  • fic prompts - 13 uses
  • gamedev challenge - 82 uses
  • hell game - 76 uses
  • nanowrimo - 11 uses
  • plants - 9 uses
  • process - 52 uses
  • programming - 51 uses
  • screenshots - 5 uses
  • writing log - 83 uses

May 2025

S M T W T F S
    123
45678 910
1112131415 1617
18192021222324
25262728293031
  • Dec. 5th, 2023
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code,
    • programming
    posted @ 03:33 pm

    anyway here's the actual math if anybody wants to help me with this :V

    so i have some stored vertices for a prism going from 0,0,0 to 0,1,0. it's a rectangular prism 0.2 units thick and 1 unit long. fine, whatever.

    i want to reposition it so that it's actually positioned between two arbitrary points, p1 and p2. the way i'm doing that currently is like this:

    const at = (vec) => {
        const base = new Point3d (0,1,0);
        const axis = base.cross (vec.normalize());
    
        const angle = base.vectorAngle (vec);
    
        return axisAngle (axis, angle).multiply(scale (1, vec.magnitude(), 1));
    };

    where

    
    // returns angle in range 0..pi
    Point3d.prototype.vectorAngle = function (vec) {
        const this_ = this.normalize();
        const vec_ = vec.normalize();
        const n = vec_.cross(this_);
    
        return Math.atan2 (n.dot(n), this_.dot (vec_));
    };
    
    function axisAngle (axis, angle) {
        const sin_a = Math.sin (angle / 2);
        const cos_a = Math.cos (angle / 2);
        const axis_ = axis.normalize();
    
        return new Quaternion (axis_.x * sin_a, axis_.y * sin_a, axis_.z * sin_a, cos_a).normalize().toMatrix();
    }

    (i mean i'm not sure that all the quaternion & vector math is right but if i got cross products and quaternion->matrix code wrong i assume it would've cropped up before now)

    the fundamental thing is that i assume given two points like...
    let a = new Point3d (0,1,0).normalize();
    let b = new Point3d (1,2,3).normalize();

    then something like m = axisAngle (a.cross(b), a.vectorAngle(b)) should generate a matrix where m.multiplyVector(a, 1) equals b

    when in fact!!! if i do it with this code & these values i get
    b = Object { x: 0.2672612419124244, y: 0.5345224838248488, z: 0.8017837257372732 }
    
    m.multiplyVector(a, 1) = Object { x: 0.25318484177091666, y: 0.5991446895152781, z: 0.7595545253127499 }


    so very close but not actually the same. it seems like they should be the same.

    (also there's a secondary issue where i know... the at code there is underspecified, because you need some spare coordinate axes to determine how points in the plane of vec get positioned, since all you have so far is the y axis transform; you need at least an x/z one too. i think this is a separate issue than the above but to be fair i haven't tried to fix it yet.)

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Mar. 3rd, 2020
  • xax: purple-orange {11/3 knotwork star, pointed down ({11/3}sided)
    Tags:
    • code,
    • programming
    posted @ 04:38 pm

    so i'm a big fan of the ctm ('connected textures mod') mod in minecraft. in large part because i use the greatwood texture pack, which has a bunch of ctm textures and really shows off what a little bit of variation can do.

    texture variation (and geometry variation) are huge things to have. vagrant story is a game that it turns out is a lot like minecraft -- every room is assembled out of meter-large block/halfblock collision (well, and stairs), and its textures are (generally) 32x32 pixels per meter. so only four times the density of vanilla minecraft's 16x16 textures, and a quarter of the fancy smooth 64x64 sphax textures that a lot of people use. but because vagrant story has a whole lot of texture variation (and more complex world geometry, though that turns out to not actually be as big a concern as you might think) vagrant story looks amazing even now, whereas minecraft looks, you know, kind of garbage. it turns out big fields of repeating textures look bad.

    so i really wanted to add more intricate texturing modes to the game i'm working on, since i feel like that'll be kind of critical for the visual style. there are a lot of different modes i want to support (automatic wang tilings would be great) but to start i decided to copy the simplest ctm modes:

    • fixed, which is a single texture
    • random, which is a list of textures with weights, to be picked from randomly
    • repeat, which is an x*y grid of textures that will be laid out in a repeating pattern

    the type i wrote for those initially was

    data Texturing
      = Fixed FilePath
      | Random [(Float, FilePath)]
      | Repeat Int Int [FilePath]
      deriving (Eq, Ord, Show, Read)
    which is pretty obvious how it works, right? i have a big hardcoded texture-loading section that looked like
      ...
      ,("sand_loose", "img/tiles/sand_loose.png")
      ,("sand_loose_side", "img/tiles/sand_loose_side.png")
    
      ,("sand_cracked", "img/tiles/sand_cracked.png")
      ,("sand_cracked_side", "img/tiles/sand_cracked_side.png")
      
      ,("sand_silty", "img/tiles/sand_silty.png")
      ,("sand_silty_side", "img/tiles/sand_silty_side.png")
      
      ,("loam_coarse", "img/tiles/loam_coarse.png")
      ,("loam_coarse_side", "img/tiles/loam_coarse_side.png")
      ...
    and then that would be trivially changed to
      ...
      ,("sand_loose", Fixed "img/tiles/sand_loose.png")
      ,("sand_loose_side", Fixed "img/tiles/sand_loose_side.png")
    
      ,("sand_cracked", Fixed "img/tiles/sand_cracked.png")
      ,("sand_cracked_side", Fixed "img/tiles/sand_cracked_side.png")
      
      ,("sand_silty", Fixed "img/tiles/sand_silty.png")
      ,("sand_silty_side", Fixed "img/tiles/sand_silty_side.png")
      
      ,("loam_coarse", Fixed "img/tiles/loam_coarse.png")
      ,("loam_coarse_side", Fixed "img/tiles/loam_coarse_side.png")
      ...
    and then i could add new textures from there.

    the thing was, just using that type above was kind of awkward? since one of the things i wanted to do was load up each image based on the given path, but without discarding the texturing information. but to anybody who's been writing haskell for any amount of time, the issue is obvious: what i actually want is to add a type parameter

    data Texturing a
      = Fixed a
      | Random Float [(Float, a)]
      | Repeat Int Int [a]
      deriving (Eq, Ord, Show, Read)
    
    instance Functor Texturing where
      fmap f t = case t of
        Fixed a -> Fixed $ f a
        Random t was -> Random t $ (fmap . fmap) f was
        Repeating w h as -> Repeating w h $ fmap f as
    and now i can fmap things through the texturing data without losing the texturing information. this is a concrete example of something having a "functor context" that's invariant under fmap (other examples are 'fmapping a list or a tree can't reorder items in the list or tree').

    except there's still an issue, since what i really want to do is mapM -- if i have [Texturing FilePath], i don't want to end up with [Texturing (IO Image)] after doing the file loading, i want IO [Texturing Image]. that's a different function, traverse, which is part of the Traversible typeclass.

    this is kind of like, warming up to this texturing data being a Type that has typeclasses, not just some tagged enum: Functor says it can be mapped over, Foldable says it can be iterated through, and Traversable extends that traversal to being able to pull it 'inside out' to do precisely that kind of sequencing above -- do a file load for each thing, but pull them all out into a single wrapper as it happens so that the IO ends up 'outside' the Texturing.

    so i went and instanced Foldable and Traversable, and then i was thinking, well, what other common typeclasses should i be thinking about? it's not Applicative or Monad since it can't really combine two things, and it's definitely not Semigroup or Monoid for the same reason. since it's not Applicative it can't be Alternative, and since it's not Monad it can't be MonadPlus. that basically covers all the common typeclasses, so i guess i'm done.

    the thing with writing types is that it's generally possible to explicitly make a type into a valid typeclass instance, right? Semigroup just means "can be added together", and you can trivially implement that by adding in a new constructor like Several [Texturing a]. it's just like, well, is that really useful? given what the typeclass means, does that make some kind of sense? like i could say, maybe, that it's a semigroup in that adding two values together just dumps their values into a Random constructor, but then i'd have to make up weights, and that works well enough if the values being combined are Fixed or Random already, but Repeat would just make a huge mess, etc. so Semigroup doesn't really make sense. Monoid extends Semigroup with the addition of an null 'empty' object, so that would mean like, "totally untextured object" i guess, which is something that we actively want to avoid having!

    but Applicative, hmm, that would maybe be useful.

    looking at the ctm mod's config files (that's optifine's impl but it's the same config format) reveals a kind of pattern: there are 'base level' methods that do something -- ctm, ctm_compact, fixed, random, repeat, vertical, horizontal, top, and overlay -- but then there are also a bunch of methods that only exist to say "run two of these methods together": horizontal+vertical, vertical+horizontal, overlay_ctm, overlay_random, overlay_repeat, and overlay_fixed. what the mod lacks is a general-purpose way to say "use this one texturing method and then use this other texturing method on top"; they've had to special-case in each combination with its own mode.

    Applicative has two functions: pure :: a -> f a, which here is trivially just Fixed, and (<*>) :: f (a -> b) -> f a -> f b, which has to do with combining two values together. so if i want to combine two texturing values together...

    data Texturing a
      = Fixed a
      | Random Float [(Float, Texturing a)]
      | Repeating Int Int [Texturing a]
      deriving (Eq, Ord, Show, Read)

    note that Random and Repeating now take a list of Texturing a, rather than just a. this makes Fixed into a kind of 'terminal value', since the other two constructors will recurse to contain more Texturing as; it's only Fixed that will stop and finally produce an a. this two type is still a Functor, Foldable, and Traversable, but it's now also an Applicative:

    instance Applicative Texturing where
      pure a = Fixed a
      tf <*> ta = case tf of
        Fixed f -> f <$> ta
        Random tw wfs -> Random tw $ second (<*> ta) <$> wfs
        Repeating w h fs -> Repeating w h $ (<*> ta) <$> fs

    this expresses how it's possible to nest these values arbitrarily, which it turns out is the general-case version of what the ctm mod has to special-case. here, i can say 'random + repeating' to make a repeating brick pattern that's randomly interrupted by a flagstone:

    Random 1
      [ (0.875, Repeating 2 2
        [ Fixed "tile_bricks_bl.png"
        , Fixed "tile_bricks_br.png"
        , Fixed "tile_bricks_tl.png"
        , Fixed "tile_bricks_tr.png"
        ]
      , (0.125, Fixed "tile_flagstone.png"
      ]
    done the other way, it's 'repeating + random', which presents a repeating brick pattern with a random chance of each section having a variant:
    Repeating 2 2
      [ Random 1
        [ (0.75, Fixed "tile_bricks_bl.png")
        , (0.25, Fixed "tile_bricks_bl_cracked.png")
        ]
      , Random 1
        [ (0.75, Fixed "tile_bricks_br.png")
        , (0.25, Fixed "tile_bricks_br_cracked.png")
        ]
      , Random 1
        [ (0.75, Fixed "tile_bricks_tl.png")
        , (0.25, Fixed "tile_bricks_tl_cracked.png")
        ]
      , Random 1
        [ (0.75, Fixed "tile_bricks_tr.png")
        , (0.25, Fixed "tile_bricks_tr_cracked.png")
        ]
      ]

    when i add in the other texturing modes like ctm or overlay, those would just be more constructors that take Texturing a values, and would combine with each other easily and arbitrarily. all these examples don't even use <*>, they just take advantage of the general restructing afforded to them by the Applicative rewrite.

    i think this, more than anything, is the good part of haskell? its typeclasses are actually useful because a lot of them represent general-purpose data transformations, and that leads you to thinking "would this transformation be useful to have here?", and a lot of the time the answer to that is 'yes'. the conceptual pattern for organizing these values was always out there, but it took haskell being like, "hey this is what a Functor is, this is what an Applicative is" for me to consciously realize that was maybe a target i should be reaching for

    the final type (as of now) is this:

    data Texturing a
      = Fixed a
      | Random Float [(Float, Texturing a)]
      | Repeating Int Int [Texturing a]
      deriving (Eq, Ord, Show, Read)
    
    instance Functor Texturing where
      fmap f t = case t of
        Fixed a -> Fixed $ f a
        Random tw was -> Random tw $ (fmap . fmap . fmap) f was
        Repeating w h as -> Repeating w h $ (fmap . fmap) f as
    
    instance Foldable Texturing where
      foldMap f t = case t of
        Fixed a -> f a
        Random _ was -> (foldMap . foldMap) f $ snd <$> was
        Repeating _ _ as -> (foldMap . foldMap) f as
    
    instance Traversable Texturing where
      sequenceA t = case t of
        Fixed a -> Fixed <$> a
        Random tw was -> Random tw <$> sequenceA (fmap (sequenceA . fmap sequenceA) was)
        Repeating w h as -> Repeating w h <$> sequenceA (sequenceA <$> as)
    
    instance Applicative Texturing where
      pure a = Fixed a
      tf <*> ta = case tf of
        Fixed f -> f <$> ta
        Random tw wfs -> Random tw $ second (<*> ta) <$> wfs
        Repeating w h fs -> Repeating w h $ (<*> ta) <$> fs
    which is a little frustrating to read (sequenceA (fmap (sequenceA . fmap sequenceA) was), you don't say) but it all works and very simply specifies the necessary information each tile type needs to be textured, and it shouldn't be particularly difficult to expand for future texturing types.


    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Aug. 13th, 2019
  • xax: yellow-orange {7/2} knotwork star, pointed down (7sided)
    Tags:
    • code,
    • gamedev challenge,
    • programming
    posted @ 12:12 pm

    i ended up writing a todo list when i started and took some degree of day-by-day notes for this project, so i figured i might as well put together a writeup.

    august 1 - 14: PICKING

    ( it's long )

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Mar. 6th, 2019
  • xax: yellow-orange {7/2} knotwork star, pointed down (7sided)
    Tags:
    • code,
    • programming
    posted @ 08:07 pm

    i made a new macro for twine!

    first, you can check out a small demo that uses it.

    this is for twine 1.4, and is a macro that adds some slightly more advanced form controls, with inline displays. this adds three new macros: <<toggle>>, <<block>>, and <<endblock>>.

    a <<toggle>> is in the form <<toggle $varname groupname value display>>, and it generates some clickable text ("display"), which, when clicked, will set $varname to value. if there are multiple toggles with the same groupname, clicking one will deselect any others.

    a <<block>> is in the form <<block groupname>>...<<endblock>>. when a toggle is selected, it will also inline refresh the text inside any blocks with matching groupnames.

    (if you want text to be refreshed based on multiple toggle groups, you can nest <<block>>s with no issue.)

    an example of use:
    <<toggle $foo foo foo_a "foo a">>
    <<toggle $foo foo foo_b "foo b">>
    <<toggle $foo foo foo_c "foo c">>
    
    <<block foo>>\
    $foo is <<print $foo>>
    <<endblock>>
    
    <<toggle $bar bar 1 one>>
    <<toggle $bar bar 2 two>>
    <<toggle $bar bar 3 three>>
    
    <<block bar>>\
    $bar is <<print $bar>>
    <<endblock>>


    would display two sets of three selections, with a "$var is ..." blurb beneath each one that's automatically updated every time you change a value.

    ( here's the code )

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Mar. 1st, 2019
  • xax: yellow-orange {7/2} knotwork star, pointed down (7sided)
    Tags:
    • code,
    • gamedev challenge,
    • programming
    posted @ 12:40 pm

    uniform-tagged shaders with gpipe

    okay, two week projects

    i didn't do something for february 1-14 since i was sick for that entire stretch (i was sick for like three weeks and it sucked), but i did get some stuff done for the 15-28 period.

    gpipe has this enormous infrastructure for type-safe shaders, so that you can never write the wrong kind of vertex pipeline to a shader, or write the wrong thing to a buffer generally, and all of its weird internal types are instanced to a mess of typeclasses so that they get marshaled correctly automatically. it's real nice. but what they don't do for you is handle uniforms. or rather, they handle uniforms (you can write to them and use them just find) but they don't have any kind of construction for "this shader uses uniforms of type x y and z and so before you run the shader you have to provide x y and z values". basically they don't really surface the shader dependency on uniforms into the type system at all; a shader is just a shader and if you need to write uniforms for it then you need to manage that yourself.

    in my existing code, the only uniform i really used was the camera, which was updated per-frame at the start of each frame, and while i had lots of ideas for useful shaders i could write, i basically had no way to store or structure them. my code ran using a list of RenderUpdate os values (where os is a phantom type value that represents which rendering context the render event came from), and what that means is that if i wanted to keep that same basic infrastructure of having a render cache full of render actions, i would need to hide all the shader information, since haskell doesn't have heterogeneous lists -- i couldn't have a list of like, RenderUpdate os ShaderFoo and RenderUpdate os ShaderBar.

    so already i was thinking this would have to be an existentially-quantified thing: have something like

    data RenderObject os = forall s. RenderObject
        { shader :: CompiledShader os s
        , chunks :: [RenderChunk os s]
        }
    


    so that the shader could vary based on the type of the chunks, but that would be hidden inside the type so that the overall type is still just RenderObject os, so i could continue using a simple list of render updates. additionally, i'd need some way to attach uniforms to shaders, which would be an entire other step that would need to be done in a similar fashion -- stuff the uniform type in an existential somewhere, so that i could automatically set them in some fashion.

    (i should also say that i put off working on this for a while since i only had a hazy idea of how this would work, and i figured it would be really complicated and involve a lot of weird type stuff.)

    so with all that being said let's talk about the code i wrote.

    ( lots of code and code talk under the cut )

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Feb. 12th, 2019
  • xax: yellow-orange {7/2} knotwork star, pointed down (7sided)
    Tags:
    • code,
    • programming
    posted @ 10:39 am

    reactive-banana and behavior lag, among other things

    backstory: i've been using the reactive-banana FRP library to handle the i/o for my haskell stuff, because if you recall i spent one of these two-week projects hacking together a haskell-style i/o management setup that i kept tying myself into knots with and eventually somebody was like "this sounds like you're reinventing the concept of FRP and you should maybe use one of these libraries". then i got wrapped up in trying to comprehend how reactive-banana works and is expected to be used, and how you can practically use it to do things. it's not exactly the most clear, so i figure it's worth it to post about my HARD-EARNED UNDERSTANDING of how it works.

    ( this post assumes that you basically understand haskell and that you've used reactive-banana enough to have some problems with it. )

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Feb. 14th, 2018
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code,
    • programming
    posted @ 12:35 pm

    this is the full code file for the new-and-improved Enumerable data type, with notes and pleas for help, in preparation for asking #haskell if they can help

    ( code here )

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Feb. 11th, 2018
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code,
    • gamedev challenge,
    • programming
    posted @ 02:50 pm

    hey i have a math problem i was hoping somebody could solve

    okay so as you may already know i wrote some haskell code for enumerating through values, and that was real neat and useful and generally speaking i accomplished a lot with it.

    now i'm trying to expand it. specifically: the code as written lets you enumerate through values, but in constructing the values any sense of relative position is lost. you can do something like V2 <$> from [1..10] <*> from [1..20] and that will let you enumerate through all 200 values, but if you have an index representing, say, V2 3 7 (index 62 btw) you can't say "i want to see the indices of all values that are one unit adjacent". basically there's no way to inspect a deck.

    SO i had the idea to track mergings of decks. change the data type to

    data Enumerable a = Enumerable
      { count :: Integer
      , segments :: [Integer]
      , selector :: Integer -> a
      }


    and make the applicative instance

    instance Applicative Enumerable where
      pure v = Enumerable 1 [1] $ const v
      (Enumerable c s f) <*> (Enumerable c' s' g) =
        Enumerable (c * c') (s <> ((c *) <$> s')) $ \i -> let
            j = i `mod` c
            k = i `div` c
          in f j $ g k


    so that V2 <$> from [1..10] <*> from [1..20] value would have 'segments' of [1,10], meaning that there are two axes: one that increases/decreases in steps of 1 and one that increases/decreases in steps of 10.

    and so then you can decompose indices into movements along those axes, like so:

    decompose :: Enumerable a -> Integer -> [Integer]
    decompose (Enumerable _ s _) i' = snd $ mapAccumR split i' s
      where
        split :: Integer -> Integer -> (Integer, Integer)
        split i c = (i `mod` c, i `div` c)


    and it will say: okay index 62 there decomposes to [2,6]. and then you can do something like...

    mutate :: Enumerable a -> [Integer] -> [[Integer]]
    mutate e i = tail $ recombine $ zip i mutvals
      where
        mutvals = uncurry spread <$> zip (maxAxes e) i
        spread m c = snd <$> filter fst
          [ (c > 0, c - 1)
          , (c < m, c + 1)
          ]
        recombine [] = [[]]
        recombine ((base,vars):rs) = ((:) <$> pure base <*> recombine rs)
          <> ((:) <$> vars <*> pure (fst <$> rs))
    
    maxAxes :: Enumerable a -> [Integer]
    maxAxes e = decompose e $ count e - 1
    
    mutateI :: Enumerable a -> Integer -> [Integer]
    mutateI e i = construct e <$> mutate e (decompose e i)
    


    to have it spit out all decomposed values that are one unit distant. so then:

    λ> let foo = ((,) <$> from [1..10] <*> from [1..20])
    λ> select foo 62
    Just (3,7)
    λ> mutateI foo 62
    [52,72,61,63]
    λ> select foo <$> mutateI foo 62
    [Just (3,6),Just (3,8),Just (2,7),Just (4,7)]


    and now you can navigate around within a enumerable deck. very neat. i'm currently using this to put together some really basic plant mutation/crossbreeding code (since you can also crossbreed two indices by decomposing them, and then picking values randomly from the two sets) and it's generally working pretty well.

    the PROBLEM is:

    the original formulation of Enumerable is also an Alternative and a Monoid:

    instance Alternative Enumerable where
      empty = Enumerable 0 $ const (error "no value")
      (Enumerable c v) <|> (Enumerable c' v') =
        Enumerable (c + c') $ \i -> case i - c of
          x | x >= 0 -> v' x
            | otherwise -> v i
    
    instance Monoid (Enumerable a) where
      mempty = empty
      mappend = (<|>)


    BUT: this new formulation would require some additional segment tracking. Applicative is really easy to store because it just creates n-dimensional prisms with totally regular axes. Alternative is a lot trickier, because it would involve... adding offsets? something? the actual lookup math above is really simple, but if i wanted to store that as a segment i'm pretty sure i would need to stop using just lists of integers.

    i've looked at a lot of math and i've tried to mark out the patterns it would make in indexing and i have yet to really figure out what i should do with it. i think the GOAL is to have it treat all choices as adjacent? so like pure 1 <|> pure 2 <|> pure 3 <|> pure 4 would be different from from [1,2,3,4] because the alternative would consider 1 adjacent to all of 2, 3, and 4, whereas the basic enumeration would only consider 1 adjacent to 2. (and then from [1,2] <|> from [3,4] <|> from [5,6] <|> from [7,8] would consider 1 and 2 to be adjacent to each other and to all of 3 5 and 7. ditto 3 & 4 with 1, 5, and 7.)

    it's just uhhhh i am totally failing to see whatever structure i need to see to codify that up, so, if anybody has any thoughts i would like to hear them.

    (spoilers this two-week project is gonna be trying to make something like this.)

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Nov. 3rd, 2017
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code,
    • programming
    posted @ 03:48 pm

    monad instance frustration

    okay so as i have posted about a bunch before, i have this haskell Enumerable data type, that deals with decks of enumerable values. the fundamental interface is something like

    data Enumerable a = ...
    
    instance Functor Enumerable ...
    instance Applicative Enumerable ...
    instance Alternative Enumerable ...
    instance Monoid Enumerable ...
    
    count :: Enumerable a -> Integer
    select :: Enumerable a -> Integer -> Maybe a
    enumerate :: Enumerable a -> [a]
    
    from :: [a] -> Enumerable a
    range :: Enum a => (a, a) -> Enumerable a

    internally, it's defined like this:

    data Enumerable a
    	= EnumerableNil
    	| Enumerable
    		{ count :: Integer
    		, selector :: Integer -> a
    		}

    and all the instances are function composition / application, so this is a reasonably-efficient way to store and generate values from a deck of values, even when that deck can be ludicrously huge. enumerate can be a dangerous function to use, given that these decks can have billions or trillions of entries, and that's simply because trying to generate billions-to-trillions of values is always a challenge. up until that point, there's no internal code that needs to exhaustively enumerate all values. so where something like
    length $ (,,) <$> [1..1000] <*> [1..1000] <*> [1..1000]
    can take ages since it has to evaluate the spine of the entire 1000000000-long list,
    count $ (,,) <$> from [1..1000] <*> from [1..1000] <*> from [1..1000] evaluates basically instantly, since all it needs to do is evaluate the three individual 1000-length lists. this is, in short, a way to get O(n) performance from combinatorial operations that would usually take O(2n) (or O(n2)?? i am not 100% on my big-os).

    (there's also roll :: RandomGen g => Enumerable a -> Rand g a, for randomly pulling from the deck, and is a fairly simple combination of count and select w/ generating a random number)

    the problem is, Enumerable should also be a monad. or rather, it is absolutely a monad, and writing a join :: Enumerable (Enumerable a) -> Enumerable a function is actually extremely trivial:

    join :: Enumerable (Enumerable a) -> Enumerable a
    join EnumerableNil = EnumerableNil
    join (Enumerable c v) = mconcat $ (\c' -> v c') <$> [0..c-1]

    the problem, though, is that [0..c-1] bit, which requires exhaustively evaluating every value in the deck, and thus completely wrecks the point of the data structure. so like, yeah, it's easy to write a Monad instance, here it is right here:

    instance Monad Enumerable where
    	return = pure
    	EnumerableNil >>= f = EnumerableNil
    	e >>= f = joinEnum $ f <$> e
    
    instance MonadPlus Enumerable where
    	mzero = EnumerableNil
    	mplus = (<|>)

    but that instance has god-awful performance on any non-trivial value, and given the way the value works (we need to know the number of combinations so we can select properly, and with a nested value there's no way to figure out how many combinations the inner Enumerable has without evaluating it) i don't think there's any feasible way to implement a Monad instance.

    this is super annoying, since the npc generator i'm working on now is INCREDIBLY simple if there's a monad instance for enumerable, and much much more complex if there's not. but. i don't think it's feasible to write any monad instance whatsoever. :/

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Oct. 18th, 2017
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code,
    • hell game,
    • programming
    posted @ 07:56 am

    or, to make the stuff in that last post more explicit and code-backed:

    (you could also think of it as a sequel to this post from a year and a half ago, because there i was talking about an earlier version of the encounter code. as you can see from this post, it's grown since then.)

    so internally an Encounter is this:

    type Encounter i vars mob = BodyGlob -> mob -> [Event mob i vars]

    where i is the scene-indexing type, vars is the local-variables type, and mob is the encounter type. Event itself looks like this:

    data Event e a l
    	= Narration [String]
    	| Narration2 EventReel
    	| Dialog String String [String]
    	| Dialog2 String String EventReel
    	| Dialog3 String EventReel EventReel
    	| ItemModify [Item]
    	| NodeGive TfNode
    	| Choices [(String, a)]
    	| Choices2 [(String, a, Maybe (Cond l))]
    	| Challenge Opponent [[Item]]
    	| FauxChoice String
    	| Jump a
    	| Shop String [Alchemy]
    	| Close
    	| DeactivateSelf
    	| InlineBranch (Cond l) [Event e a l] [Event e a l]
    	| SetFlag (Scope l) SetMod
    	| UnsetFlag (Scope l)
    	| SetEncounterData e
    	| PushEncounterData (Scope l)
    	| LoadAllSubData (Scope l) [String]
    	| PopToSubData (Scope l) [String]
    	| PeekLoadEncounterData (Scope l)
    	| Branch [(Cond l, a)] a
    	deriving (Show)

    and is basically a list of every possible discrete thing that can happen in a game event -- an event can generate text narration, or text in a dialog frame, or give/remove PC items, or give body nodes, or present a list of choices, etc etc etc onto the more abstruse ones like PeekLoadEncounterData, which i don't even remember what it does.

    the type of Encounter means that, on the server side, when generating the text for an encounter, each encounter has access to precisely two things: the pc body state, and the mob encounter state, both at the precise state they were in when the encounter request was sent (i.e., you can't change an encounter midway through and expect the later scene text to respond to that; all reachable encounter paths are generated once, at request time. when i first started coding the encounter system i made some attempts to change that, but as you might imagine that kind of flow analysis isn't super trivial so there's a certain amount of dead code devoted to path flow analysis.)

    more specifically, each game encounter file provides a few things, most notably an instance to Scene:

    class ToJSON mob => Scene i vars mob | i -> mob vars, vars -> i mob where
    	link :: i -> Encounter i vars mob
    	display :: i -> String
    	vardisplay :: vars -> String
    	varread :: String -> Maybe vars
    	varread _ = Nothing

    which has to do with translating all the random haskell code that makes up an encounter into something that can be serialized (into JSON) and sent back to the client. from the bottom up:

    • varread is dead code ostensibly used for reading the client-side encounter variable table, which is sent with the request but (currently) never used
    • vardisplay is to convert local/global variable references (which exist in code in a form like Local HasMet or Global FarmQuestActive) into plaintext so that the client-side encounter variable table has names to index its variables with
    • display is to render the scene index type, which is used because the actual ~scene api~ responds with a big lookup table of scene data, indexed by the scene index type (hence the name), so that also needs to be renderable as plaintext
    • link, which is the big one, and which determines how we look up scene parts by index on the server side. functions of type a -> b are effectively a total lookup table of the form [(a, b)], so, this is a lookup table with i as the key and Encounter i vars mob as the value.

    so for example, the scene instance for the flame demon encounter looks like this:

    instance Scene Index Vars FlameEncounter where
    	link x = case x of
    		OpeningOptions -> openingOptions
    		TalkOpening -> talkOpening
    		Talk s -> talk s
    		ShopPrompt -> shopPrompt
    		Leave -> leave
    		FuckIntro -> fuckIntro
    		JerkOff from -> jerkOff from
    		BlowTop -> blowTop
    		MouthClimax -> mouthClimax
    		ThroatClimax -> throatClimax
    		Blow from -> blow from
    		SitAnal from -> sitAnal from
    		MountAnal from -> mountAnal from
    		CagePlay -> cagePlay
    		YeahFuck from -> yeahFuck from
    		Fist from -> fist from
    		Reject -> reject
    		Bye -> bye
    		Uncurse -> uncurse
    	display = fmap toLower . show
    	vardisplay = fmap toLower . show

    (this is basically the pattern all the scene instances fall into, btw -- a case statement to link scene indexes with similarly-named functions, and then display/vardisplay being basically show.)

    SO if you read that prior post, here is the technical aspect of what i was calling 'subevents': if you have location X that could potentially store NPCs of type A, B, or C, then what in the world does its scene instance look like? specifically, given that an event is gonna return values of type Event mob i vars, and it's not really feasible to edit the encounter code directly for every possible location that might store an NPC, how do we change those values around to basically turn events on one mob/index/vartable into events on a DIFFERENT mob/index/vartable?

    if you know haskell you might already be having a sinking feeling here

    the answer is, of course, that Event mob i vars forms a TRIFUNCTOR, and can be mapped over to convert values that match one Scene instance into values that match a different one. here:

    triMap :: (e -> f) -> (a -> b) -> (l -> m) -> Event e a l -> Event f b m
    triMap ef af lf e = case e of
    	Choices cs -> Choices $ (\(s, a) -> (s, af a)) <$> cs
    	Choices2 cs -> Choices2 $ (\(s, a, mcl) -> (s, af a, (fmap lf) <$> mcl)) <$> cs
    	Jump a -> Jump $ af a
    	InlineBranch cl e e' -> InlineBranch (lf <$> cl) (triMap ef af lf <$> e) (triMap ef af lf <$> e')
    	SetFlag sl m -> SetFlag (lf <$> sl) m
    	UnsetFlag sl -> UnsetFlag (lf <$> sl)
    	SetEncounterData e -> SetEncounterData $ ef e
    	PushEncounterData sl -> PushEncounterData (lf <$> sl)
    	LoadAllSubData sl s -> LoadAllSubData (lf <$> sl) s
    	PopToSubData sl s -> PopToSubData (lf <$> sl) s
    	PeekLoadEncounterData sl -> PeekLoadEncounterData (lf <$> sl)
    	Branch cs a -> Branch ((\(cl, a') -> (lf <$> cl, af a')) <$> cs) (af a)
    
    	Narration s -> Narration s
    	Narration2 e -> Narration2 e
    	Dialog i n s -> Dialog i n s
    	Dialog2 i n e -> Dialog2 i n e
    	Dialog3 i n e -> Dialog3 i n e
    	ItemModify is -> ItemModify is
    	NodeGive tf -> NodeGive tf
    	Challenge o is -> Challenge o is
    	FauxChoice s -> FauxChoice s
    	Shop s as -> Shop s as
    	Close -> Close
    	DeactivateSelf -> DeactivateSelf

    (to the best of my knowledge there's no major haskell library that provides an actual Trifunctor class that i could instance, so i was just like 'hey whatever' and decided to just write the map function as a standalone.)

    so, while every existing scene instance took a form basically matching that example above, these parametric locations have some very different scene instances:

    data CedarTree = CDryad Dryad | CNature NatureSpirit | CUnicorn Unicorn
    	deriving (Eq, Ord, Show, Read)
    
    data Index = DIndex Dryad.Index | UIndex Unicorn.Index | NIndex Nature.Index
    	deriving (Eq, Ord, Show, Read)
    
    data Var = AsDryad Dryad.Vars | AsUnicorn () | AsNature ()
    	deriving (Eq, Ord, Show, Read)
    
    asDryad :: Event Dryad Dryad.Index Dryad.Vars -> Event CedarTree Index Var
    asDryad = triMap CDryad DIndex AsDryad
    
    asNature :: Event NatureSpirit Nature.Index () -> Event CedarTree Index Var
    asNature = triMap CNature NIndex AsNature
    
    asUni :: Event Unicorn Unicorn.Index () -> Event CedarTree Index Var
    asUni = triMap CUnicorn UIndex AsUnicorn
    
    instance Scene Index Var CedarTree where
    	link f = case f of
    		DIndex di -> \b c -> case c of
    			CDryad d -> asDryad <$> link di b d
    			_ -> error "bad cedartree scene"
    		UIndex ui -> \b c -> case c of
    			CUnicorn u -> asUni <$> link ui b u
    			_ -> error "bad cedartree scene"
    		NIndex ni -> \b c -> case c of
    			CNature n -> asNature <$> link ni b n
    			_ -> error "bad cedartree scene"
    	display = fmap toLower . show
    	vardisplay = fmap toLower . show

    amazing

    (also note the use of () here -- generally that's what i use for encounters that don't have any local variables. you might think that using () multiple times in a scene instance violates the vars -> i mob fundep in the Scene definition, and you would be correct. but because haskell doesn't exhaustively check for overlapping instances, and since (generally) each scene is in a separate compilation unit, and since i never plan on trying to uniquely identify a scene by vardisplay (), i think i'm fine.)

    (anyway this is interesting b/c the dryad code has a local variable named Met, that's used to determine if you're revisiting a dryad. in this encounter, rather than Met being set and read to, a completely different variable, AsDryad Met, is set and referred to. since that's ultimately what being able to (tri)map over events means: that it's possible to rewrite index tables and vartables to break the correspondence with one scene while creating a new correspondence with another scene. this doesn't yet work 100% for reasons that i'm unsure about, and i think that might just be "the events i've written are poorly-structured to handle having a nested entry point", but it's mostly working. for example, in the current code, the npcs in the cedar tree location get their descriptions printed twice: once because i have the cedar tree opening narration describe their contained npc before jumping into the npc's scene, and the other... in the contained npc's scene.)

    (the next issue here is just the structural one of jumping into another encounter -- right now once you jump into an encounter you're basically stuck in the subtree until it ends; there's not really any jumping 'out'. but it's entirely POSSIBLE to explicitly change the link index of a subevent. but generally those aren't surfaced, and there's no functional infrastructure to do things like "oh okay run the subevent's description code, and then instead of the usual choices present "look around the base of the tree" and "talk to the dryad"", or "instead of 'leave' in the subevent exiting the event, have it jump back to the main event's choice prompt". i could handle things like that on a case-by-case basis, but it would involve surfacing more subevent code and generally be arbitrary and tangled, so once this is fully working from a code perspective the next issue is to think about what added structure would help with nesting events)

    ANYWAY THOSE ARE SOME TECHNICAL DETAILS, HOPE YOU LIKED THEM

    • Add Memory
    • Share This Entry
    • Link
    • 1 comment
    • Reply
  • Jul. 23rd, 2017
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code,
    • programming
    posted @ 06:21 pm

    this is the more recent and streamlined version of the Enumerable code i mentioned back here.

    ( Read more... )

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Aug. 31st, 2016
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code,
    • programming
    posted @ 04:56 pm

    i’m writing some haskell & i’ve put together maybe the first complicated, math-based type i’ve ever written?

    it’s for enumerable combinatorics equations and the deal is you got

    total :: Enumerable a -> Integer
    pick :: Monoid a => Enumerable a -> Integer -> Either EnumerableError a


    where you can say ‘give me anything in [0..total x)’ and you’ll get out that specific permutation of values. so it lets you exhaustively enumerate certain random generators, basically. and also transform the random generation into just "pick a random number in the range and then just calculate that value", since it's possible to generate any given value without looking at its neighbors.

    (the monoid constraint is just b/c otherwise it would have to return [a] which is kind of annoying since that’s basically never what you’d actually want)

    i was testing this with quickcheck (also my first time using quickcheck) to make sure it actually did what i thought it did, and for a while i had a quickcheck Arbitrary instance that just did not terminate, since these are nested data structures that can store an arbitrarily complex expression. even after limiting it a bunch i still can get it to churn out nice compact expressions that have a total count of, for example, 458575485777749657103020327287430331293288631270492527580669116448151889324257271211264327503524251371913200211982149838423029009393779536545995926888936184639883868071199119583128675780396729867476981269731795729169851133510432815404825306221507642742572989858640526486864585952735243739518209812574667794254182198110257592401920000000000000000000000000000000000000000000000000000000000000000000000000000000000

    so it has kind of been illuminating about the nature of combinatorial explosions.

    i’m working on adding weights and constraints and some other stuff but that’s somewhat difficult, since some constraints are basically super easy to codify and others are known NP-complete.

    i spent a lot of time thinking about how it would or would not be an applicative functor, and then it turns out the actual Applicative instance is like ~250 characters

    anyway code follows ( Read more... )

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Apr. 5th, 2016
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code
    posted @ 03:54 pm

    i got tired of manually assembling enums that didn't even work right in javascript and tried to figure out what properties i wanted out of enums and if javascript could give them to me. thankfully it could!

    actual code: 190 characters
    comments explaining the what and why of the code: 1240 characters

    ( Read more... )

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Mar. 24th, 2016
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code,
    • hell game,
    • process,
    • programming
    posted @ 02:27 am

    today i kind of worked on hell game! marking up sex scenes mostly.

    now i'm gonna talk about some of the code

    ( Read more... )

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Nov. 10th, 2015
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    • Current Music: Dee D Jackson - Automatic Lover
    Tags:
    • code,
    • programming
    posted @ 01:04 pm

    ha ha ha ha it works it worksssssss

    {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies,
    OverlappingInstances,
    UndecidableInstances, TypeFamilies,
    TypeOperators, ViewPatterns, FlexibleContexts #-}
    
    module Generation.Layer.Render
      ( Extract(extract)
      , render
      , (?)(..)
      , Term (..)
      , Extend (..)
      ) where
    
    import Prelude hiding (map)
    
    import Data.Map
    
    render :: Extract from (Map k v) => from -> (v -> r) -> Map k r
    render t f = map f . extract $ t
    
    infixr 5 ?
    data a ? b = a :? Maybe b
    
    class Extract from result where
      extract :: from -> result
    
    instance (Term from k v) => Extract from (Map k v) where
        extract = final
    
    -- OVERLAPPING
    instance (Extend from from' j k v, Extract from' (Map j v'), Show k, Ord k) => Extract from (Map k (v' ? v)) where
        extract v = case next v of
            (rs, n) -> n `unify` (rekey v) (extract rs)
    
    class Term t k v | t -> k v where
      final :: t -> Map k v
    
    class Extend e t j k v | e -> t j k v where
      next :: e -> (t, Map k v)
      rekey :: e -> Map j a -> Map k a
    
    unify :: (Ord k, Show k) => Map k a -> Map k b -> Map k (b ? a)
    unify = mergeWithKey
      (\_ a b -> Just (b :? Just a))
      (mapWithKey $ \k b -> error $ "unify: bad generation @ " ++ show k ++ ".")
      (map $ \b -> b :? Nothing)


    (those Show instances aren't really needed except for when i crash on a bad generation, b/c uhhhh it seemed like i wanted at least some vague detail if that situation ever crops up)

    this provides an Extract type class with a single function, extract. this type class is let's say creatively instanced so that i can extract any depth of generator (the Term and Extend typeclasses) into a set of values. this makes it possible to write a render function that takes any extractable value (i.e., any generator stack regardless of depth) and a function that renders based on those values, while still typechecking to make sure the types match. (previously: i needed separate functions for each depth of generator)

    the punchline to all of this is that in nearly all practical situations you really don't need or want to render the entire generation stack; you just want to render the final value, which it's always been trivial to extract. but this lets you make debug overlays and fancy generation visualizations, so of course it's useful!!

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Nov. 9th, 2015
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    Tags:
    • code,
    • programming
    posted @ 01:46 pm

    {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances,
    UndecidableInstances, TypeFamilies #-}
    
    class Appl fun arg t where
      appl :: fun -> arg -> t
    
    instance (a ~ a') => Appl (a -> b) a' b where
      appl = id
    
    instance {-# OVERLAPPING #-} Appl fun arg (b -> t) => Appl fun (b, arg) t where
      appl f (n, rs) = appl f rs n
    
    class Appr fun arg t where
      appr :: fun -> arg -> t
    
    instance (a ~ a') => Appr (a -> b) a' b where
      appr f = f
    
    instance {-# OVERLAPPING #-} (a ~ a', Appr fun arg t) => Appr (a -> fun) (a', arg) t where
      appr f (a, x) = appr (f a) x
    

    s/o to lyxia @ #haskell who wrote the appr half of this, which was enough to finally get me to comprehend all these messed-up recursive instances

    (this generalizes the series of functions i enumerated here. the end goal is as always to unify all the render* functions in this code, which is a more complicated step, but now one i'm a lot more confident that i can figure out.)

    (also as you might discover if you try to use this code, it requires type hints for everything: function, argument, and result. there might be some way for fundeps to fix that, but i haven't figured that out yet either. i don't know how annoying that would be in practice, since in practice i think the types would be pinned down by use elsewhere.)

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Oct. 27th, 2015
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    • Current Music: The Mountain Goats - Palmcorder Yajna
    Tags:
    • code,
    • programming
    posted @ 01:07 pm

    appl1 :: (a -> t) -> a -> t
    appl1 = id
    appl2 :: (a -> b -> t) -> (a, b) -> t
    appl2 f (a, b) = appl1 (f a) b
    appl3 :: (a -> b -> c -> t) -> (a, (b, c)) -> t
    appl3 f (a, b) = appl2 (f a) b
    appl4 :: (a -> b -> c -> d -> t) -> (a, (b, (c, d))) -> t
    appl4 f (a, b) = appl3 (f a) b
    appl5 f (a, b) = appl4 (f a) b
    appl6 f (a, b) = appl5 (f a) b
    -- etc
    
    appr1 :: (a -> t) -> a -> t
    appr1 = id
    appr2 :: (a -> b -> t) -> (b, a) -> t
    appr2 f (n, rs) = appr1 f rs n
    appr3 :: (a -> b -> c -> t) -> (c, (b, a)) -> t
    appr3 f (n, rs) = appr2 f rs n
    appr4 :: (a -> b -> c -> d -> t) -> (d, (c, (b, a))) -> t
    appr4 f (n, rs) = appr3 f rs n
    appr5 f (n, rs) = appr4 f rs n
    appr6 f (n, rs) = appr5 f rs n
    -- etc

    this can be somewhat generalized so we're not stuck using only tuples forever (this is with viewpatterns btw):

    class Splittable a b c | a -> b c where
    	_fst :: a -> b
    	_snd :: a -> c
    
    instance Splittable (a, b) a b where
    	_fst = fst
    	_snd = snd
    
    pair :: Splittable a b c => a -> (b, c)
    pair a = (_fst a, _snd a)
    
    applg1 :: (a -> t) -> a -> t
    applg1 = id
    applg2 :: Splittable s a b => (a -> b -> t) -> s -> t
    applg2 f (pair -> (a, b)) = applg1 (f a) b
    applg3 :: (Splittable s a s', Splittable s' b c) => (a -> b -> c -> t) -> s -> t
    applg3 f (pair -> (a, b)) = applg2 (f a) b
    applg4 f (pair -> (a, b)) = applg3 (f a) b
    applg5 f (pair -> (a, b)) = applg4 (f a) b
    applg6 f (pair -> (a, b)) = applg5 (f a) b
    
    apprg1 :: (a -> t) -> a -> t
    apprg1 = id
    apprg2 :: Splittable s a b => (b -> a -> t) -> s -> t
    apprg2 f (pair -> (n, rs)) = apprg1 f rs n
    apprg3 :: (Splittable s a s', Splittable s' b c) => (c -> b -> a -> t) -> s -> t
    apprg3 f (pair -> (n, rs)) = apprg2 f rs n
    apprg4 f (pair -> (n, rs)) = apprg3 f rs n
    apprg5 f (pair -> (n, rs)) = apprg4 f rs n
    apprg6 f (pair -> (n, rs)) = apprg5 f rs n


    but what i'd really like is to generalize these functions so that i don't need an infinite number of them based off of their argument count. they all do the same thing!! given what i've read about polyvariadic functions in haskell this should be totally possible. i just haven't really wrapped my head around the code yet.

    and also maybe some way to set it up so the function type there doesn't have to be a function? like the only function i really want to get out of this whole deal is something like render :: (Extend (Extend (Extend ...) v') v) -> (... ? v' ? v -> t) -> Map k t except i can't even come close to formulating what that type would look like. especially given Extend isn't a type but instead a typeclass.

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Oct. 21st, 2015
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    • Current Music: Groove Armada - Paper Romance (Softwar Remix)
    Tags:
    • code,
    • programming
    posted @ 08:34 pm

    "we believe in rough consensus and running code"

    here's some running code i wrote, which i share to inspire you all: ( Read more... )

    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply
  • Sep. 6th, 2015
  • xax: purple-orange {11/3 knotwork star, pointed down (Default)
    • Current Music: The Mountain Goats - The Diaz Brothers
    Tags:
    • code,
    • programming
    posted @ 04:04 pm

    data BSPControl f a n e = BSPControl
      { minSize :: a
      , maxGeneration :: Integer
      , splitRange :: (Float, Float)
      , divideControl :: Maybe (f a -> f a -> Integer -> Bool)
      , includeSmallOverlaps :: Bool
      , rebuild :: f a -> f a -> n
      , reconnect :: f (TouchState a) -> e
      }
    
    delve :: (Graph gr, RandomGen g, Random a, Integral a, Ord a, Num (f a), Applicative f, Metric f, Traversable f) => BSPControl f a n e -> f a -> Rand g (gr n e)
    delve c size = liftM (construct c) $ bsp c size

    so this is dimension-agnostic binary-space-partitioning code, which is nice, but wow are those a lot of type constraints. for a while i had both Integral a and Floating a constraints, which basically meant that there was no possible type that could work with the code. i'd still like to get rid of that Integral requirement, since it only comes up once in the entire set of functions, and there it's just because i need something i can cast to and from a floating-point value.

    (the f values are expected to be V* values from Linear)

    the bit that was most confusing at first (how exactly do i determine if n-dimensional axis-aligned prisms are touching) ended up being radically simplified due to Applicatives:

    touching :: (Applicative f, Foldable f, Ord a, Num a) => BSPRoom (f a) -> BSPRoom (f a) -> f (TouchState a)
    touching (BSPRoom pos size _) (BSPRoom pos' size' _) =
      test <$> pos <*> size <*> pos' <*> size'
      where
        test :: (Ord a, Num a) => a -> a -> a -> a -> TouchState a
        test start1 length1 start2 length2 = if start2 < start1
          then test start2 length2 start1 length1
          else case min length2 (start1 + length1 - start2) of
            x | x < 0 -> NoContact
              | x == 0 -> Touching start2
              | otherwise -> Overlapping start2 x

    since of course if you have axis-aligned prisms you can decompose each of the dimensionality tests, so what this ends up doing is generating a vector that contains contact information for each dimension, and it turns out there are pretty simple rules for figuring out just when a contact vector is "really" touching, which i will LEAVE DETERMINING AS AN EXERCISE FOR THE READER. (hint: all dimensions being Touching means the two things touch on a single point; all dimensions being Overlapping means the two things contain a full overlapping subprism.) sadly i haven't really figured out a way to type-agnostically transform that into something useful (e.g., planes or lines) but whatever, the type variables have gotta run out sometime.


    • Add Memory
    • Share This Entry
    • Link
    • 0 comments
    • Reply

Syndicate

RSS Atom
Page generated Jul. 19th, 2025 02:34 pm
Powered by Dreamwidth Studios

Style Credit

  • Style: (No Theme) for vertical