update操作中的Redo记录(change vector的集合)

本文详细解析了如何通过修改emp表中一条记录的salary字段,并提交操作,进而触发Redo日志记录的过程。包括日志文件的强制切换、更新操作、提交过程、确认当前日志文件状态、转储日志文件、分析对应trace文件以及理解Redo记录的组成部分。深入探讨了Redo记录的内容,如改变向量、事务ID、数据块修改等关键信息,以帮助理解数据库事务处理和日志管理。

以修改emp表中一条记录中某个字段值为例,来说明Redo信息内容

 

强制切换日志文件,以保证使用新的日志文件

SQL> alter system switch logfile;
 
System altered



update操作并提交

SQL> update emp set sal=3000 where empno=7788;
 
1 row updated
 
SQL> commit;
 
Commit complete
 

确认当前日志文件是哪个

SQL> select * from v$log;
 
    GROUP#    THREAD#  SEQUENCE#      BYTES    MEMBERS ARCHIVED STATUS           FIRST_CHANGE# FIRST_TIME
---------- ---------- ---------- ---------- ---------- -------- ---------------- ------------- -----------
         1          1        118   52428800          1 NO       CURRENT               63527376 2014/3/26 8
         2          1        116   52428800          1 YES      ACTIVE                63501547 2014/3/25 2
         3          1        117   52428800          1 YES      ACTIVE                63523908 2014/3/26 8
 

SQL> select * from v$logfile;
 
    GROUP# STATUS  TYPE    MEMBER                                                                           IS_RECOVERY_DEST_FILE
---------- ------- ------- -------------------------------------------------------------------------------- ---------------------
         3         ONLINE  /home/oracle/app/oradata/ora11g/redo03.log                                       NO
         2         ONLINE  /home/oracle/app/oradata/ora11g/redo02.log                                       NO
         1         ONLINE  /home/oracle/app/oradata/ora11g/redo01.log                                       NO

 

转储日志文件

SQL> alter system dump logfile '/home/oracle/app/oradata/ora11g/redo01.log';
 
System altered

 

 

找到对应trace文件并分析

SQL> select * from v$diag_info where name ='Default Trace File';
 
   INST_ID NAME                                                             VALUE
---------- ---------------------------------------------------------------- --------------------------------------------------------------------------------
         1 Default Trace File                                               /home/oracle/app/diag/rdbms/ora11g/ora11g/trace/ora11g_ora_27538.trc
 

 

找出文件对应ID

SQL> select file#,name from v$datafile;
 
     FILE# NAME
---------- --------------------------------------------------------------------------------
         1 /home/oracle/app/oradata/ora11g/system01.dbf
         2 /home/oracle/app/oradata/ora11g/sysaux01.dbf
         3 /home/oracle/app/oradata/ora11g/undotbs01.dbf
         4 /home/oracle/app/oradata/ora11g/users01.dbf
         5 /opt/oracle/ora11g/MSGAGENT.ora
         6 /opt/oracle/ora11g/MSGAGENTIDX.ora
         7 /home/oracle/oradata/ap_table
         8 /home/oracle/oradata/ap_index
         9 /home/oracle/oradata/group_table
        10 /home/oracle/oradata/group_clob
        11 /home/oracle/oradata/group_index
        12 /home/oracle/oradata/ps_table
        13 /home/oracle/oradata/ps_clob
        14 /home/oracle/oradata/ps_index
        15 /home/oracle/oradata/ome_mc_4K
        16 /home/oracle/oradata/ome_mc_4k_index
        17 /home/oracle/oradata/ome_mc_4k_temp
        18 /home/oracle/oradata/mlee_data.dbf
        19 /home/oracle/oradata/mlee_index.dbf
        20 /home/oracle/app/oradata/ora11g/ebms0528.ora
        21 /home/oracle/app/oradata/ora11g/ebms0528IDX.ora
        22 /home/oracle/app/oradata/ora11g/report.ora
        23 /home/oracle/app/oradata/ora11g/reportIDX.ora
        24 /home/oracle/app/oradata/ora11g/egroup0528index01
        25 /home/oracle/app/oradata/ora11g/egroup0528data01
        26 /home/oracle/app/oradata/ora11g/econsole0528.ora
        27 /home/oracle/app/oradata/ora11g/econsole0528IDX.ora
        28 /home/oracle/app/oradata/ora11g/jforum.ora
        29 /home/oracle/oradata/temp/eygle.f
        30 /home/oracle/oradata/temp/eygle1.f
        31 /home/oracle/oradata/temp/eygle2.f
        32 /home/oracle/oradata/temp/eygle3.f
        33 /home/oracle/oradata/temp/eygle4.f

 

ora11g_ora_27538.trc文件中内容如下:

REDO RECORD - Thread:1 RBA: 0x000076.00000030.0170 LEN: 0x01a8 VLD: 0x01
SCN: 0x0000.03c95a0b SUBSCN:  1 03/26/2014 08:25:14
CHANGE #1 TYP:0 CLS:35 AFN:3 DBA:0x00c00110OBJ:4294967295 SCN:0x0000.03c95a01 SEQ:  1 OP:5.2 ENC:0   //改变向量1,对回滚段头的修改操作(OP得出),分配事务表,文件ID为3(AFN)得出是UNDO表空间
ktudh redo: slt: 0x000d sqn: 0x00010578 flg: 0x0412 siz: 176 fbi: 1
            uba: 0x00c00c52.2067.45    pxid:  0x0000.000.00000000
CHANGE #2 TYP:0 CLS:36 AFN:3 DBA:0x00c00c52OBJ:4294967295 SCN:0x0000.03c9594e SEQ: 54 OP:5.1 ENC:0 //改变向量2,记录前镜像信息(即sal为4000的原记录),对undo块或undo header操作(OP得出),仍是UNDO表空间(AFN)
ktudb redo: siz: 176 spc: 2538 flg: 0x0012 seq: 0x2067 rec: 0x45
            xid:  0x000a.00d.00010578 
