File size: 17,570 Bytes
d456972
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
//go:build linux || darwin || freebsd

// Package motionbricks provides a PureGo wrapper around the stable opaque C ABI.
package motionbricks

import (
	"errors"
	"fmt"
	"math"
	"unsafe"

	"github.com/ebitengine/purego"
)

type Device uint32

const (
	DeviceAuto   Device = 0
	DeviceCPU    Device = 1
	DeviceVulkan Device = 2
)

const errorBufferSize = 1024

type Library struct {
	handle                uintptr
	abiVersion            func() uint32
	statusString          func(uint32) uintptr
	optionsCreate         func(unsafe.Pointer, unsafe.Pointer, uint64) uint32
	optionsFree           func(uintptr)
	optionsSetDevice      func(uintptr, uint32, unsafe.Pointer, uint64) uint32
	modelLoad             func(unsafe.Pointer, uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	modelFree             func(uintptr)
	modelJointCount       func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	modelJointName        func(uintptr, uint32, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	modelJointParent      func(uintptr, uint32, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	modelNeutralPosition  func(uintptr, uint32, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	styleLoad             func(uintptr, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	styleFree             func(uintptr)
	styleName             func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	styleSpeed            func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	agentCreate           func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	agentFree             func(uintptr)
	agentReset            func(uintptr, uintptr, unsafe.Pointer, uint64) uint32
	agentSetContext       func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64, uint64, unsafe.Pointer, uint64) uint32
	agentPlan             func(uintptr, uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	agentAdvance          func(uintptr, uint32, unsafe.Pointer, uint64) uint32
	commandCreate         func(unsafe.Pointer, unsafe.Pointer, uint64) uint32
	commandFree           func(uintptr)
	commandSetStyle       func(uintptr, uintptr, unsafe.Pointer, uint64) uint32
	commandSetMovement    func(uintptr, float32, float32, float32, unsafe.Pointer, uint64) uint32
	commandSetFacing      func(uintptr, float32, float32, float32, unsafe.Pointer, uint64) uint32
	commandSetSpeed       func(uintptr, float32, unsafe.Pointer, uint64) uint32
	commandSetWorldTarget func(uintptr, float32, float32, float32, float32, uint32, unsafe.Pointer, uint64) uint32
	commandSetSeed        func(uintptr, uint64, unsafe.Pointer, uint64) uint32
	motionFree            func(uintptr)
	motionFrames          func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	motionJoints          func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	motionRoots           func(uintptr, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	motionRotations       func(uintptr, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	motionTargetFrames    func(uintptr, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	motionTargetRoots     func(uintptr, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, uint64) uint32
	motionTargetRotations func(uintptr, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, uint64) uint32
}

func Open(path string) (*Library, error) {
	handle, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_LOCAL)
	if err != nil {
		return nil, err
	}
	library := &Library{handle: handle}
	register := func(target any, name string) { purego.RegisterLibFunc(target, handle, name) }
	register(&library.abiVersion, "mb_abi_version")
	register(&library.statusString, "mb_status_string")
	register(&library.optionsCreate, "mb_runtime_options_create")
	register(&library.optionsFree, "mb_runtime_options_free")
	register(&library.optionsSetDevice, "mb_runtime_options_set_device")
	register(&library.modelLoad, "mb_model_load")
	register(&library.modelFree, "mb_model_free")
	register(&library.modelJointCount, "mb_model_get_joint_count")
	register(&library.modelJointName, "mb_model_get_joint_name")
	register(&library.modelJointParent, "mb_model_get_joint_parent")
	register(&library.modelNeutralPosition, "mb_model_get_neutral_joint_position")
	register(&library.styleLoad, "mb_style_load")
	register(&library.styleFree, "mb_style_free")
	register(&library.styleName, "mb_style_get_name")
	register(&library.styleSpeed, "mb_style_get_speed")
	register(&library.agentCreate, "mb_agent_create")
	register(&library.agentFree, "mb_agent_free")
	register(&library.agentReset, "mb_agent_reset")
	register(&library.agentSetContext, "mb_agent_set_context")
	register(&library.agentPlan, "mb_agent_plan")
	register(&library.agentAdvance, "mb_agent_advance")
	register(&library.commandCreate, "mb_command_create")
	register(&library.commandFree, "mb_command_free")
	register(&library.commandSetStyle, "mb_command_set_style")
	register(&library.commandSetMovement, "mb_command_set_movement_direction")
	register(&library.commandSetFacing, "mb_command_set_facing_direction")
	register(&library.commandSetSpeed, "mb_command_set_target_speed")
	register(&library.commandSetWorldTarget, "mb_command_set_world_target")
	register(&library.commandSetSeed, "mb_command_set_seed")
	register(&library.motionFree, "mb_motion_free")
	register(&library.motionFrames, "mb_motion_get_frame_count")
	register(&library.motionJoints, "mb_motion_get_joint_count")
	register(&library.motionRoots, "mb_motion_get_root_translations")
	register(&library.motionRotations, "mb_motion_get_local_rotations_xyzw")
	register(&library.motionTargetFrames, "mb_motion_get_target_frame_count")
	register(&library.motionTargetRoots, "mb_motion_get_target_root_translations")
	register(&library.motionTargetRotations, "mb_motion_get_target_local_rotations_xyzw")
	if version := library.abiVersion(); version != 1 {
		library.Close()
		return nil, fmt.Errorf("motionbricks ABI version %d is unsupported", version)
	}
	return library, nil
}

func (l *Library) Close() error {
	if l == nil || l.handle == 0 {
		return nil
	}
	err := purego.Dlclose(l.handle)
	l.handle = 0
	return err
}

func cString(value string) ([]byte, error) {
	for _, char := range value {
		if char == 0 {
			return nil, errors.New("string contains NUL")
		}
	}
	return append([]byte(value), 0), nil
}

func goString(pointer uintptr) string {
	if pointer == 0 {
		return ""
	}
	const maximum = 1 << 20
	bytes := make([]byte, 0, 64)
	for index := uintptr(0); index < maximum; index++ {
		value := *(*byte)(unsafe.Pointer(pointer + index))
		if value == 0 {
			return string(bytes)
		}
		bytes = append(bytes, value)
	}
	return ""
}

func errorPointer(buffer []byte) unsafe.Pointer { return unsafe.Pointer(&buffer[0]) }

func (l *Library) check(operation string, status uint32, buffer []byte) error {
	if status == 0 {
		return nil
	}
	message := ""
	for index, value := range buffer {
		if value == 0 {
			message = string(buffer[:index])
			break
		}
	}
	if message == "" {
		message = goString(l.statusString(status))
	}
	return fmt.Errorf("%s: %s", operation, message)
}

type Model struct {
	library *Library
	handle  uintptr
}
type Style struct {
	library *Library
	handle  uintptr
	Name    string
	Speed   float32
}
type Agent struct {
	model  *Model
	handle uintptr
}
type Command struct {
	library *Library
	handle  uintptr
}

type Joint struct {
	Name     string     `json:"name"`
	Parent   int32      `json:"parent"`
	Position [3]float32 `json:"position"`
}
type Motion struct {
	Frames    uint64     `json:"frames"`
	Joints    uint64     `json:"joints"`
	Roots     []float32  `json:"roots"`
	Rotations []float32  `json:"rotations"`
	Targets   *Keyframes `json:"-"`
}
type Keyframes struct {
	Frames    uint64    `json:"frames"`
	Joints    uint64    `json:"joints"`
	Roots     []float32 `json:"roots"`
	Rotations []float32 `json:"rotations"`
}

func (l *Library) LoadModel(path string, device Device) (*Model, error) {
	pathBytes, err := cString(path)
	if err != nil {
		return nil, err
	}
	buffer := make([]byte, errorBufferSize)
	var options uintptr
	if err = l.check("create runtime options", l.optionsCreate(unsafe.Pointer(&options), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		return nil, err
	}
	defer l.optionsFree(options)
	if err = l.check("select device", l.optionsSetDevice(options, uint32(device), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		return nil, err
	}
	var handle uintptr
	status := l.modelLoad(unsafe.Pointer(&pathBytes[0]), options, unsafe.Pointer(&handle), errorPointer(buffer), uint64(len(buffer)))
	if err = l.check("load model", status, buffer); err != nil {
		return nil, err
	}
	return &Model{library: l, handle: handle}, nil
}

func (m *Model) Close() {
	if m != nil && m.handle != 0 {
		m.library.modelFree(m.handle)
		m.handle = 0
	}
}

func (m *Model) Skeleton() ([]Joint, error) {
	buffer := make([]byte, errorBufferSize)
	var count uint32
	if err := m.library.check("get joint count", m.library.modelJointCount(m.handle, unsafe.Pointer(&count), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		return nil, err
	}
	joints := make([]Joint, count)
	for index := uint32(0); index < count; index++ {
		var name uintptr
		if err := m.library.check("get joint name", m.library.modelJointName(m.handle, index, unsafe.Pointer(&name), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
			return nil, err
		}
		joints[index].Name = goString(name)
		if err := m.library.check("get joint parent", m.library.modelJointParent(m.handle, index, unsafe.Pointer(&joints[index].Parent), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
			return nil, err
		}
		position := &joints[index].Position
		if err := m.library.check("get neutral position", m.library.modelNeutralPosition(m.handle, index, unsafe.Pointer(&position[0]), unsafe.Pointer(&position[1]), unsafe.Pointer(&position[2]), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
			return nil, err
		}
	}
	return joints, nil
}

func (m *Model) LoadStyle(path string) (*Style, error) {
	pathBytes, err := cString(path)
	if err != nil {
		return nil, err
	}
	buffer := make([]byte, errorBufferSize)
	var handle uintptr
	if err = m.library.check("load style", m.library.styleLoad(m.handle, unsafe.Pointer(&pathBytes[0]), unsafe.Pointer(&handle), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		return nil, err
	}
	style := &Style{library: m.library, handle: handle}
	var name uintptr
	if err = m.library.check("get style name", m.library.styleName(handle, unsafe.Pointer(&name), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		style.Close()
		return nil, err
	}
	style.Name = goString(name)
	if err = m.library.check("get style speed", m.library.styleSpeed(handle, unsafe.Pointer(&style.Speed), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		style.Close()
		return nil, err
	}
	return style, nil
}

func (s *Style) Close() {
	if s != nil && s.handle != 0 {
		s.library.styleFree(s.handle)
		s.handle = 0
	}
}

func (m *Model) NewAgent() (*Agent, error) {
	buffer := make([]byte, errorBufferSize)
	var handle uintptr
	if err := m.library.check("create agent", m.library.agentCreate(m.handle, unsafe.Pointer(&handle), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		return nil, err
	}
	return &Agent{model: m, handle: handle}, nil
}
func (a *Agent) Close() {
	if a != nil && a.handle != 0 {
		a.model.library.agentFree(a.handle)
		a.handle = 0
	}
}
func (a *Agent) Reset(style *Style) error {
	buffer := make([]byte, errorBufferSize)
	return a.model.library.check("reset agent", a.model.library.agentReset(a.handle, style.handle, errorPointer(buffer), uint64(len(buffer))), buffer)
}
func (a *Agent) Advance(frames uint32) error {
	buffer := make([]byte, errorBufferSize)
	return a.model.library.check("advance agent", a.model.library.agentAdvance(a.handle, frames, errorPointer(buffer), uint64(len(buffer))), buffer)
}
func (a *Agent) SetContext(roots, rotations []float32, frames uint64) error {
	if frames < 4 || len(roots) != int(frames*3) || len(rotations) != int(frames*34*4) {
		return errors.New("invalid G1 context shape")
	}
	buffer := make([]byte, errorBufferSize)
	return a.model.library.check("set agent context", a.model.library.agentSetContext(a.handle, unsafe.Pointer(&roots[0]), unsafe.Pointer(&rotations[0]), frames, 34, errorPointer(buffer), uint64(len(buffer))), buffer)
}

func (l *Library) NewCommand() (*Command, error) {
	buffer := make([]byte, errorBufferSize)
	var handle uintptr
	if err := l.check("create command", l.commandCreate(unsafe.Pointer(&handle), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		return nil, err
	}
	return &Command{library: l, handle: handle}, nil
}
func (c *Command) Close() {
	if c != nil && c.handle != 0 {
		c.library.commandFree(c.handle)
		c.handle = 0
	}
}
func (c *Command) SetStyle(style *Style) error {
	buffer := make([]byte, errorBufferSize)
	return c.library.check("set command style", c.library.commandSetStyle(c.handle, style.handle, errorPointer(buffer), uint64(len(buffer))), buffer)
}
func (c *Command) SetMovement(x, y, z float32) error {
	buffer := make([]byte, errorBufferSize)
	return c.library.check("set movement", c.library.commandSetMovement(c.handle, x, y, z, errorPointer(buffer), uint64(len(buffer))), buffer)
}
func (c *Command) SetFacing(x, y, z float32) error {
	buffer := make([]byte, errorBufferSize)
	return c.library.check("set facing", c.library.commandSetFacing(c.handle, x, y, z, errorPointer(buffer), uint64(len(buffer))), buffer)
}
func (c *Command) SetSpeed(speed float32) error {
	if math.IsNaN(float64(speed)) {
		return errors.New("speed is NaN")
	}
	buffer := make([]byte, errorBufferSize)
	return c.library.check("set speed", c.library.commandSetSpeed(c.handle, speed, errorPointer(buffer), uint64(len(buffer))), buffer)
}
func (c *Command) SetSeed(seed uint64) error {
	buffer := make([]byte, errorBufferSize)
	return c.library.check("set seed", c.library.commandSetSeed(c.handle, seed, errorPointer(buffer), uint64(len(buffer))), buffer)
}
func (c *Command) SetWorldTarget(x, y, z, heading float32, enabled bool) error {
	var flag uint32
	if enabled {
		flag = 1
	}
	buffer := make([]byte, errorBufferSize)
	return c.library.check("set world target", c.library.commandSetWorldTarget(c.handle, x, y, z, heading, flag, errorPointer(buffer), uint64(len(buffer))), buffer)
}

func (a *Agent) Plan(command *Command) (*Motion, error) {
	buffer := make([]byte, errorBufferSize)
	var handle uintptr
	if err := a.model.library.check("plan motion", a.model.library.agentPlan(a.handle, command.handle, unsafe.Pointer(&handle), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		return nil, err
	}
	defer a.model.library.motionFree(handle)
	motion := &Motion{}
	var rootsPointer, rotationsPointer, targetRootsPointer, targetRotationsPointer uintptr
	var rootsCount, rotationsCount, targetRootsCount, targetRotationsCount uint64
	if err := a.model.library.check("get frame count", a.model.library.motionFrames(handle, unsafe.Pointer(&motion.Frames), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		return nil, err
	}
	if err := a.model.library.check("get joint count", a.model.library.motionJoints(handle, unsafe.Pointer(&motion.Joints), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		return nil, err
	}
	if err := a.model.library.check("get roots", a.model.library.motionRoots(handle, unsafe.Pointer(&rootsPointer), unsafe.Pointer(&rootsCount), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		return nil, err
	}
	if err := a.model.library.check("get rotations", a.model.library.motionRotations(handle, unsafe.Pointer(&rotationsPointer), unsafe.Pointer(&rotationsCount), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		return nil, err
	}
	targets := &Keyframes{Joints: motion.Joints}
	if err := a.model.library.check("get target frame count", a.model.library.motionTargetFrames(handle, unsafe.Pointer(&targets.Frames), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		return nil, err
	}
	if err := a.model.library.check("get target roots", a.model.library.motionTargetRoots(handle, unsafe.Pointer(&targetRootsPointer), unsafe.Pointer(&targetRootsCount), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		return nil, err
	}
	if err := a.model.library.check("get target rotations", a.model.library.motionTargetRotations(handle, unsafe.Pointer(&targetRotationsPointer), unsafe.Pointer(&targetRotationsCount), errorPointer(buffer), uint64(len(buffer))), buffer); err != nil {
		return nil, err
	}
	if rootsCount != motion.Frames*3 || rotationsCount != motion.Frames*motion.Joints*4 ||
		targetRootsCount != targets.Frames*3 || targetRotationsCount != targets.Frames*targets.Joints*4 {
		return nil, errors.New("native motion data has inconsistent dimensions")
	}
	motion.Roots = append([]float32(nil), unsafe.Slice((*float32)(unsafe.Pointer(rootsPointer)), rootsCount)...)
	motion.Rotations = append([]float32(nil), unsafe.Slice((*float32)(unsafe.Pointer(rotationsPointer)), rotationsCount)...)
	targets.Roots = append([]float32(nil), unsafe.Slice((*float32)(unsafe.Pointer(targetRootsPointer)), targetRootsCount)...)
	targets.Rotations = append([]float32(nil), unsafe.Slice((*float32)(unsafe.Pointer(targetRotationsPointer)), targetRotationsCount)...)
	motion.Targets = targets
	return motion, nil
}