Skip to content

Commit 1105cd9

Browse files
authored
Merge pull request #1 from KEINOS/feat-custom-generator
Feat: custom generator ( enable user-defined function)
2 parents a12ee5a + 7da447c commit 1105cd9

File tree

14 files changed

+501
-40
lines changed

14 files changed

+501
-40
lines changed

.github/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ ARG VARIANT="1.17-alpine"
1010
FROM golang:${VARIANT}
1111

1212
RUN apk add --no-cache \
13-
git \
13+
mesa-dev \
14+
pkgconfig \
1415
alpine-sdk \
1516
build-base
1617

.github/go-mod-tidy.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
set -eu
1515

16-
echo '* Current Go version:' $(go version)
16+
echo '* Current Go version:' "$(go version)"
1717

1818
echo '* Backup modules ...'
1919
mv go.mod go.mod.bak
@@ -27,6 +27,7 @@ go get "github.com/aquilax/go-perlin"
2727
go get "github.com/ojrac/opensimplex-go"
2828
go get "github.com/pkg/errors"
2929
go get "github.com/stretchr/testify"
30+
go get "gonum.org/v1/plot/..."
3031

3132
echo '* Run go tidy ...'
3233
go mod tidy

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ In the example below, 1 dimmentional method `noise.Generator.Eval64(x)` was used
1717
// const seed = 100 // noise pattern ID
1818
// const smoothness = 100 // noise smoothness
1919
//
20+
// // noiseType choises
2021
// noiseType := noise.Perlin
2122
// noiseType := noise.OpenSimplex
23+
// noiseType := noise.Custom
2224
n, err := noise.New(noiseType, seed)
2325

2426
yy := n.Eval64(x / smoothness) // yy is between -1.0 and 1.0 of float64
@@ -27,6 +29,7 @@ y := (yy + 1) / 2 * 500 // y is between 0 and 500
2729

2830
![](./_example/2d/2d_perlin.png)
2931
![](./_example/2d/2d_opensimplex.png)
32+
![](./_example/2d/2d_pseudorandom.png)
3033

3134
- [Source](./_example/2d)
3235

@@ -84,6 +87,10 @@ go get "github.com/KEINOS/go-noise"
8487

8588
### Constructor
8689

