Commit b06ca8f7 authored by Tomi6545's avatar Tomi6545
Browse files
No related merge requests found
Pipeline #10932 passed with stage
in 38 seconds
Showing with 464 additions and 184 deletions
+464 -184
// Variablen
let selectedModel = 'robot';
/* ========================= */
/* GLOBALE VARIABLEN */
/* ========================= */
let selectedPlacedModel = null;
let currentSession = null;
let reticle = null;
let scene, camera;
let geoLocation;
const menus = ['menu-bar', 'add-menu', 'edit-menu', 'options-menu', 'map-window'];
/* ========================= */
/* MODELLE */
/* ========================= */
let models = {
bench: {
name: "Bench",
......@@ -65,12 +70,13 @@
}
};
const menus = ['menu-bar', 'add-menu', 'edit-menu', 'options-menu', 'map-window'];
/* ========================= */
/* INITIALISIERUNG */
/* ========================= */
window.onload = () => {
initializeAddMenu();
// Fügt Sound zu allen Buttons hinzu
// Allen Buttons den Sound hinzufügen
const buttons = document.querySelectorAll("button, .menu-item");
buttons.forEach(button => {
button.addEventListener("click", playButtonSound);
......@@ -95,6 +101,9 @@
`;
}
/* ========================= */
/* MENÜ-STEUERUNG */
/* ========================= */
function showMenu(menuId) {
const isMapWindow = menuId === 'map-window';
if (isMapWindow && !geoLocation) {
......@@ -111,23 +120,16 @@
else if (isMapWindow) init_map();
}
function clearSelectedModel() {
if (selectedPlacedModel) {
selectedPlacedModel.traverse((child) => {
if (child.isMesh) {
child.material.emissive.setHex(0x000000); // Markierung entfernen
}
});
selectedPlacedModel = null; // Kein Modell mehr ausgewählt
}
function closeDynamicMenu() {
const dynamicMenu = document.getElementById("dynamic-menu");
dynamicMenu.style.display = "none";
}
function selectModel(modelId) {
const model = models[modelId];
if (model && model.file) {
loadModel(model.file);
showMenu('menu-bar');
}
/* ========================= */
/* MODELL-HANDLING */
/* ========================= */
function getAllPlacedModels() {
return scene.children.filter(child => child.isPlacedModel === true);
}
function loadModel(filePath) {
......@@ -164,30 +166,11 @@
);
}
function highlightSelectedModel() {
if (selectedPlacedModel) {
removeHighlightFromAllModels();
selectedPlacedModel.traverse((child) => {
if (child.isMesh) {
child.material.emissive.setHex(0xff0000); // Rote Hervorhebung
}
});
}
}
function removeHighlightFromAllModels() {
scene.traverse((child) => {
if (child.isMesh && child.material && child.material.emissive) {
child.material.emissive.setHex(0x000000); // Markierung entfernen
}
});
}
function removeHighlightFromSelectedModel() {
if (selectedPlacedModel) {
selectedPlacedModel.traverse((child) => {
if (child.isMesh) child.material.emissive.setHex(0x000000); // Markierung entfernen
});
function selectModel(modelId) {
const model = models[modelId];
if (model && model.file) {
loadModel(model.file);
showMenu('menu-bar');
}
}
......@@ -210,6 +193,12 @@
selectedObject = selectedObject.parent;
}
// Überprüfe, ob das ausgewählte Objekt das Reticle ist
if (selectedObject === reticle) {
console.log("Reticle kann nicht ausgewählt werden.");
return;
}
// Markiere das gesamte Modell als ausgewählt
selectedPlacedModel = selectedObject;
highlightSelectedModel();
......@@ -217,6 +206,66 @@
}
}
function clearSelectedModel() {
if (selectedPlacedModel) {
selectedPlacedModel.traverse((child) => {
if (child.isMesh) {
child.material.emissive.setHex(0x000000); // Markierung entfernen
}
});
selectedPlacedModel = null;
}
}
function highlightSelectedModel() {
if (selectedPlacedModel) {
removeHighlightFromAllModels();
selectedPlacedModel.traverse((child) => {
if (child.isMesh) {
child.material.emissive.setHex(0xff0000); // Rote Hervorhebung
}
});
}
}
function removeHighlightFromSelectedModel() {
if (selectedPlacedModel) {
selectedPlacedModel.traverse((child) => {
if (child.isMesh) child.material.emissive.setHex(0x000000); // Markierung entfernen
});
}
}
function removeHighlightFromAllModels() {
scene.traverse((child) => {
if (child.isMesh && child.material && child.material.emissive) {
child.material.emissive.setHex(0x000000); // Markierung entfernen
}
});
}
function deleteModel() {
if (!selectedPlacedModel) {
console.log("Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es löschen.");
return;
}
const deleteDialog = document.getElementById('delete-confirmation-dialog');
deleteDialog.style.display = 'flex';
}
function confirmDelete(shouldDelete) {
const deleteDialog = document.getElementById('delete-confirmation-dialog');
deleteDialog.style.display = 'none';
if (shouldDelete && selectedPlacedModel) {
scene.remove(selectedPlacedModel);
selectedPlacedModel = null;
showMenu('menu-bar');
}
}
function completeEditing() {
removeHighlightFromSelectedModel();
closeDynamicMenu();
......@@ -225,14 +274,16 @@
document.getElementById('menu-bar').style.display = 'flex';
}
/* ========================= */
/* BEARBEITUNGS-MENÜS */
/* ========================= */
function openRotationMenu() {
if (!selectedPlacedModel) {
showInfoDialog("Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten.");
console.log("Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten.");
return;
}
// Holen Sie die aktuelle Y-Rotation des Modells (in Grad)
const currentRotation = Math.round(THREE.MathUtils.radToDeg(selectedPlacedModel.rotation.y));
const currentRotation = Math.round(THREE.MathUtils.radToDeg(selectedPlacedModel.rotation.y)); // Aktuelle Y-Rotation des Modells (in Grad)
const dynamicMenu = document.getElementById("dynamic-menu");
dynamicMenu.style.display = "flex";
......@@ -248,7 +299,7 @@
const radians = (value / 180) * Math.PI;
selectedPlacedModel.rotation[axis] = radians;
// Update der aktuellen Rotation im Menü
// Anzeige der aktuellen Rotation im Dynamic-Menü aktualisieren
const currentRotationDisplay = document.getElementById("current-rotation");
if (currentRotationDisplay) {
currentRotationDisplay.textContent = value; // Zeige den aktuellen Wert in Grad an
......@@ -256,127 +307,101 @@
}
}
function calculateBoundingBox(object) {
const box = new THREE.Box3().setFromObject(object);
const size = new THREE.Vector3();
box.getSize(size);
return size;
}
function calculateMaxScale(object) {
const boundingBox = calculateBoundingBox(object);
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// Berechne die maximal mögliche Skalierung, um im Viewport zu bleiben
const scaleWidth = viewportWidth / boundingBox.x;
const scaleHeight = viewportHeight / boundingBox.y;
// Wähle den kleineren Wert und reduziere ihn leicht, um sicherzugehen, dass das Objekt nicht über den Rand hinausgeht
const safeScaleFactor = 0.95; // Puffer, um sicherzustellen, dass es nicht zu groß wird
return Math.min(scaleWidth, scaleHeight) * safeScaleFactor;
}
/**
function openScaleMenu() {
if (!selectedPlacedModel) {
showInfoDialog("Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten.");
console.log("Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten.");
return;
}
// Berechne die maximale Skalierung für das spezifische Objekt
const maxScale = calculateMaxScale(selectedPlacedModel);
// Aktuelle Skalierung des Modells bestimmen
const currentScale = selectedPlacedModel.scale.x;
const dynamicMenu = document.getElementById("dynamic-menu");
dynamicMenu.style.display = "flex";
dynamicMenu.innerHTML = `
<h3>Skalierung anpassen</h3>
<label>Größe: <span id="scale-value">${currentScale.toFixed(2)}</span><input type="range" min="0.01" max="${maxScale.toFixed(2)}" step="0.0001" value="${currentScale}" onchange="updateScale(this.value)"></label>
<button onclick="closeDynamicMenu()">Zurück</button>
`;
} * */
function openScaleMenu() {
if (!selectedPlacedModel) {
showInfoDialog("Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bearbeiten.");
return;
}
// Berechne die maximale Skalierung für das spezifische Objekt
// const maxScale = calculateMaxScale(selectedPlacedModel);
// Aktuelle Skalierung des Modells bestimmen
// Aktuelle Skalierung und Grenzen bestimmen
const currentScale = selectedPlacedModel.scale.x;
const minScale = selectedPlacedModel.modelConfig.minScale;
const maxScale = selectedPlacedModel.modelConfig.maxScale;
const step = (maxScale - minScale) / 100; // Dynamische Schrittgröße basierend auf Grenzen
console.log("Slider-Werte:", { minScale, maxScale, currentScale });
const currentScalePercent = ((currentScale - minScale) / (maxScale - minScale)) * 100; // Umrechnung der Werte in Prozent
const dynamicMenu = document.getElementById("dynamic-menu");
dynamicMenu.style.display = "flex";
dynamicMenu.innerHTML = `
<h3>Skalierung anpassen</h3>
<label>Größe: <span id="scale-value">${currentScale.toFixed(2)}</span>
<input type="range" min="${minScale}" max="${maxScale}" step="${step}" value="${currentScale}" onchange="updateScale(this.value)"></label>
<label>Größe: <span id="scale-value">${currentScalePercent.toFixed(0)}%</span>
<input type="range" min="0" max="100" step="1" value="${currentScalePercent}" onchange="updateScale(this.value, ${minScale}, ${maxScale})"></label>
<button onclick="closeDynamicMenu()">Zurück</button>
`;
}
function updateScale(value) {
function updateScale(percentValue, minScale, maxScale) {
if (selectedPlacedModel) {
const scale = parseFloat(value);
// Berechnung der Skalierung basierend auf dem Prozentwert
const scale = minScale + (percentValue / 100) * (maxScale - minScale);
selectedPlacedModel.scale.set(scale, scale, scale);
// Anzeige des aktuellen Wertes aktualisieren
// Anzeige des aktuellen Prozentsatzes im Dynamic-Menü aktualisieren
const scaleValueDisplay = document.getElementById("scale-value");
if (scaleValueDisplay) {
scaleValueDisplay.textContent = `${scale.toFixed(2)}`;
scaleValueDisplay.textContent = `${parseInt(percentValue, 10)}%`;
}
}
}
function deleteModel() {
let moveDelta = 0.1; // Standardwert für die Verschiebungsgröße, kann mit dem Slider geändert werden
function openMoveMenu() {
if (!selectedPlacedModel) {
showInfoDialog("Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es löschen.");
console.log("Kein Modell ausgewählt. Bitte wählen Sie ein Modell aus, bevor Sie es bewegen.");
return;
}
// Dialog anzeigen
const deleteDialog = document.getElementById('delete-confirmation-dialog');
deleteDialog.style.display = 'flex';
}
const dynamicMenu = document.getElementById("dynamic-menu");
dynamicMenu.style.display = "flex";
function confirmDelete(shouldDelete) {
const deleteDialog = document.getElementById('delete-confirmation-dialog');
deleteDialog.style.display = 'none';
dynamicMenu.innerHTML = `
<h3>Modell bewegen</h3>
<div id="position-info">
<p>Aktuelle Position: X=${selectedPlacedModel.position.x.toFixed(2)}, Z=${selectedPlacedModel.position.z.toFixed(2)}</p>
</div>
<label>Verschiebungsgröße: <span id="move-delta-display">${moveDelta.toFixed(2)}</span></label>
<input type="range" min="0.01" max="1.0" step="0.01" value="${moveDelta}" onchange="updateMoveDelta(this.value)">
<div style="display: flex; flex-direction: column; align-items: center;">
<div>
<button onclick="moveModelDynamic('x', -1)">← X</button>
<button onclick="moveModelDynamic('x', 1)">→ X</button>
</div>
<div>
<button onclick="moveModelDynamic('z', -1)">- Z</button>
<button onclick="moveModelDynamic('z', 1)">+ Z</button>
</div>
</div>
<button onclick="closeDynamicMenu()">Zurück</button>
`;
}
if (shouldDelete && selectedPlacedModel) {
scene.remove(selectedPlacedModel);
selectedPlacedModel = null;
showMenu('menu-bar');
function updateMoveDelta(value) {
moveDelta = parseFloat(value);
const moveDeltaDisplay = document.getElementById("move-delta-display");
if (moveDeltaDisplay) {
moveDeltaDisplay.textContent = moveDelta.toFixed(2);
}
}
function closeDynamicMenu() {
const dynamicMenu = document.getElementById("dynamic-menu");
dynamicMenu.style.display = "none";
}
function moveModelDynamic(axis, direction) {
if (selectedPlacedModel) {
const delta = direction * moveDelta; // Dynamischer Wert basierend auf Slider
selectedPlacedModel.position[axis] += delta;
function getAllPlacedModels() {
return scene.children.filter(child => child.isPlacedModel === true);
// Position im Menü aktualisieren
const positionInfo = document.getElementById("position-info");
if (positionInfo) {
positionInfo.innerHTML = `
<p>Aktuelle Position: X=${selectedPlacedModel.position.x.toFixed(2)}, Z=${selectedPlacedModel.position.z.toFixed(2)}</p>`;
}
}
}
/* ========================= */
/* KARTENSTEUERUNG */
/* ========================= */
function refreshMapDialog() {
const mapDialog = document.getElementById('map-dialog');
mapDialog.style.display = 'flex';
......@@ -387,6 +412,9 @@
mapDialog.style.display = 'none';
}
/* ========================= */
/* AR-HANDLING */
/* ========================= */
async function activateXR(sceneData = null) {
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
......@@ -424,6 +452,7 @@
currentSession.addEventListener("end", () => {
currentSession = null;
document.getElementById("dynamic-menu").style.display = "none";
menus.forEach(id => {
document.getElementById(id).style.display = 'none';
});
......@@ -441,7 +470,6 @@
console.log("GeoLocation: " + JSON.stringify(geoLocation));
if (sceneData) {
sceneData.models.forEach((model) => {
const offSet = leafletToThree(sceneData.lat, sceneData.lng)
if (model.name) {
const filePath = Object.values(models).find(m => model.name === m.name).file;
const { x, z } = leafletToThree(model.lat, model.lng);
......@@ -498,14 +526,14 @@
}
function confirmExit(shouldExit) {
if (shouldExit && currentSession) {
currentSession.end();
}
if (shouldExit && currentSession) currentSession.end();
document.getElementById('confirmation-dialog').style.display = 'none';
}
/* ========================= */
/* BENUTZERINTERAKTIONEN */
/* ========================= */
let soundTimeout = false;
function playButtonSound() {
if (!soundTimeout) {
const sound = document.getElementById("button-sound");
......
/* ========================= */
/* DEBUGGING UND START */
/* ========================= */
if (navigator.xr) {
let sceneData;
const startButton = document.createElement('button');
......
Model Information:
* title: NYC Park Streetlight,
* source: https://sketchfab.com/3d-models/nyc-park-streetlight-4efdeb23f30e49aab10fac0c3064443d
* author: Anticipateants (https://sketchfab.com/anticipateants)
Model License:
* license type: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
* requirements: Author must be credited. Commercial use is allowed.
If you use this 3D model in your project be sure to copy paste this credit wherever you share it:
This work is based on "NYC Park Streetlight," (https://sketchfab.com/3d-models/nyc-park-streetlight-4efdeb23f30e49aab10fac0c3064443d) by Anticipateants (https://sketchfab.com/anticipateants) licensed under CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
File added
{
"accessors": [
{
"bufferView": 2,
"componentType": 5126,
"count": 2944,
"max": [
1.1778082847595215,
71.68600463867188,
1.177808165550232
],
"min": [
-1.1778085231781006,
-1.0,
-1.177808403968811
],
"type": "VEC3"
},
{
"bufferView": 2,
"byteOffset": 35328,
"componentType": 5126,
"count": 2944,
"max": [
0.9807855486869812,
0.7628369927406311,
0.9871309399604797
],
"min": [
-0.9807854294776917,
-1.0,
-0.9807857275009155
],
"type": "VEC3"
},
{
"bufferView": 1,
"componentType": 5126,
"count": 2944,
"max": [
0.9722591042518616,
0.971050500869751
],
"min": [
0.017211854457855225,
0.012609779834747314
],
"type": "VEC2"
},
{
"bufferView": 0,
"componentType": 5125,
"count": 4410,
"type": "SCALAR"
}
],
"asset": {
"extras": {
"author": "Anticipateants (https://sketchfab.com/anticipateants)",
"license": "CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)",
"source": "https://sketchfab.com/3d-models/nyc-park-streetlight-4efdeb23f30e49aab10fac0c3064443d",
"title": "NYC Park Streetlight,"
},
"generator": "Sketchfab-13.53.0",
"version": "2.0"
},
"bufferViews": [
{
"buffer": 0,
"byteLength": 17640,
"name": "floatBufferViews",
"target": 34963
},
{
"buffer": 0,
"byteLength": 23552,
"byteOffset": 17640,
"byteStride": 8,
"name": "floatBufferViews",
"target": 34962
},
{
"buffer": 0,
"byteLength": 70656,
"byteOffset": 41192,
"byteStride": 12,
"name": "floatBufferViews",
"target": 34962
}
],
"buffers": [
{
"byteLength": 111848,
"uri": "scene.bin"
}
],
"images": [
{
"uri": "textures/Material.001_baseColor.png"
}
],
"materials": [
{
"doubleSided": true,
"name": "Material.001",
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0
},
"metallicFactor": 0.0
}
}
],
"meshes": [
{
"name": "Object_0",
"primitives": [
{
"attributes": {
"NORMAL": 1,
"POSITION": 0,
"TEXCOORD_0": 2
},
"indices": 3,
"material": 0,
"mode": 4
}
]
}
],
"nodes": [
{
"children": [
1
],
"matrix": [
1.0,
0.0,
0.0,
0.0,
0.0,
2.220446049250313e-16,
-1.0,
0.0,
0.0,
1.0,
2.220446049250313e-16,
0.0,
0.0,
0.0,
0.0,
1.0
],
"name": "Sketchfab_model"
},
{
"children": [
2
],
"name": "root"
},
{
"children": [
3
],
"matrix": [
1.0,
0.0,
0.0,
0.0,
0.0,
2.220446049250313e-16,
1.0,
0.0,
0.0,
-1.0,
2.220446049250313e-16,
0.0,
0.0,
0.0,
0.0,
1.0
],
"name": "GLTF_SceneRootNode"
},
{
"children": [
4
],
"matrix": [
0.03656700626015663,
0.0,
0.0,
0.0,
0.0,
0.010605139657855034,
0.0,
0.0,
0.0,
0.0,
0.03656700626015663,
0.0,
-0.006100017577409744,
0.010527204722166061,
-0.0016316026449203491,
1.0
],
"name": "main_0"
},
{
"mesh": 0,
"name": "Object_4"
}
],
"samplers": [
{
"magFilter": 9729,
"minFilter": 9987,
"wrapS": 10497,
"wrapT": 10497
}
],
"scene": 0,
"scenes": [
{
"name": "Sketchfab_Scene",
"nodes": [
0
]
}
],
"textures": [
{
"sampler": 0,
"source": 0
}
]
}
public/assets/models/park_light_model/textures/Material.001_baseColor.png

2.35 MB

......@@ -5,43 +5,16 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GeoVis AR Projekt</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="style.css">
<script src="https://unpkg.com/three@0.126.0/build/three.js"></script>
<script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script>
</head>
<body>
<!-- Debug -->
<div id="debug-container" style="
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 9999;
font-family: monospace;
font-size: 12px;
display: flex;
flex-direction: column;
align-items: flex-start;
">
<div id="debug-console" style="
width: 100%;
max-height: 200px;
overflow-y: auto;
background: rgba(0, 0, 0, 0.7);
color: #00ff00;
padding: 5px;
display: none;
"></div>
<button id="debug-toggle-btn" style="
background: rgba(0, 0, 0, 0.7);
color: #00ff00;
font-size: 14px;
border: none;
padding: 5px 10px;
cursor: pointer;
display: none;
"></button>
<div id="debug-container">
<div id="debug-console"></div>
<button id="debug-toggle-btn"></button>
</div>
<!-- Standardmenü -->
......@@ -57,7 +30,7 @@
</div>
</div>
<!-- Hinzufügen-Menü -->
<!-- Hinzufügen-Menü für das Auswählen der Modelle-->
<div id="add-menu" class="menu-placeholder" style="display: none;"></div>
<!-- Bearbeiten-Menü -->
......@@ -68,6 +41,9 @@
<div class="menu-item" onclick="openScaleMenu()">
<img src="previewImages/scale-icon.png" alt="Skalierung" />
</div>
<div class="menu-item" onclick="openMoveMenu()">
<img src="previewImages/move-icon.png" alt="Bewegen" />
</div>
<div class="menu-item" onclick="deleteModel()">
<img src="previewImages/delete-icon.png" alt="Löschen" />
</div>
......@@ -89,7 +65,7 @@
</div>
</div>
<!-- Dynamisches Menü -->
<!-- Dynamisches Menü für Rotation und Scaling-->
<div id="dynamic-menu" style="display: none;"></div>
<!-- Bestätigungsdialog für Löschen -->
......@@ -112,15 +88,6 @@
</div>
</div>
<!-- Informations-Dialog -->
<div id="info-dialog" style="display: none;">
<div class="dialog-overlay"></div>
<div class="dialog-box">
<p id="info-text">Hier kommt die Nachricht hin</p>
<button onclick="closeInfoDialog()">OK</button>
</div>
</div>
<div id="map-window" class="menu-placeholder"
style="display: none; flex-direction: column; width: 100vw; height: 100vh;">
<div id="map-container" style="z-index: 1500; flex: 15; width: 100%; height: 100%;"></div>
......
public/previewImages/move-icon.png

16.4 KB

public/previewImages/park_light.png

895 KB

......@@ -100,8 +100,7 @@ body {
/* Confirmation Dialog */
#confirmation-dialog,
#delete-confirmation-dialog,
#info-dialog {
#delete-confirmation-dialog {
position: fixed;
top: 0;
left: 0;
......@@ -171,17 +170,17 @@ body {
#dynamic-menu {
position: absolute;
bottom: 80px;
bottom: 90px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20px;
gap: 10px;
background: #1e1e1e;
box-shadow: 0 -2px 6px rgba(0, 0, 0, 0.8);
color: white;
width: 100vw;
height: 200px;
height: 250px;
z-index: 20;
overflow-y: auto;
border-radius: 8px;
......@@ -205,3 +204,36 @@ body {
border-radius: 50%;
cursor: pointer;
}
#debug-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 9999;
font-family: monospace;
font-size: 12px;
display: flex;
flex-direction: column;
align-items: flex-start;
}
#debug-console {
width: 100%;
max-height: 200px;
overflow-y: auto;
background: rgba(0, 0, 0, 0.7);
color: #00ff00;
padding: 5px;
display: none;
}
#debug-toggle-btn {
background: rgba(0, 0, 0, 0.7);
color: #00ff00;
font-size: 14px;
border: none;
padding: 5px 10px;
cursor: pointer;
display: none;
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment