384 lines
9.9 KiB
Go
384 lines
9.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"html/template"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Vector3 represents a 3D vector
|
|
type Vector3 struct {
|
|
X, Y, Z float32
|
|
}
|
|
|
|
// Quaternion represents a rotation quaternion
|
|
type Quaternion struct {
|
|
X, Y, Z, W float32
|
|
}
|
|
|
|
// Transform represents a 3D transform
|
|
type Transform struct {
|
|
WorldPosition Vector3
|
|
WorldRotation Quaternion
|
|
LocalScale Vector3
|
|
}
|
|
|
|
// BoneData represents bone transformation data
|
|
type BoneData struct {
|
|
BoneIndex int32
|
|
Transform Transform
|
|
}
|
|
|
|
// BlendShapeData represents blend shape data
|
|
type BlendShapeData struct {
|
|
ShapeIndex int32
|
|
Weight float32
|
|
}
|
|
|
|
// AvatarData represents the complete avatar data structure
|
|
type AvatarData struct {
|
|
RootTransform Transform
|
|
Timestamp float64
|
|
BoneCount int32
|
|
Bones []BoneData
|
|
BlendCount int32
|
|
BlendShapes []BlendShapeData
|
|
ServerTime float64
|
|
}
|
|
|
|
// AvatarSyncServer handles the avatar synchronization
|
|
type AvatarSyncServer struct {
|
|
playerData map[string]*AvatarData
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// NewAvatarSyncServer creates a new server instance
|
|
func NewAvatarSyncServer() *AvatarSyncServer {
|
|
return &AvatarSyncServer{
|
|
playerData: map[string]*AvatarData{
|
|
"player1": nil,
|
|
"player2": nil,
|
|
},
|
|
}
|
|
}
|
|
|
|
// SerializeAvatarData converts AvatarData to binary format
|
|
func (a *AvatarData) SerializeAvatarData() ([]byte, error) {
|
|
buf := new(bytes.Buffer)
|
|
|
|
// Write root transform
|
|
if err := binary.Write(buf, binary.LittleEndian, a.RootTransform); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Write timestamp
|
|
if err := binary.Write(buf, binary.LittleEndian, a.Timestamp); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Write server time
|
|
if err := binary.Write(buf, binary.LittleEndian, a.ServerTime); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Write bone count
|
|
if err := binary.Write(buf, binary.LittleEndian, a.BoneCount); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Write bones
|
|
for _, bone := range a.Bones {
|
|
if err := binary.Write(buf, binary.LittleEndian, bone); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Write blend shape count
|
|
if err := binary.Write(buf, binary.LittleEndian, a.BlendCount); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Write blend shapes
|
|
for _, blend := range a.BlendShapes {
|
|
if err := binary.Write(buf, binary.LittleEndian, blend); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// DeserializeAvatarData converts binary data back to AvatarData
|
|
func DeserializeAvatarData(data []byte) (*AvatarData, error) {
|
|
buf := bytes.NewReader(data)
|
|
avatar := &AvatarData{}
|
|
|
|
// Read root transform
|
|
if err := binary.Read(buf, binary.LittleEndian, &avatar.RootTransform); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Read timestamp
|
|
if err := binary.Read(buf, binary.LittleEndian, &avatar.Timestamp); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Read server time
|
|
if err := binary.Read(buf, binary.LittleEndian, &avatar.ServerTime); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Read bone count
|
|
if err := binary.Read(buf, binary.LittleEndian, &avatar.BoneCount); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Read bones
|
|
avatar.Bones = make([]BoneData, avatar.BoneCount)
|
|
for i := int32(0); i < avatar.BoneCount; i++ {
|
|
if err := binary.Read(buf, binary.LittleEndian, &avatar.Bones[i]); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Read blend shape count
|
|
if err := binary.Read(buf, binary.LittleEndian, &avatar.BlendCount); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Read blend shapes
|
|
avatar.BlendShapes = make([]BlendShapeData, avatar.BlendCount)
|
|
for i := int32(0); i < avatar.BlendCount; i++ {
|
|
if err := binary.Read(buf, binary.LittleEndian, &avatar.BlendShapes[i]); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return avatar, nil
|
|
}
|
|
|
|
// setCORSHeaders sets CORS headers for cross-origin requests
|
|
func setCORSHeaders(w http.ResponseWriter) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
|
}
|
|
|
|
// handleRoot serves the status page
|
|
func (s *AvatarSyncServer) handleRoot(w http.ResponseWriter, r *http.Request) {
|
|
setCORSHeaders(w)
|
|
|
|
if r.Method == "OPTIONS" {
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
|
|
s.mutex.RLock()
|
|
player1Connected := s.playerData["player1"] != nil
|
|
player2Connected := s.playerData["player2"] != nil
|
|
s.mutex.RUnlock()
|
|
|
|
statusTemplate := `
|
|
<html>
|
|
<head><title>Avatar Sync Server (Go Binary)</title></head>
|
|
<body>
|
|
<h1>Avatar Sync Server (Go Binary)</h1>
|
|
<p>Server is running and using binary protocol for improved performance</p>
|
|
<h2>Available Endpoints:</h2>
|
|
<ul>
|
|
<li>/player1 - Player 1 data (binary)</li>
|
|
<li>/player2 - Player 2 data (binary)</li>
|
|
<li>/status - Server status</li>
|
|
</ul>
|
|
<h2>Player Status:</h2>
|
|
<ul>
|
|
<li>Player 1: {{if .Player1}}Connected{{else}}No data{{end}}</li>
|
|
<li>Player 2: {{if .Player2}}Connected{{else}}No data{{end}}</li>
|
|
</ul>
|
|
<p><strong>Note:</strong> This server uses binary protocol instead of JSON for faster data transfer.</p>
|
|
</body>
|
|
</html>
|
|
`
|
|
|
|
tmpl := template.Must(template.New("status").Parse(statusTemplate))
|
|
data := struct {
|
|
Player1 bool
|
|
Player2 bool
|
|
}{
|
|
Player1: player1Connected,
|
|
Player2: player2Connected,
|
|
}
|
|
|
|
tmpl.Execute(w, data)
|
|
}
|
|
|
|
// handleStatus provides server status information
|
|
func (s *AvatarSyncServer) handleStatus(w http.ResponseWriter, r *http.Request) {
|
|
setCORSHeaders(w)
|
|
|
|
if r.Method == "OPTIONS" {
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
|
|
s.mutex.RLock()
|
|
defer s.mutex.RUnlock()
|
|
|
|
currentTime := time.Now().Unix()
|
|
status := fmt.Sprintf("Avatar Sync Server (Go Binary)\n")
|
|
status += fmt.Sprintf("Server Time: %d\n", currentTime)
|
|
status += fmt.Sprintf("Protocol: Binary (Little Endian)\n")
|
|
status += fmt.Sprintf("Player 1: %s\n", func() string {
|
|
if s.playerData["player1"] != nil {
|
|
return fmt.Sprintf("Connected (last update: %.2f)", s.playerData["player1"].Timestamp)
|
|
}
|
|
return "No data"
|
|
}())
|
|
status += fmt.Sprintf("Player 2: %s\n", func() string {
|
|
if s.playerData["player2"] != nil {
|
|
return fmt.Sprintf("Connected (last update: %.2f)", s.playerData["player2"].Timestamp)
|
|
}
|
|
return "No data"
|
|
}())
|
|
|
|
w.Write([]byte(status))
|
|
}
|
|
|
|
// handlePlayer handles both GET and POST requests for player data
|
|
func (s *AvatarSyncServer) handlePlayer(w http.ResponseWriter, r *http.Request, playerID string) {
|
|
setCORSHeaders(w)
|
|
|
|
if r.Method == "OPTIONS" {
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case "GET":
|
|
s.mutex.RLock()
|
|
playerData := s.playerData[playerID]
|
|
s.mutex.RUnlock()
|
|
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
|
|
if playerData != nil {
|
|
// Update server timestamp before sending
|
|
playerData.ServerTime = float64(time.Now().UnixNano()) / 1e9
|
|
data, err := playerData.SerializeAvatarData()
|
|
if err != nil {
|
|
http.Error(w, "Failed to serialize data", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Write(data)
|
|
} else {
|
|
// Return empty avatar data structure
|
|
emptyAvatar := &AvatarData{
|
|
RootTransform: Transform{
|
|
WorldPosition: Vector3{X: 0.0, Y: 0.0, Z: 0.0},
|
|
WorldRotation: Quaternion{X: 0.0, Y: 0.0, Z: 0.0, W: 1.0},
|
|
LocalScale: Vector3{X: 1.0, Y: 1.0, Z: 1.0},
|
|
},
|
|
Timestamp: 0.0,
|
|
BoneCount: 0,
|
|
Bones: []BoneData{},
|
|
BlendCount: 0,
|
|
BlendShapes: []BlendShapeData{},
|
|
ServerTime: float64(time.Now().UnixNano()) / 1e9,
|
|
}
|
|
data, err := emptyAvatar.SerializeAvatarData()
|
|
if err != nil {
|
|
http.Error(w, "Failed to serialize empty data", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Write(data)
|
|
}
|
|
|
|
case "POST":
|
|
// Read binary data from request body
|
|
buf := new(bytes.Buffer)
|
|
_, err := buf.ReadFrom(r.Body)
|
|
if err != nil {
|
|
http.Error(w, "Failed to read request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Deserialize avatar data
|
|
avatarData, err := DeserializeAvatarData(buf.Bytes())
|
|
if err != nil {
|
|
http.Error(w, "Failed to deserialize avatar data", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Store the avatar data
|
|
avatarData.ServerTime = float64(time.Now().UnixNano()) / 1e9
|
|
s.mutex.Lock()
|
|
s.playerData[playerID] = avatarData
|
|
s.mutex.Unlock()
|
|
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(fmt.Sprintf("Data updated for %s at %.2f", playerID, avatarData.ServerTime)))
|
|
|
|
log.Printf("Updated data for %s at %s", playerID, time.Now().Format("15:04:05"))
|
|
|
|
default:
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
}
|
|
|
|
// getLocalIP returns the local IP address
|
|
func getLocalIP() string {
|
|
conn, err := net.Dial("udp", "8.8.8.8:80")
|
|
if err != nil {
|
|
return "127.0.0.1"
|
|
}
|
|
defer conn.Close()
|
|
|
|
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
|
return localAddr.IP.String()
|
|
}
|
|
|
|
func main() {
|
|
server := NewAvatarSyncServer()
|
|
|
|
// Set up routes
|
|
http.HandleFunc("/", server.handleRoot)
|
|
http.HandleFunc("/status", server.handleStatus)
|
|
http.HandleFunc("/player1", func(w http.ResponseWriter, r *http.Request) {
|
|
server.handlePlayer(w, r, "player1")
|
|
})
|
|
http.HandleFunc("/player2", func(w http.ResponseWriter, r *http.Request) {
|
|
server.handlePlayer(w, r, "player2")
|
|
})
|
|
|
|
port := ":8080"
|
|
localIP := getLocalIP()
|
|
|
|
fmt.Println("Avatar Sync Server (Go Binary) starting...")
|
|
fmt.Printf("Server running on:\n")
|
|
fmt.Printf(" Local: http://127.0.0.1%s\n", port)
|
|
fmt.Printf(" Network: http://%s%s\n", localIP, port)
|
|
fmt.Printf(" External: http://0.0.0.0%s\n", port)
|
|
fmt.Println()
|
|
fmt.Println("Available endpoints:")
|
|
fmt.Println(" GET /player1 - Get Player 1 avatar data (binary)")
|
|
fmt.Println(" GET /player2 - Get Player 2 avatar data (binary)")
|
|
fmt.Println(" POST /player1 - Update Player 1 avatar data (binary)")
|
|
fmt.Println(" POST /player2 - Update Player 2 avatar data (binary)")
|
|
fmt.Println(" GET /status - Server status")
|
|
fmt.Println()
|
|
fmt.Println("Protocol: Binary (Little Endian) for improved performance")
|
|
fmt.Println("Press Ctrl+C to stop the server...")
|
|
|
|
log.Fatal(http.ListenAndServe(port, nil))
|
|
} |