3DCityEvolutionSimulator / backup03042025.app.py
awacke1's picture
Rename app.py to backup03042025.app.py
607db4f verified
import streamlit as st
import streamlit.components.v1 as components
st.set_page_config(page_title="L-Grammar 3D Assembly Game", layout="wide")
st.title("L-Grammar 3D Assembly Game")
st.write("An interactive 3D game using L-Grammar to assemble primitive components")
# Create a custom HTML component to embed Three.js
html_code = """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>L-Grammar 3D Assemblies</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; }
.ui-panel {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.6);
padding: 10px;
border-radius: 5px;
color: white;
font-family: Arial, sans-serif;
}
.ui-panel button {
margin: 5px;
padding: 5px 10px;
background: #555;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
}
.ui-panel button:hover {
background: #777;
}
</style>
</head>
<body>
<div class="ui-panel">
<h3>L-Grammar Controls</h3>
<div id="rules"></div>
<button id="generate">Generate New Assembly</button>
<button id="reset">Reset View</button>
<div id="stats">
<p>Parts: <span id="parts-count">0</span></p>
<p>Complexity: <span id="complexity">0</span></p>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script>
<script>
// L-Grammar System for 3D Assemblies
class LGrammarSystem {
constructor() {
this.axiom = "F";
this.rules = {
"F": ["F[+F]F", "F[-F]F", "F[+F][-F]F", "FF"],
"+": ["+", "++"],
"-": ["-", "--"],
"[": ["["],
"]": ["]"]
};
this.angle = Math.PI / 6;
this.iterations = 3;
this.currentString = this.axiom;
}
generate() {
let result = this.axiom;
for (let i = 0; i < this.iterations; i++) {
let newString = "";
for (let j = 0; j < result.length; j++) {
const char = result[j];
if (this.rules[char]) {
const possibleRules = this.rules[char];
const selectedRule = possibleRules[Math.floor(Math.random() * possibleRules.length)];
newString += selectedRule;
} else {
newString += char;
}
}
result = newString;
}
this.currentString = result;
return result;
}
interpret(scene) {
const stack = [];
let position = new THREE.Vector3(0, 0, 0);
let direction = new THREE.Vector3(0, 1, 0);
let right = new THREE.Vector3(1, 0, 0);
let up = new THREE.Vector3(0, 0, 1);
// Clear previous objects
while(scene.children.length > 0) {
const object = scene.children[0];
if (object.type === "DirectionalLight" ||
object.type === "AmbientLight" ||
object.type === "PointLight") {
scene.children.shift();
} else {
scene.remove(object);
}
}
let partCount = 0;
for (let i = 0; i < this.currentString.length; i++) {
const char = this.currentString[i];
switch(char) {
case 'F':
// Create a part (cylinder or box)
const partType = Math.random() > 0.5 ? 'cylinder' : 'box';
const length = 2 + Math.random() * 3;
const width = 0.3 + Math.random() * 0.5;
let geometry, material, part;
if (partType === 'cylinder') {
geometry = new THREE.CylinderGeometry(width, width, length, 8);
material = new THREE.MeshPhongMaterial({
color: new THREE.Color(Math.random(), Math.random(), Math.random()),
shininess: 30
});
part = new THREE.Mesh(geometry, material);
} else {
geometry = new THREE.BoxGeometry(width, length, width);
material = new THREE.MeshPhongMaterial({
color: new THREE.Color(Math.random(), Math.random(), Math.random()),
shininess: 30
});
part = new THREE.Mesh(geometry, material);
}
// Position and orient the part
const midPoint = position.clone().add(direction.clone().multiplyScalar(length/2));
part.position.copy(midPoint);
// Calculate the rotation to align with direction
const defaultDir = new THREE.Vector3(0, 1, 0);
part.quaternion.setFromUnitVectors(defaultDir, direction.clone().normalize());
scene.add(part);
partCount++;
// Move forward
position.add(direction.clone().multiplyScalar(length));
break;
case '+':
// Rotate right around the up vector
const rotationMatrixPlus = new THREE.Matrix4().makeRotationAxis(up, this.angle);
direction.applyMatrix4(rotationMatrixPlus);
right.applyMatrix4(rotationMatrixPlus);
break;
case '-':
// Rotate left around the up vector
const rotationMatrixMinus = new THREE.Matrix4().makeRotationAxis(up, -this.angle);
direction.applyMatrix4(rotationMatrixMinus);
right.applyMatrix4(rotationMatrixMinus);
break;
case '&':
// Pitch down around the right vector
const rotationMatrixPitchDown = new THREE.Matrix4().makeRotationAxis(right, this.angle);
direction.applyMatrix4(rotationMatrixPitchDown);
up.applyMatrix4(rotationMatrixPitchDown);
break;
case '^':
// Pitch up around the right vector
const rotationMatrixPitchUp = new THREE.Matrix4().makeRotationAxis(right, -this.angle);
direction.applyMatrix4(rotationMatrixPitchUp);
up.applyMatrix4(rotationMatrixPitchUp);
break;
case '\\':
// Roll clockwise around forward vector
const rotationMatrixRollCW = new THREE.Matrix4().makeRotationAxis(direction, this.angle);
right.applyMatrix4(rotationMatrixRollCW);
up.applyMatrix4(rotationMatrixRollCW);
break;
case '/':
// Roll counter-clockwise around forward vector
const rotationMatrixRollCCW = new THREE.Matrix4().makeRotationAxis(direction, -this.angle);
right.applyMatrix4(rotationMatrixRollCCW);
up.applyMatrix4(rotationMatrixRollCCW);
break;
case '[':
// Push current state onto stack
stack.push({
position: position.clone(),
direction: direction.clone(),
right: right.clone(),
up: up.clone()
});
break;
case ']':
// Pop state from stack
if (stack.length > 0) {
const state = stack.pop();
position = state.position;
direction = state.direction;
right = state.right;
up = state.up;
}
break;
}
}
// Create a connector at each joint
for (let i = 0; i < stack.length; i++) {
const jointPosition = stack[i].position;
const jointGeometry = new THREE.SphereGeometry(0.5, 8, 8);
const jointMaterial = new THREE.MeshPhongMaterial({
color: 0xFFD700,
shininess: 50
});
const joint = new THREE.Mesh(jointGeometry, jointMaterial);
joint.position.copy(jointPosition);
scene.add(joint);
partCount++;
}
document.getElementById('parts-count').textContent = partCount;
document.getElementById('complexity').textContent = this.currentString.length;
return partCount;
}
}
// Three.js setup
let scene, camera, renderer;
let lgrammar;
let controls;
function init() {
// Scene setup
scene = new THREE.Scene();
scene.background = new THREE.Color(0x111122);
// Camera setup
const width = window.innerWidth;
const height = window.innerHeight;
camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
camera.position.set(0, 0, 30);
camera.lookAt(0, 0, 0);
// Renderer setup
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(width, height);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
// Lighting
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
const pointLight = new THREE.PointLight(0xffffff, 0.5);
pointLight.position.set(-10, 10, 10);
scene.add(pointLight);
// OrbitControls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// Initialize L-Grammar system
lgrammar = new LGrammarSystem();
generateNewAssembly();
// Event listeners
window.addEventListener('resize', onWindowResize);
document.getElementById('generate').addEventListener('click', generateNewAssembly);
document.getElementById('reset').addEventListener('click', resetView);
// Start animation loop
animate();
}
function generateNewAssembly() {
lgrammar.iterations = Math.floor(2 + Math.random() * 3);
lgrammar.angle = (Math.PI / 8) + (Math.random() * Math.PI / 4);
lgrammar.generate();
lgrammar.interpret(scene);
resetView();
}
function resetView() {
camera.position.set(0, 0, 30);
camera.lookAt(0, 0, 0);
controls.reset();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
// Add OrbitControls (simplified version)
THREE.OrbitControls = function(camera, domElement) {
this.camera = camera;
this.domElement = domElement;
this.enableDamping = false;
this.dampingFactor = 0.05;
// API
this.target = new THREE.Vector3();
// Internal state
this.rotateStart = new THREE.Vector2();
this.rotateEnd = new THREE.Vector2();
this.rotateDelta = new THREE.Vector2();
this.panStart = new THREE.Vector2();
this.panEnd = new THREE.Vector2();
this.panDelta = new THREE.Vector2();
this.dollyStart = new THREE.Vector2();
this.dollyEnd = new THREE.Vector2();
this.dollyDelta = new THREE.Vector2();
this.state = {
NONE: -1,
ROTATE: 0,
DOLLY: 1,
PAN: 2
};
this.currentState = this.state.NONE;
// Set up event listeners
this.domElement.addEventListener('mousedown', onMouseDown.bind(this));
this.domElement.addEventListener('mousemove', onMouseMove.bind(this));
this.domElement.addEventListener('mouseup', onMouseUp.bind(this));
this.domElement.addEventListener('wheel', onMouseWheel.bind(this));
function onMouseDown(event) {
event.preventDefault();
if (event.button === 0) {
this.currentState = this.state.ROTATE;
this.rotateStart.set(event.clientX, event.clientY);
} else if (event.button === 1) {
this.currentState = this.state.DOLLY;
this.dollyStart.set(event.clientX, event.clientY);
} else if (event.button === 2) {
this.currentState = this.state.PAN;
this.panStart.set(event.clientX, event.clientY);
}
}
function onMouseMove(event) {
event.preventDefault();
if (this.currentState === this.state.ROTATE) {
this.rotateEnd.set(event.clientX, event.clientY);
this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart);
const element = this.domElement;
// Rotate
const rotSpeed = 0.002;
const thetaX = 2 * Math.PI * this.rotateDelta.x / element.clientWidth * rotSpeed;
const thetaY = 2 * Math.PI * this.rotateDelta.y / element.clientHeight * rotSpeed;
// Calculate camera position relative to target
const offset = new THREE.Vector3().subVectors(this.camera.position, this.target);
// Rotate around target
const qx = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), thetaX);
offset.applyQuaternion(qx);
const qy = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), thetaY);
offset.applyQuaternion(qy);
// Update camera position
this.camera.position.copy(this.target).add(offset);
this.camera.lookAt(this.target);
this.rotateStart.copy(this.rotateEnd);
} else if (this.currentState === this.state.DOLLY) {
this.dollyEnd.set(event.clientX, event.clientY);
this.dollyDelta.subVectors(this.dollyEnd, this.dollyStart);
// Zoom speed
const zoomSpeed = 0.01;
// Calculate zoom factor
const factor = 1.0 + this.dollyDelta.y * zoomSpeed;
// Apply zoom
const offset = new THREE.Vector3().subVectors(this.camera.position, this.target);
offset.multiplyScalar(factor);
this.camera.position.copy(this.target).add(offset);
this.dollyStart.copy(this.dollyEnd);
} else if (this.currentState === this.state.PAN) {
this.panEnd.set(event.clientX, event.clientY);
this.panDelta.subVectors(this.panEnd, this.panStart);
// Pan speed
const panSpeed = 0.001;
// Calculate pan offset
const distance = this.camera.position.distanceTo(this.target);
const panX = -this.panDelta.x * distance * panSpeed;
const panY = this.panDelta.y * distance * panSpeed;
// Pan camera
const v = new THREE.Vector3();
v.copy(this.camera.position).sub(this.target);
v.cross(this.camera.up).normalize().multiplyScalar(panX);
const vpan = new THREE.Vector3().copy(this.camera.up).normalize().multiplyScalar(panY);
v.add(vpan);
this.camera.position.add(v);
this.target.add(v);
this.panStart.copy(this.panEnd);
}
}
function onMouseUp(event) {
event.preventDefault();
this.currentState = this.state.NONE;
}
function onMouseWheel(event) {
event.preventDefault();
// Zoom speed
const zoomSpeed = 0.05;
// Calculate zoom factor (based on scroll direction)
const delta = Math.sign(event.deltaY);
const factor = 1.0 - delta * zoomSpeed;
// Apply zoom
const offset = new THREE.Vector3().subVectors(this.camera.position, this.target);
offset.multiplyScalar(factor);
this.camera.position.copy(this.target).add(offset);
}
this.update = function() {
// For damping, not implemented in this simplified version
};
this.reset = function() {
this.target.set(0, 0, 0);
this.camera.position.set(0, 0, 30);
this.camera.lookAt(this.target);
};
};
// Initialize the application
init();
</script>
</body>
</html>
"""
# Display the Three.js application in an iframe
components.html(html_code, height=800)
st.sidebar.title("Game Instructions")
st.sidebar.write("""
## L-Grammar 3D Assembly Game
This game uses L-system grammars to procedurally generate 3D assemblies of parts.
### How it works:
1. The system starts with a simple axiom and applies transformation rules iteratively
2. The resulting string of characters defines the 3D structure
3. Parts are created and connected based on these rules
### Controls:
- **Left-click + drag**: Rotate the view
- **Right-click + drag**: Pan the view
- **Mouse wheel**: Zoom in/out
- **Generate New Assembly**: Creates a new random structure
- **Reset View**: Returns to the default camera position
### L-Grammar Commands:
- F: Move forward and create a part
- +/-: Rotate left/right
- [: Push current state onto stack (branch)
- ]: Pop state from stack (end branch)
Have fun exploring the procedurally generated 3D structures!
""")
st.sidebar.markdown("---")
st.sidebar.write("Made with Three.js and Streamlit")