90+
```go
91+
noise.New(noiseType noise.Algo, seed int64) (noise.Generator, error)
92+
```
93+
8794
```go
8895
import "github.com/KEINOS/go-noise"
8996

@@ -204,8 +211,8 @@ for z := 0; z < frame; z++ {
204211

205212
### License
206213

207-
- [Go-Noise](https://github.com/KEINOS/go-noise): [MIT](https://github.com/KEINOS/go-noise/blob/main/LICENSE), Copyright 2022- [KEINOS and the Go-Noise Contributors](https://github.com/KEINOS/go-noise/graphs/contributors).
208-
- [Go-Perlin](https://github.com/aquilax/go-perlin): [MIT](https://github.com/aquilax/go-perlin/blob/master/LICENSE), Copyright 2019- [Evgeniy Vasilev and his contributors](https://github.com/aquilax/go-perlin/graphs/contributors).
214+
- [Go-Noise](https://github.com/KEINOS/go-noise): [MIT](https://github.com/KEINOS/go-noise/blob/main/LICENSE), Copyright 2022 [KEINOS and the Go-Noise Contributors](https://github.com/KEINOS/go-noise/graphs/contributors).
215+
- [Go-Perlin](https://github.com/aquilax/go-perlin): [MIT](https://github.com/aquilax/go-perlin/blob/master/LICENSE), Copyright 2022 [Evgeniy Vasilev and his contributors](https://github.com/aquilax/go-perlin/graphs/contributors).
209216
- [OpenSimplex-Go](https://github.com/ojrac/opensimplex-go): [The Unlicense](https://github.com/ojrac/opensimplex-go/blob/main/LICENSE), By [Owen Raccuglia and his contributors](https://github.com/ojrac/opensimplex-go/graphs/contributors). Port of [Java implementation of OpenSimplex Noise](https://gist.github.com/KdotJPG/b1270127455a94ac5d19).
210217
- [Perlin Noise](https://en.wikipedia.org/wiki/Perlin_noise) and [Simplex Noise](https://en.wikipedia.org/wiki/Simplex_noise) are the algorithms developed by [Ken Perlin](https://en.wikipedia.org/wiki/Ken_Perlin). [OpenSimplex Noise](https://en.wikipedia.org/wiki/OpenSimplex_noise) is a [Kurt Spencer](https://github.com/KdotJPG/)'s [open sourced](https://gist.github.com/KdotJPG/b1270127455a94ac5d19#file-unlicense) [Java implementation](https://uniblock.tumblr.com/post/97868843242/noise).
211218
- Go modules used in this package: [go.mod](https://github.com/KEINOS/go-noise/blob/main/go.mod)

_example/2d/2d_pseudorandom.png

20.3 KB
Loading

_example/2d/main.go

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"log"
5+
"math/rand"
56

67
"github.com/KEINOS/go-noise"
78
"gonum.org/v1/plot"
@@ -25,6 +26,13 @@ const (
2526
smoothness = 100
2627
)
2728

29+
// rnd is the rand.Rand used by PseudoRrandom() which is the Custom noise generator.
30+
var rnd *rand.Rand
31+
32+
// ----------------------------------------------------------------------------
33+
// Main
34+
// ----------------------------------------------------------------------------
35+
2836
func main() {
2937
{
3038
// Create Perlin noise generator with seed 100.
@@ -53,11 +61,34 @@ func main() {
5361
log.Fatal(err)
5462
}
5563
}
64+
65+
{
66+
// Create user-defined Pseudorandom noise generator with seed 100.
67+
gen, err := noise.New(noise.Custom, seed)
68+
if err != nil {
69+
log.Fatal(err)
70+
}
71+
72+
// Set user-defined noise function.
73+
if err := gen.SetEval64(PseudoRrandom); err != nil {
74+
log.Fatal(err)
75+
}
76+
77+
pathFile := "./2d_pseudorandom.png"
78+
79+
if err := GenGraphImage(gen, "User Custom Noise (Pseudo Random)", pathFile); err != nil {
80+
log.Fatal(err)
81+
}
82+
}
5683
}
5784

85+
// ----------------------------------------------------------------------------
86+
// Functions
87+
// ----------------------------------------------------------------------------
88+
5889
// GenGraphImage generates a graph image with the specified generator and saves
5990
// it to the specified path.
60-
func GenGraphImage(gen noise.Noise, title string, pathFile string) error {
91+
func GenGraphImage(gen noise.Generator, title string, pathFile string) error {
6192
// Create plotter.
6293
pltr := GenPlotter(title)
6394

@@ -103,3 +134,25 @@ func GenPlotter(titile string) *plot.Plot {
103134
func Normalize(v float64) float64 {
104135
return (v + 1) / 2 * float64(height)
105136
}
137+
138+
// PseudoRrandom is a pseudo-random noise function for use as a user-defined
139+
// noise generator for the Eval64 method.
140+
func PseudoRrandom(seed int64, dim ...float64) float64 {
141+
if rnd == nil {
142+
// Set the seed.
143+
rnd = rand.New(rand.NewSource(seed))
144+
}
145+
146+
for i := 0; i < len(dim); i++ {
147+
v := int(dim[i])
148+
149+
for ii := 0; ii < v; ii++ {
150+
//nolint:gosec // Use of weak random number generation is intended for simple examples.
151+
_ = rnd.Float64()
152+
}
153+
}
154+
155+
s := rnd.Float64() // 0.0 - 0.9999...
156+
157+
return s*2 - 1 // Convert [0.0,1.0) to [-1.0,1.0]
158+
}

example_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,56 @@ func ExampleNew() {
3131
}
3232
}
3333

34+
// ----------------------------------------------------------------------------
35+
// Custom Noise (User-Defind Fucntion)
36+
// ----------------------------------------------------------------------------
37+
38+
func ExampleNew_assign_user_defined_function() {
39+
const seed = 100
40+
41+
// Create a noise generator.
42+
gen, err := noise.New(noise.Custom, seed)
43+
if err != nil {
44+
log.Fatal(err)
45+
}
46+
47+
// rnd is a random number generator.
48+
var rnd *rand.Rand
49+
50+
// Define user function to generate custom noise.
51+
// Here we define a function that returns a pseudo-random value from the
52+
// given seed and itereate 'dim' times.
53+
myFunc := func(seed int64, dim ...float32) float32 {
54+
if rnd == nil {
55+
//nolint:gosec // Use of weak random number generation is intended here.
56+
rnd = rand.New(rand.NewSource(seed))
57+
}
58+
59+
for i := 0; i < len(dim); i++ {
60+
max := int(dim[i])
61+
62+
for ii := 0; ii < max; ii++ {
63+
_ = rnd.Float32()
64+
}
65+
}
66+
67+
// Generate a pseudo-random number.
68+
v := rnd.Float32()
69+
70+
return v*2 - 1 // Convert [0.0,1.0] to [-1.0,1.0]
71+
}
72+
73+
// Assign user-defined function
74+
if err := gen.SetEval32(myFunc); err != nil {
75+
log.Fatal(err)
76+
}
77+
78+
// Get noise value at (1, 2, 3)
79+
fmt.Println(gen.Eval32(1, 2, 3))
80+
81+
// Output: -0.1159181
82+
}
83+
3484
// ----------------------------------------------------------------------------
3585
// OpenSimplex Noise
3686
// ----------------------------------------------------------------------------

noise.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package noise
22

33
import (
4+
"github.com/KEINOS/go-noise/pkg/custom"
45
"github.com/KEINOS/go-noise/pkg/opensimplex"
56
"github.com/KEINOS/go-noise/pkg/perlin"
67
"github.com/pkg/errors"
@@ -16,6 +17,8 @@ const (
1617
Perlin
1718
// OpenSimplex noise type.
1819
OpenSimplex
20+
// Custom uses the user-defined function to generate noise.
21+
Custom
1922
)
2023

2124
// ----------------------------------------------------------------------------
@@ -46,6 +49,8 @@ type Generator interface {
4649
// Implementations of this method must return the same value if the seed
4750
// value is the same.
4851
Eval64(dim ...float64) float64
52+
SetEval32(f func(seed int64, dim ...float32) float32) error
53+
SetEval64(f func(seed int64, dim ...float64) float64) error
4954
}
5055

5156
// ----------------------------------------------------------------------------
@@ -59,6 +64,8 @@ func New(noiseType Algo, seed int64) (Generator, error) {
5964
return perlin.New(seed), nil
6065
case OpenSimplex:
6166
return opensimplex.New(seed), nil
67+
case Custom:
68+
return custom.New(seed), nil
6269
}
6370

6471
return nil, errors.New("unknown noise type")

pkg/custom/custom.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
Package custom provides a noise generator using the user-defined function.
3+
Which implements github.com/KEINOS/go-noise/noise interface.
4+
5+
You need to define the function before calling `Eval32` or `Eval64`.
6+
*/
7+
package custom
8+
9+
import (
10+
"math/rand"
11+
"time"
12+
13+
"github.com/pkg/errors"
14+
)
15+
16+
// Generator holds parameters of user-defined noise generator.
17+
type Generator struct {
18+
// func32 is the user-defined function for float32.
19+
func32 func(seed int64, dim ...float32) float32
20+
// func64 is the user-defined function for float64.
21+
func64 func(seed int64, dim ...float64) float64
22+
// Seed holds the seed value for the noise.
23+
Seed int64
24+
}
25+
26+
// New returns a seeded Perlin noise instance.
27+
func New(seed int64) *Generator {
28+
// Rand that uses random values from src to generate other random values.
29+
return &Generator{
30+
Seed: seed,
31+
}
32+
}
33+
34+
// Eval32 returns a float32 noise value using the user-defined function for the given coordinates.
35+
func (n *Generator) Eval32(dim ...float32) float32 {
36+
return n.func32(n.Seed, dim...)
37+
}
38+
39+
// Eval64 returns a float64 noise value using the user-defined function for the given coordinates.
40+
func (n *Generator) Eval64(dim ...float64) float64 {
41+
return n.func64(n.Seed, dim...)
42+
}
43+
44+
// SetEval32 sets the user-defined noise generator function for float32.
45+
func (n *Generator) SetEval32(f func(seed int64, dim ...float32) float32) error {
46+
// Check the function.
47+
rand.Seed(time.Now().UnixNano())
48+
49+
//nolint:gosec // Use of weak random number generation is intended here.
50+
dummyInput := rand.Float32()
51+
52+
if v := f(n.Seed, dummyInput, dummyInput); v <= -1 || 1 < v {
53+
return errors.New("the function must return a value between -1 and 1")
54+
}
55+
56+
n.func32 = f
57+
58+
return nil
59+
}
60+
61+
// SetEval64 sets the user-defined noise generator function for float64.
62+
func (n *Generator) SetEval64(f func(seed int64, dim ...float64) float64) error {
63+
// Check the function.
64+
rand.Seed(time.Now().UnixNano())
65+
66+
//nolint:gosec // Use of weak random number generation is intended here.
67+
dummyInput := rand.Float64()
68+
69+
if v := f(n.Seed, dummyInput, dummyInput); v <= -1 || 1 < v {
70+
return errors.New("the function must return a value between -1 and 1")
71+
}
72+
73+
n.func64 = f
74+
75+
return nil
76+
}

pkg/custom/custom_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package custom
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestEval32_no_function_assigned(t *testing.T) {
11+
gen := New(12345)
12+
13+
require.Panics(t, func() {
14+
_ = gen.Eval32(1.0, 2.0, 3.0)
15+
}, "if the function is not assigned, it should panic")
16+
}
17+
18+
func TestEval64_no_function_assigned(t *testing.T) {
19+
gen := New(12345)
20+
21+
require.Panics(t, func() {
22+
_ = gen.Eval64(1.0, 2.0, 3.0)
23+
}, "if the function is not assigned, it should panic")
24+
}
25+
26+
func TestSetEval32_function_returns_out_of_range(t *testing.T) {
27+
gen := New(12345)
28+
29+
err := gen.SetEval32(func(seed int64, dim ...float32) float32 {
30+
return -2.0
31+
})
32+
33+
require.Error(t, err, "if the function returns a value out of range, an error should be returned")
34+
assert.Contains(t, err.Error(), "the function must return a value between -1 and 1")
35+
}
36+
37+
func TestSetEval64_function_returns_out_of_range(t *testing.T) {
38+
gen := New(12345)
39+
40+
err := gen.SetEval64(func(seed int64, dim ...float64) float64 {
41+
return -2.0
42+
})
43+
44+
require.Error(t, err, "if the function returns a value out of range, an error should be returned")
45+
assert.Contains(t, err.Error(), "the function must return a value between -1 and 1")
46+
}

0 commit comments

Comments
 (0)