Files
City10/app/Http/Controllers/Web/CityViewerController.php
T
marcocosta10000 79d12fa2f4 feat: add City10 platform with Laravel 13, AI agents, 3D viewer, and LGPD compliance
Initial commit of the complete City10 virtual city simulation platform.

Backend (Laravel 13):
- REST API with Sanctum tokens and session-based web authentication
- Role-based access control (admin/user) with middleware protection
- AI agent CRUD with OpenRouter.ai integration restricted to free models
- Store management with 3D coordinates and AI shopkeeper assignment
- Versioned legal documents (Terms, Privacy, Cookies) with LGPD audit trail
- Math CAPTCHA on public forms and multi-language support (pt_BR, en, es)

Frontend:
- Responsive Blade templates with Tailwind CSS v4
- Three.js 3D city viewer with SSE chat streaming to AI agents
- Admin panel for store and user administration

Infrastructure:
- MariaDB/MySQL database with comprehensive migrations
- Database seeders for demo content and test accounts
- PHPUnit test suite and Laravel Pint code styling
- Vite build configuration with hot reload
2026-10-17 20:46:15 -03:00

124 lines
4.0 KiB
PHP

<?php
namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller;
use App\Models\AiAgent;
use App\Models\Store;
use Illuminate\Http\JsonResponse;
use Illuminate\View\View;
/**
* 3D city viewer.
*
* Serves the Three.js scene page plus a lightweight JSON payload of stores
* and agents. The JSON endpoint reuses the web session so the browser can
* fetch world data without a separate API token.
*/
class CityViewerController extends Controller
{
/**
* Render the city viewer page.
*/
public function __invoke(): View
{
return view('city.viewer');
}
/**
* Return the world snapshot consumed by the viewer scene.
*/
public function data(): JsonResponse
{
$stores = Store::query()
->get(['id', 'name', 'type', 'pos_x', 'pos_y', 'pos_z', 'width', 'height', 'depth', 'is_open'])
->map(fn (Store $store): array => [
'id' => $store->id,
'name' => $store->name,
'type' => $store->type,
'position' => [(float) $store->pos_x, (float) $store->pos_y, (float) $store->pos_z],
'size' => [(float) $store->width, (float) $store->height, (float) $store->depth],
'is_open' => $store->is_open,
]);
$agents = AiAgent::query()
->where('status', '!=', 'paused')
->get(['id', 'name', 'type', 'status', 'pos_x', 'pos_y', 'pos_z', 'rotation_y', 'last_decision'])
->map(fn (AiAgent $agent): array => [
'id' => $agent->id,
'name' => $agent->name,
'type' => $agent->type,
'status' => $agent->status,
'position' => [(float) $agent->pos_x, (float) $agent->pos_y, (float) $agent->pos_z],
'rotation_y' => (float) $agent->rotation_y,
'last_decision' => $agent->last_decision,
]);
return response()->json([
'generated_at' => now()->toIso8601String(),
'stores' => $stores,
'agents' => $agents,
'layout' => self::cityLayout($stores->all()),
])->header('Cache-Control', 'no-store');
}
/**
* Derive a deterministic street layout from store positions.
*
* Returns road segments and block rectangles so the frontend can build
* a consistent grid-based city without additional database tables.
*
* @param array<int, array<string, mixed>> $stores
* @return array<string, mixed>
*/
private static function cityLayout(array $stores): array
{
if ($stores === []) {
return ['roads' => [], 'blocks' => []];
}
$xs = array_map(fn ($s) => $s['position'][0], $stores);
$zs = array_map(fn ($s) => $s['position'][2], $stores);
$minX = floor(min($xs) / 40) * 40 - 40;
$maxX = ceil(max($xs) / 40) * 40 + 40;
$minZ = floor(min($zs) / 40) * 40 - 40;
$maxZ = ceil(max($zs) / 40) * 40 + 40;
$roads = [];
// Vertical roads every 40 units.
for ($x = $minX; $x <= $maxX; $x += 40) {
$roads[] = [
'from' => [(float) $x, 0.0, (float) $minZ],
'to' => [(float) $x, 0.0, (float) $maxZ],
'width' => 8.0,
'axis' => 'z',
];
}
// Horizontal roads every 40 units.
for ($z = $minZ; $z <= $maxZ; $z += 40) {
$roads[] = [
'from' => [(float) $minX, 0.0, (float) $z],
'to' => [(float) $maxX, 0.0, (float) $z],
'width' => 8.0,
'axis' => 'x',
];
}
// Blocks = cells between roads.
$blocks = [];
for ($x = $minX; $x < $maxX; $x += 40) {
for ($z = $minZ; $z < $maxZ; $z += 40) {
$blocks[] = [
'min' => [(float) $x + 5.0, 0.0, (float) $z + 5.0],
'max' => [(float) $x + 35.0, 0.0, (float) $z + 35.0],
];
}
}
return ['roads' => $roads, 'blocks' => $blocks];
}
}