ktubl redo: slt: 13 rci: 0 opc: 11.1 [objn: 443 objd: 443 tsn: 0]
Undo type:  Regular undo        Begin trans    Last buffer split:  No
Temp Object:  No
Tablespace Undo:  No
             0x00000000  prev ctl uba: 0x00c00c56.2067.3d
prev ctl max cmt scn:  0x0000.03c955bd  prev tx cmt scn:  0x0000.03c955f5
txn start scn:  0xffff.ffffffff  logon user: 0  prev brb: 12586050  prev bcl: 0 BuExt idx: 0 flg2: 0
KDO undo record:
KTB Redo
op: 0x04  ver: 0x01 
compat bit: 4 (post-11) padding: 1
op: L  itl: xid:  0x0009.00c.000103bc uba: 0x00c00d3f.1e4e.2c
                      flg: C---    lkc:  0     scn: 0x0000.03c9272f
KDO Op code: URP row dependencies Disabled
  xtype: XAxtype KDO_KDOM2 flags: 0x00000080  bdba: 0x00400bc1  hdba: 0x00400ba8
itli: 1  ispac: 0  maxfr: 4863
tabn: 0 slot: 85(0x55) flag: 0x2c lock: 0 ckix: 119
ncol: 9 nnew: 7 size: 0
Vector content:
col  2: [ 2]  c1 03      //对于col 2的修改,修改前的值为4000
col  3: [ 1]  80
col  4: [ 1]  80
col  5: [ 1]  80
col  6: [ 1]  80
col  7: [ 1]  80
col  8: [ 7]  78 72 03 12 10 1a 27
CHANGE #3 TYP:2 CLS: 1 AFN:1 DBA:0x00400bc1 OBJ:443 SCN:0x0000.03c946c5 SEQ:  1OP:11.5 ENC:0   //改变向量3,数据块的修改操作(OP),SYSTEM表空间
KTB Redo
op: 0x01  ver: 0x01 
compat bit: 4 (post-11) padding: 1
op: F  xid:  0x000a.00d.00010578    uba: 0x00c00c52.2067.45
KDO Op code: URP row dependencies Disabled
  xtype: XAxtype KDO_KDOM2 flags: 0x00000080  bdba: 0x00400bc1  hdba: 0x00400ba8
