feat: Enhance turtle model and visualization with block surroundings and world block management

This commit is contained in:
MayaTheShy
2026-02-16 01:35:52 -05:00
parent 836d734b1f
commit 5ec385c1f2
4 changed files with 312 additions and 28 deletions

View File

@@ -1,49 +1,95 @@
import React, { useRef, useMemo } from 'react';
import React, { useRef, useMemo, useEffect } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import { OrbitControls, Grid, Text, Line } from '@react-three/drei';
import * as THREE from 'three';
import { useTurtleStore } from '../store/turtleStore';
// Turtle marker component
function TurtleMarker({ turtle, isSelected, onClick }) {
const meshRef = useRef();
const { position, mode } = turtle;
// Minecraft-style turtle model
function TurtleModel({ turtle, isSelected, onClick }) {
const groupRef = useRef();
const { position, facing, mode } = turtle;
useFrame((state) => {
if (meshRef.current && isSelected) {
meshRef.current.rotation.y += 0.02;
if (groupRef.current && isSelected) {
groupRef.current.children[0].position.y = Math.sin(state.clock.elapsedTime * 2) * 0.05;
}
});
if (!position) return null;
// Calculate rotation based on facing (0=North(-Z), 1=East(+X), 2=South(+Z), 3=West(-X))
const rotation = facing !== undefined ? [0, (facing * Math.PI / 2), 0] : [0, 0, 0];
const color = mode === 'mining' ? '#4ade80' :
mode === 'exploring' ? '#60a5fa' :
mode === 'returning' ? '#f59e0b' :
'#9ca3af';
return (
<group position={[position.x, position.y, position.z]} onClick={onClick}>
{/* Turtle body */}
<mesh ref={meshRef}>
<boxGeometry args={[0.8, 0.6, 0.8]} />
<meshStandardMaterial
color={color}
emissive={color}
emissiveIntensity={isSelected ? 0.5 : 0.2}
/>
</mesh>
<group ref={groupRef} position={[position.x, position.y, position.z]} rotation={rotation} onClick={onClick}>
<group>
{/* Shell (main body) */}
<mesh position={[0, 0.2, 0]}>
<boxGeometry args={[0.8, 0.5, 0.9]} />
<meshStandardMaterial
color={color}
emissive={color}
emissiveIntensity={isSelected ? 0.4 : 0.2}
roughness={0.4}
/>
</mesh>
{/* Head */}
<mesh position={[0, 0.15, 0.55]}>
<boxGeometry args={[0.5, 0.35, 0.3]} />
<meshStandardMaterial color={color} roughness={0.3} />
</mesh>
{/* Eyes */}
<mesh position={[-0.12, 0.2, 0.7]}>
<boxGeometry args={[0.1, 0.1, 0.05]} />
<meshStandardMaterial color="#ffffff" emissive="#ffff00" emissiveIntensity={0.5} />
</mesh>
<mesh position={[0.12, 0.2, 0.7]}>
<boxGeometry args={[0.1, 0.1, 0.05]} />
<meshStandardMaterial color="#ffffff" emissive="#ffff00" emissiveIntensity={0.5} />
</mesh>
{/* Legs */}
<mesh position={[-0.3, -0.15, 0.25]}>
<boxGeometry args={[0.2, 0.2, 0.2]} />
<meshStandardMaterial color="#666" />
</mesh>
<mesh position={[0.3, -0.15, 0.25]}>
<boxGeometry args={[0.2, 0.2, 0.2]} />
<meshStandardMaterial color="#666" />
</mesh>
<mesh position={[-0.3, -0.15, -0.25]}>
<boxGeometry args={[0.2, 0.2, 0.2]} />
<meshStandardMaterial color="#666" />
</mesh>
<mesh position={[0.3, -0.15, -0.25]}>
<boxGeometry args={[0.2, 0.2, 0.2]} />
<meshStandardMaterial color="#666" />
</mesh>
{/* Tail */}
<mesh position={[0, 0.1, -0.55]}>
<boxGeometry args={[0.15, 0.15, 0.2]} />
<meshStandardMaterial color={color} />
</mesh>
</group>
{/* Selection indicator */}
{isSelected && (
<>
<mesh position={[0, 1, 0]}>
<mesh position={[0, 1.2, 0]}>
<coneGeometry args={[0.3, 0.5, 4]} />
<meshStandardMaterial color="#ffffff" emissive="#ffffff" emissiveIntensity={0.8} />
</mesh>
{/* Selection ring */}
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.4, 0]}>
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.5, 0]}>
<ringGeometry args={[1, 1.2, 32]} />
<meshBasicMaterial color="#ffffff" side={THREE.DoubleSide} transparent opacity={0.5} />
</mesh>
@@ -58,7 +104,7 @@ function TurtleMarker({ turtle, isSelected, onClick }) {
anchorX="center"
anchorY="middle"
>
T-{turtle.turtleID}
🐢 {turtle.turtleID}
</Text>
</group>
);
@@ -75,6 +121,99 @@ function PathTrail({ turtle }) {
new THREE.Vector3(homePosition.x, homePosition.y, homePosition.z)
];
return (
<Line
points={points}
color="#60a5fa"
lineWidth={2}
transparent
opacity={0.3}
dashed
dashScale={2}
/>
);
}
// Get color for block type
function getBlockColor(blockName) {
if (!blockName) return '#888';
const name = blockName.toLowerCase();
// Ores
if (name.includes('diamond')) return '#00ffff';
if (name.includes('emerald')) return '#00ff00';
if (name.includes('gold')) return '#ffd700';
if (name.includes('iron')) return '#d4d4d4';
if (name.includes('coal')) return '#1a1a1a';
if (name.includes('redstone')) return '#ff0000';
if (name.includes('lapis')) return '#0000ff';
if (name.includes('copper')) return '#ff8c00';
// Common blocks
if (name.includes('stone')) return '#7f7f7f';
if (name.includes('dirt')) return '#8b4513';
if (name.includes('grass')) return '#228b22';
if (name.includes('sand')) return '#f4a460';
if (name.includes('gravel')) return '#808080';
if (name.includes('bedrock')) return '#222';
if (name.includes('obsidian')) return '#1a0033';
if (name.includes('netherrack')) return '#8b0000';
return '#666666'; // Default
}
// WorldBlocks component to render discovered blocks
function WorldBlocks({ blocks }) {
const meshes = useMemo(() => {
const instances = new Map();
blocks.forEach(block => {
const color = getBlockColor(block.name);
if (!instances.has(color)) {
instances.set(color, []);
}
instances.get(color).push(block);
});
return instances;
}, [blocks]);
return (
<group>
{Array.from(meshes.entries()).map(([color, blockList]) => (
<group key={color}>
{blockList.map((block, idx) => (
<mesh
key={`${block.x},${block.y},${block.z}`}
position={[block.x, block.y, block.z]}
>
<boxGeometry args={[0.9, 0.9, 0.9]} />
<meshStandardMaterial
color={color}
transparent
opacity={0.6}
roughness={0.8}
/>
</mesh>
))}
</group>
))}
</group>
);
}
// Path trail component (old version removed above)
function OldPathTrail({ turtle }) {
const { position, homePosition } = turtle;
if (!position || !homePosition) return null;
const points = [
new THREE.Vector3(position.x, position.y, position.z),
new THREE.Vector3(homePosition.x, homePosition.y, homePosition.z)
];
return (
<Line
points={points}
@@ -116,6 +255,7 @@ function Scene() {
const turtles = useTurtleStore((state) => state.getTurtleArray());
const selectedTurtleId = useTurtleStore((state) => state.selectedTurtleId);
const selectTurtle = useTurtleStore((state) => state.selectTurtle);
const worldBlocks = useTurtleStore((state) => state.worldBlocks || []);
// Calculate center point for camera focus
const centerPoint = useMemo(() => {
@@ -142,9 +282,10 @@ function Scene() {
return (
<>
<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]} intensity={1} />
<pointLight position={[-10, -10, -10]} intensity={0.5} />
<ambientLight intensity={0.6} />
<pointLight position={[10, 10, 10]} intensity={1.2} />
<pointLight position={[-10, -10, -10]} intensity={0.6} />
<pointLight position={[0, 20, 0]} intensity={0.8} color="#ffffff" />
{/* Grid */}
<Grid
@@ -161,6 +302,9 @@ function Scene() {
infiniteGrid
/>
{/* Render discovered blocks */}
<WorldBlocks blocks={worldBlocks} />
{/* Home marker */}
{homePosition && <HomeMarker position={homePosition} />}
@@ -168,7 +312,7 @@ function Scene() {
{turtles.map((turtle) => (
<React.Fragment key={turtle.turtleID}>
<PathTrail turtle={turtle} />
<TurtleMarker
<TurtleModel
turtle={turtle}
isSelected={selectedTurtleId === turtle.turtleID}
onClick={() => selectTurtle(turtle.turtleID)}