itli: 1  ispac: 0  maxfr: 4863
tabn: 0 slot: 85(0x55) flag: 0x2c lock: 1 ckix: 119
ncol: 9 nnew: 7 size: 0
Vector content:
col  2: [ 2]  c1 04   //col 2的修改,修改后的值为3000
col  3: [ 1]  80
col  4: [ 1]  80
col  5: [ 1]  80
col  6: [ 1]  80
col  7: [ 1]  80
col  8: [ 7]  78 72 03 1a 09 1a 0f
CHANGE #4 TYP:0 CLS:21 AFN:3 DBA:0x00c000a0 OBJ:4294967295 SCN:0x0000.03c959d9 SEQ:  1OP:5.4 ENC:0    //改变向量4,事务提交(OP)
ktucm redo: slt: 0x0018 sqn: 0x000103d3 srt: 0 sta: 9 flg: 0x2 ktucf redo: uba: 0x00c005b7.2158.06 ext: 2 spc: 7482 fbi: 0

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>3D文物模型标注平台</title> <style> body { margin: 0; padding: 0; font-family: Arial, sans-serif; overflow: hidden; background-color: #333; color: #eee; } #top-menu { height: 40px; background-color: #222; color: #ddd; display: flex; align-items: center; padding: 0 10px; border-bottom: 1px solid #444; } .menu-item { position: relative; padding: 10px 15px; cursor: pointer; } .menu-item:hover { background-color: #444; } .menu-item:hover .submenu { display: block; } .submenu { display: none; position: absolute; top: 100%; left: 0; background-color: #333; min-width: 200px; z-index: 1000; border: 1px solid #444; box-shadow: 0 2px 5px rgba(0,0,0,0.5); } .submenu button { display: block; width: 100%; padding: 8px 15px; text-align: left; background: none; border: none; color: #ddd; cursor: pointer; border-bottom: 1px solid #444; } .submenu button:hover { background-color: #444; } #container { display: flex; height: calc(100vh - 40px); } #sidebar { width: 250px; background-color: #2a2a2a; padding: 10px; overflow-y: auto; border-right: 1px solid #444; resize: horizontal; min-width: 200px; max-width: 500px; overflow-x: hidden; } #viewer { flex: 1; position: relative; background-color: #1a1a1a; } .view-toolbar { position: absolute; top: 10px; right: 10px; z-index: 100; background: rgba(40,40,40,0.8); padding: 5px; border-radius: 3px; display: flex; flex-direction: column; gap: 3px; border: 1px solid #444; } .annotation-list { margin-top: 10px; } .annotation-item { padding: 6px; margin-bottom: 3px; background: #333; border-radius: 3px; cursor: pointer; display: flex; justify-content: space-between; font-size: 13px; border: 1px solid #444; } .annotation-item:hover { background-color: #444; } .annotation-item.selected { background-color: #4CAF50; color: white; } .file-input { display: none; } .status-bar { position: absolute; bottom: 5px; left: 5px; background: rgba(0,0,0,0.7); color: #ddd; padding: 3px 8px; border-radius: 3px; font-size: 12px; border: 1px solid #444; } button { padding: 3px 8px; cursor: pointer; border: 1px solid #555; background: #333; border-radius: 3px; font-size: 12px; color: #ddd; } button:hover { background: #444; } button.active { background: #4CAF50; color: white; } .view-btn { width: 80px; margin: 2px 0; } h3 { color: #ddd; border-bottom: 1px solid #444; padding-bottom: 5px; } .resize-handle { width: 5px; height: 100%; position: absolute; right: 0; top: 0; cursor: col-resize; } </style> </head> <body> <div id="top-menu"> <div class="menu-item"> 文件 <div class="submenu"> <button id="open-file">打开模型...</button> <input type="file" id="file-input" class="file-input" accept=".obj,.gltf,.glb,.3ds,.fbx,.dae,.stl,.ply"> <button id="save-file">保存模型</button> <button id="export-model">导出模型...</button> <button id="save-annotations">保存标注</button> <button id="export-annotations">导出标注...</button> </div> </div> <div class="menu-item"> 编辑 <div class="submenu"> <button id="undo-btn">撤销</button> <button id="redo-btn">恢复</button> <button id="delete-btn">删除</button> <button id="clear-btn">清空标注</button> <button id="restore-colors">还原模型颜色</button> </div> </div> <div class="menu-item"> 视图 <div class="submenu"> <button id="fit-view">适应视图</button> <button id="toggle-grid">显示网格</button> <button id="toggle-axis">显示坐标轴</button> <button id="toggle-wireframe">线框模式</button> </div> </div> <div class="menu-item"> 标注工具 <div class="submenu"> <button id="box-tool">3D框标注</button> <button id="point-tool">点标注</button> <button id="polygon-tool">多边形标注</button> <button id="measure-tool">测量工具</button> <button id="text-tool">文字标注</button> </div> </div> </div> <div id="container"> <div id="sidebar"> <h3>标注列表</h3> <div class="annotation-list"> <div id="annotations-container"></div> </div> </div> <div id="viewer"> <div class="view-toolbar"> <button id="front-view" class="view-btn">前视图</button> <button id="back-view" class="view-btn">后视图</button> <button id="left-view" class="view-btn">左视图</button> <button id="right-view" class="view-btn">右视图</button> <button id="top-view" class="view-btn">顶视图</button> <button id="bottom-view" class="view-btn">底视图</button> <button id="perspective-view" class="view-btn">透视视图</button> <button id="orthographic-view" class="view-btn">正交视图</button> </div> <div class="status-bar" id="status-bar"> 就绪 </div> </div> </div> <!-- 引入Three.js库和相关插件 --> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/GLTFLoader.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/OBJLoader.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/FBXLoader.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/DRACOLoader.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/exporters/GLTFExporter.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/exporters/OBJExporter.js"></script> <script> // 初始化3D场景 const scene = new THREE.Scene(); scene.background = new THREE.Color(0x1a1a1a); let camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = 5; const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); document.getElementById('viewer').appendChild(renderer.domElement); const controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.25; controls.screenSpacePanning = false; controls.maxPolarAngle = Math.PI; controls.minDistance = 0.1; controls.maxDistance = 1000; // 添加光源 const ambientLight = new THREE.AmbientLight(0x404040, 1.5); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2); directionalLight.position.set(1, 1, 1); scene.add(directionalLight); // 添加辅助工具 const gridHelper = new THREE.GridHelper(20, 20, 0x444444, 0x222222); gridHelper.visible = true; scene.add(gridHelper); // 添加垂直网格线 const verticalGridHelper = new THREE.GridHelper(20, 20, 0x444444, 0x222222); verticalGridHelper.rotation.x = Math.PI / 2; verticalGridHelper.visible = true; scene.add(verticalGridHelper); const axesHelper = new THREE.AxesHelper(5); axesHelper.visible = false; scene.add(axesHelper); // 模型和标注相关变量 let currentModel = null; let currentTool = null; let annotations = []; let history = []; let historyIndex = -1; let isWireframe = false; let selectedObject = null; let isMouseDown = false; let isRightMouseDown = false; let lastMousePosition = { x: 0, y: 0 }; let originalMaterials = new Map(); // 保存原始材质 // 文件操作功能 document.getElementById('open-file').addEventListener('click', function() { document.getElementById('file-input').click(); }); document.getElementById('file-input').addEventListener('change', function(e) { const file = e.target.files[0]; if (!file) return; const fileName = file.name; const fileExtension = fileName.split('.').pop().toLowerCase(); const fileURL = URL.createObjectURL(file); updateStatus(`正在加载: ${fileName}`); // 根据文件类型选择不同的加载器 try { if (currentModel) { scene.remove(currentModel); } let loader; switch(fileExtension) { case 'gltf': case 'glb': loader = new THREE.GLTFLoader(); // 添加DRACO解码器支持 const dracoLoader = new THREE.DRACOLoader(); dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/'); loader.setDRACOLoader(dracoLoader); loader.load(fileURL, function(gltf) { currentModel = gltf.scene; // 保存原始材质 originalMaterials.clear(); currentModel.traverse(child => { if (child.isMesh) { originalMaterials.set(child, child.material); } }); // 自动垂直对齐模型 alignModelVertically(currentModel); scene.add(currentModel); fitViewToObject(currentModel); updateStatus(`已加载: ${fileName}`); saveHistory(); }, function(xhr) { const percentComplete = (xhr.loaded / xhr.total) * 100; updateStatus(`加载中: ${fileName} - ${percentComplete.toFixed(2)}%`); }, function(error) { console.error('加载模型出错:', error); updateStatus(`加载失败: ${fileName}`); } ); break; case 'obj': loader = new THREE.OBJLoader(); loader.load(fileURL, function(object) { currentModel = object; // 保存原始材质 originalMaterials.clear(); currentModel.traverse(child => { if (child.isMesh) { originalMaterials.set(child, child.material); } }); // 自动垂直对齐模型 alignModelVertically(currentModel); scene.add(currentModel); fitViewToObject(currentModel); updateStatus(`已加载: ${fileName}`); saveHistory(); }, function(xhr) { const percentComplete = (xhr.loaded / xhr.total) * 100; updateStatus(`加载中: ${fileName} - ${percentComplete.toFixed(2)}%`); }, function(error) { console.error('加载模型出错:', error); updateStatus(`加载失败: ${fileName}`); } ); break; case 'fbx': loader = new THREE.FBXLoader(); loader.load(fileURL, function(object) { currentModel = object; // 保存原始材质 originalMaterials.clear(); currentModel.traverse(child => { if (child.isMesh) { originalMaterials.set(child, child.material); } }); // 自动垂直对齐模型 alignModelVertically(currentModel); scene.add(currentModel); fitViewToObject(currentModel); updateStatus(`已加载: ${fileName}`); saveHistory(); }, function(xhr) { const percentComplete = (xhr.loaded / xhr.total) * 100; updateStatus(`加载中: ${fileName} - ${percentComplete.toFixed(2)}%`); }, function(error) { console.error('加载模型出错:', error); updateStatus(`加载失败: ${fileName}`); } ); break; default: updateStatus(`不支持的文件格式: ${fileExtension}`); break; } } catch (error) { console.error('加载模型出错:', error); updateStatus(`加载失败: ${fileName}`); } }); // 自动垂直对齐模型 function alignModelVertically(model) { const box = new THREE.Box3().setFromObject(model); const size = box.getSize(new THREE.Vector3()); const center = box.getCenter(new THREE.Vector3()); // 假设模型高度方向是Y轴 // 计算模型需要旋转的角度,使最长边垂直 const maxDim = Math.max(size.x, size.y, size.z); // 如果最长边不是Y轴,则需要旋转模型 if (maxDim !== size.y) { // 找到最长边对应的轴 let rotationAxis = new THREE.Vector3(0, 0, 0); if (maxDim === size.x) { rotationAxis.set(0, 0, 1); // 绕Z轴旋转90度 } else if (maxDim === size.z) { rotationAxis.set(1, 0, 0); // 绕X轴旋转90度 } // 应用旋转 model.rotation.setFromVector3(rotationAxis.clone().multiplyScalar(Math.PI / 2)); // 重新计算边界框 box.setFromObject(model); const newCenter = box.getCenter(new THREE.Vector3()); // 调整位置使中心点不变 model.position.sub(newCenter).add(center); } } document.getElementById('save-file').addEventListener('click', function() { if (!currentModel) { updateStatus('没有可保存的模型'); return; } // 这里可以添加保存到服务器的逻辑 updateStatus('模型已保存'); }); document.getElementById('export-model').addEventListener('click', function() { if (!currentModel) { updateStatus('没有可导出的模型'); return; } const exporter = new THREE.GLTFExporter(); exporter.parse(currentModel, function(gltf) { const data = JSON.stringify(gltf); const blob = new Blob([data], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'model.gltf'; a.click(); updateStatus('模型已导出为GLTF格式'); }); }); document.getElementById('save-annotations').addEventListener('click', function() { const data = JSON.stringify(annotations); localStorage.setItem('annotations', data); updateStatus('标注已保存到本地存储'); }); document.getElementById('export-annotations').addEventListener('click', function() { const data = JSON.stringify(annotations); const blob = new Blob([data], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'annotations.json'; a.click(); updateStatus('标注已导出为JSON文件'); }); // 视图控制功能 document.getElementById('fit-view').addEventListener('click', function() { if (currentModel) { fitViewToObject(currentModel); } }); document.getElementById('toggle-grid').addEventListener('click', function() { gridHelper.visible = !gridHelper.visible; verticalGridHelper.visible = gridHelper.visible; updateStatus(gridHelper.visible ? '网格已显示' : '网格已隐藏'); }); document.getElementById('toggle-axis').addEventListener('click', function() { axesHelper.visible = !axesHelper.visible; updateStatus(axesHelper.visible ? '坐标轴已显示' : '坐标轴已隐藏'); }); document.getElementById('toggle-wireframe').addEventListener('click', function() { if (!currentModel) return; isWireframe = !isWireframe; currentModel.traverse(function(child) { if (child.isMesh) { child.material.wireframe = isWireframe; } }); updateStatus(isWireframe ? '线框模式已启用' : '线框模式已禁用'); }); // 还原模型颜色功能 document.getElementById('restore-colors').addEventListener('click', function() { if (!currentModel) { updateStatus('没有可还原颜色的模型'); return; } currentModel.traverse(child => { if (child.isMesh && originalMaterials.has(child)) { child.material = originalMaterials.get(child); } }); updateStatus('已还原模型原始颜色'); }); // 标准视图 document.getElementById('front-view').addEventListener('click', function() { setStandardView('front'); }); document.getElementById('back-view').addEventListener('click', function() { setStandardView('back'); }); document.getElementById('left-view').addEventListener('click', function() { setStandardView('left'); }); document.getElementById('right-view').addEventListener('click', function() { setStandardView('right'); }); document.getElementById('top-view').addEventListener('click', function() { setStandardView('top'); }); document.getElementById('bottom-view').addEventListener('click', function() { setStandardView('bottom'); }); document.getElementById('perspective-view').addEventListener('click', function() { if (camera instanceof THREE.OrthographicCamera) { const aspect = window.innerWidth / window.innerHeight; camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000); camera.position.copy(controls.target.clone().add(new THREE.Vector3(5, 5, 5))); camera.lookAt(controls.target); controls.object = camera; controls.update(); updateStatus('切换到透视视图'); } }); document.getElementById('orthographic-view').addEventListener('click', function() { if (camera instanceof THREE.PerspectiveCamera) { const width = 10; const height = width * (window.innerHeight / window.innerWidth); camera = new THREE.OrthographicCamera( width / -2, width / 2, height / 2, height / -2, 0.1, 1000 ); camera.position.copy(controls.target.clone().add(new THREE.Vector3(5, 5, 5))); camera.lookAt(controls.target); controls.object = camera; controls.update(); updateStatus('切换到正交视图'); } }); // 编辑功能 document.getElementById('undo-btn').addEventListener('click', function() { if (historyIndex > 0) { historyIndex--; restoreFromHistory(); updateStatus('已撤销上一步操作'); } else { updateStatus('没有可撤销的操作'); } }); document.getElementById('redo-btn').addEventListener('click', function() { if (historyIndex < history.length - 1) { historyIndex++; restoreFromHistory(); updateStatus('已恢复撤销的操作'); } else { updateStatus('没有可恢复的操作'); } }); document.getElementById('delete-btn').addEventListener('click', function() { deleteSelectedObject(); }); document.getElementById('clear-btn').addEventListener('click', function() { if (confirm('确定要清空所有标注吗?')) { clearAnnotations(); updateStatus('已清空所有标注'); } }); // 标注工具功能 document.getElementById('box-tool').addEventListener('click', function() { setCurrentTool('box'); }); document.getElementById('point-tool').addEventListener('click', function() { setCurrentTool('point'); }); document.getElementById('polygon-tool').addEventListener('click', function() { setCurrentTool('polygon'); }); document.getElementById('measure-tool').addEventListener('click', function() { setCurrentTool('measure'); }); document.getElementById('text-tool').addEventListener('click', function() { setCurrentTool('text'); }); // 设置当前工具 function setCurrentTool(tool) { currentTool = tool; updateStatus(`${getToolName(tool)}工具已激活 - 点击模型表面添加标注`); // 更新按钮状态 document.querySelectorAll('.submenu button').forEach(btn => { btn.classList.remove('active'); }); document.getElementById(`${tool}-tool`).classList.add('active'); } // 获取工具名称 function getToolName(tool) { const names = { 'box': '3D框标注', 'point': '点标注', 'polygon': '多边形标注', 'measure': '测量', 'text': '文字标注' }; return names[tool] || tool; } // 鼠标交互 renderer.domElement.addEventListener('mousedown', onMouseDown, false); renderer.domElement.addEventListener('mouseup', onMouseUp, false); renderer.domElement.addEventListener('mousemove', onMouseMove, false); renderer.domElement.addEventListener('wheel', onMouseWheel, false); renderer.domElement.addEventListener('contextmenu', function(e) { e.preventDefault(); // 阻止右键菜单 }, false); // 键盘交互 document.addEventListener('keydown', function(e) { if (e.key === 'Delete' && selectedObject) { deleteSelectedObject(); } }); // 删除选中对象 function deleteSelectedObject() { if (selectedObject) { scene.remove(selectedObject); annotations = annotations.filter(anno => anno.object !== selectedObject); updateAnnotationsList(); saveHistory(); selectedObject = null; updateStatus('已删除选中对象'); } else { updateStatus('请先选择要删除的对象'); } } // 辅助函数 function fitViewToObject(object) { const box = new THREE.Box3().setFromObject(object); const center = box.getCenter(new THREE.Vector3()); const size = box.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); const fov = camera.fov * (Math.PI / 180); let cameraZ = Math.abs(maxDim / (2 * Math.tan(fov / 2))); cameraZ *= 1.5; // 稍微远一点 controls.target.copy(center); camera.position.copy(center.clone().add(new THREE.Vector3(0, 0, cameraZ))); controls.update(); updateStatus('视图已适应模型'); } function setStandardView(view) { if (!currentModel) return; const box = new THREE.Box3().setFromObject(currentModel); const center = box.getCenter(new THREE.Vector3()); const size = box.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); controls.target.copy(center); let position; switch(view) { case 'front': position = new THREE.Vector3(0, 0, maxDim * 1.5); updateStatus('切换到前视图'); break; case 'back': position = new THREE.Vector3(0, 0, -maxDim * 1.5); updateStatus('切换到后视图'); break; case 'left': position = new THREE.Vector3(-maxDim * 1.5, 0, 0); updateStatus('切换到左视图'); break; case 'right': position = new THREE.Vector3(maxDim * 1.5, 0, 0); updateStatus('切换到右视图'); break; case 'top': position = new THREE.Vector3(0, maxDim * 1.5, 0); updateStatus('切换到顶视图'); break; case 'bottom': position = new THREE.Vector3(0, -maxDim * 1.5, 0); updateStatus('切换到底视图'); break; } position.add(center); camera.position.copy(position); camera.lookAt(center); controls.update(); } function saveHistory() { // 简化版历史记录,实际应用中可能需要更复杂的实现 history = history.slice(0, historyIndex + 1); history.push({ annotations: JSON.parse(JSON.stringify(annotations)), model: currentModel ? currentModel.clone() : null }); historyIndex = history.length - 1; } function restoreFromHistory() { const state = history[historyIndex]; annotations = JSON.parse(JSON.stringify(state.annotations)); if (currentModel) scene.remove(currentModel); currentModel = state.model ? state.model.clone() : null; if (currentModel) scene.add(currentModel); updateAnnotationsList(); } function clearAnnotations() { // 移除所有标注对象 annotations.forEach(anno => { if (anno.object && anno.object.parent) { scene.remove(anno.object); } }); annotations = []; updateAnnotationsList(); saveHistory(); } function updateAnnotationsList() { const container = document.getElementById('annotations-container'); container.innerHTML = ''; annotations.forEach((annotation, index) => { const item = document.createElement('div'); item.className = 'annotation-item'; if (annotation.object === selectedObject) { item.classList.add('selected'); } item.innerHTML = ` <span>标注 ${index + 1} (${getToolName(annotation.type)})</span> <button class="delete-annotation" data-index="${index}">×</button> `; container.appendChild(item); item.querySelector('.delete-annotation').addEventListener('click', function(e) { e.stopPropagation(); scene.remove(annotation.object); annotations.splice(index, 1); updateAnnotationsList(); saveHistory(); updateStatus(`已删除标注 ${index + 1}`); }); item.addEventListener('click', function() { // 聚焦到该标注 controls.target.copy(annotation.object.position); controls.update(); selectedObject = annotation.object; updateAnnotationsList(); updateStatus(`已聚焦到标注 ${index + 1}`); }); }); } function updateStatus(message) { document.getElementById('status-bar').textContent = message; } // 鼠标交互函数 function onMouseDown(event) { if (event.button === 2) { // 右键 isRightMouseDown = true; return; } if (event.button !== 0) return; // 只处理左键 isMouseDown = true; lastMousePosition = { x: event.clientX, y: event.clientY }; // 检查是否点击了模型或标注 const mouse = new THREE.Vector2( (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1 ); const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(scene.children, true); if (intersects.length > 0) { selectedObject = intersects[0].object; // 如果是标注工具激活状态,创建新标注 if (currentTool) { createAnnotation(intersects[0].point); } else { updateStatus(`已选中对象: ${selectedObject.type}`); updateAnnotationsList(); } } else { selectedObject = null; updateAnnotationsList(); } } function onMouseUp(event) { if (event.button === 2) { // 右键 isRightMouseDown = false; } if (event.button === 0) { // 左键 isMouseDown = false; } } function onMouseMove(event) { if (isRightMouseDown) { // 右键拖动旋转视图 const deltaMove = { x: event.clientX - lastMousePosition.x, y: event.clientY - lastMousePosition.y }; controls.rotateLeft(2 * Math.PI * deltaMove.x / window.innerWidth); controls.rotateUp(2 * Math.PI * deltaMove.y / window.innerHeight); } else if (isMouseDown && !currentTool) { // 左键拖动旋转视图 const deltaMove = { x: event.clientX - lastMousePosition.x, y: event.clientY - lastMousePosition.y }; controls.rotateLeft(2 * Math.PI * deltaMove.x / window.innerWidth); controls.rotateUp(2 * Math.PI * deltaMove.y / window.innerHeight); } lastMousePosition = { x: event.clientX, y: event.clientY }; } function onMouseWheel(event) { // 缩放控制 const delta = Math.sign(event.deltaY); const zoomSpeed = 0.1; if (delta > 0) { controls.dollyOut(zoomSpeed); } else { controls.dollyIn(zoomSpeed); } controls.update(); } // 创建标注函数 function createAnnotation(position) { let annotation; switch(currentTool) { case 'point': annotation = createPointAnnotation(position); break; case 'box': annotation = createBoxAnnotation(position); break; case 'polygon': annotation = createPolygonAnnotation(position); break; case 'measure': annotation = createMeasureAnnotation(position); break; case 'text': annotation = createTextAnnotation(position); break; } if (annotation) { scene.add(annotation); annotations.push({ type: currentTool, object: annotation, position: position }); selectedObject = annotation; updateAnnotationsList(); saveHistory(); updateStatus(`已添加${getToolName(currentTool)}标注`); } } // 创建点标注 function createPointAnnotation(position) { const geometry = new THREE.SphereGeometry(0.05, 16, 16); const material = new THREE.MeshBasicMaterial({ color: 0xff0000 }); const sphere = new THREE.Mesh(geometry, material); sphere.position.copy(position); return sphere; } // 创建3D框标注 function createBoxAnnotation(position) { const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5); const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, transparent: true, opacity: 0.5, wireframe: false }); const box = new THREE.Mesh(geometry, material); box.position.copy(position); return box; } // 创建多边形标注 function createPolygonAnnotation(position) { // 简化实现,实际应用中需要更复杂的逻辑 const geometry = new THREE.CircleGeometry(0.2, 6); const material = new THREE.MeshBasicMaterial({ color: 0x0000ff, transparent: true, opacity: 0.7 }); const circle = new THREE.Mesh(geometry, material); circle.position.copy(position); return circle; } // 创建测量标注 function createMeasureAnnotation(position) { // 简化实现,实际应用中需要更复杂的逻辑 const geometry = new THREE.CylinderGeometry(0.05, 0.05, 0.5, 16); const material = new THREE.MeshBasicMaterial({ color: 0xffff00 }); const cylinder = new THREE.Mesh(geometry, material); cylinder.position.copy(position); return cylinder; } // 创建文字标注 function createTextAnnotation(position) { // 简化实现,实际应用中需要使用TextGeometry或CSS2D/3D渲染器 const geometry = new THREE.BoxGeometry(0.3, 0.1, 0.01); const material = new THREE.MeshBasicMaterial({ color: 0xffffff }); const cube = new THREE.Mesh(geometry, material); cube.position.copy(position); return cube; } // 动画循环 function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); } animate(); // 窗口大小调整 window.addEventListener('resize', function() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); // 在动画循环中添加标注对象的更新逻辑 function animate() { requestAnimationFrame(animate); // 更新标注对象的位置和状态 updateAnnotations(); controls.update(); renderer.render(scene, camera); } // 更新标注对象的状态 function updateAnnotations() { annotations.forEach(annotation => { // 可以在这里添加标注对象的动画效果或状态更新 if (annotation.type === 'point' && annotation.object) { // 点标注的脉动效果 annotation.object.scale.x = 1 + Math.sin(Date.now() * 0.005) * 0.2; annotation.object.scale.y = 1 + Math.sin(Date.now() * 0.005) * 0.2; annotation.object.scale.z = 1 + Math.sin(Date.now() * 0.005) * 0.2; } }); } // 添加标注编辑功能 function enableAnnotationEditing() { // 双击标注可以进入编辑模式 renderer.domElement.addEventListener('dblclick', function(event) { if (!currentTool && selectedObject) { const annotation = annotations.find(anno => anno.object === selectedObject); if (annotation) { editAnnotation(annotation); } } }); } // 编辑标注 function editAnnotation(annotation) { // 根据标注类型显示不同的编辑界面 switch(annotation.type) { case 'text': editTextAnnotation(annotation); break; case 'box': editBoxAnnotation(annotation); break; // 其他标注类型的编辑逻辑... } } // 编辑文字标注 function editTextAnnotation(annotation) { const text = prompt('请输入新的标注文字:', '标注内容'); if (text !== null) { // 更新标注内容 updateStatus(`文字标注已更新: ${text}`); saveHistory(); } } // 编辑3D框标注 function editBoxAnnotation(annotation) { const size = prompt('请输入新的尺寸 (宽,高,深):', '0.5,0.5,0.5'); if (size) { const dimensions = size.split(',').map(Number); if (dimensions.length === 3 && !dimensions.some(isNaN)) { annotation.object.geometry.dispose(); annotation.object.geometry = new THREE.BoxGeometry(...dimensions); updateStatus(`3D框标注尺寸已更新: ${dimensions.join('x')}`); saveHistory(); } else { updateStatus('无效的尺寸格式,请使用"宽,高,深"格式'); } } } // 添加标注分组功能 let annotationGroups = {}; function createAnnotationGroup(name) { if (!annotationGroups[name]) { annotationGroups[name] = []; updateStatus(`已创建标注分组: ${name}`); return true; } return false; } function addToAnnotationGroup(annotation, groupName) { if (annotationGroups[groupName]) { annotationGroups[groupName].push(annotation); annotation.group = groupName; updateStatus(`标注已添加到分组: ${groupName}`); return true; } return false; } // 添加模型测量工具 function setupMeasurementTools() { let startPoint = null; let endPoint = null; let measurementLine = null; document.getElementById('measure-tool').addEventListener('click', function() { setCurrentTool('measure'); startPoint = null; endPoint = null; if (measurementLine) { scene.remove(measurementLine); measurementLine = null; } }); renderer.domElement.addEventListener('click', function(event) { if (currentTool === 'measure') { const mouse = new THREE.Vector2( (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1 ); const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(scene.children, true); if (intersects.length > 0) { if (!startPoint) { startPoint = intersects[0].point; updateStatus('测量: 已选择起点,请选择终点'); } else { endPoint = intersects[0].point; createMeasurementLine(startPoint, endPoint); startPoint = null; endPoint = null; } } } }); function createMeasurementLine(start, end) { if (measurementLine) { scene.remove(measurementLine); } const geometry = new THREE.BufferGeometry().setFromPoints([start, end]); const material = new THREE.LineBasicMaterial({ color: 0xffff00 }); measurementLine = new THREE.Line(geometry, material); scene.add(measurementLine); const distance = start.distanceTo(end); updateStatus(`测量结果: ${distance.toFixed(2)} 单位`); // 添加测量标注 const annotation = { type: 'measure', object: measurementLine, start: start, end: end, distance: distance }; annotations.push(annotation); updateAnnotationsList(); saveHistory(); } } // 初始化所有功能 function initFeatures() { enableAnnotationEditing(); setupMeasurementTools(); // 添加分组管理UI setupGroupManagementUI(); } // 添加分组管理UI function setupGroupManagementUI() { const sidebar = document.getElementById('sidebar'); const groupSection = document.createElement('div'); groupSection.innerHTML = ` <h3>标注分组</h3> <div id="group-management"> <input type="text" id="new-group-name" placeholder="新分组名称"> <button id="create-group">创建分组</button> <div id="groups-list"></div> </div> `; sidebar.insertBefore(groupSection, document.querySelector('.annotation-list')); document.getElementById('create-group').addEventListener('click', function() { const groupName = document.getElementById('new-group-name').value.trim(); if (groupName && createAnnotationGroup(groupName)) { updateGroupsList(); document.getElementById('new-group-name').value = ''; } }); // 右键菜单添加到标注项 document.addEventListener('contextmenu', function(e) { if (e.target.classList.contains('annotation-item')) { e.preventDefault(); showAnnotationContextMenu(e); } }); } function updateGroupsList() { const groupsList = document.getElementById('groups-list'); groupsList.innerHTML = ''; for (const groupName in annotationGroups) { const groupItem = document.createElement('div'); groupItem.className = 'group-item'; groupItem.innerHTML = ` <span>${groupName} (${annotationGroups[groupName].length})</span> <button class="delete-group" data-group="${groupName}">×</button> `; groupsList.appendChild(groupItem); groupItem.querySelector('.delete-group').addEventListener('click', function(e) { e.stopPropagation(); deleteAnnotationGroup(groupName); }); } } function deleteAnnotationGroup(groupName) { if (confirm(`确定要删除分组 "${groupName}" 吗?`)) { delete annotationGroups[groupName]; updateGroupsList(); updateStatus(`已删除分组: ${groupName}`); } } function showAnnotationContextMenu(event) { // 实现右键菜单功能 const menu = document.createElement('div'); menu.className = 'context-menu'; menu.style.position = 'absolute'; menu.style.left = `${event.clientX}px`; menu.style.top = `${event.clientY}px`; menu.style.backgroundColor = '#333'; menu.style.border = '1px solid #444'; menu.style.padding = '5px'; menu.style.zIndex = '1000'; const index = event.target.closest('.annotation-item').querySelector('.delete-annotation').dataset.index; const annotation = annotations[index]; menu.innerHTML = ` <div class="menu-item" data-action="rename">重命名</div> <div class="menu-item" data-action="change-color">更改颜色</div> <div class="menu-item" data-action="add-to-group">添加到分组</div> `; document.body.appendChild(menu); // 点击菜单项处理 menu.querySelectorAll('.menu-item').forEach(item => { item.addEventListener('click', function() { const action = this.dataset.action; switch(action) { case 'rename': renameAnnotation(annotation); break; case 'change-color': changeAnnotationColor(annotation); break; case 'add-to-group': addAnnotationToGroup(annotation); break; } menu.remove(); }); }); // 点击其他地方关闭菜单 document.addEventListener('click', function closeMenu() { menu.remove(); document.removeEventListener('click', closeMenu); }, { once: true }); } // 初始化所有功能 initFeatures();以这份代码为基础添加高级标注功能:1.标注层次结构允许用户创建嵌套的标注组,形成层次化的标注结构;2.标注样式自定义:提供自定义标注外观的选项,如颜色、大小、透明度等;3.标注模板:允许用户创建和应用标注模板,快速标注类似特征;4.标注注释:为每个标注添加详细的文字描述、标签和元数据;5.多点测量:支持多点测量,计算复杂形状的长度、角度、面积等;6.自动测量模式:识别模型的关键特征(如边缘、角落)并自动测量;7.测量结果导出:将测量数据导出为csv或PDF格式;8.多人实协作:允许多人同编辑一个模型,并实同步标注;9.版本控制;记录标注和模型的历史版本,支持回滚到任意历史版本;10.标注评论和讨论:允许用户对标注添加评论,进行团队评论;11.自动标注推荐:利用AI识别模型对关键特征(如颜色、器物边缘),并建议标注位置;12.智能识别:支持基于AI的模型识别,自动分类和标注不同类型的文物。13.AR模式:允许用户通过移动设备在现实场景中查看和标注3D模型;14.VR标注:支持VR设备(如Oculus Quest),提供沉浸式标注体验;15.标注数据导出:支持将标注导出为多种格式(如OBJ、JSON、XML、CSV);16.3D打印准备:导出模型和标注,适配3D打印需求;17.AR/VR应用导出:将标注模型导出为AR/VR应用可用的格式;18.模型简化:为复杂模型提供简化选项,优化渲染性能;19.标注对象池:复用标注对象,减少内存分配和垃圾回收;20.LOD(细节层次):根据视距动态调整模型和标注的细节级别;21.插件架构:允许开发者扩展平台功能,如添加插件市场:集成插件市场,用户可以下载和安装第三方插件。自定义标注工具或分析插件;22.插件市场:集成插件市场,用户可以下载和安装第三方插件;23.暗/亮模式切换:提供界面主题切换功能;24.自定义布局:允许用户调整侧边栏、工具栏的位置和大小;25.快捷键支持:添加更多快捷键,提高操作效率;26.标注统计:提供标注数量、类型分布等统计信息;27.模型分析:计算模型体积、表面积等几何属性;28.标注热图:可视化显示标注密集区域;29.云存储集成:支持将模型和标注数据存储到云平台(如AWS、Google Drive);30.自动同步:实同步本地和云端数据,防止数据丢失;31.孔洞填充:自动检测并填充模型中的孔洞(模型的修复工具);32.法线修复:自动修复模型法线方向,确保渲染效果正确(模型修复工具);33.UV展开:为模型生成UV映射,便于纹理标注(模型修复工具);34.标注筛选:按类型、颜色、注释内容等条件筛选标注(高级筛选与搜索);35.模型搜索:支持通过关键词搜索模型库中的文物(高级筛选与搜索);36.标注教程:内置标注指南和教程,帮助用户快速上手;37.标注练习:提供预设模型和任务,用户可以练习标注技能;38.用户权限:支持不同用户角色(如管理员、编辑、查看者),限制操作权限;39.数据加密:对存储和传输中的模型和标注数据进行加密。根据我的39个要求添加新功能,以完整的代码输出给我
05-10
标题基于SpringBoot的马术俱乐部管理系统设计与实现AI更换标题第1章引言介绍马术俱乐部管理系统的研究背景、意义、国内外研究现状、论文方法及创新点。1.1研究背景与意义阐述马术俱乐部管理系统对提升俱乐部管理效率的重要性。1.2国内外研究现状分析国内外马术俱乐部管理系统的发展现状及存在的问题。1.3研究方法以及创新点概述本文采用的研究方法,包括SpringBoot框架的应用,以及系统的创新点。第2章相关理论总结和评述与马术俱乐部管理系统相关的现有理论。2.1SpringBoot框架理论介绍SpringBoot框架的基本原理、特点及其在Web开发中的应用。2.2数据库设计理论阐述数据库设计的基本原则、方法以及在管理系统中的应用。2.3马术俱乐部管理理论概述马术俱乐部管理的基本理论,包括会员管理、课程安排等。第3章系统设计详细描述马术俱乐部管理系统的设计方案,包括架构设计、功能模块设计等。3.1系统架构设计给出系统的整体架构,包括前端、后端和数据库的交互方式。3.2功能模块设计详细介绍系统的各个功能模块,如会员管理、课程管理、预约管理等。3.3数据库设计阐述数据库的设计方案,包括表结构、字段设计以及数据关系。第4章系统实现介绍马术俱乐部管理系统的实现过程,包括开发环境、编码实现等。4.1开发环境搭建介绍系统开发所需的环境,包括操作系统、开发工具等。4.2编码实现详细介绍系统各个功能模块的编码实现过程。4.3系统测试与调试阐述系统的测试方法、测试用例以及调试过程。第5章系统应用与分析呈现马术俱乐部管理系统的应用效果,并进行性能分析。5.1系统应用情况介绍系统在马术俱乐部中的实际应用情况。5.2系统性能分析从响应间、并发处理能力等方面对系统性能进行分析。5.3用户反馈与改进收集用户反馈,提出系统改进建议。第6章结论与展望总结马术俱乐部管理系统的设计与实现成果,并展望未来的研究
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值